/* 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/console.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) { _rnd = new Common::RandomSource("toltecs"); } ToltecsEngine::~ToltecsEngine() { delete _rnd; } Common::Error ToltecsEngine::run() { initGraphics(640, 400); _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); _music = new Music(_arc); _menuSystem = new MenuSystem(this); _sound = new Sound(this); _console = new Console(this); _cfgText = ConfMan.getBool("subtitles"); _cfgVoices = !ConfMan.getBool("speech_mute"); bool mute = false; if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, mute ? 0 : ConfMan.getInt("speech_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, mute ? 0 : ConfMan.getInt("music_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, mute ? 0 : ConfMan.getInt("sfx_volume")); syncSoundSettings(); CursorMan.showMouse(true); setupSysStrings(); #if 0 // Menu test _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(); _menuSystem->run(); /* while (1) { //updateInput(); _menuSystem->update(); updateScreen(); } */ return Common::kNoError; #endif // Start main game loop setTotalPlayTime(0); _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(); _music->stopSequence(); _sound->stopAll(); delete _console; delete _arc; delete _res; delete _screen; delete _script; delete _anim; delete _palette; delete _segmap; delete _music; 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 { _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(_screen->_frontScreen + _cameraHeight * 640, 640, 0, _cameraHeight, 640, _guiHeight); _screen->_guiRefresh = false; } _console->onFrame(); _system->updateScreen(); _system->delayMillis(10); 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); if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) _console->attach(); switch (event.kbd.keycode) { case Common::KEYCODE_F5: showMenu(kMenuIdSave); break; case Common::KEYCODE_F7: showMenu(kMenuIdLoad); break; case Common::KEYCODE_SPACE: // 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_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(); _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; // If there's another talk text at the requested slot and it's still // active, don't overwrite it. Fixes bug #3600166. if (_screen->isTalkTextActive(slotIndex)) return; 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, false); } else { _screen->keepTalkTextItemsAlive(); } } else { _screen->updateTalkText(slotIndex, slotOffset, true); } } 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; } void ToltecsEngine::showMenu(MenuID menuId) { _screen->loadMouseCursor(12); _palette->loadAddPalette(9, 224); _palette->setDeltaPalette(_palette->getMainPalette(), 7, 0, 31, 224); _screen->finishTalkTextItems(); CursorMan.showMouse(true); _menuSystem->run(menuId); _keyState.reset(); _script->setSwitchLocalDataNear(true); } void ToltecsEngine::syncSoundSettings() { Engine::syncSoundSettings(); bool mute = false; if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); _cfgVoicesVolume = (mute ? 0 : ConfMan.getInt("speech_volume")) * 20 / Audio::Mixer::kMaxChannelVolume; _cfgMusicVolume = (mute ? 0 : ConfMan.getInt("music_volume")) * 20 / Audio::Mixer::kMaxChannelVolume; _cfgSoundFXVolume = (mute ? 0 : ConfMan.getInt("sfx_volume")) * 20 / Audio::Mixer::kMaxChannelVolume; } } // End of namespace Toltecs