diff options
author | Willem Jan Palenstijn | 2011-11-20 22:46:58 +0100 |
---|---|---|
committer | Willem Jan Palenstijn | 2011-11-20 22:49:18 +0100 |
commit | 2613e7f5879c8c3ca7663dfcea8d5b91c499c107 (patch) | |
tree | cb9a57e5ad2fb81d988c99b7704795477719807d /engines/toltecs | |
parent | 528c1173d7b9d15d9b7e36f25a876c8952357f26 (diff) | |
parent | b592d73e6226c5c5375d4b40123b15e37b064b9e (diff) | |
download | scummvm-rg350-2613e7f5879c8c3ca7663dfcea8d5b91c499c107.tar.gz scummvm-rg350-2613e7f5879c8c3ca7663dfcea8d5b91c499c107.tar.bz2 scummvm-rg350-2613e7f5879c8c3ca7663dfcea8d5b91c499c107.zip |
TOLTECS: Merge toltecs engine
This is an engine for the game "3 Skulls of the Toltecs".
It is a renamed, tweaked and manual merge of the pull request at
https://github.com/scummvm/scummvm/pull/119
Diffstat (limited to 'engines/toltecs')
31 files changed, 7872 insertions, 0 deletions
diff --git a/engines/toltecs/TODO.txt b/engines/toltecs/TODO.txt new file mode 100644 index 0000000000..31215f7fff --- /dev/null +++ b/engines/toltecs/TODO.txt @@ -0,0 +1,54 @@ +NOTES +------- +- The game is completable with revision 20 with some minor glitches (see BUGS). + + +TODO +------ +- Finish music support (volume, looping perhaps?) +- Check if background resources are used before purging them when changing scenes - check + the comments inside sfLoadScene. +- Sometimes, some stray lines are drawn below fonts - probably because the text doesn't + fit inside the screen and the surface is cut off incorrectly. +- Fix some occasional artifacts on screen (e.g. when text is shown over an animation) + sometimes. +- Pathfinding bugs in Fort Apache and the Indian village - e.g. when moving to the door + on the right, the hero walks around a big path. Same when moving to the tent on the top + in the indian village. +- When getting the piano player off the well, he walks up facing to the left (his side + animation is shown). +- Load/start sound and music of the saved scene when loading. +- Some sounds are cut off prematurely (e.g. when an animation finishes before the sound + sample, when changing scenes and a dialog is immediately started afterwards). +- When saving a game, save the whole screen when an animation is playing + e.g. when playing animations (the script controlled ones, not the movies) it should save + the whole screen when the game is saved when such an animation is running since it uses + delta-frames so currently, once restored, the screen is wrong. This is only observed in + a few places. + + +BUGS +------ +None known (see TODO). + +DONE +------ +- Crashes sometimes on scene changes + (I guess because some talktext is still running although the slot used by it has changed) + Crashes sometimes (e.g. when inside the barn and going up the ladder) + These crashes are caused by non-stopping background sounds, see TODO above. +- The game music is in XMIDI. I'm assuming that the rest of the formats were because of badly + dumped resources - finalize() Calls were missing and the resulting music could contain anything. +- Implement dirty rectangles (low priority) +- Optimize segment mask redrawing (only redraw what's neccessary) +- Add movie playback functionality (movie format is known, needs implementing) +- Add sound support (after rewrite of the resource system) +- Still some clipping issues (walking upstairs in the saloon, when on top of the tower in the hideout) +- Add game menu + - Rewrite from scratch with only minor stuff from the disasm since the original menu is one huge + messy function +- Rewrite the resource system to something similar as used by M4 + - each resource type gets its own class, the resource cache manages the resource instances + - generic resource class which has a method that creates a MemoryReadStream + - rewrite parts of the engine to use the new resource system +- Extend savegame format (savegame version, description, more data) diff --git a/engines/toltecs/animation.cpp b/engines/toltecs/animation.cpp new file mode 100644 index 0000000000..eef9cef9ed --- /dev/null +++ b/engines/toltecs/animation.cpp @@ -0,0 +1,164 @@ +/* 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 "toltecs/toltecs.h" +#include "toltecs/animation.h" +#include "toltecs/palette.h" +#include "toltecs/screen.h" + +namespace Toltecs { + +AnimationPlayer::AnimationPlayer(ToltecsEngine *vm) : _vm(vm) { + _animBuffer = new byte[262144]; +} + +AnimationPlayer::~AnimationPlayer() { + delete[] _animBuffer; +} + +void AnimationPlayer::start(uint resIndex) { + debug(1, "AnimationPlayer::start(%d)", resIndex); + + _resIndex = resIndex; + + _vm->_arc->openResource(_resIndex); + _height = _vm->_arc->readUint16LE(); + _width = _vm->_arc->readUint16LE(); + _frameCount = _vm->_arc->readUint16LE(); + _vm->_arc->read(_vm->_palette->getAnimPalette(), 768); + _curFrameSize = _vm->_arc->readUint32LE(); + _nextFrameOffset = _curFrameSize + 782; + _vm->_arc->read(_animBuffer, _curFrameSize); + _nextFrameSize = _vm->_arc->readUint32LE(); + _vm->_arc->closeResource(); + + debug(1, "AnimationPlayer::start() width = %d; height = %d; frameCount = %d", _width, _height, _frameCount); + + _vm->_sceneWidth = _width; + _vm->_sceneHeight = _height; + + unpackFrame(); + + _keepFrameCounter = 0; + _frameNumber = 0; + // TODO mov screenFlag01, 0FFFFh + // TODO mov animDrawFrameFlag, 0FFFFh + + _firstNextFrameOffset = _nextFrameOffset; + _firstCurFrameSize = _curFrameSize; + _firstNextFrameSize = _nextFrameSize; + +} + +void AnimationPlayer::nextFrame() { + debug(1, "AnimationPlayer::nextFrame()"); + + if (_frameNumber == _frameCount) { + _nextFrameOffset = _firstNextFrameOffset; + _curFrameSize = _firstCurFrameSize; + _nextFrameSize = _firstNextFrameSize; + _frameNumber = 1; + } else { + _frameNumber++; + } + + debug(1, "AnimationPlayer::nextFrame() frameNumber = %d", _frameNumber); + + if (_keepFrameCounter > 0) { + _keepFrameCounter--; + return; + } + + _vm->_arc->openResource(_resIndex); + _vm->_arc->seek(_nextFrameOffset, SEEK_CUR); + _curFrameSize = _nextFrameSize; + + if (_curFrameSize == 0) + _curFrameSize = 1; + + _vm->_arc->read(_animBuffer, _curFrameSize); + _nextFrameSize = _vm->_arc->readUint32LE(); + _nextFrameOffset += _curFrameSize + 4; + + if (_curFrameSize > 1) { + unpackFrame(); + // TODO mov animDrawFrameFlag, 0FFFFh + } else { + _keepFrameCounter = _animBuffer[0] - 1; + // TODO mov animDrawFrameFlag, 0 + } + + _vm->_arc->closeResource(); + + +} + +int16 AnimationPlayer::getStatus() { + debug(1, "AnimationPlayer::getStatus()"); + int16 status = -1; + if (_frameNumber == _frameCount) + status = 0; + else if (_frameNumber == _frameCount - 1) + status = 1; + debug(1, "AnimationPlayer::getStatus() status = %d", status); + return status; +} + +void AnimationPlayer::unpackFrame() { + _vm->_screen->unpackRle(_animBuffer, _vm->_screen->_frontScreen, _width, _height); + _vm->_screen->unpackRle(_animBuffer, _vm->_screen->_backScreen, _width, _height); + _vm->_screen->_fullRefresh = true; +} + +void AnimationPlayer::saveState(Common::WriteStream *out) { + out->writeUint16LE(_resIndex); + // NOTE: The original engine doesn't save width/height, but we do + out->writeUint16LE(_width); + out->writeUint16LE(_height); + out->writeUint16LE(_frameCount); + out->writeUint16LE(_frameNumber); + out->writeUint32LE(_keepFrameCounter); + out->writeUint32LE(_curFrameSize); + out->writeUint32LE(_nextFrameSize); + out->writeUint32LE(_nextFrameOffset); + out->writeUint32LE(_firstCurFrameSize); + out->writeUint32LE(_firstNextFrameSize); + out->writeUint32LE(_firstNextFrameOffset); +} + +void AnimationPlayer::loadState(Common::ReadStream *in) { + _resIndex = in->readUint16LE(); + _width = in->readUint16LE(); + _height = in->readUint16LE(); + _frameCount = in->readUint16LE(); + _frameNumber = in->readUint16LE(); + _keepFrameCounter = in->readUint32LE(); + _curFrameSize = in->readUint32LE(); + _nextFrameSize = in->readUint32LE(); + _nextFrameOffset = in->readUint32LE(); + _firstCurFrameSize = in->readUint32LE(); + _firstNextFrameSize = in->readUint32LE(); + _firstNextFrameOffset = in->readUint32LE(); +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/animation.h b/engines/toltecs/animation.h new file mode 100644 index 0000000000..22576d7535 --- /dev/null +++ b/engines/toltecs/animation.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 TOLTECS_ANIMATION_H +#define TOLTECS_ANIMATION_H + +#include "toltecs/toltecs.h" +#include "toltecs/resource.h" + +namespace Toltecs { + +class AnimationPlayer { +public: + AnimationPlayer(ToltecsEngine *vm); + ~AnimationPlayer(); + + void start(uint resIndex); + void nextFrame(); + int16 getStatus(); + uint16 getFrameNumber() const { return _frameNumber; } + + void saveState(Common::WriteStream *out); + void loadState(Common::ReadStream *in); + +//protected: +public: + ToltecsEngine *_vm; + + // 262144 + byte *_animBuffer; + + uint16 _resIndex; + + uint16 _width, _height; + uint16 _frameNumber, _frameCount; + uint32 _keepFrameCounter; + + uint32 _curFrameSize; + uint32 _nextFrameSize, _nextFrameOffset; + + uint32 _firstNextFrameOffset, _firstCurFrameSize, _firstNextFrameSize; + + void unpackFrame(); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_ANIMATION_H */ diff --git a/engines/toltecs/detection.cpp b/engines/toltecs/detection.cpp new file mode 100644 index 0000000000..3717d33950 --- /dev/null +++ b/engines/toltecs/detection.cpp @@ -0,0 +1,287 @@ +/* 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 "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/system.h" + +#include "toltecs/toltecs.h" + + +namespace Toltecs { + +struct ToltecsGameDescription { + ADGameDescription desc; +}; + +uint32 ToltecsEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language ToltecsEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +} + +static const PlainGameDescriptor toltecsGames[] = { + {"toltecs", "3 Skulls of the Toltecs"}, + {0, 0} +}; + + +namespace Toltecs { + +static const ToltecsGameDescription gameDescriptions[] = { + + { + // 3 Skulls of the Toltecs English version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "05472037e9cfde146e953c434e74f0f4", 337643527), + Common::EN_ANY, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // 3 Skulls of the Toltecs Russian version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "ba1742d3193b68ceb9434e2ab7a09a9b", 391462783), + Common::RU_RUS, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // 3 Skulls of the Toltecs German version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "1a3292bad8e0bb5701800c73531dd75e", 345176617), + Common::DE_DEU, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // 3 Skulls of the Toltecs German Demo version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "1c85e82712d24f1d5c1ea2a66ddd75c2", 47730038), + Common::DE_DEU, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // 3 Skulls of the Toltecs French version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "4fb845635cbdac732453fe23be350df9", 327269545), + Common::FR_FRA, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // 3 Skulls of the Toltecs Spanish version + { + "toltecs", + 0, + AD_ENTRY1s("WESTERN", "479f468beccc1b0ce5873ec523d1380e", 308391018), + Common::ES_ESP, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace Toltecs + +class ToltecsMetaEngine : public AdvancedMetaEngine { +public: + ToltecsMetaEngine() : AdvancedMetaEngine(Toltecs::gameDescriptions, sizeof(Toltecs::ToltecsGameDescription), toltecsGames) { + _singleid = "toltecs"; + } + + virtual const char *getName() const { + return "Toltecs Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Toltecs Engine Revistronic (C) 1996"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool ToltecsMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || +// (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool Toltecs::ToltecsEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool ToltecsMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Toltecs::ToltecsGameDescription *gd = (const Toltecs::ToltecsGameDescription *)desc; + if (gd) { + *engine = new Toltecs::ToltecsEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList ToltecsMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Toltecs::ToltecsEngine::SaveHeader header; + Common::String pattern = target; + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Toltecs::ToltecsEngine::readSaveHeader(in, false, header) == Toltecs::ToltecsEngine::kRSHENoError) { + saveList.push_back(SaveStateDescriptor(slotNum, header.description)); + } + delete in; + } + } + } + + return saveList; +} + +int ToltecsMetaEngine::getMaximumSaveSlot() const { + return 999; +} + +void ToltecsMetaEngine::removeSaveState(const char *target, int slot) const { + // Slot 0 can't be deleted, it's for restarting the game(s) + if (slot == 0) + return; + + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::String filename = Toltecs::ToltecsEngine::getSavegameFilename(target, slot); + + saveFileMan->removeSavefile(filename.c_str()); + + Common::StringArray filenames; + Common::String pattern = target; + pattern += ".???"; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + // Rename every slot greater than the deleted slot, + // Also do not rename quicksaves. + if (slotNum > slot && slotNum < 990) { + // FIXME: Our savefile renaming done here is inconsitent with what we do in + // GUI_v2::deleteMenu. While here we rename every slot with a greater equal + // number of the deleted slot to deleted slot, deleted slot + 1 etc., + // we only rename the following slots in GUI_v2::deleteMenu until a slot + // is missing. + saveFileMan->renameSavefile(file->c_str(), filename.c_str()); + + filename = Toltecs::ToltecsEngine::getSavegameFilename(target, ++slot); + } + } + +} + +SaveStateDescriptor ToltecsMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Toltecs::ToltecsEngine::getSavegameFilename(target, slot); + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str()); + + if (in) { + Toltecs::ToltecsEngine::SaveHeader header; + Toltecs::ToltecsEngine::kReadSaveHeaderError error; + + error = Toltecs::ToltecsEngine::readSaveHeader(in, true, header); + delete in; + + if (error == Toltecs::ToltecsEngine::kRSHENoError) { + SaveStateDescriptor desc(slot, header.description); + + desc.setDeletableFlag(false); + desc.setWriteProtectedFlag(false); + desc.setThumbnail(header.thumbnail); + + return desc; + } + } + + return SaveStateDescriptor(); +} + +#if PLUGIN_ENABLED_DYNAMIC(TOLTECS) + REGISTER_PLUGIN_DYNAMIC(TOLTECS, PLUGIN_TYPE_ENGINE, ToltecsMetaEngine); +#else + REGISTER_PLUGIN_STATIC(TOLTECS, PLUGIN_TYPE_ENGINE, ToltecsMetaEngine); +#endif diff --git a/engines/toltecs/menu.cpp b/engines/toltecs/menu.cpp new file mode 100644 index 0000000000..25c240f67e --- /dev/null +++ b/engines/toltecs/menu.cpp @@ -0,0 +1,638 @@ +/* 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/savefile.h" + +#include "toltecs/toltecs.h" +#include "toltecs/menu.h" +#include "toltecs/palette.h" +#include "toltecs/render.h" +#include "toltecs/resource.h" + +namespace Toltecs { + +MenuSystem::MenuSystem(ToltecsEngine *vm) : _vm(vm) { +} + +MenuSystem::~MenuSystem() { +} + +int MenuSystem::run() { + + //debug("MenuSystem::run()"); + + _background = new Graphics::Surface(); + _background->create(640, 400, Graphics::PixelFormat::createFormatCLUT8()); + + // Save original background + Graphics::Surface backgroundOrig; + backgroundOrig.create(640, 400, Graphics::PixelFormat::createFormatCLUT8()); + memcpy(backgroundOrig.getBasePtr(0,0), _vm->_screen->_frontScreen, 640 * 400); + + _currMenuID = kMenuIdNone; + _newMenuID = kMenuIdMain; + _currItemID = kItemIdNone; + _editingDescription = false; + _cfgText = true; + _cfgVoices = true; + _cfgMasterVolume = 10; + _cfgVoicesVolume = 10; + _cfgMusicVolume = 10; + _cfgSoundFXVolume = 10; + _cfgBackgroundVolume = 10; + _running = true; + _top = 30 - _vm->_guiHeight / 2; + _needRedraw = false; + + // TODO: buildColorTransTable2 + _vm->_palette->buildColorTransTable(0, 16, 7); + + _vm->_screen->_renderQueue->clear(); + // Draw the menu background and frame + _vm->_screen->blastSprite(0x140 + _vm->_cameraX, 0x175 + _vm->_cameraY, 0, 1, 0x4000); + shadeRect(60, 39, 520, 246, 30, 94); + + memcpy(_background->pixels, _vm->_screen->_frontScreen, 640 * 400); + + while (_running) { + update(); + _vm->_system->updateScreen(); + } + + // Restore original background + memcpy(_vm->_screen->_frontScreen, backgroundOrig.getBasePtr(0,0), 640 * 400); + _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen, 640, 0, 0, 640, 400); + _vm->_system->updateScreen(); + + // Cleanup + backgroundOrig.free(); + _background->free(); + delete _background; + + return 0; +} + +void MenuSystem::update() { + + if (_currMenuID != _newMenuID) { + _currMenuID = _newMenuID; + //debug("_currMenuID = %d", _currMenuID); + initMenu(_currMenuID); + } + + handleEvents(); + + if (_needRedraw) { + //_vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen + 39 * 640 + 60, 640, 60, 39, 520, 247); + _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen, 640, 0, 0, 640, 400); + //debug("redraw"); + _needRedraw = false; + } + + _vm->_system->delayMillis(5); + +} + +void MenuSystem::handleEvents() { + + Common::Event event; + Common::EventManager *eventMan = _vm->_system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + handleKeyDown(event.kbd); + break; + case Common::EVENT_QUIT: + _running = false; + break; + case Common::EVENT_MOUSEMOVE: + handleMouseMove(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONDOWN: + handleMouseClick(event.mouse.x, event.mouse.y); + break; + default: + break; + } + } + +} + +void MenuSystem::addClickTextItem(ItemID id, int x, int y, int w, uint fontNum, const char *caption, byte defaultColor, byte activeColor) { + Item item; + item.id = id; + item.defaultColor = defaultColor; + item.activeColor = activeColor; + item.x = x; + item.y = y; + item.w = w; + item.fontNum = fontNum; + setItemCaption(&item, caption); + _items.push_back(item); +} + +void MenuSystem::drawItem(ItemID itemID, bool active) { + Item *item = getItem(itemID); + if (item) { + byte color = active ? item->activeColor : item->defaultColor; + drawString(item->rect.left, item->y, 0, item->fontNum, color, item->caption.c_str()); + } +} + +void MenuSystem::handleMouseMove(int x, int y) { + if (!_editingDescription) { + ItemID newItemID = findItemAt(x, y); + if (_currItemID != newItemID) { + leaveItem(_currItemID); + _currItemID = newItemID; + enterItem(newItemID); + } + } +} + +void MenuSystem::handleMouseClick(int x, int y) { + if (!_editingDescription) { + ItemID id = findItemAt(x, y); + clickItem(id); + } +} + +void MenuSystem::handleKeyDown(const Common::KeyState& kbd) { + if (_editingDescription) { + if (kbd.keycode >= Common::KEYCODE_SPACE && kbd.keycode <= Common::KEYCODE_z) { + _editingDescriptionItem->caption += kbd.ascii; + restoreRect(_editingDescriptionItem->rect.left, _editingDescriptionItem->rect.top, + _editingDescriptionItem->rect.width() + 1, _editingDescriptionItem->rect.height() - 2); + setItemCaption(_editingDescriptionItem, _editingDescriptionItem->caption.c_str()); + drawItem(_editingDescriptionID, true); + } else if (kbd.keycode == Common::KEYCODE_BACKSPACE) { + _editingDescriptionItem->caption.deleteLastChar(); + restoreRect(_editingDescriptionItem->rect.left, _editingDescriptionItem->rect.top, + _editingDescriptionItem->rect.width() + 1, _editingDescriptionItem->rect.height() - 2); + setItemCaption(_editingDescriptionItem, _editingDescriptionItem->caption.c_str()); + drawItem(_editingDescriptionID, true); + } else if (kbd.keycode == Common::KEYCODE_RETURN) { + SavegameItem *savegameItem = getSavegameItemByID(_editingDescriptionID); + _editingDescription = false; + _vm->requestSavegame(savegameItem->_slotNum, _editingDescriptionItem->caption); + _running = false; + } else if (kbd.keycode == Common::KEYCODE_ESCAPE) { + _editingDescription = false; + } + } +} + +ItemID MenuSystem::findItemAt(int x, int y) { + for (Common::Array<Item>::iterator iter = _items.begin(); iter != _items.end(); iter++) { + if ((*iter).rect.contains(x, y)) + return (*iter).id; + } + return kItemIdNone; +} + +MenuSystem::Item *MenuSystem::getItem(ItemID id) { + for (Common::Array<Item>::iterator iter = _items.begin(); iter != _items.end(); iter++) { + if ((*iter).id == id) + return &(*iter); + } + return NULL; +} + +void MenuSystem::setItemCaption(Item *item, const char *caption) { + Font font(_vm->_res->load(_vm->_screen->getFontResIndex(item->fontNum))->data); + int width = font.getTextWidth((const byte*)caption); + int height = font.getHeight(); + item->rect = Common::Rect(item->x, item->y - height, item->x + width, item->y); + if (item->w) { + item->rect.translate(item->w - width / 2, 0); + } + item->caption = caption; +} + +void MenuSystem::initMenu(MenuID menuID) { + + int newSlotNum; + + _items.clear(); + + memcpy(_vm->_screen->_frontScreen, _background->pixels, 640 * 400); + + switch (menuID) { + case kMenuIdMain: + drawString(0, 74, 320, 1, 229, _vm->getSysString(kStrWhatCanIDoForYou)); + addClickTextItem(kItemIdLoad, 0, 115, 320, 0, _vm->getSysString(kStrLoad), 229, 255); + addClickTextItem(kItemIdSave, 0, 135, 320, 0, _vm->getSysString(kStrSave), 229, 255); + addClickTextItem(kItemIdToggleText, 0, 165, 320, 0, _vm->getSysString(kStrTextOn), 229, 255); + addClickTextItem(kItemIdToggleVoices, 0, 185, 320, 0, _vm->getSysString(kStrVoicesOn), 229, 255); + addClickTextItem(kItemIdVolumesMenu, 0, 215, 320, 0, _vm->getSysString(kStrVolume), 229, 255); + addClickTextItem(kItemIdPlay, 0, 245, 320, 0, _vm->getSysString(kStrPlay), 229, 255); + addClickTextItem(kItemIdQuit, 0, 275, 320, 0, _vm->getSysString(kStrQuit), 229, 255); + break; + case kMenuIdLoad: + drawString(0, 74, 320, 1, 229, _vm->getSysString(kStrLoadGame)); + addClickTextItem(kItemIdSavegameUp, 0, 155, 545, 1, "^", 255, 253); + addClickTextItem(kItemIdSavegameDown, 0, 195, 545, 1, "\\", 255, 253); + addClickTextItem(kItemIdCancel, 0, 275, 320, 0, _vm->getSysString(kStrCancel), 255, 253); + addClickTextItem(kItemIdSavegame1, 0, 115 + 20 * 0, 300, 0, "SAVEGAME 1", 231, 234); + addClickTextItem(kItemIdSavegame2, 0, 115 + 20 * 1, 300, 0, "SAVEGAME 2", 231, 234); + addClickTextItem(kItemIdSavegame3, 0, 115 + 20 * 2, 300, 0, "SAVEGAME 3", 231, 234); + addClickTextItem(kItemIdSavegame4, 0, 115 + 20 * 3, 300, 0, "SAVEGAME 4", 231, 234); + addClickTextItem(kItemIdSavegame5, 0, 115 + 20 * 4, 300, 0, "SAVEGAME 5", 231, 234); + addClickTextItem(kItemIdSavegame6, 0, 115 + 20 * 5, 300, 0, "SAVEGAME 6", 231, 234); + addClickTextItem(kItemIdSavegame7, 0, 115 + 20 * 6, 300, 0, "SAVEGAME 7", 231, 234); + loadSavegamesList(); + setSavegameCaptions(); + break; + case kMenuIdSave: + drawString(0, 74, 320, 1, 229, _vm->getSysString(kStrSaveGame)); + addClickTextItem(kItemIdSavegameUp, 0, 155, 545, 1, "^", 255, 253); + addClickTextItem(kItemIdSavegameDown, 0, 195, 545, 1, "\\", 255, 253); + addClickTextItem(kItemIdCancel, 0, 275, 320, 0, _vm->getSysString(kStrCancel), 255, 253); + addClickTextItem(kItemIdSavegame1, 0, 115 + 20 * 0, 300, 0, "SAVEGAME 1", 231, 234); + addClickTextItem(kItemIdSavegame2, 0, 115 + 20 * 1, 300, 0, "SAVEGAME 2", 231, 234); + addClickTextItem(kItemIdSavegame3, 0, 115 + 20 * 2, 300, 0, "SAVEGAME 3", 231, 234); + addClickTextItem(kItemIdSavegame4, 0, 115 + 20 * 3, 300, 0, "SAVEGAME 4", 231, 234); + addClickTextItem(kItemIdSavegame5, 0, 115 + 20 * 4, 300, 0, "SAVEGAME 5", 231, 234); + addClickTextItem(kItemIdSavegame6, 0, 115 + 20 * 5, 300, 0, "SAVEGAME 6", 231, 234); + addClickTextItem(kItemIdSavegame7, 0, 115 + 20 * 6, 300, 0, "SAVEGAME 7", 231, 234); + newSlotNum = loadSavegamesList() + 1; + _savegames.push_back(SavegameItem(newSlotNum, Common::String::format("GAME %03d", _savegames.size() + 1))); + setSavegameCaptions(); + break; + case kMenuIdVolumes: + drawString(0, 74, 320, 1, 229, _vm->getSysString(kStrAdjustVolume)); + drawString(0, 130, 200, 0, 246, _vm->getSysString(kStrMaster)); + drawString(0, 155, 200, 0, 244, _vm->getSysString(kStrVoices)); + drawString(0, 180, 200, 0, 244, _vm->getSysString(kStrMusic)); + drawString(0, 205, 200, 0, 244, _vm->getSysString(kStrSoundFx)); + drawString(0, 230, 200, 0, 244, _vm->getSysString(kStrBackground)); + addClickTextItem(kItemIdDone, 0, 275, 200, 0, _vm->getSysString(kStrDone), 229, 253); + addClickTextItem(kItemIdCancel, 0, 275, 440, 0, _vm->getSysString(kStrCancel), 229, 253); + addClickTextItem(kItemIdMasterDown, 0, 130 + 25 * 0, 348, 1, "[", 229, 253); + addClickTextItem(kItemIdVoicesDown, 0, 130 + 25 * 1, 348, 1, "[", 229, 253); + addClickTextItem(kItemIdMusicDown, 0, 130 + 25 * 2, 348, 1, "[", 229, 253); + addClickTextItem(kItemIdSoundFXDown, 0, 130 + 25 * 3, 348, 1, "[", 229, 253); + addClickTextItem(kItemIdBackgroundDown, 0, 130 + 25 * 4, 348, 1, "[", 229, 253); + addClickTextItem(kItemIdMasterUp, 0, 130 + 25 * 0, 372, 1, "]", 229, 253); + addClickTextItem(kItemIdVoicesUp, 0, 130 + 25 * 1, 372, 1, "]", 229, 253); + addClickTextItem(kItemIdMusicUp, 0, 130 + 25 * 2, 372, 1, "]", 229, 253); + addClickTextItem(kItemIdSoundFXUp, 0, 130 + 25 * 3, 372, 1, "]", 229, 253); + addClickTextItem(kItemIdBackgroundUp, 0, 130 + 25 * 4, 372, 1, "]", 229, 253); + drawVolumeBar(kItemIdMaster); + drawVolumeBar(kItemIdVoices); + drawVolumeBar(kItemIdMusic); + drawVolumeBar(kItemIdSoundFX); + drawVolumeBar(kItemIdBackground); + break; + default: + break; + } + + for (Common::Array<Item>::iterator iter = _items.begin(); iter != _items.end(); iter++) { + drawItem((*iter).id, false); + } + +} + +void MenuSystem::enterItem(ItemID id) { + drawItem(id, true); +} + +void MenuSystem::leaveItem(ItemID id) { + drawItem(id, false); +} + +void MenuSystem::clickItem(ItemID id) { + //Item *item = getItem(id); + switch (id) { + // Main menu + case kItemIdSave: + _newMenuID = kMenuIdSave; + break; + case kItemIdLoad: + _newMenuID = kMenuIdLoad; + break; + case kItemIdToggleText: + setCfgText(!_cfgText, true); + if (!_cfgVoices && !_cfgText) + setCfgVoices(true, false); + break; + case kItemIdToggleVoices: + setCfgVoices(!_cfgVoices, true); + if (!_cfgVoices && !_cfgText) + setCfgText(true, false); + break; + case kItemIdVolumesMenu: + //debug("kItemIdVolumesMenu"); + _newMenuID = kMenuIdVolumes; + break; + case kItemIdPlay: + //debug("kItemIdPlay"); + _running = false; + break; + case kItemIdQuit: + _running = false; + _vm->quitGame(); + break; + // Volumes menu + case kItemIdMasterUp: + changeVolumeBar(kItemIdMaster, +1); + break; + case kItemIdVoicesUp: + changeVolumeBar(kItemIdVoices, +1); + break; + case kItemIdMusicUp: + changeVolumeBar(kItemIdMusic, +1); + break; + case kItemIdSoundFXUp: + changeVolumeBar(kItemIdSoundFX, +1); + break; + case kItemIdBackgroundUp: + changeVolumeBar(kItemIdBackground, +1); + break; + case kItemIdMasterDown: + changeVolumeBar(kItemIdMaster, -1); + break; + case kItemIdVoicesDown: + changeVolumeBar(kItemIdVoices, -1); + break; + case kItemIdMusicDown: + changeVolumeBar(kItemIdMusic, -1); + break; + case kItemIdSoundFXDown: + changeVolumeBar(kItemIdSoundFX, -1); + break; + case kItemIdBackgroundDown: + changeVolumeBar(kItemIdBackground, -1); + break; + case kItemIdCancel: + _newMenuID = kMenuIdMain; + break; + // Save/Load menu + case kItemIdSavegame1: + case kItemIdSavegame2: + case kItemIdSavegame3: + case kItemIdSavegame4: + case kItemIdSavegame5: + case kItemIdSavegame6: + case kItemIdSavegame7: + clickSavegameItem(id); + break; + case kItemIdDone: + _newMenuID = kMenuIdMain; + break; + case kItemIdSavegameUp: + scrollSavegames(-6); + break; + case kItemIdSavegameDown: + scrollSavegames(+6); + break; + default: + break; + } +} + +void MenuSystem::restoreRect(int x, int y, int w, int h) { + byte *src = (byte*)_background->getBasePtr(x, y); + byte *dst = _vm->_screen->_frontScreen + x + y * 640; + while (h--) { + memcpy(dst, src, w); + src += 640; + dst += 640; + } +} + +void MenuSystem::shadeRect(int x, int y, int w, int h, byte color1, byte color2) { + byte *src = (byte*)_background->getBasePtr(x, y); + for (int xc = 0; xc < w; xc++) { + src[xc] = color2; + src[xc + h * 640] = color1; + } + src += 640; + w -= 1; + h -= 1; + while (h--) { + src[0] = color2; + src[w] = color1; + src += 640; + } +} + +void MenuSystem::drawString(int16 x, int16 y, int w, uint fontNum, byte color, const char *text) { + fontNum = _vm->_screen->getFontResIndex(fontNum); + Font font(_vm->_res->load(fontNum)->data); + if (w) { + x = x + w - font.getTextWidth((const byte*)text) / 2; + } + _vm->_screen->drawString(x, y - font.getHeight(), color, fontNum, (const byte*)text, -1, NULL, true); + _needRedraw = true; +} + +int MenuSystem::loadSavegamesList() { + + int maxSlotNum = -1; + + _savegameListTopIndex = 0; + _savegames.clear(); + + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Toltecs::ToltecsEngine::SaveHeader header; + Common::String pattern = _vm->getTargetName(); + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + if (slotNum > maxSlotNum) + maxSlotNum = slotNum; + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Toltecs::ToltecsEngine::readSaveHeader(in, false, header) == Toltecs::ToltecsEngine::kRSHENoError) { + _savegames.push_back(SavegameItem(slotNum, header.description)); + //debug("%s -> %s", file->c_str(), header.description.c_str()); + } + delete in; + } + } + } + + return maxSlotNum; +} + +MenuSystem::SavegameItem *MenuSystem::getSavegameItemByID(ItemID id) { + switch (id) { + case kItemIdSavegame1: + case kItemIdSavegame2: + case kItemIdSavegame3: + case kItemIdSavegame4: + case kItemIdSavegame5: + case kItemIdSavegame6: + case kItemIdSavegame7: + return &_savegames[_savegameListTopIndex + id - kItemIdSavegame1]; + default: + return NULL; + } +} + +void MenuSystem::setSavegameCaptions() { + uint index = _savegameListTopIndex; + setItemCaption(getItem(kItemIdSavegame1), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame2), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame3), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame4), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame5), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame6), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); + setItemCaption(getItem(kItemIdSavegame7), index < _savegames.size() ? _savegames[index++]._description.c_str() : ""); +} + +void MenuSystem::scrollSavegames(int delta) { + int newPos = CLIP<int>(_savegameListTopIndex + delta, 0, _savegames.size() - 1); + _savegameListTopIndex = newPos; + restoreRect(80, 92, 440, 140); + setSavegameCaptions(); + drawItem(kItemIdSavegame1, false); + drawItem(kItemIdSavegame2, false); + drawItem(kItemIdSavegame3, false); + drawItem(kItemIdSavegame4, false); + drawItem(kItemIdSavegame5, false); + drawItem(kItemIdSavegame6, false); + drawItem(kItemIdSavegame7, false); +} + +void MenuSystem::clickSavegameItem(ItemID id) { + if (_currMenuID == kMenuIdLoad) { + SavegameItem *savegameItem = getSavegameItemByID(id); + //debug("slotNum = [%d]; description = [%s]", savegameItem->_slotNum, savegameItem->_description.c_str()); + //_vm->loadgame(savegameItem->_filename.c_str()); + _vm->requestLoadgame(savegameItem->_slotNum); + _running = false; + } else { + _editingDescription = true; + _editingDescriptionItem = getItem(id); + _editingDescriptionID = id; + _editingDescriptionItem->activeColor = 249; + _editingDescriptionItem->defaultColor = 249; + drawItem(_editingDescriptionID, true); + } +} + +void MenuSystem::setCfgText(bool value, bool active) { + if (_cfgText != value) { + Item *item = getItem(kItemIdToggleText); + _cfgText = value; + restoreRect(item->rect.left, item->rect.top, item->rect.width() + 1, item->rect.height() - 2); + setItemCaption(item, _vm->getSysString(_cfgText ? kStrTextOn : kStrTextOff)); + drawItem(kItemIdToggleText, true); + } +} + +void MenuSystem::setCfgVoices(bool value, bool active) { + if (_cfgVoices != value) { + Item *item = getItem(kItemIdToggleVoices); + _cfgVoices = value; + restoreRect(item->rect.left, item->rect.top, item->rect.width() + 1, item->rect.height() - 2); + setItemCaption(item, _vm->getSysString(_cfgVoices ? kStrVoicesOn : kStrVoicesOff)); + drawItem(kItemIdToggleVoices, true); + } +} + +void MenuSystem::drawVolumeBar(ItemID itemID) { + int w = 440, y, volume; + char text[21]; + + switch (itemID) { + case kItemIdMaster: + y = 130 + 25 * 0; + volume = _cfgMasterVolume; + break; + case kItemIdVoices: + y = 130 + 25 * 1; + volume = _cfgVoicesVolume; + break; + case kItemIdMusic: + y = 130 + 25 * 2; + volume = _cfgMusicVolume; + break; + case kItemIdSoundFX: + y = 130 + 25 * 3; + volume = _cfgSoundFXVolume; + break; + case kItemIdBackground: + y = 130 + 25 * 4; + volume = _cfgBackgroundVolume; + break; + default: + return; + } + + Font font(_vm->_res->load(_vm->_screen->getFontResIndex(1))->data); + restoreRect(390, y - font.getHeight(), 100, 25); + + for (int i = 0; i < volume; i++) + text[i] = '|'; + text[volume] = 0; + + drawString(0, y, w, 0, 246, text); + +} + +void MenuSystem::changeVolumeBar(ItemID itemID, int delta) { + + int *volume, newVolume; + + switch (itemID) { + case kItemIdMaster: + volume = &_cfgMasterVolume; + break; + case kItemIdVoices: + volume = &_cfgVoicesVolume; + break; + case kItemIdMusic: + volume = &_cfgMusicVolume; + break; + case kItemIdSoundFX: + volume = &_cfgSoundFXVolume; + break; + case kItemIdBackground: + volume = &_cfgBackgroundVolume; + break; + default: + return; + } + + newVolume = CLIP(*volume + delta, 0, 20); + + if (newVolume != *volume) { + *volume = newVolume; + drawVolumeBar(itemID); + } + +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/menu.h b/engines/toltecs/menu.h new file mode 100644 index 0000000000..3e2c2da8d9 --- /dev/null +++ b/engines/toltecs/menu.h @@ -0,0 +1,167 @@ +/* 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 TOLTECS_MENU_H +#define TOLTECS_MENU_H + +#include "common/array.h" +#include "common/str-array.h" + +namespace Toltecs { + +enum MenuID { + kMenuIdNone, + kMenuIdMain, + kMenuIdSave, + kMenuIdLoad, + kMenuIdVolumes +}; + +enum ItemID { + kItemIdNone, + // Main menu + kItemIdSave, + kItemIdLoad, + kItemIdToggleText, + kItemIdToggleVoices, + kItemIdVolumesMenu, + kItemIdPlay, + kItemIdQuit, + // Volumes menu + kItemIdMasterUp, + kItemIdVoicesUp, + kItemIdMusicUp, + kItemIdSoundFXUp, + kItemIdBackgroundUp, + kItemIdMasterDown, + kItemIdVoicesDown, + kItemIdMusicDown, + kItemIdSoundFXDown, + kItemIdBackgroundDown, + kItemIdMaster, + kItemIdVoices, + kItemIdMusic, + kItemIdSoundFX, + kItemIdBackground, + kItemIdDone, + kItemIdCancel, + // Save/load menu + kItemIdSavegameUp, + kItemIdSavegameDown, + kItemIdSavegame1, + kItemIdSavegame2, + kItemIdSavegame3, + kItemIdSavegame4, + kItemIdSavegame5, + kItemIdSavegame6, + kItemIdSavegame7, + // TODO + kMenuIdDummy +}; + +class MenuSystem { + +public: + MenuSystem(ToltecsEngine *vm); + ~MenuSystem(); + + int run(); + void update(); + void handleEvents(); + +protected: + + struct Item { + Common::Rect rect; + ItemID id; + Common::String caption; + byte defaultColor, activeColor; + int x, y, w; + uint fontNum; + }; + + struct SavegameItem { + int _slotNum; + Common::String _description; + SavegameItem() + : _slotNum(-1), _description("") {} + SavegameItem(int slotNum, Common::String description) + : _slotNum(slotNum), _description(description) {} + }; + + ToltecsEngine *_vm; + Graphics::Surface *_background; + + bool _running; + MenuID _currMenuID, _newMenuID; + ItemID _currItemID; + int _top; + int _savegameListTopIndex; + bool _editingDescription; + ItemID _editingDescriptionID; + Item *_editingDescriptionItem; + bool _needRedraw; + + Common::Array<Item> _items; + Common::Array<SavegameItem> _savegames; + + bool _cfgText, _cfgVoices; + int _cfgMasterVolume, _cfgVoicesVolume, _cfgMusicVolume, _cfgSoundFXVolume, _cfgBackgroundVolume; + + void addClickTextItem(ItemID id, int x, int y, int w, uint fontNum, const char *caption, byte defaultColor, byte activeColor); + + void drawItem(ItemID itemID, bool active); + void handleMouseMove(int x, int y); + void handleMouseClick(int x, int y); + void handleKeyDown(const Common::KeyState& kbd); + + ItemID findItemAt(int x, int y); + Item *getItem(ItemID id); + void setItemCaption(Item *item, const char *caption); + + void initMenu(MenuID menuID); + + void enterItem(ItemID id); + void leaveItem(ItemID id); + void clickItem(ItemID id); + + void restoreRect(int x, int y, int w, int h); + void shadeRect(int x, int y, int w, int h, byte color1, byte color2); + void drawString(int16 x, int16 y, int w, uint fontNum, byte color, const char *text); + + SavegameItem *getSavegameItemByID(ItemID id); + + int loadSavegamesList(); + void setSavegameCaptions(); + void scrollSavegames(int delta); + void clickSavegameItem(ItemID id); + void setCfgText(bool value, bool active); + void setCfgVoices(bool value, bool active); + void drawVolumeBar(ItemID itemID); + void changeVolumeBar(ItemID itemID, int delta); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_MENU_H */ diff --git a/engines/toltecs/microtiles.cpp b/engines/toltecs/microtiles.cpp new file mode 100644 index 0000000000..38e4f6578e --- /dev/null +++ b/engines/toltecs/microtiles.cpp @@ -0,0 +1,220 @@ +/* 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 "toltecs/microtiles.h" + +namespace Toltecs { + +MicroTileArray::MicroTileArray(int16 width, int16 height) { + _tilesW = (width / TileSize) + ((width % TileSize) > 0 ? 1 : 0); + _tilesH = (height / TileSize) + ((height % TileSize) > 0 ? 1 : 0); + _tiles = new BoundingBox[_tilesW * _tilesH]; + clear(); +} + +MicroTileArray::~MicroTileArray() { + delete[] _tiles; +} + +void MicroTileArray::addRect(Common::Rect r) { + + int ux0, uy0, ux1, uy1; + int tx0, ty0, tx1, ty1; + int ix0, iy0, ix1, iy1; + + r.clip(Common::Rect(0, 0, 639, 399)); + + ux0 = r.left / TileSize; + uy0 = r.top / TileSize; + ux1 = r.right / TileSize; + uy1 = r.bottom / TileSize; + + tx0 = r.left % TileSize; + ty0 = r.top % TileSize; + tx1 = r.right % TileSize; + ty1 = r.bottom % TileSize; + + for (int yc = uy0; yc <= uy1; yc++) { + for (int xc = ux0; xc <= ux1; xc++) { + ix0 = (xc == ux0) ? tx0 : 0; + ix1 = (xc == ux1) ? tx1 : TileSize - 1; + iy0 = (yc == uy0) ? ty0 : 0; + iy1 = (yc == uy1) ? ty1 : TileSize - 1; + updateBoundingBox(_tiles[xc + yc * _tilesW], ix0, iy0, ix1, iy1); + } + } + +} + +void MicroTileArray::clear() { + memset(_tiles, 0, _tilesW * _tilesH * sizeof(BoundingBox)); +} + +byte MicroTileArray::TileX0(const BoundingBox &boundingBox) { + return (boundingBox >> 24) & 0xFF; +} + +byte MicroTileArray::TileY0(const BoundingBox &boundingBox) { + return (boundingBox >> 16) & 0xFF; +} + +byte MicroTileArray::TileX1(const BoundingBox &boundingBox) { + return (boundingBox >> 8) & 0xFF; +} + +byte MicroTileArray::TileY1(const BoundingBox &boundingBox) { + return boundingBox & 0xFF; +} + +bool MicroTileArray::isBoundingBoxEmpty(const BoundingBox &boundingBox) { + return boundingBox == EmptyBoundingBox; +} + +bool MicroTileArray::isBoundingBoxFull(const BoundingBox &boundingBox) { + return boundingBox == FullBoundingBox; +} + +void MicroTileArray::setBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1) { + boundingBox = (x0 << 24) | (y0 << 16) | (x1 << 8) | y1; +} + +void MicroTileArray::updateBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1) { + if (!isBoundingBoxEmpty(boundingBox)) { + x0 = MIN(TileX0(boundingBox), x0); + y0 = MIN(TileY0(boundingBox), y0); + x1 = MAX(TileX1(boundingBox), x1); + y1 = MAX(TileY1(boundingBox), y1); + } + setBoundingBox(boundingBox, x0, y0, x1, y1); +} + +Common::Rect * MicroTileArray::getRectangles(int *num_rects, int min_x, int min_y, int max_x, int max_y) { + + Common::Rect *rects = new Common::Rect[_tilesW * _tilesH]; + + int n_rects = 0; + int x, y; + int x0, y0, x1, y1; + int i = 0; + + for (y = 0; y < _tilesH; ++y) { + for (x = 0; x < _tilesW; ++x) { + + int start; + int finish = 0; + BoundingBox boundingBox; + + boundingBox = _tiles[i]; + + if (isBoundingBoxEmpty(boundingBox)) + goto next; + + x0 = (x * TileSize) + TileX0(boundingBox); + y0 = (y * TileSize) + TileY0(boundingBox); + y1 = (y * TileSize) + TileY1(boundingBox); + + x0 = CLIP (x0, min_x, max_x); + y0 = CLIP (y0, min_y, max_y); + y1 = CLIP (y1, min_y, max_y); + + // FIXME: Why is the following code in an #if block? +#if 1 + start = i; + + if (TileX1(boundingBox) != TileSize - 1 || x == _tilesW - 1) { + /* the tile does not continue */ + goto done; + } + + while (!finish) { + ++x; + ++i; + + if (x == _tilesW || i >= _tilesW * _tilesH || + TileY0(_tiles[i]) != TileY0(boundingBox) || + TileY1(_tiles[i]) != TileY1(boundingBox) || + TileX0(_tiles[i]) != 0) + { + --x; + --i; + finish = 1; + } + } + + done: +#endif + x1 = (x * TileSize) + TileX1(_tiles[i]); + + x1 = CLIP (x1, min_x, max_x); + + // FIXME: Why is the following code in an #if block? + + #if 1 + + rects[n_rects].left = x0; + rects[n_rects].top = y0; + rects[n_rects].right = x1 + 1; + rects[n_rects].bottom = y1 + 1; + n_rects++; + + #else + + // FIXME: Why is this code disabled? + + if (glom [start] != -1 && /* try to glom */ + rects [glom [start]].left == x0 && + rects [glom [start]].right == x1 && + rects [glom [start]].bottom == y0 - 1) + { + rects [glom [start]].bottom = y1; + if (y != tilesH - 1) { + glom [start + tilesW] = glom [start]; + } + } else { + rects[n_rects].left = x0; + rects[n_rects].top = y0; + rects[n_rects].right = x1; + rects[n_rects].bottom = y1; + if (y != tilesH - 1) { + glom [start + tilesW] = n_rects; + } + n_rects ++; + } + + #endif + + next: + ++i; + + } + } + + *num_rects = n_rects; + + //delete glom; + + return rects; + +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/microtiles.h b/engines/toltecs/microtiles.h new file mode 100644 index 0000000000..53933621a9 --- /dev/null +++ b/engines/toltecs/microtiles.h @@ -0,0 +1,61 @@ +/* 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 TOLTECS_MICROTILES_H +#define TOLTECS_MICROTILES_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/rect.h" + +namespace Toltecs { + +typedef uint32 BoundingBox; + +const BoundingBox FullBoundingBox = 0x00001F1F; +const BoundingBox EmptyBoundingBox = 0x00000000; +const int TileSize = 32; + +class MicroTileArray { +public: + MicroTileArray(int16 width, int16 height); + ~MicroTileArray(); + void addRect(Common::Rect r); + void clear(); + Common::Rect *getRectangles(int *num_rects, int min_x, int min_y, int max_x, int max_y); +protected: + BoundingBox *_tiles; + int16 _tilesW, _tilesH; + byte TileX0(const BoundingBox &boundingBox); + byte TileY0(const BoundingBox &boundingBox); + byte TileX1(const BoundingBox &boundingBox); + byte TileY1(const BoundingBox &boundingBox); + bool isBoundingBoxEmpty(const BoundingBox &boundingBox); + bool isBoundingBoxFull(const BoundingBox &boundingBox); + void setBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1); + void updateBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1); +}; + +} // namespace Toltecs + +#endif // TOLTECS_MICROTILES_H diff --git a/engines/toltecs/module.mk b/engines/toltecs/module.mk new file mode 100644 index 0000000000..aa4a6f376b --- /dev/null +++ b/engines/toltecs/module.mk @@ -0,0 +1,28 @@ +MODULE := engines/toltecs + +MODULE_OBJS = \ + animation.o \ + detection.o \ + menu.o \ + microtiles.o \ + movie.o \ + music.o \ + palette.o \ + toltecs.o \ + render.o \ + resource.o \ + saveload.o \ + screen.o \ + script.o \ + segmap.o \ + sound.o \ + sprite.o + + +# This module can be built as a plugin +ifeq ($(ENABLE_TOLTECS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/toltecs/movie.cpp b/engines/toltecs/movie.cpp new file mode 100644 index 0000000000..054683b63e --- /dev/null +++ b/engines/toltecs/movie.cpp @@ -0,0 +1,290 @@ +/* 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/mixer.h" +#include "audio/decoders/raw.h" + +#include "toltecs/toltecs.h" +#include "toltecs/movie.h" +#include "toltecs/palette.h" +#include "toltecs/resource.h" +#include "toltecs/screen.h" +#include "toltecs/script.h" + +namespace Toltecs { + +enum ChunkTypes { + kChunkFirstImage = 0, + kChunkSubsequentImages = 1, + kChunkPalette = 2, + kChunkUnused = 3, + kChunkAudio = 4, + kChunkShowSubtitle = 5, + kChunkShakeScreen = 6, + kChunkSetupSubtitles = 7, + kChunkStopSubtitles = 8 +}; + +MoviePlayer::MoviePlayer(ToltecsEngine *vm) : _vm(vm) { +} + +MoviePlayer::~MoviePlayer() { +} + +void MoviePlayer::playMovie(uint resIndex) { + + const uint32 subtitleSlot = kMaxScriptSlots - 1; + int16 savedSceneWidth = _vm->_sceneWidth; + int16 savedSceneHeight = _vm->_sceneHeight; + int16 savedCameraHeight = _vm->_cameraHeight; + int16 savedCameraX = _vm->_cameraX; + int16 savedCameraY = _vm->_cameraY; + int16 savedGuiHeight = _vm->_guiHeight; + byte moviePalette[768]; + + _vm->_isSaveAllowed = false; + + memset(moviePalette, 0, sizeof(moviePalette)); + + _vm->_screen->finishTalkTextItems(); + _vm->_screen->clearSprites(); + + _vm->_arc->openResource(resIndex); + + _frameCount = _vm->_arc->readUint32LE(); + _chunkCount = _vm->_arc->readUint32LE(); + + // TODO: Figure out rest of the header + _vm->_arc->readUint32LE(); + _vm->_arc->readUint32LE(); + _framesPerSoundChunk = _vm->_arc->readUint32LE(); + _vm->_arc->readUint32LE(); + + _vm->_sceneWidth = 640; + _vm->_sceneHeight = 400; + _vm->_cameraHeight = 400; + _vm->_cameraX = 0; + _vm->_cameraY = 0; + _vm->_guiHeight = 0; + + _audioStream = Audio::makeQueuingAudioStream(22050, false); + + _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_audioStreamHandle, _audioStream); + + _soundChunkFramesLeft = 0; + _lastPrefetchOfs = 0; + + fetchAudioChunks(); + + uint32 lastTime = _vm->_mixer->getSoundElapsedTime(_audioStreamHandle); + + while (_chunkCount--) { + + byte chunkType = _vm->_arc->readByte(); + uint32 chunkSize = _vm->_arc->readUint32LE(); + byte *chunkBuffer = NULL; + uint32 movieOffset; + + debug(0, "chunkType = %d; chunkSize = %d", chunkType, chunkSize); + + // Skip audio chunks - we've already queued them in + // fetchAudioChunks() above + if (chunkType == kChunkAudio) { + _vm->_arc->skip(chunkSize); + } else { + chunkBuffer = new byte[chunkSize]; + _vm->_arc->read(chunkBuffer, chunkSize); + } + + movieOffset = _vm->_arc->pos(); + + switch (chunkType) { + case kChunkFirstImage: + case kChunkSubsequentImages: + unpackRle(chunkBuffer, _vm->_screen->_backScreen); + // TODO: Rework this + _vm->_screen->updateShakeScreen(); + _vm->_screen->_fullRefresh = true; + _vm->updateInput(); + _vm->drawScreen(); + + _soundChunkFramesLeft--; + if (_soundChunkFramesLeft <= _framesPerSoundChunk) { + fetchAudioChunks(); + } + + while (_vm->_mixer->getSoundElapsedTime(_audioStreamHandle) < lastTime + 111) { + g_system->delayMillis(10); + } + + lastTime = _vm->_mixer->getSoundElapsedTime(_audioStreamHandle); + + break; + case kChunkPalette: + unpackPalette(chunkBuffer, moviePalette, 256, 3); + _vm->_palette->setFullPalette(moviePalette); + break; + case kChunkUnused: + error("Chunk considered to be unused has been encountered"); + case kChunkAudio: + // Already processed + break; + case kChunkShowSubtitle: + // TODO: Check if the text is a subtitle (last character == 0xFE). + // If so, don't show it if text display is disabled. + memcpy(_vm->_script->getSlotData(subtitleSlot), chunkBuffer, chunkSize); + _vm->_screen->updateTalkText(subtitleSlot, 0); + break; + case kChunkShakeScreen: // start/stop shakescreen effect + if (chunkBuffer[0] == 0xFF) + _vm->_screen->stopShakeScreen(); + else + _vm->_screen->startShakeScreen(chunkBuffer[0]); + break; + case kChunkSetupSubtitles: // setup subtitle parameters + _vm->_screen->_talkTextY = READ_LE_UINT16(chunkBuffer + 0); + _vm->_screen->_talkTextX = READ_LE_UINT16(chunkBuffer + 2); + _vm->_screen->_talkTextFontColor = ((chunkBuffer[4] << 4) & 0xF0) | ((chunkBuffer[4] >> 4) & 0x0F); + debug(0, "_talkTextX = %d; _talkTextY = %d; _talkTextFontColor = %d", + _vm->_screen->_talkTextX, _vm->_screen->_talkTextY, _vm->_screen->_talkTextFontColor); + break; + case kChunkStopSubtitles: + _vm->_script->getSlotData(subtitleSlot)[0] = 0xFF; + _vm->_screen->finishTalkTextItems(); + break; + default: + error("MoviePlayer::playMovie(%04X) Unknown chunk type %d at %08X", resIndex, chunkType, _vm->_arc->pos() - 5 - chunkSize); + } + + delete[] chunkBuffer; + + _vm->_arc->seek(movieOffset, SEEK_SET); + + if (!handleInput()) + break; + + } + + _audioStream->finish(); + _vm->_mixer->stopHandle(_audioStreamHandle); + + _vm->_arc->closeResource(); + + debug(0, "playMovie() done"); + + _vm->_sceneWidth = savedSceneWidth; + _vm->_sceneHeight = savedSceneHeight; + _vm->_cameraHeight = savedCameraHeight; + _vm->_cameraX = savedCameraX; + _vm->_cameraY = savedCameraY; + _vm->_guiHeight = savedGuiHeight; + + _vm->_isSaveAllowed = true; +} + +void MoviePlayer::fetchAudioChunks() { + + uint32 startOfs = _vm->_arc->pos(); + uint32 chunkCount = _chunkCount; + uint prefetchChunkCount = 0; + + if (_lastPrefetchOfs != 0) + _vm->_arc->seek(_lastPrefetchOfs, SEEK_SET); + + while (chunkCount-- && prefetchChunkCount < _framesPerSoundChunk / 2) { + byte chunkType = _vm->_arc->readByte(); + uint32 chunkSize = _vm->_arc->readUint32LE(); + if (chunkType == 4) { + byte *chunkBuffer = (byte*)malloc(chunkSize); + _vm->_arc->read(chunkBuffer, chunkSize); + _audioStream->queueBuffer(chunkBuffer, chunkSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + chunkBuffer = NULL; + prefetchChunkCount++; + _soundChunkFramesLeft += _framesPerSoundChunk; + } else { + _vm->_arc->seek(chunkSize, SEEK_CUR); + } + } + + _lastPrefetchOfs = _vm->_arc->pos(); + + _vm->_arc->seek(startOfs, SEEK_SET); + +} + +void MoviePlayer::unpackPalette(byte *source, byte *dest, int elemCount, int elemSize) { + int ofs = 0, size = elemCount * elemSize; + while (ofs < size) { + byte len; + len = *source++; + if (len == 0) { + len = *source++; + } else { + byte value = *source++; + memset(dest, value, len); + } + ofs += len; + dest += len; + } +} + +void MoviePlayer::unpackRle(byte *source, byte *dest) { + int size = 256000; + while (size > 0) { + byte a = *source++; + byte b = *source++; + if (a == 0) { + dest += b; + size -= b; + } else { + memset(dest, b, a); + dest += a; + size -= a; + } + } +} + +bool MoviePlayer::handleInput() { + Common::Event event; + Common::EventManager *eventMan = g_system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + return false; + break; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + return false; + case Common::EVENT_QUIT: + _vm->quitGame(); + return false; + break; + default: + break; + } + } + return !_vm->shouldQuit(); +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/movie.h b/engines/toltecs/movie.h new file mode 100644 index 0000000000..aecfac240f --- /dev/null +++ b/engines/toltecs/movie.h @@ -0,0 +1,59 @@ +/* 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 TOLTECS_MOVIE_H +#define TOLTECS_MOVIE_H + +#include "audio/audiostream.h" +#include "audio/mixer.h" // for Audio::SoundHandle + +namespace Toltecs { + +class MoviePlayer { + +public: + MoviePlayer(ToltecsEngine *vm); + ~MoviePlayer(); + + void playMovie(uint resIndex); + +protected: + ToltecsEngine *_vm; + Audio::QueuingAudioStream *_audioStream; + Audio::SoundHandle _audioStreamHandle; + + uint32 _chunkCount, _frameCount, _lastPrefetchOfs; + uint32 _soundChunkFramesLeft, _framesPerSoundChunk; + + void unpackPalette(byte *source, byte *dest, int elemCount, int elemSize); + void unpackRle(byte *source, byte *dest); + + void fetchAudioChunks(); + + bool handleInput(); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_MOVIE_H */ diff --git a/engines/toltecs/music.cpp b/engines/toltecs/music.cpp new file mode 100644 index 0000000000..ef44421de9 --- /dev/null +++ b/engines/toltecs/music.cpp @@ -0,0 +1,104 @@ +/* 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. + * + */ + +// FIXME: This code is taken from SAGA and needs more work (e.g. setVolume). + +#include "toltecs/music.h" + +#include "audio/midiparser.h" + +namespace Toltecs { + +MusicPlayer::MusicPlayer(bool isGM) : _isGM(isGM), _buffer(NULL) { + MidiPlayer::createDriver(); + + int ret = _driver->open(); + if (ret == 0) { + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + _driver->setTimerCallback(this, &timerCallback); + } +} + +void MusicPlayer::send(uint32 b) { + if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } + + Audio::MidiPlayer::send(b); +} + +void MusicPlayer::playMIDI(const byte *data, uint32 size, bool loop) { + Common::StackLock lock(_mutex); + + stopAndClear(); + + _buffer = new byte[size]; + memcpy(_buffer, data, size); + + MidiParser *parser; + + if (!memcmp(data, "FORM", 4)) + parser = MidiParser::createParser_XMIDI(NULL); + else + parser = MidiParser::createParser_SMF(); + + if (parser->loadMusic(_buffer, size)) { + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(_driver->getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1); + + _parser = parser; + + setVolume(127); + + _isLooping = loop; + _isPlaying = true; + } else { + delete parser; + } +} + +void MusicPlayer::pause() { + setVolume(-1); + _isPlaying = false; +} + +void MusicPlayer::resume() { + setVolume(127); + _isPlaying = true; +} + +void MusicPlayer::stopAndClear() { + Common::StackLock lock(_mutex); + stop(); + + delete[] _buffer; + _buffer = NULL; +} + +} // End of namespace Made diff --git a/engines/toltecs/music.h b/engines/toltecs/music.h new file mode 100644 index 0000000000..c7189e89d6 --- /dev/null +++ b/engines/toltecs/music.h @@ -0,0 +1,53 @@ +/* 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. + * + */ + +// Music class + +#ifndef MADE_MUSIC_H +#define MADE_MUSIC_H + +#include "audio/midiplayer.h" + +namespace Toltecs { + +class MusicPlayer : public Audio::MidiPlayer { +public: + MusicPlayer(bool isGM = true); + + void playMIDI(const byte *data, uint32 size, bool loop = false); + void pause(); + void resume(); + void stopAndClear(); + + // MidiDriver_BASE interface implementation + virtual void send(uint32 b); + +protected: + bool _isGM; + +private: + byte *_buffer; +}; + +} // End of namespace Made + +#endif diff --git a/engines/toltecs/palette.cpp b/engines/toltecs/palette.cpp new file mode 100644 index 0000000000..8857abc698 --- /dev/null +++ b/engines/toltecs/palette.cpp @@ -0,0 +1,244 @@ +/* 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 "graphics/palette.h" + +#include "toltecs/toltecs.h" +#include "toltecs/palette.h" +#include "toltecs/resource.h" + +namespace Toltecs { + +Palette::Palette(ToltecsEngine *vm) : _vm(vm) { + + clearFragments(); + + memset(_colorTransTable, 0, sizeof(_colorTransTable)); + +} + +Palette::~Palette() { +} + +void Palette::setFullPalette(byte *palette) { + byte colors[768]; + for (int i = 0; i < 256; i++) { + colors[i * 3 + 0] = palette[i * 3 + 0] << 2; + colors[i * 3 + 1] = palette[i * 3 + 1] << 2; + colors[i * 3 + 2] = palette[i * 3 + 2] << 2; + } + _vm->_system->getPaletteManager()->setPalette((const byte *)colors, 0, 256); + _vm->_system->updateScreen(); +} + +void Palette::getFullPalette(byte *palette) { + byte colors[768]; + _vm->_system->getPaletteManager()->grabPalette(colors, 0, 256); + for (int i = 0; i < 256; i++) { + palette[i * 3 + 0] = colors[i * 3 + 0] >> 2; + palette[i * 3 + 1] = colors[i * 3 + 1] >> 2; + palette[i * 3 + 2] = colors[i * 3 + 2] >> 2; + } +} + +void Palette::setDeltaPalette(byte *palette, byte mask, char deltaValue, int16 count, int16 startIndex) { + byte colors[768]; + + byte *palPtr = palette + startIndex * 3; + int16 index = startIndex, colorCount = count; + byte rgb; + + count++; + + _vm->_system->getPaletteManager()->grabPalette(colors, 0, 256); + + deltaValue *= -1; + + while (count--) { + rgb = *palPtr++; + if (mask & 1) colors[index * 3 + 0] = CLIP<int>(rgb + deltaValue, 0, 63) << 2; + rgb = *palPtr++; + if (mask & 2) colors[index * 3 + 1] = CLIP<int>(rgb + deltaValue, 0, 63) << 2; + rgb = *palPtr++; + if (mask & 4) colors[index * 3 + 2] = CLIP<int>(rgb + deltaValue, 0, 63) << 2; + index++; + } + + debug(0, "startIndex = %d; colorCount = %d", startIndex, colorCount); + + _vm->_system->getPaletteManager()->setPalette((const byte *)colors, 0, 256); +} + +void Palette::loadAddPalette(uint resIndex, byte startIndex) { + Resource *paletteResource = _vm->_res->load(resIndex); + memcpy(&_mainPalette[startIndex * 3], paletteResource->data, paletteResource->size); +} + +void Palette::loadAddPaletteFrom(byte *source, byte startIndex, byte count) { + memcpy(&_mainPalette[startIndex * 3], source, count * 3); +} + +void Palette::addFragment(uint resIndex, int16 id) { + debug(0, "Palette::addFragment(%d, %d)", resIndex, id); + + Resource *fragmentResource = _vm->_res->load(resIndex); + byte count = fragmentResource->size / 3; + + memcpy(&_mainPalette[_fragmentIndex * 3], fragmentResource->data, count * 3); + + PaletteFragment fragment; + fragment.id = id; + fragment.index = _fragmentIndex; + fragment.count = count; + _fragments.push_back(fragment); + + debug(0, "Palette::addFragment() index = %02X; count = %02X", fragment.index, fragment.count); + + _fragmentIndex += count; + +} + +uint16 Palette::findFragment(int16 id) { + debug(0, "Palette::findFragment(%d)", id); + + uint16 result = 0; + for (PaletteFragmentArray::iterator iter = _fragments.begin(); iter != _fragments.end(); iter++) { + PaletteFragment fragment = *iter; + if (fragment.id == id) { + result = (fragment.count << 8) | fragment.index; + break; + } + } + + debug(0, "Palette::findFragment() result = %04X", result); + + return result; +} + +void Palette::clearFragments() { + debug(0, "Palette::clearFragments()"); + _fragmentIndex = 128; + _fragments.clear(); +} + +void Palette::buildColorTransTable(byte limit, char deltaValue, byte mask) { + + // TODO + + byte r = 0, g = 0, b = 0; + + mask &= 7; + + for (int i = 0; i < 256; i++) { + + if (deltaValue < 0) { + // TODO + warning("Palette::buildColorTransTable(%d, %d, %02X) not yet implemented!", limit, deltaValue, mask); + } else { + r = _mainPalette[i * 3 + 0]; + g = _mainPalette[i * 3 + 1]; + b = _mainPalette[i * 3 + 2]; + if (MAX(r, MAX(b, g)) >= limit) { + if ((mask & 1) && r >= deltaValue) + r -= deltaValue; + if ((mask & 2) && g >= deltaValue) + g -= deltaValue; + if ((mask & 4) && b >= deltaValue) + b -= deltaValue; + } + } + + int bestIndex = 0; + uint16 bestMatch = 0xFFFF; + + for (int j = 0; j < 256; j++) { + byte distance = ABS(_mainPalette[j * 3 + 0] - r) + ABS(_mainPalette[j * 3 + 1] - g) + ABS(_mainPalette[j * 3 + 2] - b); + byte maxColor = MAX(_mainPalette[j * 3 + 0], MAX(_mainPalette[j * 3 + 1], _mainPalette[j * 3 + 2])); + uint16 match = (distance << 8) | maxColor; + if (match < bestMatch) { + bestMatch = match; + bestIndex = j; + } + } + + _colorTransTable[i] = bestIndex; + + } + +} + +void Palette::buildColorTransTable2(byte limit, char deltaValue, byte mask) { + + // TODO + +} + +void Palette::saveState(Common::WriteStream *out) { + + // Save currently active palette + byte palette[768]; + getFullPalette(palette); + out->write(palette, 768); + + out->write(_mainPalette, 768); + out->write(_animPalette, 768); + out->write(_colorTransTable, 256); + + uint16 fragmentCount = _fragments.size(); + out->writeUint16LE(fragmentCount); + for (PaletteFragmentArray::iterator iter = _fragments.begin(); iter != _fragments.end(); iter++) { + PaletteFragment fragment = *iter; + out->writeUint16LE(fragment.id); + out->writeByte(fragment.index); + out->writeByte(fragment.count); + } + out->writeByte(_fragmentIndex); + +} + +void Palette::loadState(Common::ReadStream *in) { + + // Save currently active palette + byte palette[768]; + in->read(palette, 768); + setFullPalette(palette); + + in->read(_mainPalette, 768); + in->read(_animPalette, 768); + in->read(_colorTransTable, 256); + + uint16 fragmentCount = in->readUint16LE(); + _fragments.clear(); + for (uint16 i = 0; i < fragmentCount; i++) { + PaletteFragment fragment; + fragment.id = in->readUint16LE(); + fragment.index = in->readByte(); + fragment.count = in->readByte(); + _fragments.push_back(fragment); + } + _fragmentIndex = in->readByte(); + +} + + +} // End of namespace Toltecs diff --git a/engines/toltecs/palette.h b/engines/toltecs/palette.h new file mode 100644 index 0000000000..140d59b431 --- /dev/null +++ b/engines/toltecs/palette.h @@ -0,0 +1,85 @@ +/* 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 TOLTECS_PALETTE_H +#define TOLTECS_PALETTE_H + +#include "common/array.h" +#include "common/system.h" + +#include "toltecs/toltecs.h" + +namespace Toltecs { + +//#define ROT(index) (((index << 4) & 0xF0) | ((index >> 4) & 0x0F)) +//#define ROT(index) (index) + +class Palette { +public: + Palette(ToltecsEngine *vm); + ~Palette(); + + void setFullPalette(byte *palette); + void getFullPalette(byte *palette); + void setDeltaPalette(byte *palette, byte mask, char deltaValue, int16 count, int16 startIndex); + + void loadAddPalette(uint resIndex, byte startIndex); + void loadAddPaletteFrom(byte *source, byte startIndex, byte count); + + void addFragment(uint resIndex, int16 id); + uint16 findFragment(int16 id); + void clearFragments(); + + void buildColorTransTable(byte limit, char deltaValue, byte mask); + void buildColorTransTable2(byte limit, char deltaValue, byte mask); + byte getColorTransPixel(byte pixel) const { return _colorTransTable[pixel]; } + + byte *getMainPalette() { return _mainPalette; } + byte *getAnimPalette() { return _animPalette; } + + void saveState(Common::WriteStream *out); + void loadState(Common::ReadStream *in); + +protected: + + struct PaletteFragment { + int16 id; + byte index, count; + }; + + typedef Common::Array<PaletteFragment> PaletteFragmentArray; + + ToltecsEngine *_vm; + + byte _mainPalette[768]; + byte _animPalette[768]; + byte _colorTransTable[256]; + + PaletteFragmentArray _fragments; + byte _fragmentIndex; + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_PALETTE_H */ diff --git a/engines/toltecs/render.cpp b/engines/toltecs/render.cpp new file mode 100644 index 0000000000..de5e77fb50 --- /dev/null +++ b/engines/toltecs/render.cpp @@ -0,0 +1,311 @@ +/* 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/system.h" + +#include "toltecs/toltecs.h" +#include "toltecs/render.h" +#include "toltecs/resource.h" + +namespace Toltecs { + +Common::Rect makeRect(int16 x, int16 y, int16 width, int16 height) { + Common::Rect rect; + rect.left = x; + rect.top = y; + rect.setWidth(width); + rect.setHeight(height); + return rect; +} + +RenderQueue::RenderQueue(ToltecsEngine *vm) : _vm(vm) { + _currQueue = new RenderQueueArray(); + _prevQueue = new RenderQueueArray(); + _updateUta = new MicroTileArray(640, 400); +} + +RenderQueue::~RenderQueue() { + delete _currQueue; + delete _prevQueue; + delete _updateUta; +} + +void RenderQueue::addSprite(SpriteDrawItem &sprite) { + + RenderQueueItem item; + item.type = kSprite; + item.flags = kRefresh; + item.rect = makeRect(sprite.x - _vm->_cameraX, sprite.y - _vm->_cameraY, sprite.width, sprite.height); + item.priority = sprite.priority; + + item.sprite = sprite; + item.sprite.x -= _vm->_cameraX; + item.sprite.y -= _vm->_cameraY; + + // Add sprite sorted by priority + RenderQueueArray::iterator iter = _currQueue->begin(); + while (iter != _currQueue->end() && (*iter).priority <= item.priority) { + iter++; + } + _currQueue->insert(iter, item); + +} + +void RenderQueue::addText(int16 x, int16 y, byte color, uint fontResIndex, byte *text, int len) { + + Font font(_vm->_res->load(fontResIndex)->data); + + RenderQueueItem item; + item.type = kText; + item.flags = kRefresh; + item.rect = makeRect(x, y, font.getTextWidth(text), font.getHeight()); + item.priority = 1000; + + item.text.color = color; + item.text.fontResIndex = fontResIndex; + item.text.text = text; + item.text.len = len; + + _currQueue->push_back(item); + +} + +void RenderQueue::addMask(SegmapMaskRect &mask) { + + RenderQueueItem item; + item.type = kMask; + item.flags = kRefresh; + item.rect = makeRect(mask.x - _vm->_cameraX, mask.y - _vm->_cameraY, mask.width, mask.height); + item.priority = mask.priority; + + item.mask = mask; + + // Only add the mask if a sprite intersects its rect + if (rectIntersectsItem(item.rect)) { + RenderQueueArray::iterator iter = _currQueue->begin(); + while (iter != _currQueue->end() && (*iter).priority <= item.priority) { + iter++; + } + _currQueue->insert(iter, item); + } + +} + +void RenderQueue::update() { + + bool doFullRefresh = _vm->_screen->_fullRefresh; + + _updateUta->clear(); + + if (!doFullRefresh) { + + for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { + RenderQueueItem *item = &(*iter); + RenderQueueItem *prevItem = findItemInQueue(_prevQueue, *item); + if (prevItem) { + if (hasItemChanged(*prevItem, *item)) { + item->flags = kRefresh; + addDirtyRect(prevItem->rect); + } else { + item->flags = kUnchanged; + } + } else { + item->flags = kRefresh; + } + } + + for (RenderQueueArray::iterator iter = _prevQueue->begin(); iter != _prevQueue->end(); iter++) { + RenderQueueItem *prevItem = &(*iter); + RenderQueueItem *item = findItemInQueue(_currQueue, *prevItem); + if (!item) { + prevItem->flags = kRemoved; + addDirtyRect(prevItem->rect); + } + } + + restoreDirtyBackground(); + + for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { + RenderQueueItem *item = &(*iter); + if (item->flags != kUnchanged) + invalidateItemsByRect(item->rect, item); + } + + } else { + byte *destp = _vm->_screen->_frontScreen; + byte *srcp = _vm->_screen->_backScreen + _vm->_cameraX + _vm->_cameraY * _vm->_sceneWidth; + int16 w = MIN<int16>(640, _vm->_sceneWidth); + int16 h = MIN<int16>(400, _vm->_cameraHeight); + while (h--) { + memcpy(destp, srcp, w); + destp += 640; + srcp += _vm->_sceneWidth; + } + _vm->_screen->_fullRefresh = false; + } + + for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { + const RenderQueueItem *item = &(*iter); + + if (item->flags == kRefresh || doFullRefresh) { + + switch (item->type) { + case kSprite: + _vm->_screen->drawSprite(item->sprite); + break; + case kText: + _vm->_screen->drawString(item->rect.left, item->rect.top, item->text.color, item->text.fontResIndex, + item->text.text, item->text.len, NULL, true); + break; + case kMask: + _vm->_screen->drawSurface(item->rect.left, item->rect.top, item->mask.surface); + break; + default: + break; + } + + if (!doFullRefresh) + addDirtyRect(item->rect); + + } + + } + + if (doFullRefresh) { + clear(); + _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen, 640, 0, 0, 640, _vm->_cameraHeight); + } else { + updateDirtyRects(); + } + + SWAP(_currQueue, _prevQueue); + _currQueue->clear(); + +} + +void RenderQueue::clear() { + _prevQueue->clear(); + _currQueue->clear(); +} + +bool RenderQueue::rectIntersectsItem(const Common::Rect &rect) { + for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { + const RenderQueueItem *item = &(*iter); + if (rect.intersects(item->rect)) + return true; + } + return false; +} + +RenderQueueItem *RenderQueue::findItemInQueue(RenderQueueArray *queue, const RenderQueueItem &item) { + /* This checks if the given item also exists in the previously drawn frame. + The state of the item (position, color etc) is handled elsewhere. + */ + for (RenderQueueArray::iterator iter = queue->begin(); iter != queue->end(); iter++) { + RenderQueueItem *prevItem = &(*iter); + if (prevItem->type == item.type) { + switch (item.type) { + case kSprite: + if (prevItem->sprite.resIndex == item.sprite.resIndex && + prevItem->sprite.frameNum == item.sprite.frameNum) + return prevItem; + break; + case kText: + if (prevItem->text.text == item.text.text && + prevItem->text.len == item.text.len) + return prevItem; + break; + case kMask: + if (prevItem->mask.surface == item.mask.surface) + return prevItem; + break; + } + } + } + return NULL; // Not found +} + +bool RenderQueue::hasItemChanged(const RenderQueueItem &item1, const RenderQueueItem &item2) { + + if (item1.type != item2.type) + return true; + + if (item1.rect.left != item2.rect.left || + item1.rect.top != item2.rect.top || + item1.rect.right != item2.rect.right || + item1.rect.bottom != item2.rect.bottom) + return true; + + if (item1.type == kText && item1.text.color != item2.text.color) + return true; + + return false; +} + +void RenderQueue::invalidateItemsByRect(const Common::Rect &rect, const RenderQueueItem *item) { + for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { + RenderQueueItem *subItem = &(*iter); + if (item != subItem && + subItem->flags == kUnchanged && + rect.intersects(subItem->rect)) { + + subItem->flags = kRefresh; + invalidateItemsByRect(subItem->rect, subItem); + } + } +} + +void RenderQueue::addDirtyRect(const Common::Rect &rect) { + _updateUta->addRect(rect); +} + +void RenderQueue::restoreDirtyBackground() { + int n_rects = 0; + Common::Rect *rects = _updateUta->getRectangles(&n_rects, 0, 0, 639, _vm->_cameraHeight - 1); + for (int i = 0; i < n_rects; i++) { + byte *destp = _vm->_screen->_frontScreen + rects[i].left + rects[i].top * 640; + byte *srcp = _vm->_screen->_backScreen + (_vm->_cameraX + rects[i].left) + (_vm->_cameraY + rects[i].top) * _vm->_sceneWidth; + int16 w = rects[i].width(); + int16 h = rects[i].height(); + while (h--) { + memcpy(destp, srcp, w); + destp += 640; + srcp += _vm->_sceneWidth; + } + invalidateItemsByRect(rects[i], NULL); + } + delete[] rects; +} + +void RenderQueue::updateDirtyRects() { + int n_rects = 0; + Common::Rect *rects = _updateUta->getRectangles(&n_rects, 0, 0, 639, _vm->_cameraHeight - 1); + for (int i = 0; i < n_rects; i++) { + _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen + rects[i].left + rects[i].top * 640, + 640, rects[i].left, rects[i].top, rects[i].width(), rects[i].height()); + } + delete[] rects; +} + + +} // End of namespace Toltecs diff --git a/engines/toltecs/render.h b/engines/toltecs/render.h new file mode 100644 index 0000000000..bb9ec29959 --- /dev/null +++ b/engines/toltecs/render.h @@ -0,0 +1,99 @@ +/* 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 TOLTECS_RENDER_H +#define TOLTECS_RENDER_H + +#include "graphics/surface.h" + +#include "toltecs/segmap.h" +#include "toltecs/screen.h" +#include "toltecs/microtiles.h" + +namespace Toltecs { + +enum RenderType { + kSprite, + kText, + kMask +}; + +enum RenderFlags { + kNone = 1 << 0, + kRefresh = 1 << 1, + kRemoved = 1 << 2, + kMoved = 1 << 3, + kUnchanged = 1 << 4 +}; + +struct RenderTextItem { + byte color; + uint fontResIndex; + byte *text; + int len; +}; + +struct RenderQueueItem { + RenderType type; + uint flags; + Common::Rect rect; + int16 priority; + union { + SpriteDrawItem sprite; + RenderTextItem text; + SegmapMaskRect mask; + }; +}; + +class RenderQueue { +public: + RenderQueue(ToltecsEngine *vm); + ~RenderQueue(); + + void addSprite(SpriteDrawItem &sprite); + void addText(int16 x, int16 y, byte color, uint fontResIndex, byte *text, int len); + void addMask(SegmapMaskRect &mask); + void update(); + void clear(); + +protected: + typedef Common::List<RenderQueueItem> RenderQueueArray; + + ToltecsEngine *_vm; + RenderQueueArray *_currQueue, *_prevQueue; + MicroTileArray *_updateUta; + + bool rectIntersectsItem(const Common::Rect &rect); + RenderQueueItem *findItemInQueue(RenderQueueArray *queue, const RenderQueueItem &item); + bool hasItemChanged(const RenderQueueItem &item1, const RenderQueueItem &item2); + void invalidateItemsByRect(const Common::Rect &rect, const RenderQueueItem *item); + + void addDirtyRect(const Common::Rect &rect); + void restoreDirtyBackground(); + void updateDirtyRects(); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_RENDER_H */ diff --git a/engines/toltecs/resource.cpp b/engines/toltecs/resource.cpp new file mode 100644 index 0000000000..b95e0444b1 --- /dev/null +++ b/engines/toltecs/resource.cpp @@ -0,0 +1,128 @@ +/* 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/file.h" + +#include "toltecs/toltecs.h" +#include "toltecs/resource.h" + +namespace Toltecs { + + +/* ArchiveReader */ + +ArchiveReader::ArchiveReader() { +} + +ArchiveReader::~ArchiveReader() { + delete[] _offsets; +} + +void ArchiveReader::openArchive(const char *filename) { + open(filename); + uint32 firstOffs = readUint32LE(); + uint count = firstOffs / 4; + _offsets = new uint32[count]; + _offsets[0] = firstOffs; + for (uint i = 1; i < count; i++) + _offsets[i] = readUint32LE(); +} + +uint32 ArchiveReader::openResource(uint resIndex) { + uint32 resourceSize = getResourceSize(resIndex); + seek(_offsets[resIndex]); + return resourceSize; +} + +void ArchiveReader::closeResource() { +} + +uint32 ArchiveReader::getResourceSize(uint resIndex) { + return _offsets[resIndex + 1] - _offsets[resIndex]; +} + +void ArchiveReader::dump(uint resIndex, const char *prefix) { + int32 resourceSize = getResourceSize(resIndex); + byte *data = new byte[resourceSize]; + + Common::String fn; + + if (prefix) + fn = Common::String::format("%s_%04X.0", prefix, resIndex); + else + fn = Common::String::format("%04X.0", resIndex); + + openResource(resIndex); + read(data, resourceSize); + closeResource(); + + Common::DumpFile o; + o.open(fn); + o.write(data, resourceSize); + o.finalize(); + o.close(); + + delete[] data; +} + +/* ResourceCache */ + +ResourceCache::ResourceCache(ToltecsEngine *vm) : _vm(vm) { +} + +ResourceCache::~ResourceCache() { + purgeCache(); +} + +void ResourceCache::purgeCache() { + for (ResourceMap::iterator iter = _cache.begin(); iter != _cache.end(); ++iter) { + delete[] iter->_value->data; + delete iter->_value; + iter->_value = 0; + } + + _cache.clear(); +} + +Resource *ResourceCache::load(uint resIndex) { + ResourceMap::iterator item = _cache.find(resIndex); + if (item != _cache.end()) { + debug(1, "ResourceCache::load(%d) From cache", resIndex); + return (*item)._value; + } else { + debug(1, "ResourceCache::load(%d) From disk", resIndex); + + Resource *resItem = new Resource(); + resItem->size = _vm->_arc->openResource(resIndex); + resItem->data = new byte[resItem->size]; + _vm->_arc->read(resItem->data, resItem->size); + _vm->_arc->closeResource(); + + _cache[resIndex] = resItem; + + return resItem; + + } +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/resource.h b/engines/toltecs/resource.h new file mode 100644 index 0000000000..7f4694f0f6 --- /dev/null +++ b/engines/toltecs/resource.h @@ -0,0 +1,85 @@ +/* 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 TOLTECS_RESOURCE_H +#define TOLTECS_RESOURCE_H + +#include "common/file.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +#include "engines/engine.h" + +namespace Toltecs { + +const uint kMaxCacheItems = 1024; +const uint kMaxCacheSize = 8 * 1024 * 1024; // 8 MB + + +class ArchiveReader : public Common::File { +public: + ArchiveReader(); + ~ArchiveReader(); + + void openArchive(const char *filename); + + // Returns the size of the opened resource + uint32 openResource(uint resIndex); + // Closes the resource + void closeResource(); + // Returns the size of the resource + uint32 getResourceSize(uint resIndex); + + void dump(uint resIndex, const char *prefix = NULL); + +protected: + uint32 *_offsets; + +}; + +struct Resource { + uint32 size; + byte *data; +}; + +class ResourceCache { +public: + ResourceCache(ToltecsEngine *vm); + ~ResourceCache(); + + Resource *load(uint resIndex); + void purgeCache(); + +protected: + typedef Common::HashMap<uint, Resource*> ResourceMap; + + ToltecsEngine *_vm; + + ResourceMap _cache; + +}; + + +} // End of namespace Toltecs + +#endif /* TOLTECS_H */ diff --git a/engines/toltecs/saveload.cpp b/engines/toltecs/saveload.cpp new file mode 100644 index 0000000000..c421be4cd2 --- /dev/null +++ b/engines/toltecs/saveload.cpp @@ -0,0 +1,204 @@ +/* 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/savefile.h" + +#include "graphics/thumbnail.h" + +#include "toltecs/toltecs.h" +#include "toltecs/animation.h" +#include "toltecs/palette.h" +#include "toltecs/script.h" +#include "toltecs/screen.h" + +namespace Toltecs { + +/* TODO: + - Save with F7; Load with F9 + - Saving during an animation (AnimationPlayer) is not working correctly yet + - Maybe switch to SCUMM/Tinsel serialization approach? +*/ + +#define TOLTECS_SAVEGAME_VERSION 0 // 0 is dev version until in official SVN + +ToltecsEngine::kReadSaveHeaderError ToltecsEngine::readSaveHeader(Common::SeekableReadStream *in, bool loadThumbnail, SaveHeader &header) { + + header.version = in->readUint32LE(); + if (header.version != TOLTECS_SAVEGAME_VERSION) + return kRSHEInvalidVersion; + + byte descriptionLen = in->readByte(); + header.description = ""; + while (descriptionLen--) + header.description += (char)in->readByte(); + + if (loadThumbnail) { + header.thumbnail = Graphics::loadThumbnail(*in); + } else { + Graphics::skipThumbnail(*in); + } + + // Not used yet, reserved for future usage + header.gameID = in->readByte(); + header.flags = in->readUint32LE(); + + return ((in->eos() || in->err()) ? kRSHEIoError : kRSHENoError); +} + +void ToltecsEngine::savegame(const char *filename, const char *description) { + + Common::OutSaveFile *out; + if (!(out = g_system->getSavefileManager()->openForSaving(filename))) { + warning("Can't create file '%s', game not saved", filename); + return; + } + + out->writeUint32LE(TOLTECS_SAVEGAME_VERSION); + + byte descriptionLen = strlen(description); + out->writeByte(descriptionLen); + out->write(description, descriptionLen); + + Graphics::saveThumbnail(*out); + + // Not used yet, reserved for future usage + out->writeByte(0); + out->writeUint32LE(0); + + out->writeUint16LE(_cameraX); + out->writeUint16LE(_cameraY); + out->writeUint16LE(_cameraHeight); + + out->writeUint16LE(_guiHeight); + + out->writeUint16LE(_sceneWidth); + out->writeUint16LE(_sceneHeight); + out->writeUint32LE(_sceneResIndex); + + out->writeUint16LE(_walkSpeedX); + out->writeUint16LE(_walkSpeedY); + + out->writeUint32LE(_counter01); + out->writeUint32LE(_counter02); + out->writeByte(_movieSceneFlag ? 1 : 0); + out->writeByte(_flag01); + + out->writeUint16LE(_mouseX); + out->writeUint16LE(_mouseY); + out->writeUint16LE(_mouseDisabled); + + _palette->saveState(out); + _script->saveState(out); + _anim->saveState(out); + _screen->saveState(out); + + out->finalize(); + delete out; + +} + +void ToltecsEngine::loadgame(const char *filename) { + + Common::InSaveFile *in; + if (!(in = g_system->getSavefileManager()->openForLoading(filename))) { + warning("Can't open file '%s', game not loaded", filename); + return; + } + + SaveHeader header; + + kReadSaveHeaderError errorCode = readSaveHeader(in, false, header); + + if (errorCode != kRSHENoError) { + warning("Error loading savegame '%s'", filename); + delete in; + return; + } + + _cameraX = in->readUint16LE(); + _cameraY = in->readUint16LE(); + _cameraHeight = in->readUint16LE(); + + _guiHeight = in->readUint16LE(); + + _sceneWidth = in->readUint16LE(); + _sceneHeight = in->readUint16LE(); + _sceneResIndex = in->readUint32LE(); + + _walkSpeedX = in->readUint16LE(); + _walkSpeedY = in->readUint16LE(); + + _counter01 = in->readUint32LE(); + _counter02 = in->readUint32LE(); + _movieSceneFlag = in->readByte() != 0; + _flag01 = in->readByte(); + + _mouseX = in->readUint16LE(); + _mouseY = in->readUint16LE(); + _mouseDisabled = in->readUint16LE(); + + _system->warpMouse(_mouseX, _mouseY); + _system->showMouse(_mouseDisabled == 0); + + _palette->loadState(in); + _script->loadState(in); + _anim->loadState(in); + _screen->loadState(in); + + delete in; + + loadScene(_sceneResIndex); + + _newCameraX = _cameraX; + _newCameraY = _cameraY; + +} + +Common::Error ToltecsEngine::loadGameState(int slot) { + const char *fileName = getSavegameFilename(slot); + loadgame(fileName); + return Common::kNoError; +} + +Common::Error ToltecsEngine::saveGameState(int slot, const Common::String &description) { + const char *fileName = getSavegameFilename(slot); + savegame(fileName, description.c_str()); + return Common::kNoError; +} + +const char *ToltecsEngine::getSavegameFilename(int num) { + static Common::String filename; + filename = getSavegameFilename(_targetName, num); + return filename.c_str(); +} + +Common::String ToltecsEngine::getSavegameFilename(const Common::String &target, int num) { + assert(num >= 0 && num <= 999); + + char extension[5]; + sprintf(extension, "%03d", num); + + return target + "." + extension; +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/screen.cpp b/engines/toltecs/screen.cpp new file mode 100644 index 0000000000..4eda8f643f --- /dev/null +++ b/engines/toltecs/screen.cpp @@ -0,0 +1,808 @@ +/* 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 "graphics/cursorman.h" + +#include "toltecs/toltecs.h" +#include "toltecs/palette.h" +#include "toltecs/render.h" +#include "toltecs/resource.h" +#include "toltecs/screen.h" +#include "toltecs/script.h" + +namespace Toltecs { + +Screen::Screen(ToltecsEngine *vm) : _vm(vm) { + + _frontScreen = new byte[268800]; + _backScreen = new byte[870400]; + + memset(_fontResIndexArray, 0, sizeof(_fontResIndexArray)); + _fontColor1 = 0; + _fontColor2 = 0; + + // Screen shaking + _shakeActive = false; + _shakeCounterInit = 0; + _shakeCounter = 0; + _shakePos = 0; + + // Verb line + _verbLineNum = 0; + memset(_verbLineItems, 0, sizeof(_verbLineItems)); + _verbLineX = 160; + _verbLineY = 2; + _verbLineWidth = 20; + _verbLineCount = 0; + + // Talk text + _talkTextItemNum = 0; + memset(_talkTextItems, 0, sizeof(_talkTextItems)); + _talkTextX = 0; + _talkTextY = 0; + _talkTextFontColor = 0; + _talkTextMaxWidth = 520; + + _renderQueue = new RenderQueue(_vm); + _fullRefresh = false; + _guiRefresh = false; + +} + +Screen::~Screen() { + + delete[] _frontScreen; + delete[] _backScreen; + + delete _renderQueue; + +} + +void Screen::unpackRle(byte *source, byte *dest, uint16 width, uint16 height) { + int32 size = width * height; + while (size > 0) { + byte a = *source++; + byte b = *source++; + if (a == 0) { + dest += b; + size -= b; + } else { + b = ((b << 4) & 0xF0) | ((b >> 4) & 0x0F); + memset(dest, b, a); + dest += a; + size -= a; + } + } +} + +void Screen::loadMouseCursor(uint resIndex) { + byte mouseCursor[16 * 16], *mouseCursorP = mouseCursor; + byte *cursorData = _vm->_res->load(resIndex)->data; + for (int i = 0; i < 32; i++) { + byte pixel; + byte mask1 = *cursorData++; + byte mask2 = *cursorData++; + for (int j = 0; j < 8; j++) { + pixel = 0xE5; + if ((mask2 & 0x80) == 0) + pixel = 0xE0; + mask2 <<= 1; + if ((mask1 & 0x80) == 0) + pixel = 0; + mask1 <<= 1; + *mouseCursorP++ = pixel; + } + } + // FIXME: Where's the cursor hotspot? Using 8, 8 seems good enough for now. + CursorMan.replaceCursor((const byte*)mouseCursor, 16, 16, 8, 8, 0); +} + +void Screen::drawGuiImage(int16 x, int16 y, uint resIndex) { + + byte *imageData = _vm->_res->load(resIndex)->data; + int16 headerSize = READ_LE_UINT16(imageData); + int16 width = imageData[2]; + int16 height = imageData[3]; + int16 workWidth = width, workHeight = height; + imageData += headerSize; + + byte *dest = _frontScreen + x + (y + _vm->_cameraHeight) * 640; + + //debug(0, "Screen::drawGuiImage() x = %d; y = %d; w = %d; h = %d; resIndex = %d", x, y, width, height, resIndex); + + while (workHeight > 0) { + int count = 1; + byte pixel = *imageData++; + if (pixel & 0x80) { + pixel &= 0x7F; + count = *imageData++; + count += 2; + } + pixel = pixel + 0xE0; + while (count-- && workHeight > 0) { + *dest++ = pixel; + workWidth--; + if (workWidth == 0) { + workHeight--; + dest += 640 - width; + workWidth = width; + } + } + } + + _guiRefresh = true; + +} + +void Screen::startShakeScreen(int16 shakeCounter) { + _shakeActive = true; + _shakeCounterInit = shakeCounter; + _shakeCounter = shakeCounter; + _shakePos = 0; +} + +void Screen::stopShakeScreen() { + _shakeActive = false; + _vm->_system->setShakePos(0); +} + +void Screen::updateShakeScreen() { + if (_shakeActive) { + _shakeCounter--; + if (_shakeCounter == 0) { + _shakeCounter = _shakeCounterInit; + _shakePos ^= 8; + _vm->_system->setShakePos(_shakePos); + } + } +} + +void Screen::addStaticSprite(byte *spriteItem) { + + DrawRequest drawRequest; + memset(&drawRequest, 0, sizeof(drawRequest)); + + drawRequest.y = READ_LE_UINT16(spriteItem + 0); + drawRequest.x = READ_LE_UINT16(spriteItem + 2); + int16 fragmentId = READ_LE_UINT16(spriteItem + 4); + drawRequest.baseColor = _vm->_palette->findFragment(fragmentId) & 0xFF; + drawRequest.resIndex = READ_LE_UINT16(spriteItem + 6); + drawRequest.flags = READ_LE_UINT16(spriteItem + 8); + drawRequest.scaling = 0; + + debug(0, "Screen::addStaticSprite() x = %d; y = %d; baseColor = %d; resIndex = %d; flags = %04X", drawRequest.x, drawRequest.y, drawRequest.baseColor, drawRequest.resIndex, drawRequest.flags); + + addDrawRequest(drawRequest); + +} + +void Screen::addAnimatedSprite(int16 x, int16 y, int16 fragmentId, byte *data, int16 *spriteArray, bool loop, int mode) { + + //debug(0, "Screen::addAnimatedSprite(%d, %d, %d)", x, y, fragmentId); + + DrawRequest drawRequest; + memset(&drawRequest, 0, sizeof(drawRequest)); + + drawRequest.x = x; + drawRequest.y = y; + drawRequest.baseColor = _vm->_palette->findFragment(fragmentId) & 0xFF; + + if (mode == 1) { + drawRequest.scaling = _vm->_segmap->getScalingAtPoint(drawRequest.x, drawRequest.y); + } else if (mode == 2) { + drawRequest.scaling = 0; + } + + int16 count = spriteArray[0]; + + //debug(0, "count = %d", count); + + for (int16 index = 1; index <= count; index++) { + + byte *spriteItem = data + spriteArray[index]; + + uint16 loopNum = READ_LE_UINT16(spriteItem + 0) & 0x7FFF; + uint16 loopCount = READ_LE_UINT16(spriteItem + 2); + uint16 frameNum = READ_LE_UINT16(spriteItem + 4); + uint16 frameCount = READ_LE_UINT16(spriteItem + 6); + drawRequest.resIndex = READ_LE_UINT16(spriteItem + 8); + drawRequest.flags = READ_LE_UINT16(spriteItem + 10 + loopNum * 2); + + debug(0, "Screen::addAnimatedSprite(%d of %d) loopNum = %d; loopCount = %d; frameNum = %d; frameCount = %d; resIndex = %d; flags = %04X, mode = %d", + index, count, loopNum, loopCount, frameNum, frameCount, drawRequest.resIndex, drawRequest.flags, mode); + + addDrawRequest(drawRequest); + + frameNum++; + if (frameNum == frameCount) { + frameNum = 0; + loopNum++; + if (loopNum == loopCount) { + if (loop) { + loopNum = 0; + } else { + loopNum--; + } + } + } else { + loopNum |= 0x8000; + } + + WRITE_LE_UINT16(spriteItem + 0, loopNum); + WRITE_LE_UINT16(spriteItem + 4, frameNum); + + } + +} + +void Screen::clearSprites() { + +} + +void Screen::blastSprite(int16 x, int16 y, int16 fragmentId, int16 resIndex, uint16 flags) { + + DrawRequest drawRequest; + SpriteDrawItem sprite; + + drawRequest.x = x; + drawRequest.y = y; + drawRequest.baseColor = _vm->_palette->findFragment(fragmentId) & 0xFF; + drawRequest.resIndex = resIndex; + drawRequest.flags = flags; + drawRequest.scaling = 0; + + if (createSpriteDrawItem(drawRequest, sprite)) { + sprite.x -= _vm->_cameraX; + sprite.y -= _vm->_cameraY; + drawSprite(sprite); + } + +} + +void Screen::updateVerbLine(int16 slotIndex, int16 slotOffset) { + + debug(0, "Screen::updateVerbLine() _verbLineNum = %d; _verbLineX = %d; _verbLineY = %d; _verbLineWidth = %d; _verbLineCount = %d", + _verbLineNum, _verbLineX, _verbLineY, _verbLineWidth, _verbLineCount); + + Font font(_vm->_res->load(_fontResIndexArray[0])->data); + + _verbLineItems[_verbLineNum].slotIndex = slotIndex; + _verbLineItems[_verbLineNum].slotOffset = slotOffset; + + // First clear the line + int16 y = _verbLineY; + for (int16 i = 0; i < _verbLineCount; i++) { + byte *dest = _frontScreen + _verbLineX - _verbLineWidth / 2 + (y - 1 + _vm->_cameraHeight) * 640; + for (int16 j = 0; j < 20; j++) { + memset(dest, 0xE0, _verbLineWidth); + dest += 640; + } + y += 18; + } + + GuiTextWrapState wrapState; + int16 len = 0; + wrapState.width = 0; + wrapState.destString = wrapState.textBuffer; + wrapState.len1 = 0; + wrapState.len2 = 0; + + y = _verbLineY; + + memset(wrapState.textBuffer, 0, sizeof(wrapState.textBuffer)); + + for (int16 i = 0; i <= _verbLineNum; i++) { + wrapState.sourceString = _vm->_script->getSlotData(_verbLineItems[i].slotIndex) + _verbLineItems[i].slotOffset; + len = wrapGuiText(_fontResIndexArray[0], _verbLineWidth, wrapState); + wrapState.len1 += len; + } + + if (_verbLineCount != 1) { + int16 charWidth = 0; + if (*wrapState.sourceString < 0xF0) { + while (*wrapState.sourceString > 0x20 && *wrapState.sourceString < 0xF0 && len > 0) { + byte ch = *wrapState.sourceString--; + wrapState.len1--; + len--; + charWidth = font.getCharWidth(ch) + font.getSpacing() - 1; + wrapState.width -= charWidth; + } + wrapState.width += charWidth; + wrapState.sourceString++; + wrapState.len1 -= len; + wrapState.len2 = len + 1; + + drawGuiText(_verbLineX - 1 - (wrapState.width / 2), y, 0xF9, 0xFF, _fontResIndexArray[0], wrapState); + + wrapState.destString = wrapState.textBuffer; + wrapState.width = 0; + len = wrapGuiText(_fontResIndexArray[0], _verbLineWidth, wrapState); + wrapState.len1 += len; + + y += 9; + } + y += 9; + } + + wrapState.len1 -= len; + wrapState.len2 = len; + + drawGuiText(_verbLineX - 1 - (wrapState.width / 2), y, 0xF9, 0xFF, _fontResIndexArray[0], wrapState); + + _guiRefresh = true; + +} + +void Screen::updateTalkText(int16 slotIndex, int16 slotOffset) { + + int16 x, y, maxWidth, width, length; + byte durationModifier = 1; + byte *textData = _vm->_script->getSlotData(slotIndex) + slotOffset; + + TalkTextItem *item = &_talkTextItems[_talkTextItemNum]; + + item->fontNum = 0; + item->color = _talkTextFontColor; + + x = CLIP<int16>(_talkTextX - _vm->_cameraX, 120, _talkTextMaxWidth); + y = CLIP<int16>(_talkTextY - _vm->_cameraY, 4, _vm->_cameraHeight - 16); + + maxWidth = 624 - ABS(x - 320) * 2; + + while (1) { + if (*textData == 0x0A) { + x = CLIP<int16>(READ_LE_UINT16(&textData[3]), 120, _talkTextMaxWidth); + y = CLIP<int16>(READ_LE_UINT16(&textData[1]), 4, _vm->_cameraHeight - 16); + maxWidth = 624 - ABS(x - 320) * 2; + textData += 4; + } else if (*textData == 0x14) { + item->color = ((textData[1] << 4) & 0xF0) | ((textData[1] >> 4) & 0x0F); + textData += 2; + } else if (*textData == 0x19) { + durationModifier = textData[1]; + textData += 2; + } else if (*textData < 0x0A) { + item->fontNum = textData[0]; + // FIXME: Some texts request a font which isn't registered so we change it to a font that is + if (_fontResIndexArray[item->fontNum] == 0) + item->fontNum = 0; + textData += 1; + } else + break; + } + + item->slotIndex = slotIndex; + item->slotOffset = textData - _vm->_script->getSlotData(slotIndex); + + width = 0; + length = 0; + + item->duration = 0; + item->lineCount = 0; + + Font font(_vm->_res->load(_fontResIndexArray[item->fontNum])->data); + int16 wordLength, wordWidth; + + while (*textData < 0xF0) { + if (*textData == 0x1E) { + textData++; + addTalkTextRect(font, x, y, length, width, item); + width = 0; + length = 0; + } else { + wordLength = 0; + wordWidth = 0; + while (*textData >= 0x20 && *textData < 0xF0) { + byte ch = *textData++; + wordLength++; + if (ch == 0x20) { + wordWidth += font.getWidth(); + break; + } else { + wordWidth += font.getCharWidth(ch) + font.getSpacing() - 1; + } + } + if (width + wordWidth > maxWidth + font.getWidth()) { + addTalkTextRect(font, x, y, length, width, item); + width = wordWidth; + length = wordLength; + } else { + width += wordWidth; + length += wordLength; + } + } + } + + addTalkTextRect(font, x, y, length, width, item); + + if (item->lineCount > 0) { + int16 ysub = (font.getHeight() - 1) * item->lineCount; + if (item->lines[0].y - 4 < ysub) + ysub = item->lines[0].y - 4; + for (int16 l = 0; l < item->lineCount; l++) + item->lines[l].y -= ysub; + } + + int16 textDurationMultiplier = item->duration + 8; + if (_vm->_doSpeech && *textData == 0xFE) { + textDurationMultiplier += 100; + } + item->duration = 4 * textDurationMultiplier * durationModifier; + +} + +void Screen::addTalkTextRect(Font &font, int16 x, int16 &y, int16 length, int16 width, TalkTextItem *item) { + + if (width > 0) { + TextRect *textRect = &item->lines[item->lineCount]; + width = width + 1 - font.getSpacing(); + textRect->width = width; + item->duration += length; + textRect->length = length; + textRect->y = y; + textRect->x = CLIP<int16>(x - width / 2, 0, 640); + item->lineCount++; + } + + y += font.getHeight() - 1; + +} + +void Screen::addTalkTextItemsToRenderQueue() { + + for (int16 i = 0; i <= _talkTextItemNum; i++) { + TalkTextItem *item = &_talkTextItems[i]; + byte *text = _vm->_script->getSlotData(item->slotIndex) + item->slotOffset; + + if (item->fontNum == -1 || item->duration == 0) + continue; + + //item->duration -= _vm->_counter01; + item->duration--; + if (item->duration < 0) + item->duration = 0; + + for (byte j = 0; j < item->lineCount; j++) { + _renderQueue->addText(item->lines[j].x, item->lines[j].y, item->color, _fontResIndexArray[item->fontNum], + text, item->lines[j].length); + text += item->lines[j].length; + } + + } + +} + +int16 Screen::getTalkTextDuration() { + return _talkTextItems[_talkTextItemNum].duration; +} + +void Screen::finishTalkTextItems() { + for (int16 i = 0; i <= _talkTextItemNum; i++) { + _talkTextItems[i].duration = 0; + } +} + +void Screen::keepTalkTextItemsAlive() { + for (int16 i = 0; i <= _talkTextItemNum; i++) { + TalkTextItem *item = &_talkTextItems[i]; + if (item->fontNum == -1) + item->duration = 0; + else if (item->duration > 0) + item->duration = 2; + } +} + +void Screen::registerFont(uint fontIndex, uint resIndex) { + _fontResIndexArray[fontIndex] = resIndex; +} + +void Screen::drawGuiTextMulti(byte *textData) { + + int16 x = 0, y = 0; + + // Really strange stuff. + for (int i = 30; i >= 0; i--) { + if (textData[i] >= 0xF0) + break; + if (i == 0) + return; + } + + GuiTextWrapState wrapState; + wrapState.sourceString = textData; + + do { + if (*wrapState.sourceString == 0x0A) { + // Set text position + y = wrapState.sourceString[1]; + x = READ_LE_UINT32(wrapState.sourceString + 2); + wrapState.sourceString += 4; + } else if (*wrapState.sourceString == 0x0B) { + // Inc text position + y += wrapState.sourceString[1]; + x += wrapState.sourceString[2]; + wrapState.sourceString += 3; + } else { + wrapState.destString = wrapState.textBuffer; + wrapState.width = 0; + wrapState.len1 = 0; + wrapState.len2 = wrapGuiText(_fontResIndexArray[1], 640, wrapState); + drawGuiText(x - wrapState.width / 2, y, _fontColor1, _fontColor2, _fontResIndexArray[1], wrapState); + } + } while (*wrapState.sourceString != 0xFF); + + _guiRefresh = true; + +} + +int16 Screen::wrapGuiText(uint fontResIndex, int maxWidth, GuiTextWrapState &wrapState) { + + Font font(_vm->_res->load(fontResIndex)->data); + int16 len = 0; + + while (*wrapState.sourceString >= 0x20 && *wrapState.sourceString < 0xF0) { + byte ch = *wrapState.sourceString; + byte charWidth; + if (ch <= 0x20) + charWidth = font.getWidth(); + else + charWidth = font.getCharWidth(ch) + font.getSpacing() - 1; + if (wrapState.width + charWidth >= maxWidth) + break; + len++; + wrapState.width += charWidth; + *wrapState.destString++ = *wrapState.sourceString++; + } + + return len; + +} + +void Screen::drawGuiText(int16 x, int16 y, byte fontColor1, byte fontColor2, uint fontResIndex, GuiTextWrapState &wrapState) { + + debug(0, "Screen::drawGuiText(%d, %d, %d, %d, %d) wrapState.len1 = %d; wrapState.len2 = %d", x, y, fontColor1, fontColor2, fontResIndex, wrapState.len1, wrapState.len2); + + int16 ywobble = 1; + + x = drawString(x + 1, y + _vm->_cameraHeight, fontColor1, fontResIndex, wrapState.textBuffer, wrapState.len1, &ywobble, false); + x = drawString(x, y + _vm->_cameraHeight, fontColor2, fontResIndex, wrapState.textBuffer + wrapState.len1, wrapState.len2, &ywobble, false); + +} + +int16 Screen::drawString(int16 x, int16 y, byte color, uint fontResIndex, const byte *text, int len, int16 *ywobble, bool outline) { + + //debug(0, "Screen::drawString(%d, %d, %d, %d)", x, y, color, fontResIndex); + + Font font(_vm->_res->load(fontResIndex)->data); + + if (len == -1) + len = strlen((const char*)text); + + int16 yadd = 0; + if (ywobble) + yadd = *ywobble; + + while (len--) { + byte ch = *text++; + if (ch <= 0x20) { + x += font.getWidth(); + } else { + drawChar(font, _frontScreen, x, y - yadd, ch, color, outline); + x += font.getCharWidth(ch) + font.getSpacing() - 1; + yadd = -yadd; + } + } + + if (ywobble) + *ywobble = yadd; + + return x; + +} + +void Screen::drawChar(const Font &font, byte *dest, int16 x, int16 y, byte ch, byte color, bool outline) { + + int16 charWidth, charHeight; + byte *charData; + + dest += x + y * 640; + + charWidth = font.getCharWidth(ch); + //charHeight = font.getHeight() - 2;//Why was this here?! + charHeight = font.getHeight(); + charData = font.getCharData(ch); + + while (charHeight--) { + byte lineWidth = charWidth; + while (lineWidth > 0) { + byte count = charData[0] & 0x0F; + byte flags = charData[0] & 0xF0; + charData++; + if ((flags & 0x80) == 0) { + if (flags & 0x10) { + memset(dest, color, count); + } else if (outline) { + memset(dest, 0, count); + } + } + dest += count; + lineWidth -= count; + } + dest += 640 - charWidth; + } + +} + +void Screen::drawSurface(int16 x, int16 y, Graphics::Surface *surface) { + + int16 skipX = 0; + int16 width = surface->w; + int16 height = surface->h; + byte *surfacePixels = (byte*)surface->getBasePtr(0, 0); + byte *frontScreen; + + // Not on screen, skip + if (x + width < 0 || y + height < 0 || x >= 640 || y >= _vm->_cameraHeight) + return; + + if (x < 0) { + skipX = -x; + x = 0; + width -= skipX; + } + + if (y < 0) { + int16 skipY = -y; + surfacePixels += surface->w * skipY; + y = 0; + height -= skipY; + } + + if (x + width >= 640) { + width -= x + width - 640; + } + + if (y + height >= _vm->_cameraHeight) { + height -= y + height - _vm->_cameraHeight; + } + + frontScreen = _vm->_screen->_frontScreen + x + (y * 640); + + for (int16 h = 0; h < height; h++) { + surfacePixels += skipX; + for (int16 w = 0; w < width; w++) { + if (*surfacePixels != 0xFF) + *frontScreen = *surfacePixels; + frontScreen++; + surfacePixels++; + } + frontScreen += 640 - width; + surfacePixels += surface->w - width - skipX; + } + +} + +void Screen::saveState(Common::WriteStream *out) { + + // Save verb line + out->writeUint16LE(_verbLineNum); + out->writeUint16LE(_verbLineX); + out->writeUint16LE(_verbLineY); + out->writeUint16LE(_verbLineWidth); + out->writeUint16LE(_verbLineCount); + for (int i = 0; i < 8; i++) { + out->writeUint16LE(_verbLineItems[i].slotIndex); + out->writeUint16LE(_verbLineItems[i].slotOffset); + } + + // Save talk text items + out->writeUint16LE(_talkTextX); + out->writeUint16LE(_talkTextY); + out->writeUint16LE(_talkTextMaxWidth); + out->writeByte(_talkTextFontColor); + out->writeUint16LE(_talkTextItemNum); + for (int i = 0; i < 5; i++) { + out->writeUint16LE(_talkTextItems[i].duration); + out->writeUint16LE(_talkTextItems[i].slotIndex); + out->writeUint16LE(_talkTextItems[i].slotOffset); + out->writeUint16LE(_talkTextItems[i].fontNum); + out->writeByte(_talkTextItems[i].color); + out->writeByte(_talkTextItems[i].lineCount); + for (int j = 0; j < _talkTextItems[i].lineCount; j++) { + out->writeUint16LE(_talkTextItems[i].lines[j].x); + out->writeUint16LE(_talkTextItems[i].lines[j].y); + out->writeUint16LE(_talkTextItems[i].lines[j].width); + out->writeUint16LE(_talkTextItems[i].lines[j].length); + } + } + + // Save GUI bitmap + { + byte *gui = _frontScreen + _vm->_cameraHeight * 640; + for (int i = 0; i < _vm->_guiHeight; i++) { + out->write(gui, 640); + gui += 640; + } + } + + // Save fonts + for (int i = 0; i < 10; i++) + out->writeUint32LE(_fontResIndexArray[i]); + out->writeByte(_fontColor1); + out->writeByte(_fontColor2); + +} + +void Screen::loadState(Common::ReadStream *in) { + + // Load verb line + _verbLineNum = in->readUint16LE(); + _verbLineX = in->readUint16LE(); + _verbLineY = in->readUint16LE(); + _verbLineWidth = in->readUint16LE(); + _verbLineCount = in->readUint16LE(); + for (int i = 0; i < 8; i++) { + _verbLineItems[i].slotIndex = in->readUint16LE(); + _verbLineItems[i].slotOffset = in->readUint16LE(); + } + + // Load talk text items + _talkTextX = in->readUint16LE(); + _talkTextY = in->readUint16LE(); + _talkTextMaxWidth = in->readUint16LE(); + _talkTextFontColor = in->readByte(); + _talkTextItemNum = in->readUint16LE(); + for (int i = 0; i < 5; i++) { + _talkTextItems[i].duration = in->readUint16LE(); + _talkTextItems[i].slotIndex = in->readUint16LE(); + _talkTextItems[i].slotOffset = in->readUint16LE(); + _talkTextItems[i].fontNum = in->readUint16LE(); + _talkTextItems[i].color = in->readByte(); + _talkTextItems[i].lineCount = in->readByte(); + for (int j = 0; j < _talkTextItems[i].lineCount; j++) { + _talkTextItems[i].lines[j].x = in->readUint16LE(); + _talkTextItems[i].lines[j].y = in->readUint16LE(); + _talkTextItems[i].lines[j].width = in->readUint16LE(); + _talkTextItems[i].lines[j].length = in->readUint16LE(); + } + } + + // Load GUI bitmap + { + byte *gui = _frontScreen + _vm->_cameraHeight * 640; + for (int i = 0; i < _vm->_guiHeight; i++) { + in->read(gui, 640); + gui += 640; + } + _guiRefresh = true; + } + + // Load fonts + for (int i = 0; i < 10; i++) + _fontResIndexArray[i] = in->readUint32LE(); + _fontColor1 = in->readByte(); + _fontColor2 = in->readByte(); + +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/screen.h b/engines/toltecs/screen.h new file mode 100644 index 0000000000..988f59c840 --- /dev/null +++ b/engines/toltecs/screen.h @@ -0,0 +1,251 @@ +/* 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 TOLTECS_SCREEN_H +#define TOLTECS_SCREEN_H + +#include "graphics/surface.h" +#include "toltecs/toltecs.h" + +namespace Toltecs { + +struct DrawRequest { + int16 x, y; + int16 resIndex; + uint16 flags; + int16 baseColor; + int8 scaling; +}; + +struct SpriteDrawItem { + int16 x, y; + int16 width, height; + int16 origWidth, origHeight; + int16 resIndex, frameNum; + uint32 offset; + int16 xdelta, ydelta; + uint16 flags; + int16 skipX, yerror; + int16 priority; + int16 baseColor; +}; + +struct SpriteFrameEntry { + int16 y, x, h, w; + uint32 offset; + SpriteFrameEntry() { + } + SpriteFrameEntry(byte *data) { + y = READ_LE_UINT16(data + 0); + x = READ_LE_UINT16(data + 2); + h = READ_LE_UINT16(data + 4); + w = READ_LE_UINT16(data + 6); + offset = READ_LE_UINT32(data + 8); + } +}; + +class Font { +public: + Font(byte *fontData) : _fontData(fontData) { + } + ~Font() { + } + int16 getSpacing() const { + return _fontData[1]; + } + int16 getHeight() const { + return _fontData[2]; + } + int16 getWidth() const { + return _fontData[3]; + } + int16 getCharWidth(byte ch) const { + return _fontData[4 + (ch - 0x21)]; + } + byte *getCharData(byte ch) const { + return _fontData + 0x298 + READ_LE_UINT16(&_fontData[0xE0 + (ch - 0x21) * 2]); + } + int16 getTextWidth(const byte *text) { + int16 width = 0; + while (*text && *text < 0xF0) { + byte ch = *text++; + if (ch <= 0x20) { + width += getWidth(); + } else { + width += getCharWidth(ch) + getSpacing() - 1; + } + } + return width; + } + +protected: + byte *_fontData; +}; + +struct PixelPacket { + byte count; + byte pixel; +}; + +enum SpriteReaderStatus { + kSrsPixelsLeft, + kSrsEndOfLine, + kSrsEndOfSprite +}; + +class SpriteFilter { +public: + SpriteFilter(const SpriteDrawItem &sprite) : _sprite(&sprite) { + } + virtual ~SpriteFilter() {} + virtual SpriteReaderStatus readPacket(PixelPacket &packet) = 0; +protected: + const SpriteDrawItem *_sprite; +}; + +struct TextRect { + int16 x, y; + int16 width, length; +}; + +struct TalkTextItem { + int16 duration; + int16 slotIndex; + int16 slotOffset; + int16 fontNum; + byte color; + byte lineCount; + TextRect lines[15]; +}; + +struct GuiTextWrapState { + int16 len1, len2; + byte *sourceString; + byte *destString; + int16 width; + byte textBuffer[100]; +}; + +class RenderQueue; + +class Screen { +public: + Screen(ToltecsEngine *vm); + ~Screen(); + + void unpackRle(byte *source, byte *dest, uint16 width, uint16 height); + + void loadMouseCursor(uint resIndex); + + void drawGuiImage(int16 x, int16 y, uint resIndex); + + void startShakeScreen(int16 shakeCounter); + void stopShakeScreen(); + void updateShakeScreen(); + + // Sprite list + void addStaticSprite(byte *spriteItem); + void addAnimatedSprite(int16 x, int16 y, int16 fragmentId, byte *data, int16 *spriteArray, bool loop, int mode); + void clearSprites(); + + // Sprite drawing + void drawSprite(const SpriteDrawItem &sprite); + void drawSpriteCore(byte *dest, SpriteFilter &reader, const SpriteDrawItem &sprite); + void blastSprite(int16 x, int16 y, int16 fragmentId, int16 resIndex, uint16 flags); + + // Verb line + void updateVerbLine(int16 slotIndex, int16 slotOffset); + + // Talk text + void updateTalkText(int16 slotIndex, int16 slotOffset); + void addTalkTextRect(Font &font, int16 x, int16 &y, int16 length, int16 width, TalkTextItem *item); + void addTalkTextItemsToRenderQueue(); + int16 getTalkTextDuration(); + void finishTalkTextItems(); + void keepTalkTextItemsAlive(); + + // Font/text + void registerFont(uint fontIndex, uint resIndex); + void drawGuiTextMulti(byte *textData); + int16 wrapGuiText(uint fontResIndex, int maxWidth, GuiTextWrapState &wrapState); + void drawGuiText(int16 x, int16 y, byte fontColor1, byte fontColor2, uint fontResIndex, GuiTextWrapState &wrapState); + + int16 drawString(int16 x, int16 y, byte color, uint fontResIndex, const byte *text, int len = -1, int16 *ywobble = NULL, bool outline = false); + void drawChar(const Font &font, byte *dest, int16 x, int16 y, byte ch, byte color, bool outline); + + void drawSurface(int16 x, int16 y, Graphics::Surface *surface); + + void saveState(Common::WriteStream *out); + void loadState(Common::ReadStream *in); + + uint getFontResIndex(int fontNum) const { return _fontResIndexArray[fontNum]; } + +//protected: +public: + + struct VerbLineItem { + int16 slotIndex; + int16 slotOffset; + }; + + struct Rect { + int16 x, y, width, height; + }; + + ToltecsEngine *_vm; + + byte *_frontScreen, *_backScreen; + + uint _fontResIndexArray[10]; + byte _fontColor1, _fontColor2; + + // Screen shaking + bool _shakeActive; + int16 _shakeCounterInit, _shakeCounter; + int _shakePos; + + // Verb line + int16 _verbLineNum; + VerbLineItem _verbLineItems[8]; + int16 _verbLineX, _verbLineY, _verbLineWidth; + int16 _verbLineCount; + + // Talk text + int16 _talkTextX, _talkTextY; + int16 _talkTextMaxWidth; + byte _talkTextFontColor; + int16 _talkTextItemNum; + TalkTextItem _talkTextItems[5]; + + RenderQueue *_renderQueue; + bool _fullRefresh; + bool _guiRefresh; + + bool createSpriteDrawItem(const DrawRequest &drawRequest, SpriteDrawItem &sprite); + void addDrawRequest(const DrawRequest &drawRequest); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_SCREEN_H */ diff --git a/engines/toltecs/script.cpp b/engines/toltecs/script.cpp new file mode 100644 index 0000000000..708afb7008 --- /dev/null +++ b/engines/toltecs/script.cpp @@ -0,0 +1,1117 @@ +/* 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. + * + * + */ + +// TODO: Clean up game variable handling and move it to ToltecsEngine + +#include "common/error.h" + +#include "graphics/cursorman.h" + +#include "toltecs/toltecs.h" +#include "toltecs/animation.h" +#include "toltecs/menu.h" +#include "toltecs/movie.h" +#include "toltecs/music.h" +#include "toltecs/palette.h" +#include "toltecs/resource.h" +#include "toltecs/script.h" +#include "toltecs/segmap.h" +#include "toltecs/sound.h" + +namespace Toltecs { + +ScriptInterpreter::ScriptInterpreter(ToltecsEngine *vm) : _vm(vm) { + + _stack = new byte[kScriptStackSize]; + + memset(_slots, 0, sizeof(_slots)); + + _savedSp = 0; + + _slots[kMaxScriptSlots - 1].size = 1024; + _slots[kMaxScriptSlots - 1].data = new byte[_slots[kMaxScriptSlots - 1].size]; + + setupScriptFunctions(); + +} + +ScriptInterpreter::~ScriptInterpreter() { + delete[] _stack; + for (int i = 0; i < kMaxScriptSlots; i++) + delete[] _slots[i].data; + for (uint i = 0; i < _scriptFuncs.size(); ++i) + delete _scriptFuncs[i]; +} + +typedef Common::Functor0Mem<void, ScriptInterpreter> ScriptFunctionF; +#define RegisterScriptFunction(x) \ + _scriptFuncs.push_back(new ScriptFunctionF(this, &ScriptInterpreter::x)); \ + _scriptFuncNames.push_back(#x); +void ScriptInterpreter::setupScriptFunctions() { + + // 0 + RegisterScriptFunction(sfNop); + RegisterScriptFunction(sfNop); + RegisterScriptFunction(sfGetGameVar); + RegisterScriptFunction(sfSetGameVar); + RegisterScriptFunction(sfUpdateScreen); + // 5 + RegisterScriptFunction(sfGetRandomNumber); + RegisterScriptFunction(sfDrawGuiTextMulti); + RegisterScriptFunction(sfUpdateVerbLine); + RegisterScriptFunction(sfSetFontColor); + RegisterScriptFunction(sfGetTalkTextDuration); + // 10 + RegisterScriptFunction(sfTalk); + RegisterScriptFunction(sfFindPaletteFragment); + RegisterScriptFunction(sfClearPaletteFragments); + RegisterScriptFunction(sfAddPaletteFragment); + RegisterScriptFunction(sfSetDeltaAnimPalette); + // 15 + RegisterScriptFunction(sfSetUnkPaletteEffect); + RegisterScriptFunction(sfBuildColorTransTable); + RegisterScriptFunction(sfSetDeltaMainPalette); + RegisterScriptFunction(sfLoadScript); + RegisterScriptFunction(sfRegisterFont); + // 20 + RegisterScriptFunction(sfLoadAddPalette); + RegisterScriptFunction(sfLoadScene); + RegisterScriptFunction(sfSetGuiHeight); + RegisterScriptFunction(sfFindMouseInRectIndex1); + RegisterScriptFunction(sfFindMouseInRectIndex2); + // 25 + RegisterScriptFunction(sfDrawGuiImage); + RegisterScriptFunction(sfAddAnimatedSpriteNoLoop); + RegisterScriptFunction(sfAddAnimatedSprite); + RegisterScriptFunction(sfAddStaticSprite); + RegisterScriptFunction(sfAddAnimatedSpriteScaled); + // 30 + RegisterScriptFunction(sfFindPath); + RegisterScriptFunction(sfWalk); + RegisterScriptFunction(sfScrollCameraUp); + RegisterScriptFunction(sfScrollCameraDown); + RegisterScriptFunction(sfScrollCameraLeft); + // 35 + RegisterScriptFunction(sfScrollCameraRight); + RegisterScriptFunction(sfScrollCameraUpEx); + RegisterScriptFunction(sfScrollCameraDownEx); + RegisterScriptFunction(sfScrollCameraLeftEx); + RegisterScriptFunction(sfScrollCameraRightEx); + // 40 + RegisterScriptFunction(sfSetCamera); + RegisterScriptFunction(sfGetCameraChanged); + RegisterScriptFunction(sfGetRgbModifiertAtPoint); + RegisterScriptFunction(sfStartAnim); + RegisterScriptFunction(sfAnimNextFrame); + // 45 + RegisterScriptFunction(sfNop); + RegisterScriptFunction(sfGetAnimFrameNumber); + RegisterScriptFunction(sfGetAnimStatus); + RegisterScriptFunction(sfStartShakeScreen); + RegisterScriptFunction(sfStopShakeScreen); + // 50 + RegisterScriptFunction(sfStartSequence); + RegisterScriptFunction(sfEndSequence); + RegisterScriptFunction(sfSetSequenceVolume); + RegisterScriptFunction(sfPlayPositionalSound); + RegisterScriptFunction(sfPlaySound2); + // 55 + RegisterScriptFunction(sfClearScreen); + RegisterScriptFunction(sfNop); + RegisterScriptFunction(sfHandleInput); + RegisterScriptFunction(sfRunOptionsScreen); + RegisterScriptFunction(sfPrecacheSprites); + // 60 + RegisterScriptFunction(sfPrecacheSounds1); + RegisterScriptFunction(sfDeletePrecachedFiles); + RegisterScriptFunction(sfPrecacheSounds2); + RegisterScriptFunction(sfRestoreStackPtr); + RegisterScriptFunction(sfSaveStackPtr); + // 65 + RegisterScriptFunction(sfPlayMovie); + RegisterScriptFunction(sfNop); + +} + +void ScriptInterpreter::loadScript(uint resIndex, uint slotIndex) { + + delete[] _slots[slotIndex].data; + + _slots[slotIndex].resIndex = resIndex; + Resource *scriptResource = _vm->_res->load(resIndex); + _slots[slotIndex].size = scriptResource->size; + _slots[slotIndex].data = new byte[_slots[slotIndex].size]; + memcpy(_slots[slotIndex].data, scriptResource->data, _slots[slotIndex].size); + +} + +void ScriptInterpreter::setMainScript(uint slotIndex) { + _switchLocalDataNear = true; + _switchLocalDataFar = false; + _switchLocalDataToStack = false; + _cmpBitTest = false; + _regs.reg0 = 0; + _regs.reg1 = 0; + _regs.reg2 = 0; + _regs.reg3 = 0; + _regs.reg4 = slotIndex; + _regs.reg5 = 0; + _regs.reg6 = 0; + _regs.sp = 4096; + _regs.reg8 = 0; + _code = getSlotData(_regs.reg4); +} + +void ScriptInterpreter::runScript() { + + while (!_vm->shouldQuit()) { + + if (_vm->_movieSceneFlag) + _vm->_mouseButton = 0; + + if (_vm->_saveLoadRequested != 0) { + if (_vm->_saveLoadRequested == 1) + _vm->loadGameState(_vm->_saveLoadSlot); + else if (_vm->_saveLoadRequested == 2) + _vm->saveGameState(_vm->_saveLoadSlot, _vm->_saveLoadDescription); + _vm->_saveLoadRequested = 0; + } + + if (_switchLocalDataNear) { + _switchLocalDataNear = false; + _localData = getSlotData(_regs.reg4); + } + + if (_switchLocalDataFar) { + _switchLocalDataFar = false; + _localData = getSlotData(_regs.reg5); + _switchLocalDataNear = true; + } + + if (_switchLocalDataToStack) { + _switchLocalDataToStack = false; + _localData = _stack + 2; + _switchLocalDataNear = true; + } + + byte opcode = readByte(); + execOpcode(opcode); + + // Call updateScreen roughly every 10ms else the mouse cursor will be jerky + if (_vm->_system->getMillis() % 10 == 0) + _vm->_system->updateScreen(); + + } + +} + +byte ScriptInterpreter::readByte() { + return *_code++; +} + +int16 ScriptInterpreter::readInt16() { + int16 value = READ_LE_UINT16(_code); + _code += 2; + return value; +} + +void ScriptInterpreter::execOpcode(byte opcode) { + + int16 ofs; + + debug(1, "opcode = %d", opcode); + + switch (opcode) { + case 0: + { + // ok + _subCode = _code; + byte length = readByte(); + debug(1, "length = %d", length); + uint16 index = readInt16(); + debug(1, "callScriptFunction %d", index); + execScriptFunction(index); + _code += length - 2; + break; + } + case 1: + // ok + _regs.reg0 = readInt16(); + break; + case 2: + // ok + _regs.reg1 = readInt16(); + break; + case 3: + // ok + _regs.reg3 = readInt16(); + break; + case 4: + // ok + _regs.reg5 = _regs.reg0; + break; + case 5: + // ok + _regs.reg3 = _regs.reg0; + break; + case 6: + // ok + _regs.reg1 = _regs.reg0; + break; + case 7: + _regs.reg1 = localRead16(_regs.reg3); + break; + case 8: + localWrite16(_regs.reg3, _regs.reg0); + break; + case 9: + localWrite16(readInt16(), _regs.reg0); + break; + case 10: + localWrite8(readInt16(), _regs.reg0); + break; + case 11: + localWrite16(readInt16(), _regs.reg5); + break; + case 12: + localWrite16(readInt16(), _regs.reg4); + break; + case 13: + localWrite16(readInt16(), _regs.reg3); + break; + case 14: + _regs.reg3 = localRead16(readInt16()); + break; + case 15: + _regs.reg2 = localRead16(_regs.reg1); + break; + case 16: + _regs.reg2 = localRead16(_regs.reg1 + readInt16()); + break; + case 17: + _regs.reg2 = _regs.reg0; + break; + case 18: + _regs.reg0 += readInt16(); + break; + case 19: + localWrite16(_regs.reg3, localRead16(_regs.reg3) + _regs.reg0); + break; + case 20: + _regs.reg0 += _regs.reg2; + break; + case 21: + _regs.reg3 += _regs.sp; + break; + case 22: + _regs.reg1 += _regs.sp; + break; + case 23: + localWrite16(_regs.reg3, localRead16(_regs.reg3) - _regs.reg0); + break; + case 24: + _regs.reg0 /= readInt16(); + break; + case 25: + localWrite16(_regs.reg3, localRead16(_regs.reg3) / _regs.reg0); + break; + case 26: + // NOP + break; + case 27: + _regs.reg0 *= readInt16(); + break; + case 28: + localWrite16(_regs.reg3, localRead16(_regs.reg3) * _regs.reg0); + break; + case 29: + _regs.reg0 *= _regs.reg2; + break; + case 30: + localWrite16(_regs.reg3, localRead16(_regs.reg3) + 1); + break; + case 31: + localWrite16(_regs.reg3, localRead16(_regs.reg3) - 1); + break; + case 32: + _switchLocalDataFar = true; + break; + case 33: + _switchLocalDataToStack = true; + break; + case 34: + pushInt16(_regs.reg0); + break; + case 35: + pushInt16(_regs.reg1); + break; + case 36: + _regs.reg1 = popInt16(); + break; + case 37: + _regs.reg0 = popInt16(); + break; + case 38: + _regs.reg2 = -_regs.reg2; + break; + case 39: + _regs.reg8 = readInt16(); + _cmpBitTest = false; + break; + case 40: + _regs.reg8 = _regs.reg0; + _cmpBitTest = false; + break; + case 41: + _regs.reg8 = readInt16(); + _cmpBitTest = true; + break; + case 42: + _regs.reg8 = _regs.reg0; + _cmpBitTest = true; + break; + case 43: + _code = getSlotData(_regs.reg4) + _regs.reg0; + break; + case 44: + _code = getSlotData(_regs.reg5) + _regs.reg0; + _regs.reg4 = _regs.reg5; + _switchLocalDataNear = true; + break; + case 45: + pushInt16(_code - getSlotData(_regs.reg4)); + pushInt16(_regs.reg4); + _code = getSlotData(_regs.reg4) + _regs.reg0; + break; + case 46: + pushInt16(_code - getSlotData(_regs.reg4)); + pushInt16(_regs.reg4); + _code = getSlotData(_regs.reg5) + _regs.reg0; + _regs.reg4 = _regs.reg5; + _switchLocalDataNear = true; + break; + case 47: + _regs.reg4 = popInt16(); + ofs = popInt16(); + _code = getSlotData(_regs.reg4) + ofs; + _switchLocalDataNear = true; + break; + case 48: + _regs.reg4 = popInt16(); + ofs = popInt16(); + _code = getSlotData(_regs.reg4) + ofs; + _regs.sp += _regs.reg0; + _switchLocalDataNear = true; + break; + case 49: + ofs = readByte(); + _code += ofs; + break; + case 50: + if (_cmpBitTest) { + _regs.reg1 &= _regs.reg8; + if (_regs.reg1 == 0) + _code += 4; + } else { + if (_regs.reg1 == _regs.reg8) + _code += 4; + } + _code++; + break; + case 51: + if (_cmpBitTest) { + _regs.reg1 &= _regs.reg8; + if (_regs.reg1 != 0) + _code += 4; + } else { + if (_regs.reg1 != _regs.reg8) + _code += 4; + } + _code++; + break; + case 52: + if ((uint16)_regs.reg1 >= (uint16)_regs.reg8) + _code += 4; + _code++; + break; + case 53: + if ((uint16)_regs.reg1 <= (uint16)_regs.reg8) + _code += 4; + _code++; + break; + case 54: + if ((uint16)_regs.reg1 < (uint16)_regs.reg8) + _code += 4; + _code++; + break; + case 55: + if ((uint16)_regs.reg1 > (uint16)_regs.reg8) + _code += 4; + _code++; + break; + default: + error("Invalid opcode %d", opcode); + } + +} + +void ScriptInterpreter::execScriptFunction(uint16 index) { + debug(4, "execScriptFunction(%d)", index); + if (index >= _scriptFuncs.size()) + error("ScriptInterpreter::execScriptFunction() Invalid script function index %d", index); + debug(4, "%s", _scriptFuncNames[index]); + (*_scriptFuncs[index])(); +} + +VarType ScriptInterpreter::getGameVarType(uint variable) { + switch (variable) { + case 0: return vtByte; + case 1: return vtWord; + case 2: return vtWord; + case 3: return vtByte; + case 4: return vtWord; + case 5: return vtWord; + case 6: return vtWord; + case 7: return vtWord; + case 8: return vtWord; + case 9: return vtWord; + case 10: return vtWord; + case 11: return vtWord; + case 12: return vtByte; + case 13: return vtWord; + case 14: return vtWord; + case 15: return vtWord; + case 16: return vtWord; + case 17: return vtWord; + case 18: return vtWord; + case 19: return vtWord; + case 20: return vtWord; + case 21: return vtWord; + default: + error("Invalid game variable"); + } +} + +const char *getVarName(uint variable) { + switch (variable) { + case 0: return "mouseDisabled"; + case 1: return "mouseY"; + case 2: return "mouseX"; + case 3: return "mouseButton"; + case 4: return "verbLineY"; + case 5: return "verbLineX"; + case 6: return "verbLineWidth"; + case 7: return "verbLineCount"; + case 8: return "verbLineNum"; + case 9: return "talkTextItemNum"; + case 10: return "talkTextY"; + case 11: return "talkTextX"; + case 12: return "talkTextFontColor"; + case 13: return "cameraY"; + case 14: return "cameraX"; + case 15: return "walkSpeedY"; + case 16: return "walkSpeedX"; + case 17: return "flag01"; + case 18: return "sceneResIndex"; + case 19: return "guiHeight"; + case 20: return "sceneHeight"; + case 21: return "sceneWidth"; + } + return "(invalid)"; +} + +int16 ScriptInterpreter::getGameVar(uint variable) { + debug(0, "ScriptInterpreter::getGameVar(%d{%s})", variable, getVarName(variable)); + + switch (variable) { + case 0: return _vm->_mouseDisabled; + case 1: return _vm->_mouseY; + case 2: return _vm->_mouseX; + case 3: return _vm->_mouseButton; + case 4: return _vm->_screen->_verbLineY; + case 5: return _vm->_screen->_verbLineX; + case 6: return _vm->_screen->_verbLineWidth; + case 7: return _vm->_screen->_verbLineCount; + case 8: return _vm->_screen->_verbLineNum; + case 9: return _vm->_screen->_talkTextItemNum; + case 10: return _vm->_screen->_talkTextY; + case 11: return _vm->_screen->_talkTextX; + case 12: return _vm->_screen->_talkTextFontColor; + case 13: return _vm->_cameraY; + case 14: return _vm->_cameraX; + case 15: return _vm->_walkSpeedY; + case 16: return _vm->_walkSpeedX; + case 17: return _vm->_flag01; + case 18: return _vm->_sceneResIndex; + case 19: return _vm->_guiHeight; + case 20: return _vm->_sceneHeight; + case 21: return _vm->_sceneWidth; + default: + warning("Getting unimplemented game variable %s (%d)", getVarName(variable), variable); + return 0; + } +} + +void ScriptInterpreter::setGameVar(uint variable, int16 value) { + debug(0, "ScriptInterpreter::setGameVar(%d{%s}, %d)", variable, getVarName(variable), value); + + switch (variable) { + case 0: + _vm->_mouseDisabled = value; + CursorMan.showMouse(value == 0); + break; + case 3: + _vm->_mouseButton = value; + break; + case 4: + _vm->_screen->_verbLineY = value; + break; + case 5: + _vm->_screen->_verbLineX = value; + break; + case 6: + _vm->_screen->_verbLineWidth = value; + break; + case 7: + _vm->_screen->_verbLineCount = value; + break; + case 8: + _vm->_screen->_verbLineNum = value; + break; + case 9: + _vm->_screen->_talkTextItemNum = value; + break; + case 10: + _vm->_screen->_talkTextY = value; + break; + case 11: + _vm->_screen->_talkTextX = value; + break; + case 12: + _vm->_screen->_talkTextFontColor = value; + break; + case 13: + _vm->_cameraY = value; + break; + case 14: + _vm->_cameraX = value; + break; + case 15: + _vm->_walkSpeedY = value; + break; + case 16: + _vm->_walkSpeedX = value; + break; + case 17: + _vm->_flag01 = value != 0; + break; + case 18: + _vm->_sceneResIndex = value; + break; + case 19: + _vm->_guiHeight = value; + break; + case 20: + _vm->_sceneHeight = value; + break; + case 21: + _vm->_sceneWidth = value; + break; + case 1: + case 2: + default: + warning("Setting unimplemented game variable %s (%d) to %d", getVarName(variable), variable, value); + break; + } + +} + +byte ScriptInterpreter::arg8(int16 offset) { + return _subCode[offset]; +} + +int16 ScriptInterpreter::arg16(int16 offset) { + return READ_LE_UINT16(&_subCode[offset]); +} + +void ScriptInterpreter::pushInt16(int16 value) { + WRITE_LE_UINT16(_stack + _regs.sp, value); + _regs.sp -= 2; +} + +int16 ScriptInterpreter::popInt16() { + _regs.sp += 2; + return READ_LE_UINT16(_stack + _regs.sp); +} + +void ScriptInterpreter::localWrite8(int16 offset, byte value) { + //debug(1, "localWrite8(%d, %d)", offset, value); + _localData[offset] = value; +} + +byte ScriptInterpreter::localRead8(int16 offset) { + //debug(1, "localRead8(%d) -> %d", offset, _localData[offset]); + return _localData[offset]; +} + +void ScriptInterpreter::localWrite16(int16 offset, int16 value) { + //debug(1, "localWrite16(%d, %d)", offset, value); + WRITE_LE_UINT16(&_localData[offset], value); +} + +int16 ScriptInterpreter::localRead16(int16 offset) { + //debug(1, "localRead16(%d) -> %d", offset, (int16)READ_LE_UINT16(&_localData[offset])); + return (int16)READ_LE_UINT16(&_localData[offset]); +} + +byte *ScriptInterpreter::localPtr(int16 offset) { + //debug(1, "localPtr(%d)", offset); + return &_localData[offset]; +} + +void ScriptInterpreter::saveState(Common::WriteStream *out) { + + // Save registers + out->writeUint16LE(_regs.reg0); + out->writeUint16LE(_regs.reg1); + out->writeUint16LE(_regs.reg2); + out->writeUint16LE(_regs.reg3); + out->writeUint16LE(_regs.reg4); + out->writeUint16LE(_regs.reg5); + out->writeUint16LE(_regs.reg6); + out->writeUint16LE(_regs.sp); + out->writeUint16LE(_regs.reg8); + + // Save slots + for (int slot = 0; slot < kMaxScriptSlots; slot++) { + out->writeUint32LE(_slots[slot].size); + out->writeUint16LE(_slots[slot].resIndex); + if (_slots[slot].size > 0) + out->write(_slots[slot].data, _slots[slot].size); + } + + // Save stack + out->write(_stack, kScriptStackSize); + out->writeUint16LE(_savedSp); + + // Save IP + out->writeUint16LE((int16)(_code - getSlotData(_regs.reg4))); + +} + +void ScriptInterpreter::loadState(Common::ReadStream *in) { + + // Load registers + _regs.reg0 = in->readUint16LE(); + _regs.reg1 = in->readUint16LE(); + _regs.reg2 = in->readUint16LE(); + _regs.reg3 = in->readUint16LE(); + _regs.reg4 = in->readUint16LE(); + _regs.reg5 = in->readUint16LE(); + _regs.reg6 = in->readUint16LE(); + _regs.sp = in->readUint16LE(); + _regs.reg8 = in->readUint16LE(); + + // Load slots + for (int slot = 0; slot < kMaxScriptSlots; slot++) { + _slots[slot].size = in->readUint32LE(); + _slots[slot].resIndex = in->readUint16LE(); + _slots[slot].data = NULL; + if (_slots[slot].size > 0) { + _slots[slot].data = new byte[_slots[slot].size]; + in->read(_slots[slot].data, _slots[slot].size); + } + } + + // Load stack + in->read(_stack, kScriptStackSize); + _savedSp = in->readUint16LE(); + + // Load IP + _code = getSlotData(_regs.reg4) + in->readUint16LE(); + +} + +void ScriptInterpreter::sfNop() { + // NOP +} + +void ScriptInterpreter::sfGetGameVar() { + int16 value = getGameVar(arg16(3)); + localWrite16(arg16(5), value); +} + +void ScriptInterpreter::sfSetGameVar() { + int16 varIndex = arg16(3); + VarType varType = getGameVarType(varIndex); + int16 value = 0; + if (varType == vtByte) + value = arg8(5); + else if (varType == vtWord) + value = arg16(5); + setGameVar(varIndex, value); +} + +void ScriptInterpreter::sfUpdateScreen() { + _vm->updateScreen(); +} + +void ScriptInterpreter::sfGetRandomNumber() { + localWrite16(arg16(5), _vm->_rnd->getRandomNumber(arg16(3) - 1)); +} + +void ScriptInterpreter::sfDrawGuiTextMulti() { + _vm->_screen->drawGuiTextMulti((byte*)localPtr(arg16(3))); +} + +void ScriptInterpreter::sfUpdateVerbLine() { + _vm->_screen->updateVerbLine(arg16(5), arg16(3)); +} + +void ScriptInterpreter::sfSetFontColor() { + _vm->_screen->_fontColor1 = 0; + _vm->_screen->_fontColor2 = arg8(3); +} + +void ScriptInterpreter::sfGetTalkTextDuration() { + localWrite16(arg16(3), _vm->_screen->getTalkTextDuration()); +} + +void ScriptInterpreter::sfTalk() { + _vm->talk(arg16(5), arg16(3)); +} + +void ScriptInterpreter::sfFindPaletteFragment() { + localWrite16(arg16(5), _vm->_palette->findFragment(arg16(3))); +} + +void ScriptInterpreter::sfClearPaletteFragments() { + _vm->_palette->clearFragments(); +} + +void ScriptInterpreter::sfAddPaletteFragment() { + _vm->_palette->addFragment(arg16(3), arg16(5)); +} + +void ScriptInterpreter::sfSetDeltaAnimPalette() { + _vm->_palette->setDeltaPalette(_vm->_palette->getAnimPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3)); +} + +void ScriptInterpreter::sfSetUnkPaletteEffect() { + // TODO + debug("ScriptInterpreter::sfSetUnkPaletteEffect"); +} + +void ScriptInterpreter::sfBuildColorTransTable() { + _vm->_palette->buildColorTransTable(arg8(4), (char)arg8(3), arg8(5)); +} + +void ScriptInterpreter::sfSetDeltaMainPalette() { + _vm->_palette->setDeltaPalette(_vm->_palette->getMainPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3)); +} + +void ScriptInterpreter::sfLoadScript() { + int16 codeOfs = _code - getSlotData(_regs.reg4); + loadScript(arg16(4), arg8(3)); + _code = getSlotData(_regs.reg4) + codeOfs; + _switchLocalDataNear = true; +} + +void ScriptInterpreter::sfRegisterFont() { + _vm->_screen->registerFont(arg8(3), arg16(4)); +} + +void ScriptInterpreter::sfLoadAddPalette() { + _vm->_palette->loadAddPalette(arg16(4), arg8(3)); +} + +void ScriptInterpreter::sfLoadScene() { + if (arg8(3) == 0) { + // FIXME: Originally, this was stopSpeech(). However, we need to stop + // ALL sounds here (including sound effects and background sounds) + // before purgeCache() is called, otherwise the sound buffers will be + // invalidated. This is apparent when moving from a scene that has + // background sounds (such as the canyon at the beginning), to another + // one that doesn't (such as the map), and does not stop the sounds + // already playing. In this case, the engine will either crash or + // garbage will be heard through the speakers. + // TODO: We should either move purgeCache() elsewhere, or monitor + // which resources are still used before purging the cache. + _vm->_sound->stopAll(); + _vm->_res->purgeCache(); + _vm->loadScene(arg16(4)); + } else { + _vm->_screen->loadMouseCursor(arg16(4)); + } +} + +void ScriptInterpreter::sfSetGuiHeight() { + _vm->setGuiHeight(arg8(3)); +} + +void ScriptInterpreter::sfFindMouseInRectIndex1() { + int16 index = -1; + if (_vm->_mouseY < _vm->_cameraHeight) { + int16 slotIndex = arg16(5); + index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3), + _vm->_mouseX + _vm->_cameraX, + _vm->_mouseY + _vm->_cameraY, + arg16(11) + 1, arg16(7), + getSlotData(slotIndex) + _slots[slotIndex].size); + } + localWrite16(arg16(9), index); +} + +void ScriptInterpreter::sfFindMouseInRectIndex2() { + int16 index = -1; + if (_vm->_sceneResIndex != 0) { + if (_vm->_mouseY < _vm->_cameraHeight) { + int16 slotIndex = arg16(5); + index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3), + _vm->_mouseX + _vm->_cameraX, + _vm->_mouseY + _vm->_cameraY, + 0, arg16(7), + getSlotData(slotIndex) + _slots[slotIndex].size); + } + } + localWrite16(arg16(9), index); +} + +void ScriptInterpreter::sfDrawGuiImage() { + _vm->_screen->drawGuiImage(arg16(5), arg16(3), arg16(7)); +} + +void ScriptInterpreter::sfAddAnimatedSpriteNoLoop() { + _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte*)localPtr(0), (int16*)localPtr(arg16(9)), false, 2); +} + +void ScriptInterpreter::sfAddAnimatedSprite() { + _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte*)localPtr(0), (int16*)localPtr(arg16(9)), true, 2); +} + +void ScriptInterpreter::sfAddStaticSprite() { + _vm->_screen->addStaticSprite(_subCode + 3); +} + +void ScriptInterpreter::sfAddAnimatedSpriteScaled() { + _vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte*)localPtr(0), (int16*)localPtr(arg16(9)), true, 1); +} + +void ScriptInterpreter::sfFindPath() { + _vm->_segmap->findPath((int16*)(getSlotData(arg16(13)) + arg16(11)), arg16(9), arg16(7), arg16(5), arg16(3)); +} + +void ScriptInterpreter::sfWalk() { + _vm->walk(getSlotData(arg16(5)) + arg16(3)); +} + +void ScriptInterpreter::sfScrollCameraUp() { + _vm->scrollCameraUp(4); +} + +void ScriptInterpreter::sfScrollCameraDown() { + _vm->scrollCameraDown(4); +} + +void ScriptInterpreter::sfScrollCameraLeft() { + _vm->scrollCameraLeft(4); +} + +void ScriptInterpreter::sfScrollCameraRight() { + _vm->scrollCameraRight(4); +} + +void ScriptInterpreter::sfScrollCameraUpEx() { + _vm->scrollCameraUp(arg16(3)); +} + +void ScriptInterpreter::sfScrollCameraDownEx() { + _vm->scrollCameraDown(arg16(3)); +} + +void ScriptInterpreter::sfScrollCameraLeftEx() { + _vm->scrollCameraLeft(arg16(3)); +} + +void ScriptInterpreter::sfScrollCameraRightEx() { + _vm->scrollCameraRight(arg16(3)); +} + +void ScriptInterpreter::sfSetCamera() { + _vm->setCamera(arg16(5), arg16(3)); +} + +void ScriptInterpreter::sfGetCameraChanged() { + localWrite16(arg16(3), _vm->getCameraChanged() ? 1 : 0); +} + +void ScriptInterpreter::sfGetRgbModifiertAtPoint() { + byte *rgb = getSlotData(arg16(11)) + arg16(9); + _vm->_segmap->getRgbModifiertAtPoint(arg16(5), arg16(3), arg16(7), rgb[0], rgb[1], rgb[2]); +} + +void ScriptInterpreter::sfStartAnim() { + _vm->_anim->start(arg16(3)); +} + +void ScriptInterpreter::sfAnimNextFrame() { + _vm->_anim->nextFrame(); +} + +void ScriptInterpreter::sfGetAnimFrameNumber() { + localWrite16(arg16(3), _vm->_anim->getFrameNumber()); +} + +void ScriptInterpreter::sfGetAnimStatus() { + int16 status = _vm->_anim->getStatus(); + if (status == 0 || status == 1) { + // TODO mov screenFlag01, 0 + } + localWrite16(arg16(3), status); +} + +void ScriptInterpreter::sfStartShakeScreen() { + _vm->_screen->startShakeScreen(arg16(3)); +} + +void ScriptInterpreter::sfStopShakeScreen() { + _vm->_screen->stopShakeScreen(); +} + +void ScriptInterpreter::sfStartSequence() { + int16 sequenceResIndex = arg16(3); + //debug("ScriptInterpreter::sfStartSequence(%d)", sequenceResIndex); + if (sequenceResIndex >= 0) { + //_vm->_arc->dump(sequenceResIndex, "music"); // DEBUG: Dump music so we know what's in there + + int32 resourceSize = _vm->_arc->getResourceSize(sequenceResIndex); + byte *data = new byte[resourceSize]; + _vm->_arc->openResource(sequenceResIndex); + _vm->_arc->read(data, resourceSize); + _vm->_arc->closeResource(); + + if (!memcmp(data, "FORM", 4)) { + // TODO: It seems that music is always looping? + _vm->_musicPlayer->playMIDI(data, resourceSize, true); + } else { + // Sanity check: this should never occur + error("sfStartSequence: resource %d isn't XMIDI", sequenceResIndex); + } + + delete[] data; + } +} + +void ScriptInterpreter::sfEndSequence() { + //debug("ScriptInterpreter::sfEndSequence"); + _vm->_musicPlayer->stopAndClear(); +} + +void ScriptInterpreter::sfSetSequenceVolume() { + // TODO + //debug("ScriptInterpreter::sfSetSequenceVolume"); +} + +void ScriptInterpreter::sfPlayPositionalSound() { + _vm->_sound->playSoundAtPos(arg16(3), arg16(9), arg16(7)); +} + +void ScriptInterpreter::sfPlaySound2() { + _vm->_sound->playSound(arg16(3), arg16(5), arg16(7)); +} + +void ScriptInterpreter::sfClearScreen() { + // TODO: Occurs on every scene change, but seems unneeded + //debug("ScriptInterpreter::sfClearScreen"); +} + +void ScriptInterpreter::sfHandleInput() { + int16 varOfs = arg16(3); + int16 keyCode = 0; + if (_vm->_rightButtonDown) { + keyCode = 1; + } else { + /* Convert keyboard scancode to IBM PC scancode + Only scancodes known to be used (so far) are converted + */ + switch (_vm->_keyState.keycode) { + case Common::KEYCODE_ESCAPE: + keyCode = 1; + break; + case Common::KEYCODE_F10: + keyCode = 68; + break; + default: + break; + } + } + localWrite16(varOfs, keyCode); +} + +void ScriptInterpreter::sfRunOptionsScreen() { + _vm->_screen->loadMouseCursor(12); + _vm->_palette->loadAddPalette(9, 224); + _vm->_palette->setDeltaPalette(_vm->_palette->getMainPalette(), 7, 0, 31, 224); + _vm->_screen->finishTalkTextItems(); + _vm->_screen->clearSprites(); + CursorMan.showMouse(true); + _vm->_menuSystem->run(); + _vm->_keyState.reset(); + _switchLocalDataNear = true; +} + +/* NOTE: The opcodes sfPrecacheSprites, sfPrecacheSounds1, sfPrecacheSounds2 and + sfDeletePrecachedFiles were used by the original engine to handle precaching + of data so the game doesn't stall while playing (due to the slow speed of + CD-Drives back then). This is not needed in ScummVM since all supported + systems are fast enough to load data in-game. */ + +void ScriptInterpreter::sfPrecacheSprites() { + // See note above +} + +void ScriptInterpreter::sfPrecacheSounds1() { + // See note above +} + +void ScriptInterpreter::sfDeletePrecachedFiles() { + // See note above +} + +void ScriptInterpreter::sfPrecacheSounds2() { + // See note above +} + +void ScriptInterpreter::sfRestoreStackPtr() { + _regs.sp = _savedSp; +} + +void ScriptInterpreter::sfSaveStackPtr() { + _savedSp = _regs.sp; +} + +void ScriptInterpreter::sfPlayMovie() { + _vm->_moviePlayer->playMovie(arg16(3)); +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/script.h b/engines/toltecs/script.h new file mode 100644 index 0000000000..1a5a9c90ad --- /dev/null +++ b/engines/toltecs/script.h @@ -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. + * + * + */ + +#ifndef TOLTECS_SCRIPT_H +#define TOLTECS_SCRIPT_H + +#include "common/func.h" + +namespace Toltecs { + +const int kMaxScriptSlots = 50; +const int kScriptStackSize = 4096 + 4; + +enum VarType { + vtByte, + vtWord +}; + +typedef Common::Functor0<void> ScriptFunction; + +class ScriptInterpreter { +public: + ScriptInterpreter(ToltecsEngine *vm); + ~ScriptInterpreter(); + + void loadScript(uint resIndex, uint slotIndex); + void setMainScript(uint slotIndex); + void runScript(); + + byte *getSlotData(int slotIndex) const { return _slots[slotIndex].data; } + + VarType getGameVarType(uint variable); + int16 getGameVar(uint variable); + void setGameVar(uint variable, int16 value); + + void saveState(Common::WriteStream *out); + void loadState(Common::ReadStream *in); + +protected: + + struct ScriptRegs { + int16 reg0; + int16 reg1; + int16 reg2; + int16 reg3; + int16 reg4; + int16 reg5; + int16 reg6; + int16 sp; + int16 reg8; + }; + + struct ScriptSlot { + byte *data; + int32 size; + uint resIndex; + }; + + ToltecsEngine *_vm; + Common::Array<const ScriptFunction*> _scriptFuncs; + Common::Array<const char *> _scriptFuncNames; + + byte *_stack; + + byte *_code, *_subCode; + byte *_localData; + bool _switchLocalDataNear, _switchLocalDataFar, _switchLocalDataToStack; + bool _cmpBitTest; + + ScriptSlot _slots[kMaxScriptSlots]; + + ScriptRegs _regs; + int16 _savedSp; + + byte readByte(); + int16 readInt16(); + + void execOpcode(byte opcode); + + void setupScriptFunctions(); + void execScriptFunction(uint16 index); + + byte arg8(int16 offset); + int16 arg16(int16 offset); + + void pushInt16(int16 value); + int16 popInt16(); + + void localWrite8(int16 offset, byte value); + byte localRead8(int16 offset); + void localWrite16(int16 offset, int16 value); + int16 localRead16(int16 offset); + byte *localPtr(int16 offset); + + void sfNop(); + void sfGetGameVar(); + void sfSetGameVar(); + void sfUpdateScreen(); + void sfGetRandomNumber(); + void sfDrawGuiTextMulti(); + void sfUpdateVerbLine(); + void sfSetFontColor(); + void sfGetTalkTextDuration(); + void sfTalk(); + void sfFindPaletteFragment(); + void sfClearPaletteFragments(); + void sfAddPaletteFragment(); + void sfSetDeltaAnimPalette(); + void sfSetUnkPaletteEffect(); + void sfBuildColorTransTable(); + void sfSetDeltaMainPalette(); + void sfLoadScript(); + void sfRegisterFont(); + void sfLoadAddPalette(); + void sfLoadScene(); + void sfSetGuiHeight(); + void sfFindMouseInRectIndex1(); + void sfFindMouseInRectIndex2(); + void sfDrawGuiImage(); + void sfAddAnimatedSpriteNoLoop(); + void sfAddAnimatedSprite(); + void sfAddStaticSprite(); + void sfAddAnimatedSpriteScaled(); + void sfFindPath(); + void sfWalk(); + void sfScrollCameraUp(); + void sfScrollCameraDown(); + void sfScrollCameraLeft(); + void sfScrollCameraRight(); + void sfScrollCameraUpEx(); + void sfScrollCameraDownEx(); + void sfScrollCameraLeftEx(); + void sfScrollCameraRightEx(); + void sfSetCamera(); + void sfGetCameraChanged(); + void sfGetRgbModifiertAtPoint(); + void sfStartAnim(); + void sfAnimNextFrame(); + void sfGetAnimFrameNumber(); + void sfGetAnimStatus(); + void sfStartShakeScreen(); + void sfStopShakeScreen(); + void sfStartSequence(); + void sfEndSequence(); + void sfSetSequenceVolume(); + void sfPlayPositionalSound(); + void sfPlaySound2(); + void sfClearScreen(); + void sfHandleInput(); + void sfRunOptionsScreen(); + void sfPrecacheSprites(); + void sfPrecacheSounds1(); + void sfDeletePrecachedFiles(); + void sfPrecacheSounds2(); + void sfRestoreStackPtr(); + void sfSaveStackPtr(); + void sfPlayMovie(); + +}; + + +} // End of namespace Toltecs + +#endif /* TOLTECS_H */ diff --git a/engines/toltecs/segmap.cpp b/engines/toltecs/segmap.cpp new file mode 100644 index 0000000000..75b0c633cd --- /dev/null +++ b/engines/toltecs/segmap.cpp @@ -0,0 +1,408 @@ +/* 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 "toltecs/toltecs.h" +#include "toltecs/render.h" +#include "toltecs/segmap.h" + +namespace Toltecs { + +SegmentMap::SegmentMap(ToltecsEngine *vm) : _vm(vm) { +} + +SegmentMap::~SegmentMap() { + freeSegmapMaskRectSurfaces(); +} + +void SegmentMap::load(byte *source) { + + freeSegmapMaskRectSurfaces(); + _maskRects.clear(); + _pathRects.clear(); + _infoRects.clear(); + + // Load mask rects + byte *maskData = source + 2; + uint16 maskSize = READ_LE_UINT16(source); + source += 2; + uint16 maskRectCount = READ_LE_UINT16(source); + source += 2; + uint16 maskRectDataSize = maskRectCount * 12 + 2; + + debug(0, "SegmentMap::load() maskRectCount = %d", maskRectCount); + + for (uint16 i = 0; i < maskRectCount; i++) { + SegmapMaskRect maskRect; + int16 maskOffset; + maskRect.y = READ_LE_UINT16(source); + maskRect.x = READ_LE_UINT16(source + 2); + maskRect.height = READ_LE_UINT16(source + 4); + maskRect.width = READ_LE_UINT16(source + 6); + maskOffset = READ_LE_UINT16(source + 8); + maskRect.priority = READ_LE_UINT16(source + 10); + loadSegmapMaskRectSurface(maskData + maskOffset, maskRect); + + debug(0, "SegmentMap::load() (%d, %d, %d, %d, %04X, %d)", + maskRect.x, maskRect.y, maskRect.width, maskRect.height, maskOffset, maskRect.priority); + + source += 12; + _maskRects.push_back(maskRect); + } + + source += maskSize - maskRectDataSize; + + // Load path rects + + source += 2; // skip rects array size + + uint16 pathRectCount = READ_LE_UINT16(source); + source += 2; + + debug(0, "SegmentMap::load() pathRectCount = %d", pathRectCount); + + for (uint16 i = 0; i < pathRectCount; i++) { + SegmapPathRect pathRect; + pathRect.y1 = READ_LE_UINT16(source); + pathRect.x1 = READ_LE_UINT16(source + 2); + pathRect.y2 = pathRect.y1 + READ_LE_UINT16(source + 4); + pathRect.x2 = pathRect.x1 + READ_LE_UINT16(source + 6); + + debug(0, "SegmentMap::load() (%d, %d, %d, %d)", pathRect.x1, pathRect.y1, pathRect.x2, pathRect.y2); + + source += 8; + _pathRects.push_back(pathRect); + } + + // Load info rects + + source += 2; // skip rects array size + + uint16 infoRectCount = READ_LE_UINT16(source); + source += 2; + debug(0, "SegmentMap::load() infoRectCount = %d", infoRectCount); + for (uint16 i = 0; i < infoRectCount; i++) { + SegmapInfoRect infoRect; + infoRect.y = READ_LE_UINT16(source); + infoRect.x = READ_LE_UINT16(source + 2); + infoRect.height = READ_LE_UINT16(source + 4); + infoRect.width = READ_LE_UINT16(source + 6); + infoRect.id = source[8]; + infoRect.a = source[9]; + infoRect.b = source[10]; + infoRect.c = source[11]; + + debug(0, "SegmentMap::load() (%d, %d, %d, %d) (%d, %d, %d, %d)", + infoRect.x, infoRect.y, infoRect.width, infoRect.height, + infoRect.id, (int8)infoRect.a, (int8)infoRect.b, (int8)infoRect.c); + + source += 12; + _infoRects.push_back(infoRect); + } + + // TODO Other stuff + + +} + +int16 SegmentMap::findPathRectAtPoint(int16 x, int16 y) { + for (int16 rectIndex = 0; rectIndex < (int16)_pathRects.size(); rectIndex++) { + if (y >= _pathRects[rectIndex].y1 && y <= _pathRects[rectIndex].y2 && + x >= _pathRects[rectIndex].x1 && x <= _pathRects[rectIndex].x2) { + return rectIndex; + } + } + return -1; +} + +void SegmentMap::adjustPathPoint(int16 &x, int16 &y) { + + if (findPathRectAtPoint(x, y) != -1) + return; + + uint32 minDistance = 0xFFFFFFFF, distance; + int16 adjustedX = 0, adjustedY = 0, x2, y2; + + for (int16 rectIndex = 0; rectIndex < (int16)_pathRects.size(); rectIndex++) { + + if (x >= _pathRects[rectIndex].x1 && x < _pathRects[rectIndex].x2) { + x2 = x; + } else if (ABS(x - _pathRects[rectIndex].x1) >= ABS(x - _pathRects[rectIndex].x2)) { + x2 = _pathRects[rectIndex].x2; + } else { + x2 = _pathRects[rectIndex].x1; + } + + if (ABS(y - _pathRects[rectIndex].y1) >= ABS(y - _pathRects[rectIndex].y2)) { + y2 = _pathRects[rectIndex].y2; + } else { + y2 = _pathRects[rectIndex].y1; + } + + distance = ABS(y - y2) + ABS(x - x2); + if (distance < minDistance) { + if (x >= _pathRects[rectIndex].x1 && x <= _pathRects[rectIndex].x2) { + adjustedX = x; + } else { + adjustedX = x2; + } + if (y >= _pathRects[rectIndex].y1 && y <= _pathRects[rectIndex].y2) { + adjustedY = y; + } else { + adjustedY = y2; + } + minDistance = distance; + } + + } + + x = adjustedX; + y = adjustedY; + +} + +int16 SegmentMap::findNextPathRect(int16 srcRectIndex, int16 destX, int16 destY) { + + int16 result; + uint16 minDistance, distance; + int16 x1, y1, x2, y2; + int16 xmin, xmax, ymax, ymin; + + result = -1; + minDistance = 0xFFFF; + + x1 = _pathRects[srcRectIndex].x1; + y1 = _pathRects[srcRectIndex].y1; + x2 = _pathRects[srcRectIndex].x2; + y2 = _pathRects[srcRectIndex].y2; + + for (int16 rectIndex = 0; rectIndex < (int16)_pathRects.size(); rectIndex++) { + + int16 nodeX = -1, nodeY = -1; + + // Check if the current rectangle is connected to the source rectangle + if (x1 == _pathRects[rectIndex].x2 && y1 < _pathRects[rectIndex].y2 && y2 > _pathRects[rectIndex].y1) { + nodeX = x1; + } else if (x2 == _pathRects[rectIndex].x1 && y1 < _pathRects[rectIndex].y2 && y2 > _pathRects[rectIndex].y1) { + nodeX = x2 - 1; + } else if (y1 == _pathRects[rectIndex].y2 && x1 < _pathRects[rectIndex].x2 && x2 > _pathRects[rectIndex].x1) { + nodeY = y1; + } else if (y2 == _pathRects[rectIndex].y1 && x1 < _pathRects[rectIndex].x2 && x2 > _pathRects[rectIndex].x1) { + nodeY = y2 - 1; + } else + continue; + + if (nodeX == -1) { + xmin = MAX<int16>(x1, _pathRects[rectIndex].x1); + xmax = MIN<int16>(x2, _pathRects[rectIndex].x2) - 1; + if (destX > xmin && destX < xmax) { + nodeX = destX; + } else if (ABS(destX - xmin) >= ABS(destX - xmax)) { + nodeX = xmax - 1; + } else { + nodeX = xmin; + } + } + + if (nodeY == -1) { + ymin = MAX<int16>(y1, _pathRects[rectIndex].y1); + ymax = MIN<int16>(y2, _pathRects[rectIndex].y2) - 1; + if (destY > ymin && destY < ymax) { + nodeY = destY; + } else if (ABS(destY - ymin) >= ABS(destY - ymax)) { + nodeY = ymax - 1; + } else { + nodeY = ymin; + } + } + + distance = ABS(destX - nodeX) + ABS(destY - nodeY); + + for (uint i = 0; i < _closedPathRectsCount; i++) { + if (rectIndex == _closedPathRects[i]) { + distance = minDistance; + break; + } + } + + for (uint i = 0; i < _deadEndPathRectsCount; i++) { + if (rectIndex == _deadEndPathRects[i]) { + distance = minDistance; + break; + } + } + + if (distance < minDistance) { + result = rectIndex; + minDistance = distance; + _pathNodes[_pathNodesCount].x = nodeX; + _pathNodes[_pathNodesCount].y = nodeY; + } + + } + + return result; +} + +struct LineData { + int pitch; + byte *surf; +}; + +void plotProc(int x, int y, int color, void *data) { + LineData *ld = (LineData*)data; + ld->surf[x + y * ld->pitch] = color; +} + +void SegmentMap::findPath(int16 *pointsArray, int16 destX, int16 destY, int16 sourceX, int16 sourceY) { + + // TODO: Writes to pointsArray aren't endian-safe yet + + int16 currentRectIndex, destRectIndex; + int16 pointsCount; + + debug(0, "SegmentMap::findPath(fromX: %d; fromY: %d; toX: %d; toY: %d)", sourceX, sourceY, destX, destY); + + _deadEndPathRectsCount = 0; + _closedPathRectsCount = 0; + _pathNodesCount = 0; + + pointsCount = 2; + + adjustPathPoint(sourceX, sourceY); + currentRectIndex = findPathRectAtPoint(sourceX, sourceY); + + adjustPathPoint(destX, destY); + destRectIndex = findPathRectAtPoint(destX, destY); + + if (currentRectIndex != -1) { + if (destRectIndex != currentRectIndex) { + while (1) { + do { + _closedPathRects[_closedPathRectsCount++] = currentRectIndex; + currentRectIndex = findNextPathRect(currentRectIndex, destX, destY); + _pathNodesCount++; + } while (currentRectIndex != -1 && currentRectIndex != destRectIndex); + if (currentRectIndex != -1 && currentRectIndex == destRectIndex) + break; + _deadEndPathRects[_deadEndPathRectsCount++] = _closedPathRects[--_closedPathRectsCount]; + _pathNodesCount -= 2; + currentRectIndex = _closedPathRects[--_closedPathRectsCount]; + } + for (int16 i = 0; i < _pathNodesCount; i++) { + pointsArray[pointsCount++] = _pathNodes[i].y; + pointsArray[pointsCount++] = _pathNodes[i].x; + } + } + + pointsArray[pointsCount++] = destY; + pointsArray[pointsCount++] = destX; + + pointsArray[0] = 0; + pointsArray[1] = _pathNodesCount + 1; + } + + debug(0, "SegmentMap::findPath() count = %d", pointsArray[1]); + +#if 0 // DEBUG: Draw the path we found + int sx = sourceX, sy = sourceY; + LineData ld; + ld.pitch = _vm->_sceneWidth; + ld.surf = _vm->_screen->_backScreen; + for (int16 i = 0; i < pointsArray[1] * 2; i+=2) { + debug(0, "x = %d; y = %d", pointsArray[3+i], pointsArray[2+i]); + Graphics::drawLine(sx, sy, pointsArray[3+i], pointsArray[2+i], 0xFF, plotProc, &ld); + sx = pointsArray[3+i]; + sy = pointsArray[2+i]; + } +#endif + +} + +int8 SegmentMap::getScalingAtPoint(int16 x, int16 y) { + int8 scaling = 0; + for (uint i = 0; i < _infoRects.size(); i++) { + if (_infoRects[i].id == 0 && _infoRects[i].isPointInside(x, y)) { + int8 topScaling = (int8)_infoRects[i].b; + int8 bottomScaling = (int8)_infoRects[i].c; + if (y - _infoRects[i].y != 0) { + scaling = (ABS(y - _infoRects[i].y) * (bottomScaling - topScaling) / _infoRects[i].height) + topScaling; + } + } + } + debug(0, "SegmentMap::getScalingAtPoint(%d, %d) %d", x, y, scaling); + return scaling; +} + +void SegmentMap::getRgbModifiertAtPoint(int16 x, int16 y, int16 id, byte &r, byte &g, byte &b) { + r = 0; + g = 0; + b = 0; + for (uint i = 0; i < _infoRects.size(); i++) { + if (_infoRects[i].id == id && _infoRects[i].isPointInside(x, y)) { + r = _infoRects[i].a; + g = _infoRects[i].b; + b = _infoRects[i].c; + } + } + debug(0, "SegmentMap::getRgbModifiertAtPoint() r: %d; g: %d; b: %d", r, g, b); +} + +void SegmentMap::loadSegmapMaskRectSurface(byte *maskData, SegmapMaskRect &maskRect) { + + maskRect.surface = new Graphics::Surface(); + maskRect.surface->create(maskRect.width, maskRect.height, Graphics::PixelFormat::createFormatCLUT8()); + + byte *backScreen = _vm->_screen->_backScreen + maskRect.x + (maskRect.y * _vm->_sceneWidth); + byte *dest = (byte*)maskRect.surface->getBasePtr(0, 0); + + for (int16 h = 0; h < maskRect.height; h++) { + int16 w = maskRect.width; + while (w > 0) { + byte mask = *maskData++; + byte count = mask & 0x7F; + if (mask & 0x80) + memcpy(dest, backScreen, count); + else + memset(dest, 0xFF, count); + w -= count; + dest += count; + backScreen += count; + } + backScreen += _vm->_sceneWidth - maskRect.width; + } + +} + +void SegmentMap::freeSegmapMaskRectSurfaces() { + for (uint i = 0; i < _maskRects.size(); i++) { + delete _maskRects[i].surface; + } +} + +void SegmentMap::addMasksToRenderQueue() { + for (uint i = 0; i < _maskRects.size(); i++) { + _vm->_screen->_renderQueue->addMask(_maskRects[i]); + } +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/segmap.h b/engines/toltecs/segmap.h new file mode 100644 index 0000000000..30182a6b71 --- /dev/null +++ b/engines/toltecs/segmap.h @@ -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. + * + * + */ + +#ifndef TOLTECS_SEGMAP_H +#define TOLTECS_SEGMAP_H + +#include "common/array.h" + +#include "toltecs/screen.h" + +namespace Toltecs { + +struct ScriptWalk { + int16 y, x; + int16 y1, x1, y2, x2; + int16 yerror, xerror; + int16 mulValue; + int16 scaling; +}; + +struct SegmapMaskRect { + int16 x, y; + int16 width, height; + int16 priority; + Graphics::Surface *surface; +}; + +class SegmentMap { +public: + SegmentMap(ToltecsEngine *vm); + ~SegmentMap(); + + void load(byte *source); + + int16 findPathRectAtPoint(int16 x, int16 y); + void adjustPathPoint(int16 &x, int16 &y); + + void findPath(int16 *pointsArray, int16 destX, int16 destY, int16 sourceX, int16 sourceY); + + int8 getScalingAtPoint(int16 x, int16 y); + void getRgbModifiertAtPoint(int16 x, int16 y, int16 id, byte &r, byte &g, byte &b); + + void addMasksToRenderQueue(); + +//protected: +public: // for debugging purposes + + struct SegmapPathRect { + int16 x1, y1, x2, y2; + }; + + struct SegmapInfoRect { + int16 y, x; + int16 height, width; + byte id; + byte a, b, c; + inline bool isPointInside(int16 px, int16 py) { + return py >= y && py <= y + height && px >= x && px <= x + width; + } + }; + + struct PathPoint { + int16 y, x; + }; + + typedef Common::Array<SegmapMaskRect> SegmapMaskRectArray; + typedef Common::Array<SegmapPathRect> SegmapPathRectArray; + typedef Common::Array<SegmapInfoRect> SegmapInfoRectArray; + + ToltecsEngine *_vm; + + SegmapMaskRectArray _maskRects; + byte *_maskRectData; + + SegmapPathRectArray _pathRects; + SegmapInfoRectArray _infoRects; + + int16 _deadEndPathRects[1000]; + uint _deadEndPathRectsCount; + + int16 _closedPathRects[1000]; + uint _closedPathRectsCount; + + PathPoint _pathNodes[1000]; + int16 _pathNodesCount; + + int16 findNextPathRect(int16 srcRectIndex, int16 destX, int16 destY); + + void loadSegmapMaskRectSurface(byte *maskData, SegmapMaskRect &maskRect); + void freeSegmapMaskRectSurfaces(); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_SEGMAP_H */ diff --git a/engines/toltecs/sound.cpp b/engines/toltecs/sound.cpp new file mode 100644 index 0000000000..47d52637fb --- /dev/null +++ b/engines/toltecs/sound.cpp @@ -0,0 +1,188 @@ +/* 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/mixer.h" +#include "audio/decoders/raw.h" + +#include "toltecs/toltecs.h" +#include "toltecs/resource.h" +#include "toltecs/segmap.h" +#include "toltecs/sound.h" + +namespace Toltecs { + +Sound::Sound(ToltecsEngine *vm) : _vm(vm) { + for (int i = 0; i < kMaxChannels; i++) { + channels[i].type = kChannelTypeEmpty; + channels[i].resIndex = -1; + } +} + +Sound::~Sound() { +} + +void Sound::playSpeech(int16 resIndex) { + debug(0, "playSpeech(%d)", resIndex); + internalPlaySound(resIndex, kChannelTypeSpeech, 50 /*TODO*/, 0); +} + +void Sound::playSound(int16 resIndex, int16 type, int16 volume) { + + // TODO: Use the right volumes + + debug(0, "playSound(%d, %d, %d)", resIndex, type, volume); + + if (volume == -1 || type == -2) { + if (type == kChannelTypeBackground) { + internalPlaySound(resIndex, type, 50 /*TODO*/, 0); + } else { + internalPlaySound(resIndex, type, 100 /*TODO*/, 0); + } + } else { + internalPlaySound(resIndex, type, 100 /*TODO*/, 0); + } + +} + +void Sound::playSoundAtPos(int16 resIndex, int16 x, int16 y) { + + debug(0, "playSoundAtPos(%d, %d, %d)", resIndex, x, y); + + int16 volume, panning = 0, deltaX = 0; + int8 scaling = _vm->_segmap->getScalingAtPoint(x, y); + + if (scaling >= 0) + volume = 50 + ABS(scaling) / 2; + else + volume = 50 - ABS(scaling) / 2; + + if (_vm->_cameraX > x) + deltaX = _vm->_cameraX - x; + else if (_vm->_cameraX + 640 < x) + deltaX = x - (_vm->_cameraX + 640); + if (deltaX > 600) + deltaX = 600; + + volume = ((100 - deltaX / 6) * volume) / 100; + + if (_vm->_cameraX + 320 != x) { + panning = CLIP(x - (_vm->_cameraX + 320), -381, 381) / 3; + } + + internalPlaySound(resIndex, 1, volume, panning); + +} + +void Sound::internalPlaySound(int16 resIndex, int16 type, int16 volume, int16 panning) { + + if (resIndex == -1) { + // Stop all sounds + _vm->_mixer->stopAll(); + _vm->_screen->keepTalkTextItemsAlive(); + for (int i = 0; i < kMaxChannels; i++) { + channels[i].type = kChannelTypeEmpty; + channels[i].resIndex = -1; + } + } else if (type == -2) { + // Stop sounds with specified resIndex + for (int i = 0; i < kMaxChannels; i++) { + if (channels[i].resIndex == resIndex) { + _vm->_mixer->stopHandle(channels[i].handle); + channels[i].type = kChannelTypeEmpty; + channels[i].resIndex = -1; + } + } + } else { + + if (type == -3) { + // Stop speech and play new sound + stopSpeech(); + } + + // Play new sound in empty channel + int freeChannel = -1; + for (int i = 0; i < kMaxChannels; i++) { + if (channels[i].type == kChannelTypeEmpty || !_vm->_mixer->isSoundHandleActive(channels[i].handle)) { + freeChannel = i; + break; + } + } + + // If all channels are in use no new sound will be played + if (freeChannel >= 0) { + + Resource *soundResource = _vm->_res->load(resIndex); + + Audio::AudioStream *stream = Audio::makeLoopingAudioStream( + Audio::makeRawStream(soundResource->data, soundResource->size, 22050, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO), + type == kChannelTypeBackground ? 0 : 1); + + channels[freeChannel].type = type; + channels[freeChannel].resIndex = resIndex; + + Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType; + /* + switch (type) { + } + */ + + _vm->_mixer->playStream(soundType, &channels[freeChannel].handle, + stream, -1, volume, panning); + + } + + } + +} + +void Sound::updateSpeech() { + for (int i = 0; i < kMaxChannels; i++) { + if (channels[i].type == kChannelTypeSpeech && _vm->_mixer->isSoundHandleActive(channels[i].handle)) { + _vm->_screen->keepTalkTextItemsAlive(); + break; + } + } +} + +void Sound::stopSpeech() { + for (int i = 0; i < kMaxChannels; i++) { + if (channels[i].type == kChannelTypeSpeech) { + _vm->_mixer->stopHandle(channels[i].handle); + _vm->_screen->keepTalkTextItemsAlive(); + channels[i].type = kChannelTypeEmpty; + channels[i].resIndex = -1; + } + } +} + +void Sound::stopAll() { + for (int i = 0; i < kMaxChannels; i++) { + _vm->_mixer->stopHandle(channels[i].handle); + _vm->_screen->keepTalkTextItemsAlive(); + channels[i].type = kChannelTypeEmpty; + channels[i].resIndex = -1; + } +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/sound.h b/engines/toltecs/sound.h new file mode 100644 index 0000000000..e4580effa1 --- /dev/null +++ b/engines/toltecs/sound.h @@ -0,0 +1,74 @@ +/* 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 TOLTECS_SOUND_H +#define TOLTECS_SOUND_H + +#include "audio/mixer.h" // for Audio::SoundHandle + +#include "toltecs/toltecs.h" + +namespace Toltecs { + +// 0x1219 + +enum SoundChannelType { + kChannelTypeEmpty = 0, + kChannelTypeBackground = -1, + kChannelTypeSfx = -2, + kChannelTypeSpeech = -3 +}; + +struct SoundChannel { + int16 resIndex; + int16 type; + Audio::SoundHandle handle; +}; + +const int kMaxChannels = 4; + +class Sound { +public: + Sound(ToltecsEngine *vm); + ~Sound(); + + void playSpeech(int16 resIndex); + void playSound(int16 resIndex, int16 type, int16 volume); + void playSoundAtPos(int16 resIndex, int16 x, int16 y); + void updateSpeech(); + void stopSpeech(); + void stopAll(); + +protected: + ToltecsEngine *_vm; + + SoundChannel channels[kMaxChannels]; + + void internalPlaySound(int16 resIndex, int16 type, int16 volume, int16 panning); + +}; + + +} // End of namespace Toltecs + +#endif /* TOLTECS_SOUND_H */ diff --git a/engines/toltecs/sprite.cpp b/engines/toltecs/sprite.cpp new file mode 100644 index 0000000000..7a02663793 --- /dev/null +++ b/engines/toltecs/sprite.cpp @@ -0,0 +1,509 @@ +/* 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 "toltecs/toltecs.h" +#include "toltecs/palette.h" +#include "toltecs/render.h" +#include "toltecs/resource.h" + +namespace Toltecs { + +class SpriteReader : public SpriteFilter { +public: + SpriteReader(byte *source, const SpriteDrawItem &sprite) : SpriteFilter(sprite), _source(source) { + _curWidth = _sprite->origWidth; + _curHeight = _sprite->origHeight; + } + SpriteReaderStatus readPacket(PixelPacket &packet) { + if (_sprite->flags & 0x40) { + // shadow sprite + packet.count = _source[0] & 0x7F; + if (_source[0] & 0x80) + packet.pixel = 1; + else + packet.pixel = 0; + _source++; + } else if (_sprite->flags & 0x10) { + // 256-color sprite + packet.pixel = *_source++; + packet.count = *_source++; + } else { + // 16-color sprite + packet.count = _source[0] & 0x0F; + packet.pixel = (_source[0] & 0xF0) >> 4; + _source++; + } + _curWidth -= packet.count; + if (_curWidth <= 0) { + _curHeight--; + if (_curHeight == 0) { + return kSrsEndOfSprite; + } else { + _curWidth = _sprite->origWidth; + return kSrsEndOfLine; + } + } else { + return kSrsPixelsLeft; + } + } + byte *getSource() { + return _source; + } + void setSource(byte *source) { + _source = source; + _curHeight++; + } +protected: + byte *_source; + int16 _curWidth, _curHeight; +}; + +class SpriteFilterScaleDown : public SpriteFilter { +public: + SpriteFilterScaleDown(const SpriteDrawItem &sprite, SpriteReader *reader) : SpriteFilter(sprite), _reader(reader) { + _height = _sprite->height; + _yerror = _sprite->yerror; + _origHeight = _sprite->origHeight; + _scalerStatus = 0; + } + SpriteReaderStatus readPacket(PixelPacket &packet) { + SpriteReaderStatus status = kSrsPixelsLeft; + if (_scalerStatus == 0) { + _xerror = _sprite->xdelta; + _yerror -= 100; + while (_yerror <= 0) { + do { + status = _reader->readPacket(packet); + } while (status == kSrsPixelsLeft); + _yerror += _sprite->ydelta - 100; + } + if (status == kSrsEndOfSprite) + return kSrsEndOfSprite; + _scalerStatus = 1; + } + if (_scalerStatus == 1) { + status = _reader->readPacket(packet); + byte updcount = packet.count; + while (updcount--) { + _xerror -= 100; + if (_xerror <= 0) { + if (packet.count > 0) + packet.count--; + _xerror += _sprite->xdelta; + } + } + if (status == kSrsEndOfLine) { + if (--_height == 0) + return kSrsEndOfSprite; + _scalerStatus = 0; + return kSrsEndOfLine; + } + } + return kSrsPixelsLeft; + } +protected: + SpriteReader *_reader; + int16 _xerror, _yerror; + int16 _height; + int16 _origHeight; + int _scalerStatus; +}; + +class SpriteFilterScaleUp : public SpriteFilter { +public: + SpriteFilterScaleUp(const SpriteDrawItem &sprite, SpriteReader *reader) : SpriteFilter(sprite), _reader(reader) { + _height = _sprite->height; + _yerror = _sprite->yerror; + _origHeight = _sprite->origHeight; + _scalerStatus = 0; + } + SpriteReaderStatus readPacket(PixelPacket &packet) { + SpriteReaderStatus status; + if (_scalerStatus == 0) { + _xerror = _sprite->xdelta; + _sourcep = _reader->getSource(); + _scalerStatus = 1; + } + if (_scalerStatus == 1) { + status = _reader->readPacket(packet); + byte updcount = packet.count; + while (updcount--) { + _xerror -= 100; + if (_xerror <= 0) { + packet.count++; + _xerror += _sprite->xdelta; + } + } + if (status == kSrsEndOfLine) { + if (--_height == 0) + return kSrsEndOfSprite; + _yerror -= 100; + if (_yerror <= 0) { + _reader->setSource(_sourcep); + _yerror += _sprite->ydelta + 100; + } + _scalerStatus = 0; + return kSrsEndOfLine; + } + } + return kSrsPixelsLeft; + } +protected: + SpriteReader *_reader; + byte *_sourcep; + int16 _xerror, _yerror; + int16 _height; + int16 _origHeight; + int _scalerStatus; +}; + +bool Screen::createSpriteDrawItem(const DrawRequest &drawRequest, SpriteDrawItem &sprite) { + int16 scaleValueX, scaleValueY; + int16 xoffs, yoffs; + byte *spriteData; + int16 frameNum; + + memset(&sprite, 0, sizeof(SpriteDrawItem)); + + if (drawRequest.flags == 0xFFFF) + return false; + + frameNum = drawRequest.flags & 0x0FFF; + + sprite.flags = 0; + sprite.baseColor = drawRequest.baseColor; + sprite.x = drawRequest.x; + sprite.y = drawRequest.y; + sprite.priority = drawRequest.y; + sprite.resIndex = drawRequest.resIndex; + sprite.frameNum = frameNum; + + spriteData = _vm->_res->load(drawRequest.resIndex)->data; + + if (drawRequest.flags & 0x1000) { + sprite.flags |= 4; + } + + if (drawRequest.flags & 0x2000) { + sprite.flags |= 0x10; + } + + if (drawRequest.flags & 0x4000) { + sprite.flags |= 0x40; + } + + // First initialize the sprite item with the values from the sprite resource + + SpriteFrameEntry spriteFrameEntry(spriteData + frameNum * 12); + + if (spriteFrameEntry.w == 0 || spriteFrameEntry.h == 0) + return false; + + sprite.offset = spriteFrameEntry.offset; + + sprite.width = spriteFrameEntry.w; + sprite.height = spriteFrameEntry.h; + sprite.origWidth = spriteFrameEntry.w; + sprite.origHeight = spriteFrameEntry.h; + + if (drawRequest.flags & 0x1000) { + xoffs = spriteFrameEntry.w - spriteFrameEntry.x; + } else { + xoffs = spriteFrameEntry.x; + } + + yoffs = spriteFrameEntry.y; + + // If the sprite should be scaled we need to initialize some values now + + if (drawRequest.scaling != 0) { + + byte scaleValue = ABS(drawRequest.scaling); + + scaleValueX = scaleValue * sprite.origWidth; + sprite.xdelta = (10000 * sprite.origWidth) / scaleValueX; + scaleValueX /= 100; + + scaleValueY = scaleValue * sprite.origHeight; + sprite.ydelta = (10000 * sprite.origHeight) / scaleValueY; + scaleValueY /= 100; + + if (drawRequest.scaling > 0) { + sprite.flags |= 2; + sprite.width = sprite.origWidth + scaleValueX; + sprite.height = sprite.origHeight + scaleValueY; + xoffs += (xoffs * scaleValue) / 100; + yoffs += (yoffs * scaleValue) / 100; + } else { + sprite.flags |= 1; + sprite.width = sprite.origWidth - scaleValueX; + sprite.height = sprite.origHeight - 1 - scaleValueY; + if (sprite.width <= 0 || sprite.height <= 0) + return false; + xoffs -= (xoffs * scaleValue) / 100; + yoffs -= (yoffs * scaleValue) / 100; + } + + } + + sprite.x -= xoffs; + sprite.y -= yoffs; + + sprite.yerror = sprite.ydelta; + + // Now we check if the sprite needs to be clipped + + // Clip Y + if (sprite.y - _vm->_cameraY < 0) { + + int16 clipHeight = ABS(sprite.y - _vm->_cameraY); + int16 skipHeight = clipHeight; + byte *spriteFrameData; + + sprite.height -= clipHeight; + if (sprite.height <= 0) + return false; + + sprite.y = _vm->_cameraY; + + // If the sprite is scaled + if (sprite.flags & 3) { + int16 chopHeight = sprite.ydelta; + if ((sprite.flags & 2) == 0) { + do { + chopHeight -= 100; + if (chopHeight <= 0) { + skipHeight++; + chopHeight += sprite.ydelta; + } else { + clipHeight--; + } + } while (clipHeight > 0); + } else { + do { + chopHeight -= 100; + if (chopHeight < 0) { + skipHeight--; + chopHeight += sprite.ydelta + 100; + } + clipHeight--; + } while (clipHeight > 0); + } + sprite.yerror = chopHeight; + } + + spriteFrameData = spriteData + sprite.offset; + // Now the sprite's offset is adjusted to point to the starting line + if ((sprite.flags & 0x10) == 0) { + while (skipHeight--) { + int16 lineWidth = 0; + while (lineWidth < sprite.origWidth) { + sprite.offset++; + lineWidth += spriteFrameData[0] & 0x0F; + spriteFrameData++; + } + } + } else { + while (skipHeight--) { + int16 lineWidth = 0; + while (lineWidth < sprite.origWidth) { + sprite.offset += 2; + lineWidth += spriteFrameData[1]; + spriteFrameData += 2; + } + } + } + + } + + if (sprite.y + sprite.height - _vm->_cameraY - _vm->_cameraHeight > 0) + sprite.height -= sprite.y + sprite.height - _vm->_cameraY - _vm->_cameraHeight; + if (sprite.height <= 0) + return false; + + sprite.skipX = 0; + + if (drawRequest.flags & 0x1000) { + // Left border + if (sprite.x - _vm->_cameraX < 0) { + sprite.width -= ABS(sprite.x - _vm->_cameraX); + sprite.x = _vm->_cameraX; + } + // Right border + if (sprite.x + sprite.width - _vm->_cameraX - 640 > 0) { + sprite.flags |= 8; + sprite.skipX = sprite.x + sprite.width - _vm->_cameraX - 640; + sprite.width -= sprite.skipX; + } + } else { + // Left border + if (sprite.x - _vm->_cameraX < 0) { + sprite.flags |= 8; + sprite.skipX = ABS(sprite.x - _vm->_cameraX); + sprite.width -= sprite.skipX; + sprite.x = _vm->_cameraX; + } + // Right border + if (sprite.x + sprite.width - _vm->_cameraX - 640 > 0) { + sprite.flags |= 8; + sprite.width -= sprite.x + sprite.width - _vm->_cameraX - 640; + } + } + + if (sprite.width <= 0) + return false; + + return true; +} + +void Screen::addDrawRequest(const DrawRequest &drawRequest) { + SpriteDrawItem sprite; + if (createSpriteDrawItem(drawRequest, sprite)) + _renderQueue->addSprite(sprite); +} + +void Screen::drawSprite(const SpriteDrawItem &sprite) { + + debug(0, "Screen::drawSprite() x = %d; y = %d; flags = %04X; resIndex = %d; offset = %08X; drawX = %d; drawY = %d", + sprite.x, sprite.y, sprite.flags, sprite.resIndex, sprite.offset, + sprite.x - _vm->_cameraX, sprite.y - _vm->_cameraY); + debug(0, "Screen::drawSprite() width = %d; height = %d; origWidth = %d; origHeight = %d", + sprite.width, sprite.height, sprite.origWidth, sprite.origHeight); + + byte *source = _vm->_res->load(sprite.resIndex)->data + sprite.offset; + byte *dest = _frontScreen + sprite.x + sprite.y * 640; + + SpriteReader spriteReader(source, sprite); + + if (sprite.flags & 0x40) { + // Shadow sprites + if (sprite.flags & 1) { + SpriteFilterScaleDown spriteScaler(sprite, &spriteReader); + drawSpriteCore(dest, spriteScaler, sprite); + } else if (sprite.flags & 2) { + SpriteFilterScaleUp spriteScaler(sprite, &spriteReader); + drawSpriteCore(dest, spriteScaler, sprite); + } else { + drawSpriteCore(dest, spriteReader, sprite); + } + } else if (sprite.flags & 0x10) { + // 256 color sprite + drawSpriteCore(dest, spriteReader, sprite); + } else { + // 16 color sprite + if (sprite.flags & 1) { + SpriteFilterScaleDown spriteScaler(sprite, &spriteReader); + drawSpriteCore(dest, spriteScaler, sprite); + } else if (sprite.flags & 2) { + SpriteFilterScaleUp spriteScaler(sprite, &spriteReader); + drawSpriteCore(dest, spriteScaler, sprite); + } else { + drawSpriteCore(dest, spriteReader, sprite); + } + } + + debug(0, "Screen::drawSprite() ok"); + +} + +void Screen::drawSpriteCore(byte *dest, SpriteFilter &reader, const SpriteDrawItem &sprite) { + + int16 destInc; + + if (sprite.flags & 4) { + destInc = -1; + dest += sprite.width; + } else { + destInc = 1; + } + + SpriteReaderStatus status; + PixelPacket packet; + + byte *destp = dest; + int16 skipX = sprite.skipX; + + int16 w = sprite.width; + int16 h = sprite.height; + + do { + status = reader.readPacket(packet); + + if (skipX > 0) { + while (skipX > 0) { + skipX -= packet.count; + if (skipX < 0) { + packet.count = ABS(skipX); + break; + } + status = reader.readPacket(packet); + } + } + + if (w - packet.count < 0) + packet.count = w; + + w -= packet.count; + + if (((sprite.flags & 0x40) && (packet.pixel != 0)) || + ((sprite.flags & 0x10) && (packet.pixel != 0xFF)) || + (!(sprite.flags & 0x10) && (packet.pixel != 0))) + { + if (sprite.flags & 0x40) { + while (packet.count--) { + *dest = _vm->_palette->getColorTransPixel(*dest); + dest += destInc; + } + } else { + if (sprite.flags & 0x10) { + packet.pixel = ((packet.pixel << 4) & 0xF0) | ((packet.pixel >> 4) & 0x0F); + } else { + packet.pixel += sprite.baseColor - 1; + } + while (packet.count--) { + *dest = packet.pixel; + dest += destInc; + } + } + } else { + dest += packet.count * destInc; + } + + if (status == kSrsEndOfLine || w <= 0) { + if (w <= 0) { + while (status == kSrsPixelsLeft) { + status = reader.readPacket(packet); + } + } + dest = destp + 640; + destp = dest; + skipX = sprite.skipX; + w = sprite.width; + h--; + } + + } while (status != kSrsEndOfSprite && h > 0); + +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/toltecs.cpp b/engines/toltecs/toltecs.cpp new file mode 100644 index 0000000000..046578cffd --- /dev/null +++ b/engines/toltecs/toltecs.cpp @@ -0,0 +1,654 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + */ + +#include "common/config-manager.h" +#include "common/events.h" +#include "common/random.h" +#include "common/str.h" +#include "common/error.h" +#include "common/textconsole.h" + +#include "base/plugins.h" +#include "base/version.h" + +#include "graphics/cursorman.h" + +#include "engines/util.h" + +#include "audio/mixer.h" + +#include "toltecs/toltecs.h" +#include "toltecs/animation.h" +#include "toltecs/menu.h" +#include "toltecs/movie.h" +#include "toltecs/music.h" +#include "toltecs/palette.h" +#include "toltecs/render.h" +#include "toltecs/resource.h" +#include "toltecs/script.h" +#include "toltecs/screen.h" +#include "toltecs/segmap.h" +#include "toltecs/sound.h" +#include "toltecs/microtiles.h" + +namespace Toltecs { + +struct GameSettings { + const char *gameid; + const char *description; + byte id; + uint32 features; + const char *detectname; +}; + +ToltecsEngine::ToltecsEngine(OSystem *syst, const ToltecsGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { + + // Setup mixer + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + + _rnd = new Common::RandomSource("toltecs"); +} + +ToltecsEngine::~ToltecsEngine() { + delete _rnd; +} + +void ToltecsEngine::syncSoundSettings() { + /* + _music->setVolume(ConfMan.getInt("music_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + */ +} + +Common::Error ToltecsEngine::run() { + initGraphics(640, 400, true); + + _isSaveAllowed = true; + + _counter01 = 0; + _counter02 = 0; + _movieSceneFlag = false; + _flag01 = 0; + + _saveLoadRequested = 0; + + _cameraX = 0; + _cameraY = 0; + _newCameraX = 0; + _newCameraY = 0; + _cameraHeight = 0; + + _guiHeight = 26; + + _sceneWidth = 0; + _sceneHeight = 0; + + _doSpeech = true; + _doText = true; + + _walkSpeedY = 5; + _walkSpeedX = 1; + + _mouseX = 0; + _mouseY = 0; + _mouseDblClickTicks = 60; + _mouseWaitForRelease = false; + _mouseButton = 0; + _mouseDisabled = 0; + _leftButtonDown = false; + _rightButtonDown = false; + + _arc = new ArchiveReader(); + _arc->openArchive("WESTERN"); + + _res = new ResourceCache(this); + + _screen = new Screen(this); + + _script = new ScriptInterpreter(this); + _anim = new AnimationPlayer(this); + _palette = new Palette(this); + _segmap = new SegmentMap(this); + _moviePlayer = new MoviePlayer(this); + _musicPlayer = new MusicPlayer(); + _menuSystem = new MenuSystem(this); + + _sound = new Sound(this); + + syncSoundSettings(); + + CursorMan.showMouse(true); + + setupSysStrings(); + +//#define TEST_MENU +#ifdef TEST_MENU + _screen->registerFont(0, 0x0D); + _screen->registerFont(1, 0x0E); + _screen->loadMouseCursor(12); + _palette->loadAddPalette(9, 224); + _palette->setDeltaPalette(_palette->getMainPalette(), 7, 0, 31, 224); + _screen->finishTalkTextItems(); + _screen->clearSprites(); + _menuSystem->run(); + /* + while (1) { + //updateInput(); + _menuSystem->update(); + updateScreen(); + } + */ + return Common::kNoError; +#endif + + // Start main game loop + _script->loadScript(0, 0); + _script->setMainScript(0); + if (ConfMan.hasKey("save_slot")) { + int saveSlot = ConfMan.getInt("save_slot"); + if (saveSlot >= 0 && saveSlot <= 99) { + _screen->loadMouseCursor(12); + loadGameState(saveSlot); + } + } + _script->runScript(); + + _musicPlayer->stopAndClear(); + _sound->stopAll(); + + delete _arc; + delete _res; + delete _screen; + delete _script; + delete _anim; + delete _palette; + delete _segmap; + delete _musicPlayer; + delete _moviePlayer; + delete _menuSystem; + + delete _sound; + + return Common::kNoError; +} + +void ToltecsEngine::setupSysStrings() { + Resource *sysStringsResource = _res->load(15); + const char *sysStrings = (const char*)sysStringsResource->data; + for (int i = 0; i < kSysStrCount; i++) { + debug(1, "sysStrings[%d] = [%s]", i, sysStrings); + _sysStrings[i] = sysStrings; + sysStrings += strlen(sysStrings) + 1; + } + // TODO: Set yes/no chars +} + +void ToltecsEngine::requestSavegame(int slotNum, Common::String &description) { + _saveLoadRequested = 2; + _saveLoadSlot = slotNum; + _saveLoadDescription = description; +} + +void ToltecsEngine::requestLoadgame(int slotNum) { + _saveLoadRequested = 1; + _saveLoadSlot = slotNum; +} + +void ToltecsEngine::loadScene(uint resIndex) { + + Resource *sceneResource = _res->load(resIndex); + byte *scene = sceneResource->data; + + uint32 imageSize = READ_LE_UINT32(scene); + _sceneResIndex = resIndex; + _sceneHeight = READ_LE_UINT16(scene + 4); + _sceneWidth = READ_LE_UINT16(scene + 6); + + // Load scene palette + _palette->loadAddPaletteFrom(scene + 8, 0, 128); + + // Load scene background + byte *source = scene + 392; + byte *destp = _screen->_backScreen; + byte *destEnd = destp + _sceneWidth * _sceneHeight; + while (destp < destEnd) { + int count = 1; + byte pixel = *source++; + if (pixel & 0x80) { + pixel &= 0x7F; + count = *source++; + count += 2; + } + memset(destp, pixel, count); + destp += count; + } + + debug(0, "_sceneWidth = %d; _sceneHeight = %d", _sceneWidth, _sceneHeight); + + // Load scene segmap + _segmap->load(scene + imageSize + 4); + + _screen->_fullRefresh = true; + _screen->_renderQueue->clear(); + +} + +void ToltecsEngine::updateScreen() { + + _sound->updateSpeech(); + + _screen->updateShakeScreen(); + + // TODO: Set quit flag + if (shouldQuit()) + return; + + if (!_movieSceneFlag) + updateInput(); + else + _mouseButton = 0; + + // TODO? Check keyb + + _counter01--; + if (_counter01 <= 0) { + _counter01 = MIN(_counter02, 30); + _counter02 = 0; + drawScreen(); + _flag01 = 1; + _counter02 = 1; + } else { + _screen->clearSprites(); + _flag01 = 0; + } + + static uint32 prevUpdateTime = 0; + uint32 currUpdateTime; + do { + currUpdateTime = _system->getMillis(); + _counter02 = (currUpdateTime - prevUpdateTime) / 13; + } while (_counter02 == 0); + prevUpdateTime = currUpdateTime; + +} + +void ToltecsEngine::drawScreen() { + + // FIXME: Quick hack, sometimes cameraY was negative (the code in updateCamera was at fault) + if (_cameraY < 0) _cameraY = 0; + + _segmap->addMasksToRenderQueue(); + _screen->addTalkTextItemsToRenderQueue(); + + _screen->_renderQueue->update(); + + //debug("_guiHeight = %d\n", _guiHeight); + + if (_screen->_guiRefresh && _guiHeight > 0 && _cameraHeight > 0) { + // Update the GUI when needed and it's visible + _system->copyRectToScreen((const byte *)_screen->_frontScreen + _cameraHeight * 640, + 640, 0, _cameraHeight, 640, _guiHeight); + _screen->_guiRefresh = false; + } + + _system->updateScreen(); + + updateCamera(); + +} + +void ToltecsEngine::updateInput() { + + Common::Event event; + Common::EventManager *eventMan = _system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + _keyState = event.kbd; + + //debug("key: flags = %02X; keycode = %d", _keyState.flags, _keyState.keycode); + + // FIXME: This is just for debugging + switch (event.kbd.keycode) { + case Common::KEYCODE_F7: + savegame("toltecs.001", "Quicksave"); + break; + case Common::KEYCODE_F9: + loadgame("toltecs.001"); + break; + case Common::KEYCODE_ESCAPE: + // Skip current dialog line, if a dialog is active + if (_screen->getTalkTextDuration() > 0) { + _sound->stopSpeech(); + _screen->finishTalkTextItems(); + _keyState.reset(); // event consumed + } + break; + default: + break; + } + + break; + case Common::EVENT_KEYUP: + _keyState.reset(); + break; + case Common::EVENT_QUIT: + quitGame(); + break; + case Common::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + break; + case Common::EVENT_LBUTTONDOWN: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _leftButtonDown = true; + break; + case Common::EVENT_LBUTTONUP: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _leftButtonDown = false; + break; + case Common::EVENT_RBUTTONDOWN: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _rightButtonDown = true; + break; + case Common::EVENT_RBUTTONUP: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _rightButtonDown = false; + break; + default: + break; + } + } + + if (!_mouseDisabled) { + + if (_mouseDblClickTicks > 0) + _mouseDblClickTicks--; + + byte mouseButtons = 0; + if (_leftButtonDown) + mouseButtons |= 1; + if (_rightButtonDown) + mouseButtons |= 2; + + if (mouseButtons != 0) { + if (!_mouseWaitForRelease) { + _mouseButton = mouseButtons; + if (_mouseDblClickTicks > 0) + _mouseButton = 0x80; + //if (_mouseButton == 0x80) debug("DBL!"); + _mouseDblClickTicks = 30; // maybe TODO + _mouseWaitForRelease = true; + } else { + _mouseButton = 0; + } + } else { + _mouseWaitForRelease = false; + _mouseButton = 0; + } + + } + +} + +void ToltecsEngine::setGuiHeight(int16 guiHeight) { + if (guiHeight != _guiHeight) { + _guiHeight = guiHeight; + _cameraHeight = 400 - _guiHeight; + _screen->_guiRefresh = true; + debug(0, "ToltecsEngine::setGuiHeight() _guiHeight = %d; _cameraHeight = %d", _guiHeight, _cameraHeight); + // TODO: clearScreen(); + } +} + +void ToltecsEngine::setCamera(int16 x, int16 y) { + + _screen->finishTalkTextItems(); + + _screen->clearSprites(); + + _cameraX = x; + _newCameraX = x; + + _cameraY = y; + _newCameraY = y; + +} + +bool ToltecsEngine::getCameraChanged() { + return _cameraX != _newCameraX || _cameraY != _newCameraY; +} + +void ToltecsEngine::scrollCameraUp(int16 delta) { + if (_newCameraY > 0) { + if (_newCameraY < delta) + _newCameraY = 0; + else + _newCameraY -= delta; + } +} + +void ToltecsEngine::scrollCameraDown(int16 delta) { + debug(0, "ToltecsEngine::scrollCameraDown(%d)", delta); + if (_newCameraY != _sceneHeight - _cameraHeight) { + if (_sceneHeight - _cameraHeight < _newCameraY + delta) + delta += (_sceneHeight - _cameraHeight) - (delta + _newCameraY); + _newCameraY += delta; + debug(0, "ToltecsEngine::scrollCameraDown() _newCameraY = %d; delta = %d", _newCameraY, delta); + } +} + +void ToltecsEngine::scrollCameraLeft(int16 delta) { + if (_newCameraX > 0) { + if (_newCameraX < delta) + _newCameraX = 0; + else + _newCameraX -= delta; + } +} + +void ToltecsEngine::scrollCameraRight(int16 delta) { + debug(0, "ToltecsEngine::scrollCameraRight(%d)", delta); + if (_newCameraX != _sceneWidth - 640) { + if (_sceneWidth - 640 < delta + _newCameraX) + delta += (_sceneWidth - 640) - (delta + _newCameraX); + _newCameraX += delta; + debug(0, "ToltecsEngine::scrollCameraRight() _newCameraX = %d; delta = %d", _newCameraY, delta); + } +} + +void ToltecsEngine::updateCamera() { + + if (_cameraX != _newCameraX) { + _cameraX = _newCameraX; + _screen->_fullRefresh = true; + _screen->finishTalkTextItems(); + } + + if (_cameraY != _newCameraY) { + _cameraY = _newCameraY; + _screen->_fullRefresh = true; + _screen->finishTalkTextItems(); + } + + //debug(0, "ToltecsEngine::updateCamera() _cameraX = %d; _cameraY = %d", _cameraX, _cameraY); + +} + +void ToltecsEngine::talk(int16 slotIndex, int16 slotOffset) { + + byte *scanData = _script->getSlotData(slotIndex) + slotOffset; + + while (*scanData < 0xF0) { + if (*scanData == 0x19) { + scanData++; + } else if (*scanData == 0x14) { + scanData++; + } else if (*scanData == 0x0A) { + scanData += 4; + } else if (*scanData < 0x0A) { + scanData++; + } + scanData++; + } + + if (*scanData == 0xFE) { + if (_doSpeech) { + int16 resIndex = READ_LE_UINT16(scanData + 1); + debug(0, "ToltecsEngine::talk() playSound(resIndex: %d)", resIndex); + _sound->playSpeech(resIndex); + } + if (_doText) { + _screen->updateTalkText(slotIndex, slotOffset); + } else { + _screen->keepTalkTextItemsAlive(); + } + } else { + _screen->updateTalkText(slotIndex, slotOffset); + } + +} + +void ToltecsEngine::walk(byte *walkData) { + + int16 xdelta, ydelta, v8, v10, v11; + int16 xstep, ystep; + ScriptWalk walkInfo; + + walkInfo.y = READ_LE_UINT16(walkData + 0); + walkInfo.x = READ_LE_UINT16(walkData + 2); + walkInfo.y1 = READ_LE_UINT16(walkData + 4); + walkInfo.x1 = READ_LE_UINT16(walkData + 6); + walkInfo.y2 = READ_LE_UINT16(walkData + 8); + walkInfo.x2 = READ_LE_UINT16(walkData + 10); + walkInfo.yerror = READ_LE_UINT16(walkData + 12); + walkInfo.xerror = READ_LE_UINT16(walkData + 14); + walkInfo.mulValue = READ_LE_UINT16(walkData + 16); + walkInfo.scaling = READ_LE_UINT16(walkData + 18); + + walkInfo.scaling = -_segmap->getScalingAtPoint(walkInfo.x, walkInfo.y); + + if (walkInfo.y1 < walkInfo.y2) + ystep = -1; + else + ystep = 1; + ydelta = ABS(walkInfo.y1 - walkInfo.y2) * _walkSpeedY; + + if (walkInfo.x1 < walkInfo.x2) + xstep = -1; + else + xstep = 1; + xdelta = ABS(walkInfo.x1 - walkInfo.x2) * _walkSpeedX; + + debug(0, "ToltecsEngine::walk() xdelta = %d; ydelta = %d", xdelta, ydelta); + + if (xdelta > ydelta) + SWAP(xdelta, ydelta); + + v8 = 100 * xdelta; + if (v8 != 0) { + if (walkInfo.scaling > 0) + v8 -= v8 * ABS(walkInfo.scaling) / 100; + else + v8 += v8 * ABS(walkInfo.scaling) / 100; + if (ydelta != 0) + v8 /= ydelta; + } + + if (ydelta > ABS(walkInfo.x1 - walkInfo.x2) * _walkSpeedX) { + v10 = 100 - walkInfo.scaling; + v11 = v8; + } else { + v10 = v8; + v11 = 100 - walkInfo.scaling; + } + + walkInfo.yerror += walkInfo.mulValue * v10; + while (walkInfo.yerror >= 100 * _walkSpeedY) { + walkInfo.yerror -= 100 * _walkSpeedY; + if (walkInfo.y == walkInfo.y1) { + walkInfo.x = walkInfo.x1; + break; + } + walkInfo.y += ystep; + } + + walkInfo.xerror += walkInfo.mulValue * v11; + while (walkInfo.xerror >= 100 * _walkSpeedX) { + walkInfo.xerror -= 100 * _walkSpeedX; + if (walkInfo.x == walkInfo.x1) { + walkInfo.y = walkInfo.y1; + break; + } + walkInfo.x += xstep; + } + + WRITE_LE_UINT16(walkData + 0, walkInfo.y); + WRITE_LE_UINT16(walkData + 2, walkInfo.x); + WRITE_LE_UINT16(walkData + 4, walkInfo.y1); + WRITE_LE_UINT16(walkData + 6, walkInfo.x1); + WRITE_LE_UINT16(walkData + 8, walkInfo.y2); + WRITE_LE_UINT16(walkData + 10, walkInfo.x2); + WRITE_LE_UINT16(walkData + 12, walkInfo.yerror); + WRITE_LE_UINT16(walkData + 14, walkInfo.xerror); + WRITE_LE_UINT16(walkData + 16, walkInfo.mulValue); + WRITE_LE_UINT16(walkData + 18, walkInfo.scaling); + +} + +int16 ToltecsEngine::findRectAtPoint(byte *rectData, int16 x, int16 y, int16 index, int16 itemSize, + byte *rectDataEnd) { + + rectData += index * itemSize; + + while (rectData < rectDataEnd) { + int16 rectY = READ_LE_UINT16(rectData); + if (rectY == -10) + break; + int16 rectX = READ_LE_UINT16(rectData + 2); + int16 rectH = READ_LE_UINT16(rectData + 4); + int16 rectW = READ_LE_UINT16(rectData + 6); + + debug(0, "x = %d; y = %d; x1 = %d; y2 = %d; w = %d; h = %d", + x, y, rectX, rectY, rectW, rectH); + + if (x >= rectX && x <= rectX + rectW && y >= rectY && y <= rectY + rectH) { + return index; + } + index++; + rectData += itemSize; + } + + return -1; + +} + +} // End of namespace Toltecs diff --git a/engines/toltecs/toltecs.h b/engines/toltecs/toltecs.h new file mode 100644 index 0000000000..279016cce8 --- /dev/null +++ b/engines/toltecs/toltecs.h @@ -0,0 +1,213 @@ +/* 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 TOLTECS_H +#define TOLTECS_H + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/events.h" +#include "common/file.h" +#include "common/keyboard.h" +#include "common/random.h" +#include "common/textconsole.h" + +#include "engines/engine.h" + +#include "graphics/surface.h" + +namespace Toltecs { + +struct ToltecsGameDescription; + +class AnimationPlayer; +class ArchiveReader; +class Input; +class MenuSystem; +class MoviePlayer; +class MusicPlayer; +class Palette; +class ResourceCache; +class ScriptInterpreter; +class Screen; +class SegmentMap; +class Sound; + +enum SysString { + kStrLoadingPleaseWait, + kStrWhatCanIDoForYou, + kStrLoad, + kStrSave, + kStrTextOn, + kStrTextOff, + kStrVoicesOn, + kStrVoicesOff, + kStrVolume, + kStrPlay, + kStrQuit, + kStrLoadGame, + kStrSaveGame, + kStrAdjustVolume, + kStrMaster, + kStrVoices, + kStrMusic, + kStrSoundFx, + kStrBackground, + kStrCancel, + kStrDone, + kStrAreYouSure, + kStrYes, + kStrNo, + kSysStrCount +}; + +class ToltecsEngine : public ::Engine { + Common::KeyState _keyPressed; + +protected: + Common::Error run(); +// void shutdown(); + +public: + ToltecsEngine(OSystem *syst, const ToltecsGameDescription *gameDesc); + virtual ~ToltecsEngine(); + + virtual bool hasFeature(EngineFeature f) const; + virtual void syncSoundSettings(); + + Common::RandomSource *_rnd; + const ToltecsGameDescription *_gameDescription; + uint32 getFeatures() const; + Common::Language getLanguage() const; + const Common::String& getTargetName() const { return _targetName; } + + void setupSysStrings(); + void requestSavegame(int slotNum, Common::String &description); + void requestLoadgame(int slotNum); + + void loadScene(uint resIndex); + + void updateScreen(); + void drawScreen(); + void updateInput(); + + void setGuiHeight(int16 guiHeight); + + void setCamera(int16 x, int16 y); + bool getCameraChanged(); + void scrollCameraUp(int16 delta); + void scrollCameraDown(int16 delta); + void scrollCameraLeft(int16 delta); + void scrollCameraRight(int16 delta); + void updateCamera(); + + void talk(int16 slotIndex, int16 slotOffset); + + void walk(byte *walkData); + + int16 findRectAtPoint(byte *rectData, int16 x, int16 y, int16 index, int16 itemSize, + byte *rectDataEnd); + +public: + + AnimationPlayer *_anim; + ArchiveReader *_arc; + Input *_input; + MenuSystem *_menuSystem; + MoviePlayer *_moviePlayer; + MusicPlayer *_musicPlayer; + Palette *_palette; + ResourceCache *_res; + ScriptInterpreter *_script; + Screen *_screen; + SegmentMap *_segmap; + Sound *_sound; + + Common::String _sysStrings[kSysStrCount]; + + int _saveLoadRequested; + int _saveLoadSlot; + Common::String _saveLoadDescription; + + uint _sceneResIndex; + int16 _sceneWidth, _sceneHeight; + + int _counter01, _counter02; + bool _movieSceneFlag; + byte _flag01; + + int16 _cameraX, _cameraY; + int16 _newCameraX, _newCameraY; + int16 _cameraHeight; + int16 _guiHeight; + + bool _doSpeech, _doText; + + int16 _walkSpeedY, _walkSpeedX; + + Common::KeyState _keyState; + int16 _mouseX, _mouseY; + int16 _mouseDblClickTicks; + bool _mouseWaitForRelease; + byte _mouseButton; + int16 _mouseDisabled; + bool _leftButtonDown, _rightButtonDown; + + const char *getSysString(int index) const { return _sysStrings[index].c_str(); } + + /* Save/load */ + + enum kReadSaveHeaderError { + kRSHENoError = 0, + kRSHEInvalidType = 1, + kRSHEInvalidVersion = 2, + kRSHEIoError = 3 + }; + + struct SaveHeader { + Common::String description; + uint32 version; + byte gameID; + uint32 flags; + Graphics::Surface *thumbnail; + }; + + bool _isSaveAllowed; + + bool canLoadGameStateCurrently() { return _isSaveAllowed; } + bool canSaveGameStateCurrently() { return _isSaveAllowed; } + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &description); + void savegame(const char *filename, const char *description); + void loadgame(const char *filename); + + const char *getSavegameFilename(int num); + static Common::String getSavegameFilename(const Common::String &target, int num); + + static kReadSaveHeaderError readSaveHeader(Common::SeekableReadStream *in, bool loadThumbnail, SaveHeader &header); + +}; + +} // End of namespace Toltecs + +#endif /* TOLTECS_H */ |