/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "kyra/kyra_v1.h" #include "kyra/kyra_mr.h" #include "kyra/screen_mr.h" #include "kyra/wsamovie.h" #include "kyra/sound.h" #include "kyra/text_mr.h" #include "kyra/vqa.h" #include "kyra/gui.h" #include "kyra/timer.h" #include "kyra/debugger.h" #include "kyra/gui_mr.h" #include "kyra/resource.h" #include "common/system.h" #include "common/config-manager.h" namespace Kyra { const KyraEngine_v2::EngineDesc KyraEngine_MR::_mrEngineDesc = { // Generic shape related 248, KyraEngine_MR::_characterFrameTable, // Scene script 9, // Animation script specific 9, // Item specific 71 }; KyraEngine_MR::KyraEngine_MR(OSystem *system, const GameFlags &flags) : KyraEngine_v2(system, flags, _mrEngineDesc) { _soundDigital = 0; _musicSoundChannel = -1; _menuAudioFile = "TITLE1"; _lastMusicCommand = -1; _itemBuffer1 = _itemBuffer2 = 0; _scoreFile = 0; _cCodeFile = 0; _scenesFile = 0; _itemFile = 0; _gamePlayBuffer = 0; _interface = _interfaceCommandLine = 0; _costPalBuffer = 0; memset(_sceneShapes, 0, sizeof(_sceneShapes)); memset(_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); _gfxBackUpRect = 0; _paletteOverlay = 0; _sceneList = 0; _mainCharacter.sceneId = 9; _mainCharacter.height = 0x4C; _mainCharacter.facing = 5; _mainCharacter.animFrame = 0x57; _mainCharacter.walkspeed = 5; memset(_activeItemAnim, 0, sizeof(_activeItemAnim)); _nextAnimItem = 0; _text = 0; _commandLineY = 189; _inventoryState = false; memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); _overwriteSceneFacing = false; _maskPageMinY = _maskPageMaxY = 0; _sceneStrings = 0; _enterNewSceneLock = 0; _mainCharX = _mainCharY = -1; _animList = 0; _drawNoShapeFlag = false; _wasPlayingVQA = false; _lastCharPalLayer = -1; _charPalUpdate = false; _runFlag = false; _unk5 = 0; _unkSceneScreenFlag1 = false; _noScriptEnter = true; _itemInHand = _mouseState = -1; _unk3 = -1; _unk4 = 0; _loadingState = false; _noStartupChat = false; _pathfinderFlag = 0; _talkObjectList = 0; memset(&_chatScriptState, 0, sizeof(_chatScriptState)); memset(&_chatScriptData, 0, sizeof(_chatScriptData)); _voiceSoundChannel = -1; _charBackUpWidth2 = _charBackUpHeight2 = -1; _charBackUpWidth = _charBackUpHeight = -1; _useActorBuffer = false; _curStudioSFX = 283; _badConscienceShown = false; _currentChapter = 1; _unkHandleSceneChangeFlag = false; memset(_sceneShapeDescs, 0, sizeof(_sceneShapeDescs)); _cnvFile = _dlgBuffer = 0; _curDlgChapter = _curDlgIndex = _curDlgLang = -1; _isStartupDialog = 0; _stringBuffer = 0; _dialogSceneAnim = _dialogSceneScript = -1; memset(&_dialogScriptData, 0, sizeof(_dialogScriptData)); memset(&_dialogScriptState, 0, sizeof(_dialogScriptState)); _dialogScriptFuncStart = _dialogScriptFuncProc = _dialogScriptFuncEnd = 0; _malcolmsMood = 1; _nextIdleAnim = 0; _nextIdleType = false; _inventoryScrollSpeed = -1; _invWsa = 0; _invWsaFrame = -1; _score = 0; memset(_scoreFlagTable, 0, sizeof(_scoreFlagTable)); _mainButtonData = 0; _mainButtonList = 0; _mainButtonListInitialized = false; _enableInventory = true; _goodConscienceShown = false; _goodConscienceAnim = -1; _goodConsciencePosition = false; _menuDirectlyToLoad = false; _optionsFile = 0; _actorFile = 0; _chatAltFlag = false; _albumChatActive = false; memset(&_album, 0, sizeof(_album)); _configHelium = false; _fadeOutMusicChannel = -1; memset(_scaleTable, 0, sizeof(_scaleTable)); } KyraEngine_MR::~KyraEngine_MR() { delete _screen; delete _soundDigital; delete[] _itemBuffer1; delete[] _itemBuffer2; delete[] _scoreFile; delete[] _cCodeFile; delete[] _scenesFile; delete[] _itemFile; delete[] _actorFile; delete[] _gamePlayBuffer; delete[] _interface; delete[] _interfaceCommandLine; delete[] _costPalBuffer; for (uint i = 0; i < ARRAYSIZE(_sceneShapes); ++i) delete[] _sceneShapes[i]; for (uint i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) delete _sceneAnimMovie[i]; delete[] _gfxBackUpRect; delete[] _paletteOverlay; for (ShapeMap::iterator i = _gameShapes.begin(); i != _gameShapes.end(); ++i) { delete[] i->_value; i->_value = 0; } _gameShapes.clear(); delete[] _sceneStrings; delete[] _talkObjectList; for (Common::Array::iterator i = _opcodesDialog.begin(); i != _opcodesDialog.end(); ++i) delete *i; _opcodesDialog.clear(); delete _cnvFile; delete _dlgBuffer; delete[] _stringBuffer; delete _invWsa; delete[] _mainButtonData; delete _gui; delete[] _optionsFile; delete _album.wsa; delete _album.leftPage.wsa; delete _album.rightPage.wsa; } Common::Error KyraEngine_MR::init() { _screen = new Screen_MR(this, _system); assert(_screen); _screen->setResolution(); KyraEngine_v1::init(); initStaticResource(); _debugger = new Debugger_v2(this); assert(_debugger); _soundDigital = new SoundDigital(this, _mixer); assert(_soundDigital); if (!_soundDigital->init()) error("_soundDigital->init() failed"); KyraEngine_v1::_text = _text = new TextDisplayer_MR(this, _screen); assert(_text); _gui = new GUI_MR(this); assert(_gui); _gui->initStaticData(); _screen->loadFont(Screen::FID_6_FNT, "6.FNT"); _screen->loadFont(Screen::FID_8_FNT, "8FAT.FNT"); _screen->loadFont(Screen::FID_BOOKFONT_FNT, "BOOKFONT.FNT"); _screen->setFont(Screen::FID_6_FNT); _screen->setAnimBlockPtr(3500); _screen->setScreenDim(0); _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); _screen->setScreenPalette(_screen->getPalette(0)); return Common::kNoError; } Common::Error KyraEngine_MR::go() { bool running = true; preinit(); _screen->hideMouse(); initMainMenu(); _screen->clearPage(0); _screen->clearPage(2); const bool firstTimeGame = !saveFileLoadable(0); if (firstTimeGame) { playVQA("K3INTRO"); _wasPlayingVQA = false; } if (_gameToLoad != -1 || firstTimeGame) { while (!_screen->isMouseVisible()) _screen->showMouse(); uninitMainMenu(); _musicSoundChannel = -1; startup(); runLoop(); running = false; } while (running && !shouldQuit()) { _screen->_curPage = 0; _screen->clearPage(0); _screen->setScreenPalette(_screen->getPalette(0)); playMenuAudioFile(); for (int i = 0; i < 64 && !shouldQuit(); ++i) { uint32 nextRun = _system->getMillis() + 3 * _tickLength; _menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0); _screen->updateScreen(); delayUntil(nextRun); } for (int i = 64; i > 29 && !shouldQuit(); --i) { uint32 nextRun = _system->getMillis() + 3 * _tickLength; _menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0); _screen->updateScreen(); delayUntil(nextRun); } _eventList.clear(); switch (_menu->handle(3)) { case 2: _menuDirectlyToLoad = true; // fall through case 0: uninitMainMenu(); fadeOutMusic(60); _screen->fadeToBlack(60); _musicSoundChannel = -1; startup(); runLoop(); running = false; break; case 1: playVQA("K3INTRO"); _wasPlayingVQA = false; _screen->hideMouse(); break; case 3: default: fadeOutMusic(60); _screen->fadeToBlack(60); uninitMainMenu(); quitGame(); running = false; } } if (_showOutro) playVQA("CREDITS"); return Common::kNoError; } void KyraEngine_MR::initMainMenu() { _menuAnim = new WSAMovie_v2(this); _menuAnim->open("REVENGE.WSA", 1, &_screen->getPalette(0)); _screen->getPalette(0).fill(0, 1, 0); _menu = new MainMenu(this); MainMenu::StaticData data = { { _mainMenuStrings[_lang*4+0], _mainMenuStrings[_lang*4+1], _mainMenuStrings[_lang*4+2], _mainMenuStrings[_lang*4+3], 0 }, { 0x01, 0x04, 0x0C, 0x04, 0x00, 0x80, 0xFF }, { 0x16, 0x19, 0x1A, 0x16 }, Screen::FID_8_FNT, 240 }; if (_flags.lang == Common::ES_ESP) { for (int i = 0; i < 4; ++i) data.strings[i] = _mainMenuSpanishFan[i]; } else if (_flags.lang == Common::IT_ITA) { for (int i = 0; i < 4; ++i) data.strings[i] = _mainMenuItalianFan[i]; } MainMenu::Animation anim; anim.anim = _menuAnim; anim.startFrame = 29; anim.endFrame = 63; anim.delay = 2; _menu->init(data, anim); } void KyraEngine_MR::uninitMainMenu() { delete _menuAnim; _menuAnim = 0; delete _menu; _menu = 0; } void KyraEngine_MR::playVQA(const char *name) { VQAMovie vqa(this, _system); char filename[20]; int size = 0; // TODO: Movie size is 0, 1 or 2. snprintf(filename, sizeof(filename), "%s%d.VQA", name, size); if (vqa.open(filename)) { for (int i = 0; i < 4; ++i) { if (i != _musicSoundChannel) _soundDigital->stopSound(i); } _screen->hideMouse(); _screen->copyPalette(1, 0); fadeOutMusic(60); _screen->fadeToBlack(60); _screen->clearPage(0); vqa.setDrawPage(0); vqa.play(); vqa.close(); _soundDigital->stopAllSounds(); _screen->showMouse(); // Taken from original, it used '1' here too _screen->getPalette(0).fill(0, 256, 1); _screen->setScreenPalette(_screen->getPalette(0)); _screen->clearPage(0); _screen->copyPalette(0, 1); _wasPlayingVQA = true; } } #pragma mark - void KyraEngine_MR::playMenuAudioFile() { if (_soundDigital->isPlaying(_musicSoundChannel)) return; _musicSoundChannel = _soundDigital->playSound(_menuAudioFile, 0xFF, Audio::Mixer::kMusicSoundType, 255, true); } void KyraEngine_MR::snd_playWanderScoreViaMap(int track, int force) { if (_musicSoundChannel != -1 && !_soundDigital->isPlaying(_musicSoundChannel)) force = 1; else if (_musicSoundChannel == -1) force = 1; if (track == _lastMusicCommand && !force) return; stopMusicTrack(); if (_musicSoundChannel == -1) { assert(track < _soundListSize && track >= 0); char file[13]; sprintf(file, "%s", _soundList[track]); _musicSoundChannel = _soundDigital->playSound(file, 0xFF, Audio::Mixer::kMusicSoundType); } _lastMusicCommand = track; } void KyraEngine_MR::stopMusicTrack() { if (_musicSoundChannel != -1 && _soundDigital->isPlaying(_musicSoundChannel)) _soundDigital->stopSound(_musicSoundChannel); _lastMusicCommand = -1; _musicSoundChannel = -1; } int KyraEngine_MR::musicUpdate(int forceRestart) { static uint32 mTimer = 0; static uint16 lock = 0; if (ABS(_system->getMillis() - mTimer) > (int)(0x0F * _tickLength)) mTimer = _system->getMillis(); if (_system->getMillis() < mTimer && !forceRestart) return 1; if (!lock) { lock = 1; if (_musicSoundChannel >= 0) { if (!_soundDigital->isPlaying(_musicSoundChannel)) { if (_lastMusicCommand != -1) snd_playWanderScoreViaMap(_lastMusicCommand, 1); } } lock = 0; mTimer = _system->getMillis() + 0x0F * _tickLength; } return 1; } void KyraEngine_MR::fadeOutMusic(int ticks) { if (_musicSoundChannel >= 0) { _fadeOutMusicChannel = _musicSoundChannel; _soundDigital->beginFadeOut(_musicSoundChannel, ticks); _lastMusicCommand = -1; } } void KyraEngine_MR::snd_playSoundEffect(int item, int volume) { if (_sfxFileMap[item*2+0] != 0xFF) { char filename[16]; assert(_sfxFileMap[item*2+0] < _sfxFileListSize); snprintf(filename, 16, "%s", _sfxFileList[_sfxFileMap[item*2+0]]); uint8 priority = _sfxFileMap[item*2+1]; _soundDigital->playSound(filename, priority, Audio::Mixer::kSFXSoundType, volume); } } void KyraEngine_MR::playVoice(int high, int low) { snd_playVoiceFile(high * 1000 + low); } void KyraEngine_MR::snd_playVoiceFile(int file) { char filename[16]; snprintf(filename, 16, "%.08u", (uint)file); if (speechEnabled()) _voiceSoundChannel = _soundDigital->playSound(filename, 0xFE, Audio::Mixer::kSpeechSoundType, 255); } bool KyraEngine_MR::snd_voiceIsPlaying() { return _soundDigital->isPlaying(_voiceSoundChannel); } void KyraEngine_MR::snd_stopVoice() { if (_voiceSoundChannel != -1) _soundDigital->stopSound(_voiceSoundChannel); } void KyraEngine_MR::playStudioSFX(const char *str) { if (!_configStudio) return; if (_rnd.getRandomNumberRng(1, 2) != 2) return; const int strSize = strlen(str) - 1; if (str[strSize] != '?' && str[strSize] != '!') return; snd_playSoundEffect(_curStudioSFX++, 128); if (_curStudioSFX > 291) _curStudioSFX = 283; } #pragma mark - void KyraEngine_MR::preinit() { _itemBuffer1 = new int8[72]; _itemBuffer2 = new int8[144]; initMouseShapes(); initItems(); _screen->setMouseCursor(0, 0, _gameShapes[0]); } void KyraEngine_MR::initMouseShapes() { uint8 *data = _res->fileData("MOUSE.SHP", 0); assert(data); for (int i = 0; i <= 6; ++i) _gameShapes[i] = _screen->makeShapeCopy(data, i); delete[] data; } void KyraEngine_MR::startup() { _album.wsa = new WSAMovie_v2(this); assert(_album.wsa); _album.leftPage.wsa = new WSAMovie_v2(this); assert(_album.leftPage.wsa); _album.rightPage.wsa = new WSAMovie_v2(this); assert(_album.rightPage.wsa); musicUpdate(0); _gamePlayBuffer = new uint8[64000]; musicUpdate(0); musicUpdate(0); _interface = new uint8[17920]; _interfaceCommandLine = new uint8[3840]; _screen->setFont(Screen::FID_6_FNT); _stringBuffer = new char[500]; musicUpdate(0); allocAnimObjects(1, 16, 50); musicUpdate(0); memset(_sceneShapes, 0, sizeof(_sceneShapes)); _screenBuffer = new uint8[64000]; musicUpdate(0); musicUpdate(0); if (!loadLanguageFile("ITEMS.", _itemFile)) error("Couldn't load ITEMS"); if (!loadLanguageFile("SCORE.", _scoreFile)) error("Couldn't load SCORE"); if (!loadLanguageFile("C_CODE.", _cCodeFile)) error("Couldn't load C_CODE"); if (!loadLanguageFile("SCENES.", _scenesFile)) error("Couldn't load SCENES"); if (!loadLanguageFile("OPTIONS.", _optionsFile)) error("Couldn't load OPTIONS"); if (!loadLanguageFile("_ACTOR.", _actorFile)) error("couldn't load _ACTOR"); musicUpdate(0); openTalkFile(0); musicUpdate(0); _currentTalkFile = 0; openTalkFile(1); loadCostPal(); musicUpdate(0); for (int i = 0; i < 16; ++i) { _sceneAnims[i].flags = 0; _sceneAnimMovie[i] = new WSAMovie_v2(this); assert(_sceneAnimMovie[i]); } _screen->_curPage = 0; _talkObjectList = new TalkObject[88]; memset(_talkObjectList, 0, sizeof(TalkObject)*88); for (int i = 0; i < 88; ++i) _talkObjectList[i].sceneId = 0xFF; musicUpdate(0); _gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)]; initItemList(50); resetItemList(); loadShadowShape(); musicUpdate(0); loadExtrasShapes(); musicUpdate(0); _characterShapeFile = 0; loadCharacterShapes(_characterShapeFile); updateMalcolmShapes(); musicUpdate(0); initMainButtonList(true); loadButtonShapes(); loadInterfaceShapes(); musicUpdate(0); _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); _paletteOverlay = new uint8[256]; _screen->generateOverlay(_screen->getPalette(0), _paletteOverlay, 0xF0, 0x19); loadInterface(); musicUpdate(0); clearAnimObjects(); _scoreMax = 0; for (int i = 0; i < _scoreTableSize; ++i) { if (_scoreTable[i] > 0) _scoreMax += _scoreTable[i]; } musicUpdate(0); memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); memset(_conversationState, -1, sizeof(_conversationState)); _sceneList = new SceneDesc[98]; assert(_sceneList); memset(_sceneList, 0, sizeof(SceneDesc)*98); _sceneListSize = 98; musicUpdate(0); runStartupScript(1, 0); _res->exists("MOODOMTR.WSA", true); _invWsa = new WSAMovie_v2(this); assert(_invWsa); _invWsa->open("MOODOMTR.WSA", 1, 0); _invWsaFrame = 6; saveGameState(0, "New Game", 0); _soundDigital->beginFadeOut(_musicSoundChannel, 60); delayWithTicks(60); if (_gameToLoad == -1) enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1); else loadGameStateCheck(_gameToLoad); if (_menuDirectlyToLoad) (*_mainButtonData[0].buttonCallback)(&_mainButtonData[0]); _screen->updateScreen(); musicUpdate(0); _screen->showMouse(); setNextIdleAnimTimer(); setWalkspeed(_configWalkspeed); } void KyraEngine_MR::loadCostPal() { _res->exists("_COSTPAL.DAT", true); uint32 size = 0; _costPalBuffer = _res->fileData("_COSTPAL.DAT", &size); assert(_costPalBuffer); assert(size == 864); } void KyraEngine_MR::loadShadowShape() { _screen->loadBitmap("SHADOW.CSH", 3, 3, 0); addShapeToPool(_screen->getCPagePtr(3), 421, 0); } void KyraEngine_MR::loadExtrasShapes() { _screen->loadBitmap("EXTRAS.CSH", 3, 3, 0); for (int i = 0; i < 20; ++i) addShapeToPool(_screen->getCPagePtr(3), i+433, i); addShapeToPool(_screen->getCPagePtr(3), 453, 20); addShapeToPool(_screen->getCPagePtr(3), 454, 21); } void KyraEngine_MR::loadInterfaceShapes() { _screen->loadBitmap("INTRFACE.CSH", 3, 3, 0); for (int i = 422; i <= 432; ++i) addShapeToPool(_screen->getCPagePtr(3), i, i-422); } void KyraEngine_MR::loadInterface() { _screen->loadBitmap("INTRFACE.CPS", 3, 3, 0); memcpy(_interface, _screen->getCPagePtr(3), 17920); memcpy(_interfaceCommandLine, _screen->getCPagePtr(3), 3840); } void KyraEngine_MR::initItems() { _screen->loadBitmap("ITEMS.CSH", 3, 3, 0); for (int i = 248; i <= 319; ++i) addShapeToPool(_screen->getCPagePtr(3), i, i-248); _screen->loadBitmap("ITEMS2.CSH", 3, 3, 0); for (int i = 320; i <= 397; ++i) addShapeToPool(_screen->getCPagePtr(3), i, i-320); uint32 size = 0; uint8 *itemsDat = _res->fileData("_ITEMS.DAT", &size); assert(size >= 72+144); memcpy(_itemBuffer1, itemsDat , 72); memcpy(_itemBuffer2, itemsDat+72, 144); delete[] itemsDat; _screen->_curPage = 0; } void KyraEngine_MR::runStartupScript(int script, int unk1) { EMCState state; EMCData data; memset(&state, 0, sizeof(state)); memset(&data, 0, sizeof(data)); char filename[13]; strcpy(filename, "_START0X.EMC"); filename[7] = (script % 10) + '0'; _emc->load(filename, &data, &_opcodes); _emc->init(&state, &data); _emc->start(&state, 0); state.regs[6] = unk1; while (_emc->isValid(&state)) _emc->run(&state); _emc->unload(&data); } void KyraEngine_MR::openTalkFile(int file) { char talkFilename[16]; if (file == 0) { strcpy(talkFilename, "ANYTALK.TLK"); } else { if (_currentTalkFile > 0) { sprintf(talkFilename, "CH%dTALK.TLK", _currentTalkFile); _res->unloadPakFile(talkFilename); } sprintf(talkFilename, "CH%dTALK.TLK", file); } _currentTalkFile = file; if (!_res->loadPakFile(talkFilename)) { if (speechEnabled()) { warning("Couldn't load voice file '%s', falling back to text only mode", talkFilename); _configVoice = 0; // Sync the config manager with the new settings writeSettings(); } } } #pragma mark - void KyraEngine_MR::loadCharacterShapes(int newShapes) { static const uint8 numberOffset[] = { 3, 3, 4, 4, 3, 3 }; static const uint8 startShape[] = { 0x32, 0x58, 0x78, 0x98, 0xB8, 0xD8 }; static const uint8 endShape[] = { 0x57, 0x77, 0x97, 0xB7, 0xD7, 0xF7 }; static const char * const filenames[] = { "MSW##.SHP", "MTA##.SHP", "MTFL##.SHP", "MTFR##.SHP", "MTL##.SHP", "MTR##.SHP" }; for (int i = 50; i <= 247; ++i) { if (i == 87) continue; ShapeMap::iterator iter = _gameShapes.find(i); if (iter != _gameShapes.end()) { delete[] iter->_value; iter->_value = 0; } } const char lowNum = (newShapes % 10) + '0'; const char highNum = (newShapes / 10) + '0'; for (int i = 0; i < 6; ++i) { char filename[16]; strcpy(filename, filenames[i]); filename[numberOffset[i]+0] = highNum; filename[numberOffset[i]+1] = lowNum; _res->exists(filename, true); _res->loadFileToBuf(filename, _screenBuffer, 64000); for (int j = startShape[i]; j <= endShape[i]; ++j) { if (j == 87) continue; addShapeToPool(_screenBuffer, j, j-startShape[i]); } } _characterShapeFile = newShapes; updateMalcolmShapes(); } void KyraEngine_MR::updateMalcolmShapes() { assert(_characterShapeFile >= 0 && _characterShapeFile < _shapeDescsSize); _malcolmShapeXOffset = _shapeDescs[_characterShapeFile].xOffset; _malcolmShapeYOffset = _shapeDescs[_characterShapeFile].yOffset; _animObjects[0].width = _shapeDescs[_characterShapeFile].width; _animObjects[0].height = _shapeDescs[_characterShapeFile].height; } #pragma mark - int KyraEngine_MR::getCharacterWalkspeed() const { return _mainCharacter.walkspeed; } void KyraEngine_MR::updateCharAnimFrame(int character, int *table) { ++_mainCharacter.animFrame; int facing = _mainCharacter.facing; if (table) { if (table[0] != table[-1] && table[1] == table[-1]) { facing = getOppositeFacingDirection(table[-1]); table[0] = table[-1]; } } if (facing) { if (facing == 7 || facing == 1) { if (_characterAnimTable[0] > 2) facing = 0; memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); } else if (facing == 4) { ++_characterAnimTable[1]; } else if (facing == 5 || facing == 3) { if (_characterAnimTable[1] > 2) facing = 4; memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); } } else { ++_characterAnimTable[0]; } switch (facing) { case 0: if (_mainCharacter.animFrame < 79 || _mainCharacter.animFrame > 86) _mainCharacter.animFrame = 79; break; case 1: case 2: case 3: if (_mainCharacter.animFrame < 71 || _mainCharacter.animFrame > 78) _mainCharacter.animFrame = 71; break; case 4: if (_mainCharacter.animFrame < 55 || _mainCharacter.animFrame > 62) _mainCharacter.animFrame = 55; break; case 5: case 6: case 7: if (_mainCharacter.animFrame < 63 || _mainCharacter.animFrame > 70) _mainCharacter.animFrame = 63; break; default: break; } updateCharacterAnim(0); } void KyraEngine_MR::updateCharPal(int unk1) { int layer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1) - 1; const uint8 *src = _costPalBuffer + _characterShapeFile * 72; Palette &dst = _screen->getPalette(0); const int8 *sceneDatPal = &_sceneDatPalette[layer * 3]; if (layer != _lastCharPalLayer && unk1) { for (int i = 144; i < 168; ++i) { for (int j = 0; j < 3; ++j) { uint8 col = dst[i * 3 + j]; int subCol = src[(i - 144) * 3 + j] + sceneDatPal[j]; subCol = CLIP(subCol, 0, 63); subCol = (col - subCol) / 2; dst[i * 3 + j] -= subCol; } } _charPalUpdate = true; _screen->setScreenPalette(_screen->getPalette(0)); _lastCharPalLayer = layer; } else if (_charPalUpdate || !unk1) { dst.copy(_costPalBuffer, _characterShapeFile * 24, 24, 144); for (int i = 144; i < 168; ++i) { for (int j = 0; j < 3; ++j) { int col = dst[i * 3 + j] + sceneDatPal[j]; dst[i * 3 + j] = CLIP(col, 0, 63); } } _screen->setScreenPalette(_screen->getPalette(0)); _charPalUpdate = false; } } bool KyraEngine_MR::checkCharCollision(int x, int y) { int scale = getScale(_mainCharacter.x1, _mainCharacter.y1); int width = (scale * 37) >> 8; int height = (scale * 76) >> 8; int x1 = _mainCharacter.x1 - width/2; int x2 = _mainCharacter.x1 + width/2; int y1 = _mainCharacter.y1 - height; int y2 = _mainCharacter.y1; if (x >= x1 && x <= x2 && y >= y1 && y <= y2) return true; return false; } #pragma mark - void KyraEngine_MR::runLoop() { _eventList.clear(); _runFlag = true; while (_runFlag && !shouldQuit()) { if (_deathHandler >= 0) { removeHandItem(); delay(5); _drawNoShapeFlag = 0; _gui->optionsButton(0); _deathHandler = -1; if (!_runFlag || shouldQuit()) break; } if (_system->getMillis() >= _nextIdleAnim) showIdleAnim(); int inputFlag = checkInput(_mainButtonList, true); removeInputTop(); update(); _timer->update(); if (inputFlag == 198 || inputFlag == 199) { _unk3 = _mouseState; Common::Point mouse = getMousePos(); handleInput(mouse.x, mouse.y); } _system->delayMillis(10); } } void KyraEngine_MR::handleInput(int x, int y) { if (_inventoryState) return; setNextIdleAnimTimer(); if (_unk5) { _unk5 = 0; return; } if (!_screen->isMouseVisible()) return; if (_unk3 == -3) { snd_playSoundEffect(0x0D, 0x80); return; } setNextIdleAnimTimer(); int skip = 0; if (checkCharCollision(x, y) && _unk3 >= -1 && runSceneScript2()) { return; } else if (_itemInHand != 27 && pickUpItem(x, y, 1)) { return; } else if (checkItemCollision(x, y) == -1) { resetGameFlag(1); skip = runSceneScript1(x, y); if (queryGameFlag(1)) { resetGameFlag(1); return; } else if (_unk5) { _unk5 = 0; return; } } if (_deathHandler >= 0) skip = 1; if (skip) return; if (checkCharCollision(x, y)) { if (runSceneScript2()) return; } else if (_itemInHand >= 0 && _unk3 >= 0) { if (_itemInHand == 27) { makeCharFacingMouse(); } else if (y <= 187) { if (_itemInHand == 43) removeHandItem(); else dropItem(0, _itemInHand, x, y, 1); } return; } else if (_unk3 == -3) { return; } else { if (y > 187 && _unk3 > -4) return; if (_unk5) { _unk5 = 0; return; } } inputSceneChange(x, y, 1, 1); } int KyraEngine_MR::inputSceneChange(int x, int y, int unk1, int unk2) { uint16 curScene = _mainCharacter.sceneId; _pathfinderFlag = 15; if (!_unkHandleSceneChangeFlag) { if (_unk3 == -4) { if (_sceneList[curScene].exit4 != 0xFFFF) { x = 4; y = _sceneEnterY4; _pathfinderFlag = 7; } } else if (_unk3 == -6) { if (_sceneList[curScene].exit2 != 0xFFFF) { x = 316; y = _sceneEnterY2; _pathfinderFlag = 7; } } else if (_unk3 == -7) { if (_sceneList[curScene].exit1 != 0xFFFF) { x = _sceneEnterX1; y = _sceneEnterY1 - 2; _pathfinderFlag = 14; } } else if (_unk3 == -5) { if (_sceneList[curScene].exit3 != 0xFFFF) { x = _sceneEnterX3; y = 191; _pathfinderFlag = 11; } } } if (ABS(_mainCharacter.x1 - x) < 4 && ABS(_mainCharacter.y1 - y) < 2) { _pathfinderFlag = 0; return 0; } int x1 = _mainCharacter.x1 & (~3); int y1 = _mainCharacter.y1 & (~1); x &= ~3; y &= ~1; int size = findWay(x1, y1, x, y, _movFacingTable, 600); _pathfinderFlag = 0; if (!size || size == 0x7D00) return 0; return trySceneChange(_movFacingTable, unk1, unk2); } void KyraEngine_MR::update() { updateInput(); musicUpdate(0); refreshAnimObjectsIfNeed(); musicUpdate(0); updateMouse(); updateSpecialSceneScripts(); updateCommandLine(); updateItemAnimations(); musicUpdate(0); _screen->updateScreen(); } void KyraEngine_MR::updateWithText() { updateInput(); musicUpdate(0); updateMouse(); updateItemAnimations(); updateSpecialSceneScripts(); updateCommandLine(); musicUpdate(0); restorePage3(); drawAnimObjects(); if (textEnabled() && _chatText) { int curPage = _screen->_curPage; _screen->_curPage = 2; objectChatPrintText(_chatText, _chatObject); _screen->_curPage = curPage; } refreshAnimObjects(0); _screen->updateScreen(); } void KyraEngine_MR::updateMouse() { int shape = 0, offsetX = 0, offsetY = 0; Common::Point mouse = getMousePos(); bool hasItemCollision = checkItemCollision(mouse.x, mouse.y) != -1; if (mouse.y > 187) { bool setItemCursor = false; if (_mouseState == -6) { if (mouse.x < 311) setItemCursor = true; } else if (_mouseState == -5) { if (mouse.x < _sceneMinX || mouse.x > _sceneMaxX) setItemCursor = true; } else if (_mouseState == -4) { if (mouse.x > 8) setItemCursor = true; } if (setItemCursor) { setItemMouseCursor(); return; } } if (_inventoryState) { if (mouse.y >= 144) return; hideInventory(); } if (hasItemCollision && _mouseState < -1 && _itemInHand < 0) { _mouseState = -1; _itemInHand = -1; _screen->setMouseCursor(0, 0, _gameShapes[0]); } int type = 0; if (mouse.y <= 199) { if (mouse.x <= 8) { if (_sceneExit4 != 0xFFFF) { type = -4; shape = 4; offsetX = 0; offsetY = 0; } } else if (mouse.x >= 311) { if (_sceneExit2 != 0xFFFF) { type = -6; shape = 2; offsetX = 13; offsetY = 8; } } else if (mouse.y >= 171) { if (_sceneExit3 != 0xFFFF) { if (mouse.x >= _sceneMinX && mouse.x <= _sceneMaxX) { type = -5; shape = 3; offsetX = 8; offsetY = 13; } } } else if (mouse.y <= 8) { if (_sceneExit1 != 0xFFFF) { type = -7; shape = 1; offsetX = 8; offsetY = 0; } } } for (int i = 0; i < _specialExitCount; ++i) { if (checkSpecialSceneExit(i, mouse.x, mouse.y)) { switch (_specialExitTable[20+i]) { case 0: type = -7; shape = 1; offsetX = 8; offsetY = 0; break; case 2: type = -6; shape = 2; offsetX = 13; offsetY = 8; break; case 4: type = -5; shape = 3; offsetX = 8; offsetY = 13; break; case 6: type = -4; shape = 4; offsetX = 0; offsetY = 8; break; default: break; } } } if (type != 0 && type != _mouseState && !hasItemCollision) { _mouseState = type; _screen->setMouseCursor(offsetX, offsetY, _gameShapes[shape]); } else if (type == 0 && _mouseState != _itemInHand && mouse.x > 8 && mouse.x < 311 && mouse.y < 171 && mouse.y > 8) { setItemMouseCursor(); } else if (mouse.y > 187 && _mouseState > -4 && type == 0 && !_inventoryState) { showInventory(); } } #pragma mark - void KyraEngine_MR::makeCharFacingMouse() { if (_mainCharacter.x1 > _mouseX) _mainCharacter.facing = 5; else _mainCharacter.facing = 3; _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; updateCharacterAnim(0); refreshAnimObjectsIfNeed(); } #pragma mark - int KyraEngine_MR::getDrawLayer(int x, int y) { int layer = _screen->getLayer(x, y) - 1; layer = _sceneDatLayerTable[layer]; return MAX(0, MIN(layer, 6)); } int KyraEngine_MR::getScale(int x, int y) { return _scaleTable[_screen->getLayer(x, y) - 1]; } #pragma mark - void KyraEngine_MR::backUpGfxRect32x32(int x, int y) { _screen->copyRegionToBuffer(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); } void KyraEngine_MR::restoreGfxRect32x32(int x, int y) { _screen->copyBlockToPage(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); } #pragma mark - char *KyraEngine_MR::appendLanguage(char *buf, int lang, int bufSize) { assert(lang < _languageExtensionSize); int size = strlen(buf) + strlen(_languageExtension[lang]); if (size > bufSize) { warning("buffer too small to append language extension"); return 0; } char *temp = buf + strlen(buf); bufSize -= strlen(buf); strncat(temp, _languageExtension[lang], bufSize); return buf; } int KyraEngine_MR::loadLanguageFile(const char *file, uint8 *&buffer) { delete[] buffer; buffer = 0; uint32 size = 0; char nBuf[32]; strncpy(nBuf, file, 32); buffer = _res->fileData(appendLanguage(nBuf, _lang, sizeof(nBuf)), &size); return buffer ? size : 0 ; } uint8 *KyraEngine_MR::getTableEntry(uint8 *buffer, int id) { uint16 tableEntries = READ_LE_UINT16(buffer); const uint16 *indexTable = (const uint16*)(buffer + 2); const uint16 *offsetTable = indexTable + tableEntries; int num = 0; while (id != READ_LE_UINT16(indexTable)) { ++indexTable; ++num; } return buffer + READ_LE_UINT16(offsetTable + num); } void KyraEngine_MR::getTableEntry(Common::SeekableReadStream *stream, int id, char *dst) { stream->seek(0, SEEK_SET); uint16 tableEntries = stream->readUint16LE(); int num = 0; while (id != stream->readUint16LE()) ++num; stream->seek(2+tableEntries*2+num*2, SEEK_SET); stream->seek(stream->readUint16LE(), SEEK_SET); char c = 0; while ((c = stream->readByte()) != 0) *dst++ = c; *dst = 0; } #pragma mark - bool KyraEngine_MR::talkObjectsInCurScene() { for (int i = 0; i < 88; ++i) { if (_talkObjectList[i].sceneId == _mainCharacter.sceneId) return true; } return false; } #pragma mark - bool KyraEngine_MR::updateScore(int scoreId, int strId) { int scoreIndex = (scoreId >> 3); int scoreBit = scoreId & 7; if ((_scoreFlagTable[scoreIndex] & (1 << scoreBit)) != 0) return false; setNextIdleAnimTimer(); _scoreFlagTable[scoreIndex] |= (1 << scoreBit); _screen->hideMouse(); strcpy(_stringBuffer, (const char *)getTableEntry(_scoreFile, strId)); strcat(_stringBuffer, ": "); assert(scoreId < _scoreTableSize); int count = _scoreTable[scoreId]; if (count > 0) scoreIncrease(count, _stringBuffer); _screen->showMouse(); setNextIdleAnimTimer(); return true; } void KyraEngine_MR::scoreIncrease(int count, const char *str) { int drawOld = 1; _screen->hideMouse(); showMessage(str, 0xFF, 0xF0); const int x = getScoreX(str); for (int i = 0; i < count; ++i) { int oldScore = _score; int newScore = ++_score; if (newScore > _scoreMax) { _score = _scoreMax; break; } drawScoreCounting(oldScore, newScore, drawOld, x); if (_inventoryState) drawScore(0, 215, 191); _screen->updateScreen(); delay(20, true); snd_playSoundEffect(0x0E, 0xC8); drawOld = 0; } _screen->showMouse(); } #pragma mark - void KyraEngine_MR::changeChapter(int newChapter, int sceneId, int malcolmShapes, int facing) { resetItemList(); _currentChapter = newChapter; runStartupScript(newChapter, 0); _mainCharacter.dlgIndex = 0; _malcolmsMood = 1; memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); if (malcolmShapes >= 0) loadCharacterShapes(malcolmShapes); enterNewScene(sceneId, facing, 0, 0, 0); } #pragma mark - bool KyraEngine_MR::skipFlag() const { if (!_configSkip) return false; return KyraEngine_v2::skipFlag(); } void KyraEngine_MR::resetSkipFlag(bool removeEvent) { if (!_configSkip) { if (removeEvent) _eventList.clear(); return; } KyraEngine_v2::resetSkipFlag(removeEvent); } #pragma mark - void KyraEngine_MR::registerDefaultSettings() { KyraEngine_v1::registerDefaultSettings(); // Most settings already have sensible defaults. This one, however, is // specific to the Kyra engine. ConfMan.registerDefault("walkspeed", 5); ConfMan.registerDefault("studio_audience", true); ConfMan.registerDefault("skip_support", true); ConfMan.registerDefault("helium_mode", false); } void KyraEngine_MR::writeSettings() { switch (_lang) { case 1: _flags.lang = Common::FR_FRA; break; case 2: _flags.lang = Common::DE_DEU; break; case 0: default: _flags.lang = Common::EN_ANY; } if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) _flags.lang = _flags.fanLang; ConfMan.set("language", Common::getLanguageCode(_flags.lang)); ConfMan.setBool("studio_audience", _configStudio); ConfMan.setBool("skip_support", _configSkip); ConfMan.setBool("helium_mode", _configHelium); KyraEngine_v1::writeSettings(); } void KyraEngine_MR::readSettings() { KyraEngine_v1::readSettings(); _configStudio = ConfMan.getBool("studio_audience"); _configSkip = ConfMan.getBool("skip_support"); _configHelium = ConfMan.getBool("helium_mode"); } } // end of namespace Kyra