diff options
Diffstat (limited to 'engines/kyra/engine')
51 files changed, 38025 insertions, 0 deletions
diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp new file mode 100644 index 0000000000..c080acc130 --- /dev/null +++ b/engines/kyra/engine/chargen.cpp @@ -0,0 +1,1982 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_intern.h" + +#include "common/savefile.h" +#include "common/str-array.h" + +#include "common/config-manager.h" +#include "base/plugins.h" +#include "engines/metaengine.h" +#include "engines/game.h" + +namespace Kyra { + +// Character Generator + +class CharacterGenerator { +public: + CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen); + ~CharacterGenerator(); + + bool start(EoBCharacter *characters, uint8 ***faceShapes); + +private: + void init(); + void initButtonsFromList(int first, int numButtons); + void initButton(int index, int x, int y, int w, int h, int keyCode); + void checkForCompleteParty(); + void toggleSpecialButton(int index, int bodyCustom, int pageNum); + void processSpecialButton(int index); + int viewDeleteCharacter(); + void createPartyMember(); + int raceSexMenu(); + int classMenu(int raceSex); + int alignmentMenu(int cClass); + int getInput(Button *buttonList); + void updateMagicShapes(); + void generateStats(int index); + void modifyMenu(); + void statsAndFacesMenu(); + void faceSelectMenu(); + int getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits); + void processFaceMenuSelection(int index); + void printStats(int index, int mode); + void processNameInput(int index, int textColor); + int rollDice(); + int modifyStat(int index, int8 *stat1, int8 *stat2); + int getMaxHp(int cclass, int constitution, int level1, int level2, int level3); + int getMinHp(int cclass, int constitution, int level1, int level2, int level3); + void finish(); + + uint8 **_chargenMagicShapes; + uint8 *_chargenButtonLabels[17]; + int _activeBox; + int _magicShapesBox; + int _updateBoxShapesIndex; + int _lastUpdateBoxShapesIndex; + uint32 _chargenMagicShapeTimer; + int8 _chargenSelectedPortraits[4]; + int8 _chargenSelectedPortraits2[4]; + + uint16 _chargenMinStats[7]; + uint16 _chargenMaxStats[7]; + + const char *const *_chargenStrings1; + const char *const *_chargenStrings2; + const char *const *_chargenStatStrings; + const char *const *_chargenRaceSexStrings; + const char *const *_chargenClassStrings; + const char *const *_chargenAlignmentStrings; + const char *const *_chargenEnterGameStrings; + + const uint8 *_chargenStartLevels; + const uint8 *_chargenClassMinStats; + const uint8 *_chargenRaceMinStats; + const uint16 *_chargenRaceMaxStats; + + const EoBChargenButtonDef *_chargenButtonDefs; + + static const EoBChargenButtonDef _chargenButtonDefsDOS[]; + static const uint16 _chargenButtonKeyCodesFMTOWNS[]; + static const CreatePartyModButton _chargenModButtons[]; + static const EoBRect8 _chargenButtonBodyCoords[]; + static const int16 _chargenBoxX[]; + static const int16 _chargenBoxY[]; + static const int16 _chargenNameFieldX[]; + static const int16 _chargenNameFieldY[]; + + static const int32 _classMenuMasks[]; + static const int32 _alignmentMenuMasks[]; + + static const int16 _raceModifiers[]; + + EoBCharacter *_characters; + uint8 **_faceShapes; + + EoBCoreEngine *_vm; + Screen_EoB *_screen; +}; + +CharacterGenerator::CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen), + _characters(0), _faceShapes(0), _chargenMagicShapes(0), _chargenMagicShapeTimer(0), + _updateBoxShapesIndex(0), _lastUpdateBoxShapesIndex(0), _magicShapesBox(6), _activeBox(0) { + + _chargenStatStrings = _vm->_chargenStatStrings; + _chargenRaceSexStrings = _vm->_chargenRaceSexStrings; + _chargenClassStrings = _vm->_chargenClassStrings; + _chargenAlignmentStrings = _vm->_chargenAlignmentStrings; + + memset(_chargenSelectedPortraits, -1, sizeof(_chargenSelectedPortraits)); + memset(_chargenSelectedPortraits2, 0, sizeof(_chargenSelectedPortraits2)); + memset(_chargenMinStats, 0, sizeof(_chargenMinStats)); + memset(_chargenMaxStats, 0, sizeof(_chargenMaxStats)); + + int temp; + _chargenStrings1 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings1, temp); + _chargenStrings2 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings2, temp); + _chargenStartLevels = _vm->staticres()->loadRawData(kEoBBaseChargenStartLevels, temp); + _chargenEnterGameStrings = _vm->staticres()->loadStrings(kEoBBaseChargenEnterGameStrings, temp); + _chargenClassMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenClassMinStats, temp); + _chargenRaceMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenRaceMinStats, temp); + _chargenRaceMaxStats = _vm->staticres()->loadRawDataBe16(kEoBBaseChargenRaceMaxStats, temp); + + EoBChargenButtonDef *chargenButtonDefs = new EoBChargenButtonDef[41]; + memcpy(chargenButtonDefs, _chargenButtonDefsDOS, 41 * sizeof(EoBChargenButtonDef)); + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + const uint16 *c = _chargenButtonKeyCodesFMTOWNS; + for (int i = 0; i < 41; ++i) { + if (chargenButtonDefs[i].keyCode) + chargenButtonDefs[i].keyCode = *c++; + } + } + + _chargenButtonDefs = chargenButtonDefs; +} + +CharacterGenerator::~CharacterGenerator() { + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + } + + for (int i = 0; i < 17; i++) + delete[] _chargenButtonLabels[i]; + + delete[] _chargenButtonDefs; + + _screen->clearPage(2); +} + +bool CharacterGenerator::start(EoBCharacter *characters, uint8 ***faceShapes) { + if (!characters || !faceShapes) { + warning("CharacterGenerator::start: Called without character data"); + return true; + } + + _characters = characters; + _faceShapes = *faceShapes; + + _vm->snd_stopSound(); + _vm->delay(_vm->_tickLength); + + init(); + + _screen->setScreenDim(2); + + checkForCompleteParty(); + initButtonsFromList(0, 5); + + _vm->snd_playSong(_vm->game() == GI_EOB1 ? 20 : 13); + _activeBox = 0; + + for (bool loop = true; loop && (!_vm->shouldQuit());) { + _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); + int inputFlag = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (inputFlag) { + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + _activeBox ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + _activeBox ^= 2; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + // Unlike the original we allow returning to the main menu + _vm->snd_stopSound(); + return false; + } + _vm->_gui->updateBoxFrameHighLight(-1); + } + + if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (inputFlag == 4) { + loop = false; + } else { + _activeBox = inputFlag; + inputFlag = _vm->_keyMap[Common::KEYCODE_RETURN]; + } + } + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5]) { + _vm->_gui->updateBoxFrameHighLight(-1); + if (_characters[_activeBox].name[0]) { + int b = _activeBox; + if (viewDeleteCharacter()) + loop = false; + if (b != _activeBox && !_characters[_activeBox].name[0]) + createPartyMember(); + } else { + createPartyMember(); + } + + initButtonsFromList(0, 5); + checkForCompleteParty(); + } + + if (loop == false) { + for (int i = 0; i < 4; i++) { + if (!_characters[i].name[0]) + loop = true; + } + } + } + + if (!_vm->shouldQuit()) { + processSpecialButton(15); + finish(); + } + + if (_vm->game() == GI_EOB2) + _vm->snd_fadeOut(); + + *faceShapes = _faceShapes; + return true; +} + +void CharacterGenerator::init() { + /*_screen->loadEoBBitmap("MENU", 0, 3, 3, 2); + Common::SeekableReadStream *s = _res->createReadStream("facedat.dmp"); + _screen->loadFileDataToPage(s, 2, 64000);*/ + + _screen->loadShapeSetBitmap("CHARGENA", 3, 3); + if (_faceShapes) { + for (int i = 0; i < 44; i++) + delete[] _faceShapes[i]; + delete[] _faceShapes; + } + + _faceShapes = new uint8*[44]; + for (int i = 0; i < 44; i++) + _faceShapes[i] = _screen->encodeShape((i % 10) << 2, (i / 10) << 5, 4, 32, true, _vm->_cgaMappingDefault); + _screen->_curPage = 0; + + _screen->loadEoBBitmap("CHARGEN", _vm->_cgaMappingDefault, 3, 3, 0); + _screen->loadShapeSetBitmap("CHARGENB", 3, 3); + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + } + + _chargenMagicShapes = new uint8*[10]; + for (int i = 0; i < 10; i++) + _chargenMagicShapes[i] = _screen->encodeShape(i << 2, 0, 4, 32, true, _vm->_cgaMappingDefault); + + for (int i = 0; i < 17; i++) { + const CreatePartyModButton *c = &_chargenModButtons[i]; + _chargenButtonLabels[i] = c->labelW ? _screen->encodeShape(c->encodeLabelX, c->encodeLabelY, c->labelW, c->labelH, true, _vm->_cgaMappingDefault) : 0; + } + + _screen->convertPage(3, 2, _vm->_cgaMappingDefault); + _screen->_curPage = 0; + _screen->convertToHiColor(2); + _screen->shadeRect(142, 63, 306, 193, 4); + _screen->copyRegion(144, 64, 0, 0, 180, 128, 0, 2, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); +} + +void CharacterGenerator::initButtonsFromList(int first, int numButtons) { + _vm->gui_resetButtonList(); + + for (int i = 0; i < numButtons; i++) { + const EoBChargenButtonDef *e = &_chargenButtonDefs[first + i]; + initButton(i, e->x, e->y, e->w, e->h, e->keyCode); + } + + _vm->gui_notifyButtonListChanged(); +} + +void CharacterGenerator::initButton(int index, int x, int y, int w, int h, int keyCode) { + Button *b = 0; + int cnt = 1; + + if (_vm->_activeButtons) { + Button *n = _vm->_activeButtons; + while (n->nextButton) { + ++cnt; + n = n->nextButton; + } + + ++cnt; + b = n->nextButton = &_vm->_activeButtonData[cnt]; + } else { + b = &_vm->_activeButtonData[0]; + _vm->_activeButtons = b; + } + + *b = Button(); + b->flags = 0x1100; + b->data0Val2 = 12; + b->data1Val2 = b->data2Val2 = 15; + b->data3Val2 = 8; + + b->index = index + 1; + b->x = x << 3; + b->y = y; + b->width = w; + b->height = h; + b->keyCode = keyCode; + b->keyCode2 = keyCode | 0x100; +} + +void CharacterGenerator::checkForCompleteParty() { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + int x = (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168; + _screen->printShadedText(_chargenStrings1[8], x, 16, 15, 0); + _screen->setCurPage(cp); + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + + int numChars = 0; + for (int i = 0; i < 4; i++) { + if (_characters[i].name[0]) + numChars++; + } + + if (numChars == 4) { + _screen->setCurPage(2); + _screen->printShadedText(_chargenStrings1[0], x, 61, 15, 0); + _screen->setCurPage(0); + _screen->copyRegion(168, 61, 152, 125, 136, 40, 2, 0, Screen::CR_NO_P_CHECK); + toggleSpecialButton(15, 0, 0); + } else { + toggleSpecialButton(14, 0, 0); + } + + _screen->updateScreen(); +} + +void CharacterGenerator::toggleSpecialButton(int index, int bodyCustom, int pageNum) { + if (index >= 17) + return; + + const CreatePartyModButton *c = &_chargenModButtons[index]; + const EoBRect8 *p = &_chargenButtonBodyCoords[c->bodyIndex + bodyCustom]; + + int x2 = 20; + int y2 = 0; + + if (pageNum) { + x2 = c->destX + 2; + y2 = c->destY - 64; + } + + _screen->copyRegion(p->x << 3, p->y, x2 << 3, y2, p->w << 3, p->h, 2, 2, Screen::CR_NO_P_CHECK); + if (c->labelW) + _screen->drawShape(2, _chargenButtonLabels[index], (x2 << 3) + c->labelX, y2 + c->labelY, 0); + + if (pageNum == 2) + return; + + _screen->copyRegion(160, 0, c->destX << 3, c->destY, p->w << 3, p->h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); +} + +void CharacterGenerator::processSpecialButton(int index) { + toggleSpecialButton(index, 1, 0); + _vm->snd_playSoundEffect(76); + _vm->_system->delayMillis(80); + toggleSpecialButton(index, 0, 0); +} + +int CharacterGenerator::viewDeleteCharacter() { + initButtonsFromList(0, 7); + _vm->removeInputTop(); + + _vm->_gui->updateBoxFrameHighLight(-1); + printStats(_activeBox, 2); + + int res = 0; + for (bool loop = true; loop && _characters[_activeBox].name[0] && !_vm->shouldQuit();) { + _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); + int inputFlag = getInput(_vm->_activeButtons); + int cbx = _activeBox; + _vm->removeInputTop(); + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + processSpecialButton(9); + res = 0; + loop = false; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + cbx ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + cbx ^= 2; + } + + if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (inputFlag == 4) { + res = 1; + loop = false; + } else if (inputFlag == 5) { + processSpecialButton(9); + res = 0; + loop = false; + } else if (inputFlag == 6) { + if (_characters[_activeBox].name[0]) { + processSpecialButton(16); + _characters[_activeBox].name[0] = 0; + processNameInput(_activeBox, 12); + processFaceMenuSelection(_activeBox + 50); + } + } else { + cbx = inputFlag; + } + } + + if (loop == false) + _vm->_gui->updateBoxFrameHighLight(-1); + + if (!_characters[cbx].name[0]) + loop = false; + + if (cbx != _activeBox) { + _activeBox = cbx; + _vm->_gui->updateBoxFrameHighLight(-1); + if (loop) + printStats(_activeBox, 2); + } + } + + return res; +} + +void CharacterGenerator::createPartyMember() { + _screen->setScreenDim(2); + assert(_vm->_gui); + + for (int i = 0; i != 3 && !_vm->shouldQuit(); i++) { + bool bck = false; + + switch (i) { + case 0: + _characters[_activeBox].raceSex = raceSexMenu(); + break; + case 1: + _characters[_activeBox].cClass = classMenu(_characters[_activeBox].raceSex); + if (_characters[_activeBox].cClass == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + bck = true; + break; + case 2: + _characters[_activeBox].alignment = alignmentMenu(_characters[_activeBox].cClass); + if (_characters[_activeBox].alignment == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + bck = true; + break; + default: + break; + } + + if (bck) + i -= 2; + }; + + if (!_vm->shouldQuit()) { + generateStats(_activeBox); + statsAndFacesMenu(); + + for (_characters[_activeBox].name[0] = 0; _characters[_activeBox].name[0] == 0 && !_vm->shouldQuit();) { + processFaceMenuSelection(_chargenMinStats[6]); + printStats(_activeBox, 0); + _screen->printShadedText(_chargenStrings2[11], 149, 100, 9, 0); + if (!_vm->shouldQuit()) { + _vm->_gui->getTextInput(_characters[_activeBox].name, 24, 100, 10, 15, 0, 8); + processNameInput(_activeBox, 2); + } + } + } +} + +int CharacterGenerator::raceSexMenu() { + _screen->drawBox(_chargenBoxX[_activeBox], _chargenBoxY[_activeBox], _chargenBoxX[_activeBox] + 32, _chargenBoxY[_activeBox] + 33, 12); + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[8], 147, 67, 9, 0); + _vm->removeInputTop(); + + _vm->_gui->simpleMenu_setup(1, 0, _chargenRaceSexStrings, -1, 0, 0); + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + res = _vm->_gui->simpleMenu_process(1, _chargenRaceSexStrings, 0, -1, 0); + updateMagicShapes(); + } + + return res; +} + +int CharacterGenerator::classMenu(int raceSex) { + int32 itemsMask = -1; + + for (int i = 0; i < 4; i++) { + // check for evil characters + if (_characters[i].name[0] && _characters[i].alignment > 5) + itemsMask = 0xFFFB; + } + + _vm->removeInputTop(); + updateMagicShapes(); + + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[9], 147, 67, 9, 0); + + toggleSpecialButton(5, 0, 0); + + itemsMask &= _classMenuMasks[raceSex / 2]; + _vm->_gui->simpleMenu_setup(2, 15, _chargenClassStrings, itemsMask, 0, 0); + + _vm->_mouseX = _vm->_mouseY = 0; + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + updateMagicShapes(); + int in = getInput(0) & 0xFF; + Common::Point mp = _vm->getMousePos(); + + if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { + if (in == 199 || in == 201) + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + else + _vm->removeInputTop(); + } else { + res = _vm->_gui->simpleMenu_process(2, _chargenClassStrings, 0, itemsMask, 0); + } + } + + _vm->removeInputTop(); + + if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + processSpecialButton(5); + + return res; +} + +int CharacterGenerator::alignmentMenu(int cClass) { + int32 itemsMask = -1; + + for (int i = 0; i < 4; i++) { + // check for paladins + if (_characters[i].name[0] && _characters[i].cClass == 2) + itemsMask = 0xFE3F; + } + + _vm->removeInputTop(); + updateMagicShapes(); + + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[10], 147, 67, 9, 0); + + toggleSpecialButton(5, 0, 0); + + itemsMask &= _alignmentMenuMasks[cClass]; + _vm->_gui->simpleMenu_setup(3, 9, _chargenAlignmentStrings, itemsMask, 0, 0); + + _vm->_mouseX = _vm->_mouseY = 0; + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + updateMagicShapes(); + int in = getInput(0) & 0xFF; + Common::Point mp = _vm->getMousePos(); + + if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { + if (in == 199 || in == 201) + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + else + _vm->removeInputTop(); + } else { + res = _vm->_gui->simpleMenu_process(3, _chargenAlignmentStrings, 0, itemsMask, 0); + } + } + + _vm->removeInputTop(); + + if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + processSpecialButton(5); + + return res; +} + +int CharacterGenerator::getInput(Button *buttonList) { + if (_vm->game() == GI_EOB1 && _vm->sound()->checkTrigger()) { + _vm->sound()->resetTrigger(); + _vm->snd_playSong(20); + } else if (_vm->game() == GI_EOB2 && !_vm->sound()->isPlaying()) { + // WORKAROUND for EOB II: The original implements the same sound trigger check as in EOB I. + // However, Westwood seems to have forgotten to set the trigger at the end of the AdLib song, + // so that the music will not loop. We simply check whether the sound driver is still playing. + _vm->delay(3 * _vm->_tickLength); + _vm->snd_playSong(13); + } + return _vm->checkInput(buttonList, false, 0); +} + +void CharacterGenerator::updateMagicShapes() { + if (_magicShapesBox != _activeBox) { + _chargenMagicShapeTimer = 0; + _magicShapesBox = _activeBox; + } + + if (_chargenMagicShapeTimer < _vm->_system->getMillis()) { + if (++_updateBoxShapesIndex > 9) + _updateBoxShapesIndex = 0; + _chargenMagicShapeTimer = _vm->_system->getMillis() + 2 * _vm->_tickLength; + } + + if (_updateBoxShapesIndex == _lastUpdateBoxShapesIndex) + return; + + _screen->copyRegion(_activeBox << 5, 128, 288, 128, 32, 32, 2, 2, Screen::CR_NO_P_CHECK); + _screen->drawShape(2, _chargenMagicShapes[_updateBoxShapesIndex], 288, 128, 0); + _screen->copyRegion(288, 128, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 32, 32, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + _lastUpdateBoxShapesIndex = _updateBoxShapesIndex; +} + +void CharacterGenerator::generateStats(int index) { + EoBCharacter *c = &_characters[index]; + + for (int i = 0; i < 3; i++) { + c->level[i] = _chargenStartLevels[(c->cClass << 2) + i]; + c->experience[i] = (_vm->game() == GI_EOB2 ? 69000 : 5000) / _chargenStartLevels[(c->cClass << 2) + 3]; + } + + int rc = c->raceSex >> 1; + for (int i = 0; i < 6; i++) { + _chargenMinStats[i] = MAX(_chargenClassMinStats[c->cClass * 6 + i], _chargenRaceMinStats[rc * 6 + i]); + _chargenMaxStats[i] = _chargenRaceMaxStats[rc * 6 + i]; + } + + if (_vm->_charClassModifier[c->cClass]) + _chargenMaxStats[0] = 18; + + uint16 sv[6]; + for (int i = 0; i < 6; i++) { + sv[i] = MAX<uint16>(rollDice() + _raceModifiers[rc * 6 + i], _chargenMinStats[i]); + if (!i && sv[i] == 18) + sv[i] |= (uint16)(_vm->rollDice(1, 100) << 8); + if (sv[i] > _chargenMaxStats[i]) + sv[i] = _chargenMaxStats[i]; + } + + c->strengthCur = c->strengthMax = sv[0] & 0xFF; + c->strengthExtCur = c->strengthExtMax = sv[0] >> 8; + c->intelligenceCur = c->intelligenceMax = sv[1] & 0xFF; + c->wisdomCur = c->wisdomMax = sv[2] & 0xFF; + c->dexterityCur = c->dexterityMax = sv[3] & 0xFF; + c->constitutionCur = c->constitutionMax = sv[4] & 0xFF; + c->charismaCur = c->charismaMax = sv[5] & 0xFF; + c->armorClass = 10 + _vm->getDexterityArmorClassModifier(sv[3] & 0xFF); + c->hitPointsCur = 0; + + for (int l = 0; l < 3; l++) { + for (int i = 0; i < c->level[l]; i++) + c->hitPointsCur += _vm->generateCharacterHitpointsByLevel(index, 1 << l); + } + + c->hitPointsMax = c->hitPointsCur; +} + +void CharacterGenerator::modifyMenu() { + _vm->removeInputTop(); + printStats(_activeBox, 3); + + EoBCharacter *c = &_characters[_activeBox]; + int8 hpLO = c->hitPointsCur; + + for (int i = 0; i >= 0 && i < 7;) { + switch (i) { + case 0: + i = modifyStat(i, &c->strengthCur, &c->strengthExtCur); + break; + case 1: + i = modifyStat(i, &c->intelligenceCur, 0); + break; + case 2: + i = modifyStat(i, &c->wisdomCur, 0); + break; + case 3: + i = modifyStat(i, &c->dexterityCur, 0); + break; + case 4: + i = modifyStat(i, &c->constitutionCur, 0); + break; + case 5: + i = modifyStat(i, &c->charismaCur, 0); + break; + case 6: + hpLO = c->hitPointsCur; + i = modifyStat(i, &hpLO, 0); + c->hitPointsCur = hpLO; + break; + default: + break; + } + + if (i == -2 || _vm->shouldQuit()) + break; + else if (i < 0) + i = 6; + i %= 7; + + printStats(_activeBox, 3); + } + + printStats(_activeBox, 1); +} + +void CharacterGenerator::statsAndFacesMenu() { + faceSelectMenu(); + printStats(_activeBox, 1); + initButtonsFromList(27, 4); + _vm->removeInputTop(); + int in = 0; + + while (!in && !_vm->shouldQuit()) { + updateMagicShapes(); + in = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (in == 0x8001) { + processSpecialButton(4); + updateMagicShapes(); + generateStats(_activeBox); + in = -1; + } else if (in == 0x8002) { + processSpecialButton(7); + modifyMenu(); + in = -1; + } else if (in == 0x8003) { + processSpecialButton(8); + faceSelectMenu(); + in = -1; + } else if (in == 0x8004 || in == _vm->_keyMap[Common::KEYCODE_KP5]) { + processSpecialButton(6); + in = 1; + } else { + in = 0; + } + + if (in & 0x8000) { + printStats(_activeBox, 1); + initButtonsFromList(27, 4); + in = 0; + } + } + + _vm->_gui->updateBoxFrameHighLight(6 + _activeBox); + _vm->_gui->updateBoxFrameHighLight(-1); +} + +void CharacterGenerator::faceSelectMenu() { + int8 sp[4]; + memcpy(sp, _chargenSelectedPortraits2, sizeof(sp)); + _vm->removeInputTop(); + initButtonsFromList(21, 6); + + int charSex = _characters[_activeBox].raceSex % 2; + int8 shp = charSex ? 26 : 0; + + printStats(_activeBox, 4); + toggleSpecialButton(12, 0, 0); + toggleSpecialButton(13, 0, 0); + _vm->_gui->updateBoxFrameHighLight(-1); + + shp = getNextFreeFaceShape(shp, charSex, 1, _chargenSelectedPortraits); + + int res = -1; + int box = 1; + + while (res == -1 && !_vm->shouldQuit()) { + int8 shpOld = shp; + + for (int i = 0; i < 4; i++) { + sp[i] = shp; + _screen->drawShape(0, _faceShapes[sp[i]], 176 + (i << 5), 66, 0); + shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); + } + + shp = shpOld; + int in = 0; + + while (!in && !_vm->shouldQuit()) { + updateMagicShapes(); + in = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + _vm->_gui->updateBoxFrameHighLight(box + 10); + + if (in == 0x8002 || in == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + processSpecialButton(13); + in = 2; + } else if (in > 0x8002 && in < 0x8007) { + box = (in & 7) - 3; + in = 3; + } else if (in == 0x8001 || in == _vm->_keyMap[Common::KEYCODE_LEFT]) { + processSpecialButton(12); + in = 1; + } else if (in == _vm->_keyMap[Common::KEYCODE_RETURN] || in == _vm->_keyMap[Common::KEYCODE_KP5]) { + in = 3; + } else if (in & 0x8000) { + in &= 0xFF; + } else { + in = 0; + } + } + + _vm->_gui->updateBoxFrameHighLight(-1); + + if (in == 1) + shp = getNextFreeFaceShape(shp - 1, charSex, -1, _chargenSelectedPortraits); + else if (in == 2) + shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); + else if (in == 3) + res = box; + } + + if (!_vm->shouldQuit()) { + _vm->_gui->updateBoxFrameHighLight(-1); + updateMagicShapes(); + + _chargenSelectedPortraits[_activeBox] = sp[res]; + _characters[_activeBox].portrait = sp[res]; + _characters[_activeBox].faceShape = _faceShapes[sp[res]]; + + printStats(_activeBox, 1); + } +} + +int CharacterGenerator::getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits) { + int shpCur = ((shpIndex < 0) ? 43 : shpIndex) % 44; + bool notUsable = false; + + do { + notUsable = false; + for (int i = 0; i < 4; i++) { + if (_characters[i].name[0] && selectedPortraits[i] == shpCur) + notUsable = true; + } + + if ((charSex && (shpCur < 26)) || (!charSex && (shpCur > 28))) + notUsable = true; + + if (notUsable) { + shpCur += step; + shpCur = ((shpCur < 0) ? 43 : shpCur) % 44; + } + } while (notUsable); + + return shpCur; +} + +void CharacterGenerator::processFaceMenuSelection(int index) { + _vm->_gui->updateBoxFrameHighLight(-1); + if (index <= 48) + _screen->drawShape(0, _characters[_activeBox].faceShape, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 0); + else + toggleSpecialButton(index - 50, 0, 0); +} + +void CharacterGenerator::printStats(int index, int mode) { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + _screen->_curPage = 2; + + EoBCharacter *c = &_characters[index]; + + if (mode != 4) + _screen->drawShape(2, c->faceShape, 224, 2, 0); + + _screen->printShadedText(c->name, 160 + ((160 - _screen->getTextWidth(c->name)) / 2), 35, 15, 0); + _screen->printShadedText(_chargenRaceSexStrings[c->raceSex], 160 + ((20 - strlen(_chargenRaceSexStrings[c->raceSex])) << 2), 45, 15, 0); + _screen->printShadedText(_chargenClassStrings[c->cClass], 160 + ((20 - strlen(_chargenClassStrings[c->cClass])) << 2), 54, 15, 0); + + for (int i = 0; i < 6; i++) + _screen->printShadedText(_chargenStatStrings[i], 163, (i + 8) << 3, 15, 0); + + _screen->printShadedText(_chargenStrings1[2], 248, 64, 15, 0); + + Common::String str = Common::String::format(_chargenStrings1[3], _vm->getCharStrength(c->strengthCur, c->strengthExtCur).c_str(), c->intelligenceCur, c->wisdomCur, c->dexterityCur, c->constitutionCur, c->charismaCur); + _screen->printShadedText(str.c_str(), 192, 64, 15, 0); + + str = Common::String::format(_chargenStrings1[4], c->armorClass, c->hitPointsMax); + _screen->printShadedText(str.c_str(), 280, 64, 15, 0); + + const char *lvlStr = c->level[2] ? _chargenStrings1[7] : (c->level[1] ? _chargenStrings1[6] : _chargenStrings1[5]); + str = Common::String::format(lvlStr, c->level[0], c->level[1], c->level[2]); + _screen->printShadedText(str.c_str(), 280, 80, 15, 0); + + switch (mode) { + case 1: + toggleSpecialButton(4, 0, 2); + toggleSpecialButton(7, 0, 2); + toggleSpecialButton(8, 0, 2); + toggleSpecialButton(6, 0, 2); + break; + + case 2: + toggleSpecialButton(16, 0, 2); + toggleSpecialButton(9, 0, 2); + break; + + case 3: + toggleSpecialButton(10, 0, 2); + toggleSpecialButton(11, 0, 2); + toggleSpecialButton(9, 0, 2); + break; + + default: + break; + } + + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + + if (mode != 3) + _screen->updateScreen(); + + _screen->_curPage = 0; +} + +void CharacterGenerator::processNameInput(int index, int textColor) { + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->fillRect(_chargenNameFieldX[index], _chargenNameFieldY[index], _chargenNameFieldX[index] + 59, _chargenNameFieldY[index] + 5, 12); + int xOffs = (60 - _screen->getTextWidth(_characters[index].name)) >> 1; + _screen->printText(_characters[index].name, _chargenNameFieldX[index] + xOffs, _chargenNameFieldY[index], textColor, 0); + _screen->updateScreen(); + _screen->setFont(of); +} + +int CharacterGenerator::rollDice() { + int res = 0; + int min = 10; + + for (int i = 0; i < 4; i++) { + int d = _vm->rollDice(1, 6, 0); + res += d; + if (d < min) + min = d; + } + + res -= min; + return res; +} + +int CharacterGenerator::modifyStat(int index, int8 *stat1, int8 *stat2) { + uint8 *s1 = (uint8 *)stat1; + uint8 *s2 = (uint8 *)stat2; + + initButtonsFromList(31, 10); + Button *b = _vm->gui_getButton(_vm->_activeButtons, index + 1); + + printStats(_activeBox, 3); + _vm->removeInputTop(); + + Common::String statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + + _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); + _screen->updateScreen(); + + EoBCharacter *c = &_characters[_activeBox]; + + int ci = index; + uint8 v2 = s2 ? *s2 : 0; + + if (index == 6) { + _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + } + + for (bool loop = true; loop && !_vm->shouldQuit();) { + uint8 v1 = *s1; + updateMagicShapes(); + int inputFlag = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP4] || inputFlag == _vm->_keyMap[Common::KEYCODE_MINUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_MINUS] || inputFlag == 0x8009) { + processSpecialButton(11); + v1--; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP6] || inputFlag == _vm->_keyMap[Common::KEYCODE_PLUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_PLUS] || inputFlag == 0x8008) { + processSpecialButton(10); + v1++; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP8]) { + ci = (ci - 1) % 7; + loop = false; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP2]) { + ci = (ci + 1) % 7; + loop = false; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_o] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE] || inputFlag == 0x800A) { + processSpecialButton(9); + loop = false; + ci = -2; + + } else if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (index != inputFlag) { + ci = inputFlag; + loop = false; + } + } + + if (v1 == *s1) + continue; + + if (!index) { + while (v1 > 18) { + v1--; + v2++; + } + while (v2 > 0 && v1 < 18) { + v1++; + v2--; + } + + v1 = CLIP<uint8>(v1, _chargenMinStats[index], _chargenMaxStats[index] & 0xFF); + v2 = (v1 == 18 && _chargenMaxStats[index] >= 19) ? CLIP<uint8>(v2, 0, 100) : 0; + if (s2) + *s2 = v2; + else + error("CharacterGenerator::modifyStat:..."); + } else { + v1 = CLIP<uint8>(v1, _chargenMinStats[index], _chargenMaxStats[index]); + } + + *s1 = v1; + + if (index == 6) + _characters[_activeBox].hitPointsMax = v1; + + statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + + _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); + _screen->updateScreen(); + + if (index == 4) { + int oldVal = c->hitPointsCur; + _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + c->hitPointsMax = c->hitPointsCur = CLIP<int16>(c->hitPointsCur, _chargenMinStats[6], _chargenMaxStats[6]); + + if (c->hitPointsCur != oldVal) { + statStr = Common::String::format("%d", c->hitPointsCur); + _screen->copyRegion(120, 72, 264, 136, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), 264, 136, 15, 0); + _screen->updateScreen(); + } + + } else if (index == 3) { + int oldVal = c->armorClass; + c->armorClass = _vm->getDexterityArmorClassModifier(v1) + 10; + + if (c->armorClass != oldVal) { + statStr = Common::String::format("%d", c->armorClass); + _screen->copyRegion(120, 64, 264, 128, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), 264, 128, 15, 0); + _screen->updateScreen(); + } + } + + if (loop == false) { + statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + _screen->printText(statStr.c_str(), b->x + 32, b->y, 15, 0); + _screen->updateScreen(); + } + } + + return ci; +} + +int CharacterGenerator::getMaxHp(int cclass, int constitution, int level1, int level2, int level3) { + int res = 0; + constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); + + int m = _vm->getCharacterClassType(cclass, 0); + if (m != -1) + res = _vm->getModifiedHpLimits(m, constitution, level1, false); + + m = _vm->getCharacterClassType(cclass, 1); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level2, false); + + m = _vm->getCharacterClassType(cclass, 2); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level3, false); + + res /= _vm->_numLevelsPerClass[cclass]; + + return res; +} + +int CharacterGenerator::getMinHp(int cclass, int constitution, int level1, int level2, int level3) { + int res = 0; + constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); + + int m = _vm->getCharacterClassType(cclass, 0); + if (m != -1) + res = _vm->getModifiedHpLimits(m, constitution, level1, true); + + m = _vm->getCharacterClassType(cclass, 1); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level2, true); + + m = _vm->getCharacterClassType(cclass, 2); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level3, true); + + res /= _vm->_numLevelsPerClass[cclass]; + + return res; +} + +void CharacterGenerator::finish() { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + _screen->printShadedText(_chargenEnterGameStrings[0], (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168, 32, 15, 0); + _screen->setCurPage(cp); + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + if (_vm->game() == GI_EOB1) { + static const int8 classDefaultItemsList[] = { + 1, 17, 2, 17, 46, -1, 4, -1, 5, -1, 6, + 2, 7, -1, 8, -1, 9, 21, 10, 2, 31, 2 + }; + + static const int8 classDefaultItemsListIndex[] = { + 4, 8, 0, -1, 4, 3, 0, -1, 4, 10, + 0, 8, 3, 6, 1, -1, 2, 7, 0, -1, + 4, 5, 0, -1, 4, 7, 0, 8, 4, 5, + 0, 8, 4, 6, 8, 8, 4, 6, 5, 8, + 3, 6, 5, -1, 2, 7, 5, 0, 4, 6, + 7, 0, 4, 3, 7, 0, 2, 6, 7, 1 + }; + + _characters[0].inventory[2] = _vm->duplicateItem(35); + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->flags = 1; + c->food = 100; + c->id = i; + c->inventory[3] = _vm->duplicateItem(10); + + for (int ii = 0; ii < 4; ii++) { + int l = classDefaultItemsListIndex[(c->cClass << 2) + ii] << 1; + if (classDefaultItemsList[l] == -1) + continue; + + int d = classDefaultItemsList[l]; + int slot = classDefaultItemsList[l + 1]; + + if (slot < 0) { + slot = 0; + if (c->inventory[slot]) + slot++; + if (c->inventory[slot]) + slot++; + } + + if (slot != 2 && c->inventory[slot]) + continue; + + if (d == 5 && (c->raceSex >> 1) == 3) + d = 36; + + if (slot == 2) { + while (c->inventory[slot]) + slot++; + } + + c->inventory[slot] = _vm->duplicateItem(d); + } + + _vm->recalcArmorClass(i); + } + + } else { + static const uint8 classDefaultItemsListIndex[] = { 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 3, 2, 0, 0, 2 }; + static const int8 itemList0[] = { 3, 36, 0, 17, -1, 0, 0, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 1, 1 }; + static const int8 itemList1[] = { 1, 2, 0, 17, -1, 0, 0 }; + static const int8 itemList2[] = { 2, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 0, 1 }; + static const int8 itemList3[] = { 2, 1, 1, 17, 31, 1, 1, 1, 0, 17, 31, 2, 1 }; + static const int8 *const itemList[] = { itemList0, itemList1, itemList2, itemList3 }; + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->flags = 1; + c->food = 100; + c->id = i; + const int8 *df = itemList[classDefaultItemsListIndex[c->cClass]]; + int v1 = _vm->rollDice(1, *df++, -1); + + df = &df[v1 * 6]; + for (int ii = 0; ii < 2; ii++) { + if (df[0] == -1) + break; + _vm->createInventoryItem(c, df[0], df[1], df[2]); + df = &df[3]; + } + + uint16 m = _vm->_classModifierFlags[c->cClass]; + v1 = _vm->rollDice(1, 2, -1); + + int ival = 0; + int itype = 0; + + if (m & 0x31) { + if ((c->raceSex >> 1) == 3) { + itype = 22; + ival = 1; + } else { + if (v1 == 0) { + itype = 5; + ival = 1; + } else { + itype = 34; + } + } + } else if (m & 0x04) { + itype = 26; + if (v1 != 0) + ival = 1; + } else if (m & 0x08) { + ival = 1; + itype = ((c->raceSex >> 1) == 3) ? 22 : 5; + } else { + if (v1 == 0) { + itype = 3; + } else { + itype = 4; + ival = 1; + } + } + + _vm->createInventoryItem(c, itype, ival, 0); + _vm->createInventoryItem(c, 10, -1, 2); + _vm->createInventoryItem(c, 10, -1, 2); + _vm->createInventoryItem(c, 24, 2, 2); + + if (_vm->_classModifierFlags[c->cClass] & 2) { + _vm->createInventoryItem(c, 7, -1, 1); + _vm->createInventoryItem(c, 21, 4, 2); + _vm->createInventoryItem(c, 21, 13, 2); + } + + if (_vm->_classModifierFlags[c->cClass] & 0x14) { + if (c->cClass == 2) + _vm->createInventoryItem(c, 27, -1, 1); + else + _vm->createInventoryItem(c, 8, -1, 1); + + _vm->createInventoryItem(c, 20, 49, 1); + } + + if (_vm->_classModifierFlags[c->cClass] & 8) + _vm->createInventoryItem(c, 6, -1, 1); + + if (i == 0) + _vm->createInventoryItem(c, 93, -1, 2); + + _vm->recalcArmorClass(i); + } + } + + for (int i = 0; i < 4; i++) { + if (_vm->_classModifierFlags[_characters[i].cClass] & 2) + _characters[i].mageSpellsAvailableFlags = (_vm->game() == GI_EOB2) ? 0x81CB6 : 0x26C; + + if (_vm->_classModifierFlags[_characters[i].cClass] & 0x14 && _vm->game() == GI_EOB2) { + // Cleric/Paladin: Add Turn Undead spell + _characters[i].clericSpells[0] = 29; + } + } + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->strengthMax = c->strengthCur; + c->strengthExtMax = c->strengthExtCur; + c->intelligenceMax = c->intelligenceCur; + c->wisdomMax = c->wisdomCur; + c->dexterityMax = c->dexterityCur; + c->constitutionMax = c->constitutionCur; + c->charismaMax = c->charismaCur; + c->hitPointsMax = c->hitPointsCur; + } + + _vm->gui_resetButtonList(); + + if (_faceShapes) { + for (int i = 0; i < 44; i++) { + bool del = true; + for (int ii = 0; ii < 4; ii++) { + if (_characters[ii].faceShape == _faceShapes[i]) + del = false; + } + if (del) + delete[] _faceShapes[i]; + } + delete[] _faceShapes; + _faceShapes = 0; + } + + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + _chargenMagicShapes = 0; + } + + for (int i = 0; i < 17; i++) { + delete[] _chargenButtonLabels[i]; + _chargenButtonLabels[i] = 0; + } +} + +const EoBChargenButtonDef CharacterGenerator::_chargenButtonDefsDOS[] = { + { 0x01, 0x37, 0x31, 0x32, 0x70 }, + { 0x09, 0x37, 0x31, 0x32, 0x71 }, + { 0x01, 0x77, 0x31, 0x32, 0x72 }, + { 0x09, 0x77, 0x31, 0x32, 0x73 }, + { 0x03, 0xB5, 0x53, 0x10, 0x1A }, + { 0x21, 0xAC, 0x26, 0x10, 0x19 }, + { 0x1C, 0xAC, 0x26, 0x10, 0x21 }, + { 0x21, 0xAC, 0x26, 0x10, 0x32 }, + { 0x13, 0x50, 0x9A, 0x08, 0x00 }, + { 0x13, 0x58, 0x9A, 0x08, 0x00 }, + { 0x13, 0x60, 0x9A, 0x08, 0x00 }, + { 0x13, 0x68, 0x9A, 0x08, 0x00 }, + { 0x13, 0x70, 0x9A, 0x08, 0x00 }, + { 0x13, 0x78, 0x9A, 0x08, 0x00 }, + { 0x13, 0x80, 0x9A, 0x08, 0x00 }, + { 0x13, 0x88, 0x9A, 0x08, 0x00 }, + { 0x13, 0x90, 0x9A, 0x08, 0x00 }, + { 0x13, 0x98, 0x9A, 0x08, 0x00 }, + { 0x13, 0xA0, 0x9A, 0x08, 0x00 }, + { 0x13, 0xA8, 0x9A, 0x08, 0x00 }, + { 0x13, 0xB0, 0x9A, 0x08, 0x00 }, + { 0x12, 0x42, 0x20, 0x10, 0x00 }, + { 0x12, 0x52, 0x20, 0x10, 0x00 }, + { 0x16, 0x42, 0x20, 0x20, 0x00 }, + { 0x1A, 0x42, 0x20, 0x20, 0x00 }, + { 0x1E, 0x42, 0x20, 0x20, 0x00 }, + { 0x22, 0x42, 0x20, 0x20, 0x00 }, + { 0x1C, 0x9C, 0x26, 0x10, 0x14 }, + { 0x21, 0x9C, 0x26, 0x10, 0x34 }, + { 0x1C, 0xAC, 0x26, 0x10, 0x22 }, + { 0x21, 0xAC, 0x26, 0x10, 0x26 }, + { 0x12, 0x80, 0x35, 0x08, 0x00 }, + { 0x12, 0x88, 0x35, 0x08, 0x00 }, + { 0x12, 0x90, 0x35, 0x08, 0x00 }, + { 0x12, 0x98, 0x35, 0x08, 0x00 }, + { 0x12, 0xA0, 0x35, 0x08, 0x00 }, + { 0x12, 0xA8, 0x35, 0x08, 0x00 }, + { 0x1D, 0x88, 0x35, 0x08, 0x00 }, + { 0x1B, 0xAC, 0x15, 0x10, 0x0D }, + { 0x1E, 0xAC, 0x15, 0x10, 0x0C }, + { 0x21, 0xAC, 0x25, 0x10, 0x19 } +}; + +const uint16 CharacterGenerator::_chargenButtonKeyCodesFMTOWNS[] = { + 93, 94, 95, 96, 80, 79, 68, 66, 82, 77, 70, 75, 43, 45, 79 +}; + +const CreatePartyModButton CharacterGenerator::_chargenModButtons[] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x40 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x80 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0A, 0x80 }, + { 0x00, 0xC0, 0x04, 0x05, 0x07, 0x05, 0x08, 0x1C, 0x9C }, + { 0x04, 0xC0, 0x03, 0x05, 0x0A, 0x05, 0x08, 0x21, 0xAC }, + { 0x07, 0xC0, 0x03, 0x05, 0x0B, 0x05, 0x08, 0x21, 0xAC }, + { 0x0A, 0xC0, 0x04, 0x05, 0x06, 0x05, 0x08, 0x21, 0x9C }, + { 0x18, 0xC0, 0x03, 0x05, 0x09, 0x05, 0x08, 0x1C, 0xAC }, + { 0x0E, 0xC0, 0x02, 0x05, 0x0F, 0x05, 0x08, 0x21, 0xAC }, + { 0x10, 0xC0, 0x01, 0x05, 0x09, 0x05, 0x04, 0x1B, 0xAC }, + { 0x11, 0xC0, 0x01, 0x01, 0x09, 0x07, 0x04, 0x1E, 0xAC }, + { 0x12, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x42 }, + { 0x15, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x52 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x03, 0xB5 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x03, 0xB5 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x1C, 0xAC } +}; + +const EoBRect8 CharacterGenerator::_chargenButtonBodyCoords[] = { + { 0x00, 0x80, 0x04, 0x20 }, + { 0x04, 0x80, 0x04, 0x20 }, + { 0x08, 0x80, 0x04, 0x20 }, + { 0x0C, 0x80, 0x04, 0x20 }, + { 0x0E, 0xA0, 0x03, 0x10 }, + { 0x0B, 0xA0, 0x03, 0x10 }, + { 0x10, 0x80, 0x04, 0x10 }, + { 0x10, 0x90, 0x04, 0x10 }, + { 0x11, 0xA0, 0x05, 0x10 }, + { 0x11, 0xB0, 0x05, 0x10 }, + { 0x16, 0xA0, 0x05, 0x10 }, + { 0x16, 0xB0, 0x05, 0x10 }, + { 0x00, 0xA0, 0x0B, 0x10 }, + { 0x14, 0x80, 0x0B, 0x10 }, + { 0x14, 0x90, 0x0B, 0x10 } +}; + +const int16 CharacterGenerator::_chargenBoxX[] = { 0x10, 0x50, 0x10, 0x50 }; +const int16 CharacterGenerator::_chargenBoxY[] = { 0x3F, 0x3F, 0x7F, 0x7F }; +const int16 CharacterGenerator::_chargenNameFieldX[] = { 0x02, 0x42, 0x02, 0x42 }; +const int16 CharacterGenerator::_chargenNameFieldY[] = { 0x6B, 0x6B, 0xAB, 0xAB }; + +const int32 CharacterGenerator::_classMenuMasks[] = { + 0x003F, 0x07BB, 0x77FB, 0x00F1, 0x08F1, 0x00B1 +}; + +const int32 CharacterGenerator::_alignmentMenuMasks[] = { + 0x01FF, 0x0007, 0x0001, 0x01FF, 0x01FF, 0x01FE, 0x01FF, 0x01FE, + 0x01FF, 0x01FE, 0x01FE, 0x01FE, 0x01FF, 0x0007, 0x01FF +}; + +const int16 CharacterGenerator::_raceModifiers[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, -1, 0, 1, -1, 0, 0, 0, -1, 0, 0, 1, 0, 0 +}; + +// Transfer Party + +class TransferPartyWiz { +public: + TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen); + ~TransferPartyWiz(); + + bool start(); + +private: + bool selectAndLoadTransferFile(); + bool transferFileDialogue(Common::String &dest); + + + int selectCharactersMenu(); + void drawCharPortraitWithStats(int charIndex, bool enabled); + void updateHighlight(int index); + + void convertStats(); + void convertInventory(); + Item convertItem(Item eob1Item); + void giveKhelbensCoin(); + + EoBCoreEngine *_vm; + Screen_EoB *_screen; + + int _highlight; + EoBItem *_oldItems; + + const uint16 *_portraitFrames; + const uint8 *_convertTable; + const uint8 *_itemTable; + const uint32 *_expTable; + const char *const *_strings1; + const char *const *_strings2; + const char *const *_labels; +}; + +TransferPartyWiz::TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen) { + int temp; + _portraitFrames = _vm->staticres()->loadRawDataBe16(kEoB2TransferPortraitFrames, temp); + _convertTable = _vm->staticres()->loadRawData(kEoB2TransferConvertTable, temp); + _itemTable = _vm->staticres()->loadRawData(kEoB2TransferItemTable, temp); + _expTable = _vm->staticres()->loadRawDataBe32(kEoB2TransferExpTable, temp); + _strings1 = _vm->staticres()->loadStrings(kEoB2TransferStrings1, temp); + _strings2 = _vm->staticres()->loadStrings(kEoB2TransferStrings2, temp); + _labels = _vm->staticres()->loadStrings(kEoB2TransferLabels, temp); + _highlight = -1; + _oldItems = 0; +} + +TransferPartyWiz::~TransferPartyWiz() { + delete[] _oldItems; +} + +bool TransferPartyWiz::start() { + _screen->copyPage(0, 12); + + if (!selectAndLoadTransferFile()) + return false; + + convertStats(); + + _oldItems = new EoBItem[600]; + memcpy(_oldItems, _vm->_items, sizeof(EoBItem) * 600); + _vm->loadItemDefs(); + + int selection = selectCharactersMenu(); + if (selection == 0) { + for (int i = 0; i < 6; i++) + delete[] _vm->_characters[i].faceShape; + memset(_vm->_characters, 0, sizeof(EoBCharacter) * 6); + return false; + } + + int ch = 0; + for (int i = 0; i < 6; i++) { + if (selection & (1 << i)) { + if (ch != i) { + delete[] _vm->_characters[ch].faceShape; + memcpy(&_vm->_characters[ch], &_vm->_characters[i], sizeof(EoBCharacter)); + _vm->_characters[i].faceShape = 0; + } + ch++; + } + } + memset(&_vm->_characters[4], 0, sizeof(EoBCharacter) * 2); + + convertInventory(); + giveKhelbensCoin(); + + return true; +} + +bool TransferPartyWiz::selectAndLoadTransferFile() { + do { + _screen->copyPage(12, 0); + if (transferFileDialogue(_vm->_savegameFilename)) + break; + } while (_vm->_gui->confirmDialogue2(15, 68, 1)); + + if (_vm->_savegameFilename.empty()) + return false; + + if (_vm->loadGameState(-1).getCode() != Common::kNoError) + return false; + + return true; +} + + bool TransferPartyWiz::transferFileDialogue(Common::String &dest) { + _vm->_gui->transferWaitBox(); + + Common::Array<Common::String> eobTargets; + const Common::ConfigManager::DomainMap dom = ConfMan.getGameDomains(); + + for (Common::ConfigManager::DomainMap::const_iterator i = dom.begin(); i != dom.end(); ++i) { + if (ConfMan.get("gameid", i->_key).equals("eob")) + eobTargets.push_back(i->_key); + _vm->updateInput(); + } + + if (eobTargets.empty()) + return false; + + Common::String target = _vm->_gui->transferTargetMenu(eobTargets); + _screen->copyPage(12, 0); + + if (target.empty()) + return true; + + dest = target + ".fin"; + Common::InSaveFile *in = _vm->_saveFileMan->openForLoading(dest); + if (in) { + delete in; + if (_vm->_gui->confirmDialogue2(15, -2, 1)) + return true; + } + + _screen->copyPage(12, 0); + + bool result = _vm->_gui->transferFileMenu(target, dest); + _screen->copyPage(12, 0); + + return result; +} + +int TransferPartyWiz::selectCharactersMenu() { + _screen->setCurPage(2); + _screen->setFont(Screen::FID_6_FNT); + _screen->clearCurPage(); + + _vm->gui_drawBox(0, 0, 320, 163, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + _screen->printText(_strings2[0], 5, 3, 15, 0); + _screen->printText(_strings2[1], 5, 10, 15, 0); + + for (int i = 0; i < 6; i++) + drawCharPortraitWithStats(i, 0); + + _vm->gui_drawBox(4, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + _vm->gui_drawBox(272, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + + _screen->printShadedText(_labels[0], 9, 151, 15, 0); + _screen->printShadedText(_labels[1], 288, 151, 15, 0); + + _screen->setCurPage(0); + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + int selection = 0; + int highlight = 0; + bool update = false; + + for (bool loop = true; loop && (!_vm->shouldQuit());) { + int inputFlag = _vm->checkInput(0, false, 0) & 0x8FF; + _vm->removeInputTop(); + + if (inputFlag) { + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + highlight ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP]) { + highlight -= 2; + if (highlight < 0) + highlight += 8; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + highlight += 2; + if (highlight >= 8) + highlight -= 8; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE]) { + update = true; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + update = true; + highlight = 6; + } else if (inputFlag == 199) { + for (int i = 0; i < 8; i++) { + int t = i << 2; + if (_vm->posWithinRect(_vm->_mouseX, _vm->_mouseY, _portraitFrames[t], _portraitFrames[t + 1], _portraitFrames[t + 2], _portraitFrames[t + 3])) { + highlight = i; + update = true; + break; + } + } + } + } + + updateHighlight(highlight); + + if (!update) + continue; + + update = false; + + if (highlight < 6) { + if (_vm->_characters[highlight].flags & 1) { + selection ^= (1 << highlight); + drawCharPortraitWithStats(highlight, (selection & (1 << highlight)) ? true : false); + _screen->updateScreen(); + } + continue; + } + + int x = (highlight - 6) * 268 + 4; + _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.fill, _vm->guiSettings()->colors.fill, -1); + _screen->updateScreen(); + _vm->_system->delayMillis(80); + _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, -1); + _screen->updateScreen(); + + if (highlight == 6 || _vm->shouldQuit()) { + _screen->setFont(Screen::FID_8_FNT); + return 0; + } + + int count = 0; + for (int i = 0; i < 6; i++) { + if (selection & (1 << i)) + count++; + } + + if (count == 4 || _vm->shouldQuit()) + loop = false; + else + _vm->_gui->messageDialogue(16, count < 4 ? 69 : 70, 6); + + _screen->updateScreen(); + } + + _screen->setFont(Screen::FID_8_FNT); + if (_vm->shouldQuit()) + return 0; + else + _vm->_gui->messageDialogue(16, 71, 6); + + return selection; +} + +void TransferPartyWiz::drawCharPortraitWithStats(int charIndex, bool enabled) { + int16 x = (charIndex % 2) * 159; + int16 y = (charIndex / 2) * 40; + EoBCharacter *c = &_vm->_characters[charIndex]; + + _screen->fillRect(x + 4, y + 24, x + 36, y + 57, 12); + _vm->gui_drawBox(x + 40, y + 24, 118, 34, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + + if (!(c->flags & 1)) + return; + + _screen->drawShape(_screen->_curPage, c->faceShape, x + 4, y + 25, 0); + + int color1 = 15; + int color2 = 12; + + if (enabled) { + color1 = 6; + color2 = 15; + } else { + _screen->drawShape(_screen->_curPage, _vm->_disabledCharGrid, x + 4, y + 25, 0); + } + + _screen->printShadedText(c->name, x + 44, y + 27, color1, 0); + _screen->printText(_vm->_chargenRaceSexStrings[c->raceSex], x + 43, y + 36, color2, 0); + _screen->printText(_vm->_chargenClassStrings[c->cClass], x + 43, y + 43, color2, 0); + + Common::String tmp = Common::String::format(_strings1[0], c->level[0]); + for (int i = 1; i < _vm->_numLevelsPerClass[c->cClass]; i++) + tmp += Common::String::format(_strings1[1], c->level[i]); + _screen->printText(tmp.c_str(), x + 43, y + 50, color2, 0); +} + +void TransferPartyWiz::updateHighlight(int index) { + static const int16 xPos[] = { 9, 288 }; + if (_highlight > 5 && _highlight != index) + _screen->printText(_labels[_highlight - 6], xPos[_highlight - 6], 151, 15, 0); + + if (index < 6) { + _vm->_gui->updateBoxFrameHighLight(14 + index); + _highlight = index; + return; + } + + if (_highlight == index) + return; + + if (_highlight < 6) + _vm->_gui->updateBoxFrameHighLight(-1); + + _screen->printText(_labels[index - 6], xPos[index - 6], 151, 6, 0); + _screen->updateScreen(); + _highlight = index; +} + +void TransferPartyWiz::convertStats() { + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_vm->_characters[i]; + uint32 aflags = 0; + + for (int ii = 0; ii < 25; ii++) { + if (c->mageSpellsAvailableFlags & (1 << ii)) { + int8 f = (int8)_convertTable[ii + 1] - 1; + if (f != -1) + aflags |= (1 << f); + } + } + c->mageSpellsAvailableFlags = aflags; + + c->armorClass = 0; + c->disabledSlots = 0; + c->flags &= 1; + c->hitPointsCur = c->hitPointsMax; + c->food = 100; + + c->effectFlags = 0; + c->damageTaken = 0; + memset(c->clericSpells, 0, sizeof(int8) * 80); + memset(c->mageSpells, 0, sizeof(int8) * 80); + memset(c->timers, 0, sizeof(uint32) * 10); + memset(c->events, 0, sizeof(int8) * 10); + memset(c->effectsRemainder, 0, sizeof(uint8) * 4); + memset(c->slotStatus, 0, sizeof(int8) * 5); + + for (int ii = 0; ii < 3; ii++) { + int t = _vm->getCharacterClassType(c->cClass, ii); + if (t == -1) + continue; + if (c->experience[ii] > _expTable[t]) + c->experience[ii] = _expTable[t]; + } + } +} + +void TransferPartyWiz::convertInventory() { + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_vm->_characters[i]; + + for (int slot = 0; slot < 27; slot++) { + Item itm = c->inventory[slot]; + if (slot == 16) { + Item first = itm; + c->inventory[slot] = 0; + + for (bool forceLoop = true; (itm && (itm != first)) || forceLoop; itm = _oldItems[itm].prev) { + forceLoop = false; + _vm->setItemPosition(&c->inventory[slot], -2, convertItem(itm), 0); + } + } else { + c->inventory[slot] = convertItem(itm); + } + } + } +} + +Item TransferPartyWiz::convertItem(Item eob1Item) { + if (!eob1Item) + return 0; + + EoBItem *itm1 = &_oldItems[eob1Item]; + + if (!_itemTable[itm1->type]) + return 0; + + Item newItem = _vm->duplicateItem(1); + EoBItem *itm2 = &_vm->_items[newItem]; + bool match = false; + + itm2->flags = itm1->flags | 0x40; + itm2->icon = itm1->icon; + itm2->type = itm1->type; + itm2->level = 0xFF; + + switch (itm2->type) { + case 35: + itm1->value += 25; + // fall through + case 34: + itm2->value = _convertTable[itm1->value]; + if (!itm2->value) { + itm2->block = -1; + return 0; + } + break; + case 39: + itm2->value = itm1->value - 1; + break; + case 48: + if (itm1->value == 5) { + memset(itm2, 0, sizeof(EoBItem)); + itm2->block = -1; + return 0; + } + itm2->value = itm1->value; + itm2->flags = ((itm1->flags & 0x3F) + 3) | 0x40; + break; + case 18: + itm2->icon = 19; + // fall through + default: + itm2->value = itm1->value; + break; + } + + switch ((_vm->_itemTypes[itm2->type].extraProperties & 0x7F) - 1) { + case 0: + case 1: + case 2: + if (itm2->value) + itm2->flags |= 0x80; + break; + case 4: + case 5: + case 8: + case 9: + case 13: + case 15: + case 17: + itm2->flags |= 0x80; + break; + default: + break; + } + + for (int i = 1; i < 600; i++) { + if (i == 60 || i == 62 || i == 63 || i == 83) + continue; + EoBItem *tmp = &_vm->_items[i]; + if (tmp->level || tmp->block == -2 || tmp->type != itm2->type || tmp->icon != itm2->icon) + continue; + itm2->nameUnid = tmp->nameUnid; + itm2->nameId = tmp->nameId; + match = true; + break; + } + + if (!match) { + for (int i = 1; i < 600; i++) { + if (i == 60 || i == 62 || i == 63 || i == 83) + continue; + EoBItem *tmp = &_vm->_items[i]; + if (tmp->level || tmp->block == -2 || tmp->type != itm2->type) + continue; + itm2->nameUnid = tmp->nameUnid; + itm2->nameId = tmp->nameId; + match = true; + break; + } + } + + if (!match) { + memset(itm2, 0, sizeof(EoBItem)); + itm2->block = -1; + return 0; + } + + itm2->level = 0; + return newItem; +} + +void TransferPartyWiz::giveKhelbensCoin() { + bool success = false; + for (int i = 0; i < 4 && !success; i++) { + EoBCharacter *c = &_vm->_characters[i]; + + for (int slot = 2; slot < 16; slot++) { + if (c->inventory[slot]) + continue; + _vm->createInventoryItem(c, 93, -1, slot); + success = true; + break; + } + } + + if (!success) { + _vm->_characters[0].inventory[2] = 0; + _vm->createInventoryItem(&_vm->_characters[0], 93, -1, 2); + } +} + +// Start functions + +bool EoBCoreEngine::startCharacterGeneration() { + return CharacterGenerator(this, _screen).start(_characters, &_faceShapes); +} + +bool EoBCoreEngine::startPartyTransfer() { + return TransferPartyWiz(this, _screen).start(); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/darkmoon.cpp b/engines/kyra/engine/darkmoon.cpp new file mode 100644 index 0000000000..9731f00533 --- /dev/null +++ b/engines/kyra/engine/darkmoon.cpp @@ -0,0 +1,493 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/darkmoon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +DarkMoonEngine::DarkMoonEngine(OSystem *system, const GameFlags &flags) : EoBCoreEngine(system, flags) { + _dscDoorType5Offs = 0; + _numSpells = 70; + _menuChoiceInit = 4; + + _kheldranStrings = _npcStrings[0] = _npcStrings[1] = _hornStrings = 0; + _utilMenuStrings = _ascii2SjisTables = _ascii2SjisTables2 = 0; + _npcShpData = _dscDoorType5Offs = _hornSounds = 0; + _dreamSteps = 0; +} + +DarkMoonEngine::~DarkMoonEngine() { +} + +Common::Error DarkMoonEngine::init() { + Common::Error err = EoBCoreEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initStaticResource(); + + _monsterProps = new EoBMonsterProperty[10]; + + if (_configRenderMode == Common::kRenderEGA) { + Palette pal(16); + _screen->loadPalette(_egaDefaultPalette, pal, 16); + _screen->setScreenPalette(pal); + } + + _screen->loadPalette(_flags.platform == Common::kPlatformFMTowns ? "MENU.PAL" : "PALETTE.COL", _screen->getPalette(0)); + _screen->setScreenPalette(_screen->getPalette(0)); + + // adjust menu settings for EOB II FM-Towns + if (_flags.platform == Common::kPlatformFMTowns) { + _screen->modifyScreenDim(6, 10, 100, 21, 40); + _screen->modifyScreenDim(27, 0, 0, 21, 2); + } + + return Common::kNoError; +} + +void DarkMoonEngine::startupNew() { + _currentLevel = 4; + _currentSub = 0; + loadLevel(4, 0); + _currentBlock = 171; + _currentDirection = 2; + setHandItem(0); + EoBCoreEngine::startupNew(); +} + +void DarkMoonEngine::drawNpcScene(int npcIndex) { + const uint8 *shpDef = &_npcShpData[npcIndex << 3]; + for (int i = npcIndex; i != 255; i = shpDef[7]) { + shpDef = &_npcShpData[i << 3]; + _screen->_curPage = 2; + const uint8 *shp = _screen->encodeShape(READ_LE_UINT16(shpDef), shpDef[2], shpDef[3], shpDef[4]); + _screen->_curPage = 0; + _screen->drawShape(0, shp, 88 + shpDef[5] - (shp[2] << 2), 104 + shpDef[6] - shp[1], 5); + delete[] shp; + } +} + +void DarkMoonEngine::runNpcDialogue(int npcIndex) { + if (npcIndex == 0) { + snd_playSoundEffect(57); + if (npcJoinDialogue(0, 1, 3, 2)) + setScriptFlags(0x40); + } else if (npcIndex == 1) { + snd_playSoundEffect(53); + gui_drawDialogueBox(); + + _txt->printDialogueText(4, 0); + int r = runDialogue(-1, 2, _npcStrings[0][0], _npcStrings[0][1]) - 1; + + if (r == 0) { + snd_stopSound(); + delay(3 * _tickLength); + snd_playSoundEffect(91); + npcJoinDialogue(1, 5, 6, 7); + } else if (r == 1) { + setScriptFlags(0x20); + } + + } else if (npcIndex == 2) { + snd_playSoundEffect(55); + gui_drawDialogueBox(); + + _txt->printDialogueText(8, 0); + int r = runDialogue(-1, 2, _npcStrings[1][0], _npcStrings[1][1]) - 1; + + if (r == 0) { + if (rollDice(1, 2, -1)) + _txt->printDialogueText(9, _okStrings[0]); + else + npcJoinDialogue(2, 102, 103, 104); + setScriptFlags(8); + } else if (r == 1) { + _currentDirection = 0; + } + } +} + +void DarkMoonEngine::updateUsedCharacterHandItem(int charIndex, int slot) { + EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; + if (itm->type == 48 || itm->type == 62) { + if (itm->value == 5) + return; + int charges = itm->flags & 0x3F; + if (--charges) + --itm->flags; + else + deleteInventoryItem(charIndex, slot); + } else if (itm->type == 26 || itm->type == 34 || itm->type == 35) { + deleteInventoryItem(charIndex, slot); + } +} + +void DarkMoonEngine::generateMonsterPalettes(const char *file, int16 monsterIndex) { + int cp = _screen->setCurPage(2); + _screen->loadShapeSetBitmap(file, 3, 3); + uint8 tmpPal[16]; + uint8 newPal[16]; + + for (int i = 0; i < 6; i++) { + int dci = monsterIndex + i; + memcpy(tmpPal, _monsterShapes[dci] + 4, 16); + int colx = 302 + 3 * i; + + for (int ii = 0; ii < 16; ii++) { + uint8 col = _screen->getPagePixel(_screen->_curPage, colx, 184 + ii); + int iii = 0; + for (; iii < 16; iii++) { + if (tmpPal[iii] == col) { + newPal[ii] = iii; + break; + } + } + + if (iii == 16) + newPal[ii] = 0; + } + + for (int ii = 1; ii < 3; ii++) { + memcpy(tmpPal, _monsterShapes[dci] + 4, 16); + + for (int iii = 0; iii < 16; iii++) { + uint8 col = _screen->getPagePixel(_screen->_curPage, colx + ii, 184 + iii); + if (newPal[iii]) + tmpPal[newPal[iii]] = col; + } + + int c = i; + if (monsterIndex >= 18) + c += 6; + + c = (c << 1) + (ii - 1); + assert(c < 24); + memcpy(_monsterPalettes[c], tmpPal, 16); + } + } + + _screen->setCurPage(cp); +} + +void DarkMoonEngine::loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) { + int len = stream->readUint16LE(); + Common::List<SpriteDecoration*> activeDecorations; + + for (int i = 0; i < len; i++) { + for (int ii = 0; ii < 6; ii++) { + uint8 dc[6]; + stream->read(dc, 6); + if (!dc[2] || !dc[3]) + continue; + + SpriteDecoration *m = &_monsterDecorations[i * 6 + ii + monsterIndex]; + if (_flags.platform != Common::kPlatformFMTowns) + m->shp = _screen->encodeShape(dc[0], dc[1], dc[2], dc[3]); + m->x = (int8)dc[4]; + m->y = (int8)dc[5]; + activeDecorations.push_back(m); + } + } + + if (_flags.platform == Common::kPlatformFMTowns) { + while (!activeDecorations.empty()) { + activeDecorations.front()->shp = loadTownsShape(stream); + activeDecorations.pop_front(); + } + } +} + +void DarkMoonEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { + uint8 flg = _levelBlockProperties[block].flags & 7; + + if (flg == 7 || _currentBlock == block || (flg && (_monsterProps[type].u30 || pos == 4))) + return; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != block) + continue; + if (_monsters[i].pos == 4 || _monsterProps[_monsters[i].type].u30) + return; + } + + int index = -1; + int maxDist = 0; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) { + index = i; + break; + } + + if (_monsters[i].flags & 0x40) + continue; + + // WORKAROUND for bug #3611077 (Dran's dragon transformation sequence triggered prematurely): + // The boss level and the mindflayer level share the same monster data. If you hang around + // long enough in the mindflayer level all 30 monster slots will be used up. When this + // happens it will trigger the dragon transformation sequence when Dran is moved around by script. + // We avoid removing Dran here by prefering monster slots occupied by monsters from another + // sub level. + if (_monsters[i].sub != _currentSub) { + index = i; + break; + } + + int dist = getBlockDistance(_monsters[i].block, _currentBlock); + + if (dist > maxDist) { + maxDist = dist; + index = i; + } + } + + if (index == -1) + return; + + if (_monsters[index].hitPointsCur > 0) + killMonster(&_monsters[index], false); + + initMonster(index, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); +} + +bool DarkMoonEngine::killMonsterExtra(EoBMonsterInPlay *m) { + // WORKAROUND for bug #3611077 (see DarkMoonEngine::replaceMonster()) + // The mindflayers have monster type 0, just like Dran. Using a monster slot occupied by a mindflayer would trigger the dragon transformation + // sequence when all 30 monster slots are used up. We avoid this by checking for m->sub == 1. + if (_currentLevel == 16 && _currentSub == 1 && m->sub == 1 && (_monsterProps[m->type].capsFlags & 4)) { + if (m->type) { + _playFinale = true; + _runFlag = false; + delay(850); + } else { + m->hitPointsCur = 150; + m->curRemoteWeapon = 0; + m->numRemoteAttacks = 255; + m->shpIndex++; + m->type++; + seq_dranDragonTransformation(); + } + return false; + } + return true; +} + +const uint8 *DarkMoonEngine::loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { + _screen->loadShapeSetBitmap(filename, 3, 3); + for (int i = 0; i < 3; i++) { + _doorShapes[doorIndex * 3 + i] = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); + shapeDefs += 8; + } + + for (int i = 0; i < 2; i++) { + _doorSwitches[doorIndex * 3 + i].shp = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); + shapeDefs += 8; + _doorSwitches[doorIndex * 3 + i].x = *shapeDefs; + shapeDefs += 2; + _doorSwitches[doorIndex * 3 + i].y = *shapeDefs; + shapeDefs += 2; + } + _screen->_curPage = 0; + return shapeDefs; +} + +void DarkMoonEngine::drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16) { + int shapeIndex = type * 3 + 2 - mDim; + uint8 *shp = _doorShapes[shapeIndex]; + if (!shp) + return; + + if ((_doorType[type] == 0) || (_doorType[type] == 1)) { + y = _dscDoorY1[mDim] - shp[1]; + x -= (shp[2] << 2); + + if (_doorType[type] == 1) { + drawBlockObject(0, 2, shp, x, y, 5); + shp = _doorShapes[3 + shapeIndex]; + } + + y -= ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); + + if (_specialWallTypes[wall] == 5) + y -= _dscDoorType5Offs[shapeIndex]; + + } else if (_doorType[type] == 2) { + x -= (shp[2] << 2); + y = _dscDoorY2[mDim] - ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]); + } + + drawBlockObject(0, 2, shp, x, y, 5); + + if (_doorType[type] == 2) { + shp = _doorShapes[shapeIndex + 3]; + y = _dscDoorFrameY2[mDim] - shp[1] + (((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]) >> 1) - 1; + drawBlockObject(0, 2, shp, x, y, 5); + } + + if (_wllShapeMap[wall] == -1 && !_noDoorSwitch[type]) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); +} + +void DarkMoonEngine::restParty_npc() { + int insalId = -1; + int numChar = 0; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (testCharacter(i, 2) && _characters[i].portrait == -1) + insalId = i; + numChar++; + } + + if (insalId == -1 || numChar < 5) + return; + + removeCharacterFromParty(insalId); + if (insalId < 4) + exchangeCharacters(insalId, testCharacter(5, 1) ? 5 : 4); + + clearScriptFlags(6); + + if (!stripPartyItems(1, 1, 1, 1)) + stripPartyItems(2, 1, 1, 1); + stripPartyItems(31, 0, 1, 3); + stripPartyItems(39, 1, 0, 3); + stripPartyItems(47, 0, 1, 2); + + _items[createItemOnCurrentBlock(28)].value = 26; + + gui_drawPlayField(false); + gui_drawAllCharPortraitsWithStats(); + + _screen->setClearScreenDim(10); + _screen->set16bitShadingLevel(4); + gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); + gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->set16bitShadingLevel(0); + _gui->messageDialogue2(11, 63, 6); + _gui->messageDialogue2(11, 64, 6); +} + +bool DarkMoonEngine::restParty_extraAbortCondition() { + if (_currentLevel != 3) + return false; + + seq_nightmare(); + + return true; +} + +void DarkMoonEngine::useHorn(int charIndex, int weaponSlot) { + int v = _items[_characters[charIndex].inventory[weaponSlot]].value - 1; + _txt->printMessage(_hornStrings[v]); + snd_playSoundEffect(_hornSounds[v]); +} + +bool DarkMoonEngine::checkPartyStatusExtra() { + if (checkScriptFlags(0x100000)) + seq_kheldran(); + return _gui->confirmDialogue2(14, 67, 1); +} + +void DarkMoonEngine::drawLightningColumn() { + int f = rollDice(1, 2, -1); + int y = 0; + + for (int i = 0; i < 6; i++) { + f ^= 1; + drawBlockObject(f, 2, _lightningColumnShape, 72, y, 5); + y += 64; + } +} + +int DarkMoonEngine::resurrectionSelectDialogue() { + countResurrectionCandidates(); + + _rrNames[_rrCount] = _abortStrings[0]; + _rrId[_rrCount++] = 99; + + int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; + if (r == 99) + return 0; + + if (r < 0) { + r = -r; + if (prepareForNewPartyMember(33, r)) + initNpc(r - 1); + } else { + _characters[r].hitPointsCur = 1; + } + + return 1; +} + +int DarkMoonEngine::charSelectDialogue() { + int cnt = 0; + const char *namesList[7]; + memset(namesList, 0, 7 * sizeof(const char *)); + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + namesList[cnt++] = _characters[i].name; + } + + namesList[cnt++] = _abortStrings[0]; + + int r = runDialogue(-1, 7, namesList[0], namesList[1], namesList[2], namesList[3], namesList[4], namesList[5], namesList[6]) - 1; + if (r == cnt - 1) + return 99; + + for (cnt = 0; cnt < 6; cnt++) { + if (!testCharacter(cnt, 3)) + continue; + if (--r < 0) + break; + } + return cnt; +} + +void DarkMoonEngine::characterLevelGain(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + int s = _numLevelsPerClass[c->cClass]; + for (int i = 0; i < s; i++) { + uint32 er = getRequiredExperience(c->cClass, i, c->level[i] + 1); + if (er == 0xFFFFFFFF) + continue; + + increaseCharacterExperience(charIndex, er - c->experience[i] + 1); + } +} + +const KyraRpgGUISettings *DarkMoonEngine::guiSettings() { + return (_flags.platform == Common::kPlatformFMTowns) ? &_guiSettingsFMTowns : &_guiSettingsDOS; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/darkmoon.h b/engines/kyra/engine/darkmoon.h new file mode 100644 index 0000000000..3577bdbcec --- /dev/null +++ b/engines/kyra/engine/darkmoon.h @@ -0,0 +1,139 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#ifndef KYRA_EOB2_H +#define KYRA_EOB2_H + +#include "kyra/engine/eobcommon.h" + +namespace Kyra { + +class DarkmoonSequenceHelper; + +struct DarkMoonAnimCommand { + uint8 command; + uint8 obj; + int16 x1; + uint8 y1; + uint8 delay; + uint8 pal; + uint8 x2; + uint8 y2; + uint8 w; + uint8 h; +}; + +class DarkMoonEngine : public EoBCoreEngine { +friend class GUI_EoB; +friend class DarkmoonSequenceHelper; +public: + DarkMoonEngine(OSystem *system, const GameFlags &flags); + ~DarkMoonEngine(); + +private: + // Init / Release + Common::Error init(); + void initStaticResource(); + void initSpells(); + + // Main Menu + int mainMenu(); + int mainMenuLoop(); + void townsUtilitiesMenu(); + + int _menuChoiceInit; + + // Main loop + void startupNew(); + void startupLoad() {} + + // Intro/Outro + void seq_playIntro(); + void seq_playFinale(); + void seq_playCredits(DarkmoonSequenceHelper *sq, const uint8 *data, int sd, int backupPage, int tempPage, int speed); + + // Ingame sequence + void seq_nightmare(); + void seq_kheldran(); + void seq_dranDragonTransformation(); + + const int8 *_dreamSteps; + const char *const *_kheldranStrings; + + // characters + void drawNpcScene(int npcIndex); + void runNpcDialogue(int npcIndex); + + const uint8 *_npcShpData; + const char *const *_npcStrings[2]; + + // items + void updateUsedCharacterHandItem(int charIndex, int slot); + + // Monsters + void generateMonsterPalettes(const char *file, int16 monsterIndex); + void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex); + void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); + bool killMonsterExtra(EoBMonsterInPlay *m); + + // Level + void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) {} + const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs); + void drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16); + + const uint8 *_dscDoorType5Offs; + + // Fight + static const uint8 _monsterAcHitChanceTbl1[]; + static const uint8 _monsterAcHitChanceTbl2[]; + + // Rest party + void restParty_npc(); + bool restParty_extraAbortCondition(); + + // misc + void useHorn(int charIndex, int weaponSlot); + bool checkPartyStatusExtra(); + void drawLightningColumn(); + int resurrectionSelectDialogue(); + int charSelectDialogue(); + void characterLevelGain(int charIndex); + + const KyraRpgGUISettings *guiSettings(); + + const char *const *_hornStrings; + const uint8 *_hornSounds; + + const char *const *_utilMenuStrings; + + static const KyraRpgGUISettings _guiSettingsDOS; + static const KyraRpgGUISettings _guiSettingsFMTowns; + static const uint8 _egaDefaultPalette[]; +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eob.cpp b/engines/kyra/engine/eob.cpp new file mode 100644 index 0000000000..18ed9c623a --- /dev/null +++ b/engines/kyra/engine/eob.cpp @@ -0,0 +1,573 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eob.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +EoBEngine::EoBEngine(OSystem *system, const GameFlags &flags) + : EoBCoreEngine(system, flags) { + _numSpells = 53; + _menuChoiceInit = 4; + + _turnUndeadString = 0; + _finBonusStrings = _npcStrings[1] = _npcStrings[2] = 0; + _npcStrings[3] = _npcStrings[4] = _npcStrings[5] = _npcStrings[6] = 0; + _npcStrings[7] = _npcStrings[8] = _npcStrings[9] = _npcStrings[10] = 0; + _npcShpData = _npcSubShpIndex1 = _npcSubShpIndex2 = _npcSubShpY = 0; + _dscDoorScaleMult4 = _dscDoorScaleMult5 = _dscDoorScaleMult6 = _dscDoorY3 = 0; + _dscDoorY4 = _dscDoorY5 = _dscDoorY6 = _dscDoorY7 = _doorShapeEncodeDefs = 0; + _doorSwitchShapeEncodeDefs = _doorSwitchCoords = 0; + _dscDoorCoordsExt = 0; +} + +EoBEngine::~EoBEngine() { + delete[] _itemsOverlay; +} + +Common::Error EoBEngine::init() { + Common::Error err = EoBCoreEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initStaticResource(); + + if (_configRenderMode != Common::kRenderCGA) + _itemsOverlay = _res->fileData((_configRenderMode == Common::kRenderEGA) ? "ITEMRMP.EGA" : "ITEMRMP.VGA", 0); + + _screen->modifyScreenDim(7, 0x01, 0xB3, 0x22, 0x12); + _screen->modifyScreenDim(9, 0x01, 0x7D, 0x26, 0x3F); + _screen->modifyScreenDim(12, 0x01, 0x04, 0x14, 0xA0); + + _scriptTimersCount = 1; + + if (_configRenderMode == Common::kRenderEGA) { + Palette pal(16); + _screen->loadPalette(_egaDefaultPalette, pal, 16); + _screen->setScreenPalette(pal); + } else { + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + } + + return Common::kNoError; +} + +void EoBEngine::startupNew() { + _currentLevel = 1; + _currentSub = 0; + loadLevel(1, 0); + _currentBlock = 490; + _currentDirection = 0; + setHandItem(0); + + EoBCoreEngine::startupNew(); +} + +void EoBEngine::startupLoad() { + _sound->loadSoundFile("ADLIB"); +} + +void EoBEngine::drawNpcScene(int npcIndex) { + _screen->copyRegion(0, 0, 0, 0, 176, 120, 6, 0, Screen::CR_NO_P_CHECK); + switch (npcIndex) { + case 0: + encodeDrawNpcSeqShape(2, 88, 104); + break; + + case 1: + if (_npcSequenceSub == -1) { + encodeDrawNpcSeqShape(0, 88, 104); + } else { + encodeDrawNpcSeqShape(0, 60, 104); + encodeDrawNpcSeqShape(5, 116, 104); + } + break; + + case 2: + if (_npcSequenceSub == -1) { + encodeDrawNpcSeqShape(3, 88, 104); + } else { + encodeDrawNpcSeqShape(3, 60, 104); + encodeDrawNpcSeqShape(_npcSubShpIndex1[_npcSequenceSub], 116, 104); + encodeDrawNpcSeqShape(_npcSubShpIndex2[_npcSequenceSub], 116, _npcSubShpY[_npcSequenceSub]); + } + break; + + case 3: + encodeDrawNpcSeqShape(7, 88, 104); + break; + + case 4: + encodeDrawNpcSeqShape(6, 88, 104); + break; + + case 5: + encodeDrawNpcSeqShape(18, 88, 88); + break; + + case 6: + encodeDrawNpcSeqShape(17, 88, 104); + break; + + case 7: + encodeDrawNpcSeqShape(4, 88, 104); + break; + + default: + break; + } +} + +void EoBEngine::encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY) { + const uint8 *shpDef = &_npcShpData[npcIndex << 2]; + _screen->_curPage = 2; + const uint8 *shp = _screen->encodeShape(shpDef[0], shpDef[1], shpDef[2], shpDef[3], false, _cgaMappingDefault); + _screen->_curPage = 0; + _screen->drawShape(0, shp, drawX - (shp[2] << 2), drawY - shp[1], 5); + delete[] shp; +} + +#define DLG2(txt, buttonstr) (runDialogue(txt, 2, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1]) - 1) +#define DLG3(txt, buttonstr) (runDialogue(txt, 3, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1], _npcStrings[buttonstr][2]) - 1) +#define DLG2A3(cond, txt, buttonstr1, buttonstr2) ((cond) ? (DLG2(txt, buttonstr1) ? 2 : 0) : DLG3(txt, buttonstr2)) +#define TXT(txt) _txt->printDialogueText(txt, _moreStrings[0]) + +void EoBEngine::runNpcDialogue(int npcIndex) { + int r = 0; + int a = 0; + Item itm = 0; + + switch (npcIndex) { + case 0: + for (r = 1; r == 1;) { + gui_drawDialogueBox(); + r = DLG2A3(checkScriptFlags(0x2000), 8, 2, 1); + if (r == 1) { + TXT(1); + setScriptFlags(0x2000); + } else if (r == 0) { + npcJoinDialogue(6, 12, 23, 2); + setScriptFlags(0x4000); + } + } + break; + + case 1: + if (!checkScriptFlags(0x10000)) { + if (checkScriptFlags(0x8000)) { + a = 13; + } else { + setScriptFlags(0x8000); + r = DLG2(3, 3); + a = 4; + } + if (!r) + r = DLG2(a, 4); + + if (!r) { + for (a = 0; a < 6; a++) + createItemOnCurrentBlock(55); + createItemOnCurrentBlock(62); + setScriptFlags(0x10000); + TXT(6); + npcJoinDialogue(7, 7, 29, 30); + } else { + TXT(5); + } + r = 1; + } + + if (!checkScriptFlags(0x80000)) { + for (a = 0; a < 6; a++) { + if (testCharacter(a, 1) && _characters[a].portrait == -9) + break; + } + if (a != 6) { + TXT(25); + TXT(26); + setScriptFlags(0x80000); + r = 1; + } + } + + if (!checkScriptFlags(0x100000)) { + if (deletePartyItems(6, -1)) { + _npcSequenceSub = 0; + drawNpcScene(npcIndex); + TXT(28); + createItemOnCurrentBlock(32); + setScriptFlags(0x100000); + r = 1; + } + } + + if (!r) + _txt->printDialogueText(_npcStrings[0][0], true); + + break; + + case 2: + if (checkScriptFlags(0x10000)) { + if (checkScriptFlags(0x20000)) { + TXT(11); + } else { + r = DLG2A3(!countResurrectionCandidates(), 9, 5, 6); + if (r < 2) { + if (r == 0) + healParty(); + else + resurrectionSelectDialogue(); + setScriptFlags(0x20000); + } + } + } else { + TXT(24); + } + break; + + case 3: + if (!DLG2(18, 7)) { + setScriptFlags(0x8400000); + for (a = 0; a < 30; a++) { + if (_monsters[a].mode == 8) + _monsters[a].mode = 5; + } + } else if (deletePartyItems(49, -1)) { + TXT(20); + setScriptFlags(0x400000); + } else { + TXT(19); + } + break; + + case 4: + r = DLG3(14, 8); + if (r == 0) + setScriptFlags(0x200000); + else if (r == 1) + TXT(15); + setScriptFlags(0x800000); + break; + + case 5: + if (!DLG2(16, 9)) { + TXT(17); + for (a = 0; a < 6; a++) { + for (r = 0; r < 2; r++) { + itm = _characters[a].inventory[r]; + if (itm && (_items[itm].type < 51 || _items[itm].type > 56)) { + _characters[a].inventory[r] = 0; + setItemPosition((Item *)&_levelBlockProperties[_currentBlock].drawObjects, _currentBlock, itm, _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + } + } + } + } + setScriptFlags(0x2000000); + break; + + case 6: + TXT(21); + setScriptFlags(0x1000000); + break; + + case 7: + r = DLG3(22, 10); + if (r < 2) { + if (r == 0) + npcJoinDialogue(8, 27, 44, 45); + else + TXT(31); + setScriptFlags(0x4000000); + } + break; + + default: + break; + } +} + +#undef TXT +#undef DLG2 +#undef DLG3 +#undef DLG2A3 + +void EoBEngine::updateUsedCharacterHandItem(int charIndex, int slot) { + EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; + if (itm->type == 48) { + int charges = itm->flags & 0x3F; + if (--charges) + --itm->flags; + else + deleteInventoryItem(charIndex, slot); + } else if (itm->type == 34 || itm->type == 35) { + deleteInventoryItem(charIndex, slot); + } +} + +void EoBEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { + if (_levelBlockProperties[block].flags & 7) + return; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) { + initMonster(i, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); + break; + } + } +} + +bool EoBEngine::killMonsterExtra(EoBMonsterInPlay *m) { + if (m->type == 21) { + _playFinale = true; + _runFlag = false; + } + return true; +} + +void EoBEngine::updateScriptTimersExtra() { + int cnt = 0; + for (int i = 1; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) + cnt++; + } + + if (!cnt) { + for (int i = 1; i < 30; i++) { + if (getBlockDistance(_monsters[i].block, _currentBlock) > 3) { + killMonster(&_monsters[i], true); + break; + } + } + } +} + +void EoBEngine::loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) { + _screen->loadShapeSetBitmap("DOOR", 5, 3); + _screen->_curPage = 2; + + if (doorType1 != 0xFF) { + for (int i = 0; i < 3; i++) { + const uint8 *enc = &_doorShapeEncodeDefs[(doorType1 * 3 + i) << 2]; + _doorShapes[shapeId1 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + enc = &_doorSwitchShapeEncodeDefs[(doorType1 * 3 + i) << 2]; + _doorSwitches[shapeId1 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + _doorSwitches[shapeId1 + i].x = _doorSwitchCoords[doorType1 * 6 + i * 2]; + _doorSwitches[shapeId1 + i].y = _doorSwitchCoords[doorType1 * 6 + i * 2 + 1]; + } + } + + if (doorType2 != 0xFF) { + for (int i = 0; i < 3; i++) { + const uint8 *enc = &_doorShapeEncodeDefs[(doorType2 * 3 + i) << 2]; + _doorShapes[shapeId2 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + enc = &_doorSwitchShapeEncodeDefs[(doorType2 * 3 + i) << 2]; + _doorSwitches[shapeId2 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + _doorSwitches[shapeId2 + i].x = _doorSwitchCoords[doorType2 * 6 + i * 2]; + _doorSwitches[shapeId2 + i].y = _doorSwitchCoords[doorType2 * 6 + i * 2 + 1]; + } + } + + _screen->_curPage = 0; +} + +void EoBEngine::drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) { + int shapeIndex = type + 2 - mDim; + uint8 *shp = _doorShapes[shapeIndex]; + if (!shp) + return; + + int d1 = 0; + int d2 = 0; + int v = 0; + const ScreenDim *td = _screen->getScreenDim(5); + + switch (_currentLevel) { + case 4: + case 5: + case 6: + y = _dscDoorY6[mDim] - shp[1]; + d1 = _dscDoorCoordsExt[index << 1] >> 3; + d2 = _dscDoorCoordsExt[(index << 1) + 1] >> 3; + if (_shpDmX1 > d1) + d1 = _shpDmX1; + if (_shpDmX2 < d2) + d2 = _shpDmX2; + _screen->modifyScreenDim(5, d1, td->sy, d2 - d1, td->h); + v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim] : _dscDoorScaleMult4[mDim]) * -1; + v -= (shp[2] << 3); + drawBlockObject(0, 2, shp, x + v, y, 5); + v += (shp[2] << 3); + drawBlockObject(1, 2, shp, x - v, y, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w - v, _doorSwitches[shapeIndex].y, 5); + break; + + case 7: + case 8: + case 9: + y = _dscDoorY3[mDim] - _doorShapes[shapeIndex + 3][1]; + d1 = x - (_doorShapes[shapeIndex + 3][2] << 2); + x -= (shp[2] << 2); + drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], d1, y, 5); + setDoorShapeDim(index, y1, y2, 5); + y = _dscDoorY3[mDim] - ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim] : _dscDoorScaleMult2[mDim]); + drawBlockObject(0, 2, shp, x, y, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + + case 10: + case 11: + v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult5[mDim] : _dscDoorScaleMult6[mDim]) * -1; + x -= (shp[2] << 2); + y = _dscDoorY4[mDim] + v; + drawBlockObject(0, 2, shp, x, y + v, 5); + v = (v >> 3) + (v >> 2); + y = _dscDoorY5[mDim]; + drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], x, y - v, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + + default: + y = (_currentLevel == 12 ? _dscDoorY6[mDim] : _dscDoorY1[mDim]) - shp[1]; + x -= (shp[2] << 2); + y -= (wall >= 30 ? _dscDoorScaleMult2[mDim] : (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); + drawBlockObject(0, 2, shp, x, y, 5); + + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + } +} + +void EoBEngine::turnUndeadAuto() { + if (_currentLevel != 2 && _currentLevel != 7) + return; + + int oc = _openBookChar; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x0D)) + continue; + + EoBCharacter *c = &_characters[i]; + + if (_itemTypes[_items[c->inventory[0]].type].extraProperties != 6 && _itemTypes[_items[c->inventory[1]].type].extraProperties != 6) + continue; + + int l = getCharacterLevelIndex(2, c->cClass); + if (l > -1) { + if (c->level[l] > _openBookCasterLevel) { + _openBookCasterLevel = c->level[l]; + _openBookChar = i; + } + } else { + l = getCharacterLevelIndex(4, c->cClass); + if (l > -1) { + if ((c->level[l] - 2) > _openBookCasterLevel) { + _openBookCasterLevel = (c->level[l] - 2); + _openBookChar = i; + } + } + } + } + + if (_openBookCasterLevel) + spellCallback_start_turnUndead(); + + _openBookChar = oc; + _openBookCasterLevel = 0; +} + +void EoBEngine::turnUndeadAutoHit() { + _txt->printMessage(_turnUndeadString[0], -1, _characters[_openBookChar].name); + sparkEffectOffensive(); +} + +bool EoBEngine::checkPartyStatusExtra() { + _screen->copyPage(0, 10); + int cd = _screen->curDimIndex(); + gui_drawBox(0, 121, 320, 80, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _txt->setupField(9, false); + _txt->printMessage(_menuStringsDefeat[0]); + while (!shouldQuit()) { + removeInputTop(); + if (checkInput(0, false, 0) & 0xFF) + break; + } + _screen->copyPage(10, 0); + _eventList.clear(); + _screen->setScreenDim(cd); + _txt->removePageBreakFlag(); + return true; +} + +int EoBEngine::resurrectionSelectDialogue() { + gui_drawDialogueBox(); + _txt->printDialogueText(_npcStrings[0][1]); + + int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; + + if (r < 0) { + r = -r; + deletePartyItems(33, r); + _npcSequenceSub = r - 1; + drawNpcScene(2); + npcJoinDialogue(_npcSequenceSub, 32 + (_npcSequenceSub << 1), -1, 33 + (_npcSequenceSub << 1)); + } else { + _characters[r].hitPointsCur = _characters[r].hitPointsMax; + } + + return 1; +} + +void EoBEngine::healParty() { + int cnt = rollDice(1, 3, 2); + for (int i = 0; i < 6 && cnt; i++) { + if (testCharacter(i, 3)) + continue; + + _characters[i].flags &= ~4; + neutralizePoison(i); + + if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) + continue; + + cnt--; + _characters[i].hitPointsCur += rollDice(1, 8, 9); + if (_characters[i].hitPointsCur > _characters[i].hitPointsMax) + _characters[i].hitPointsCur = _characters[i].hitPointsMax; + } +} + +const KyraRpgGUISettings *EoBEngine::guiSettings() { + return (_configRenderMode == Common::kRenderCGA || _configRenderMode == Common::kRenderEGA) ? &_guiSettingsEGA : &_guiSettingsVGA; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eob.h b/engines/kyra/engine/eob.h new file mode 100644 index 0000000000..0eb8fd3a64 --- /dev/null +++ b/engines/kyra/engine/eob.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#ifndef KYRA_EOB1_H +#define KYRA_EOB1_H + +#include "kyra/engine/eobcommon.h" + +namespace Kyra { + +class EoBEngine : public EoBCoreEngine { +friend class GUI_EoB; +friend class EoBIntroPlayer; +public: + EoBEngine(OSystem *system, const GameFlags &flags); + ~EoBEngine(); + +private: + // Init / Release + Common::Error init(); + void initStaticResource(); + void initSpells(); + + // Main Menu + int mainMenu(); + int mainMenuLoop(); + int _menuChoiceInit; + + // Main loop + void startupNew(); + void startupLoad(); + + // Intro/Outro + void seq_playIntro(); + void seq_playFinale(); + void seq_xdeath(); + + const char *const *_finBonusStrings; + + // characters + void drawNpcScene(int npcIndex); + void encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY); + void runNpcDialogue(int npcIndex); + + const uint8 *_npcShpData; + const uint8 *_npcSubShpIndex1; + const uint8 *_npcSubShpIndex2; + const uint8 *_npcSubShpY; + const char *const *_npcStrings[11]; + + // items + void updateUsedCharacterHandItem(int charIndex, int slot); + + // Monsters + void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); + bool killMonsterExtra(EoBMonsterInPlay *m); + void updateScriptTimersExtra(); + + // Level + const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { return 0; } + void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2); + void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2); + + const int16 *_dscDoorCoordsExt; + const uint8 *_dscDoorScaleMult4; + const uint8 *_dscDoorScaleMult5; + const uint8 *_dscDoorScaleMult6; + const uint8 *_dscDoorY3; + const uint8 *_dscDoorY4; + const uint8 *_dscDoorY5; + const uint8 *_dscDoorY6; + const uint8 *_dscDoorY7; + + const uint8 *_doorShapeEncodeDefs; + const uint8 *_doorSwitchShapeEncodeDefs; + const uint8 *_doorSwitchCoords; + + // Fight + static const uint8 _monsterAcHitChanceTbl1[]; + static const uint8 _monsterAcHitChanceTbl2[]; + + // Magic + void turnUndeadAuto(); + void turnUndeadAutoHit(); + + const char *const *_turnUndeadString; + + // Misc + bool checkPartyStatusExtra(); + int resurrectionSelectDialogue(); + void healParty(); + + const KyraRpgGUISettings *guiSettings(); + + static const KyraRpgGUISettings _guiSettingsVGA; + static const KyraRpgGUISettings _guiSettingsEGA; + static const uint8 _egaDefaultPalette[]; +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp new file mode 100644 index 0000000000..58cc394abd --- /dev/null +++ b/engines/kyra/engine/eobcommon.cpp @@ -0,0 +1,2536 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_intern.h" +#include "kyra/sound/sound_adlib.h" +#include "kyra/script/script_eob.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" + +#include "common/config-manager.h" +#include "common/translation.h" + +#include "backends/keymapper/keymapper.h" + +namespace Kyra { + +const char *const EoBCoreEngine::kKeymapName = "eob"; + +EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) + : KyraRpgEngine(system, flags), _numLargeItemShapes(flags.gameID == GI_EOB1 ? 14 : 11), + _numSmallItemShapes(flags.gameID == GI_EOB1 ? 23 : 26), + _numThrownItemShapes(flags.gameID == GI_EOB1 ? 12 : 9), + _numItemIconShapes(flags.gameID == GI_EOB1 ? 89 : 112), + _teleporterWallId(flags.gameID == GI_EOB1 ? 52 : 44) { + + _screen = 0; + _gui = 0; + _debugger = 0; + + _playFinale = false; + _runFlag = true; + _configMouse = _config2431 = true; + _loading = false; + + _enableHiResDithering = false; + + _envAudioTimer = 0; + _flashShapeTimer = 0; + _drawSceneTimer = 0; + + _largeItemShapes = _smallItemShapes = _thrownItemShapes = _spellShapes = _firebeamShapes = 0; + _itemIconShapes = _wallOfForceShapes = _teleporterShapes = _sparkShapes = _compassShapes = 0; + _redSplatShape = _greenSplatShape = _deadCharShape = _disabledCharGrid = 0; + _blackBoxSmallGrid = _weaponSlotGrid = _blackBoxWideGrid = _lightningColumnShape = 0; + + _monsterAcHitChanceTable1 = _monsterAcHitChanceTable2 = 0; + + _monsterDustStrings = 0; + _enemyMageSpellList = 0; + _enemyMageSfx = 0; + _beholderSpellList = 0; + _beholderSfx = 0; + + _faceShapes = 0; + _characters = 0; + _items = 0; + _itemTypes = 0; + _itemNames = 0; + _itemInHand = -1; + _numItems = _numItemNames = 0; + + _castScrollSlot = 0; + _currentSub = 0; + + _itemsOverlay = 0; + + _partyEffectFlags = 0; + _lastUsedItem = 0; + + _levelDecorationRects = 0; + _doorSwitches = 0; + _monsterProps = 0; + _monsterDecorations = 0; + _monsterFlashOverlay = _monsterStoneOverlay = 0; + _monsters = 0; + _dstMonsterIndex = 0; + _preventMonsterFlash = false; + + _teleporterPulse = 0; + + _dscShapeCoords = 0; + _dscItemPosIndex = 0; + _dscItemShpX = 0; + _dscItemScaleIndex = 0; + _dscItemTileIndex = 0; + _dscItemShapeMap = 0; + _dscDoorScaleOffs = 0; + _dscDoorScaleMult1 = 0; + _dscDoorScaleMult2 = 0; + _dscDoorScaleMult3 = 0; + _dscDoorY1 = 0; + _dscDoorXE = 0; + + _greenFadingTable = _blueFadingTable = _lightBlueFadingTable = _blackFadingTable = _greyFadingTable = 0; + + _menuDefs = 0; + + _exchangeCharacterId = -1; + _charExchangeSwap = 0; + _configHpBarGraphs = true; + _configMouseBtSwap = false; + + memset(_dialogueLastBitmap, 0, 13); + _npcSequenceSub = 0; + _moveCounter = 0; + _partyResting = false; + + _flyingObjects = 0; + + _inf = 0; + _stepCounter = 0; + _stepsUntilScriptCall = 0; + _scriptTimersMode = 3; + _currentDirection = 0; + + _openBookSpellLevel = 0; + _openBookSpellSelectedItem = 0; + _openBookSpellListOffset = 0; + _openBookChar = _openBookCharBackup = _openBookCasterLevel = 0; + _openBookType = _openBookTypeBackup = 0; + _openBookSpellList = 0; + _openBookAvailableSpells = 0; + _activeSpellCharId = 0; + _activeSpellCharacterPos = 0; + _activeSpell = 0; + _characterSpellTarget = 0; + _returnAfterSpellCallback = false; + _spells = 0; + _spellAnimBuffer = 0; + _clericSpellOffset = 0; + _restPartyElapsedTime = 0; + _allowSkip = false; + _allowImport = false; + + _wallsOfForce = 0; + + _rrCount = 0; + memset(_rrNames, 0, 10 * sizeof(const char *)); + memset(_rrId, 0, 10 * sizeof(int8)); + + _mainMenuStrings = _levelGainStrings = _monsterSpecAttStrings = _characterGuiStringsHp = 0; + _characterGuiStringsWp = _characterGuiStringsWr = _characterGuiStringsSt = 0; + _characterGuiStringsIn = _characterStatusStrings7 = _characterStatusStrings8 = 0; + _characterStatusStrings9 = _characterStatusStrings12 = _characterStatusStrings13 = 0; + _classModifierFlags = _saveThrowLevelIndex = _saveThrowModDiv = _saveThrowModExt = 0; + _wandTypes = _drawObjPosIndex = _flightObjFlipIndex = _expObjectTblIndex = 0; + _expObjectShpStart = _expObjectTlMode = _expObjectAnimTbl1 = _expObjectAnimTbl2 = _expObjectAnimTbl3 = 0; + _monsterStepTable0 = _monsterStepTable1 = _monsterStepTable2 = _monsterStepTable3 = 0; + _projectileWeaponAmmoTypes = _flightObjShpMap = _flightObjSclIndex = 0; + _monsterCloseAttPosTable1 = _monsterCloseAttPosTable2 = _monsterCloseAttChkTable1 = 0; + _monsterCloseAttChkTable2 = _monsterCloseAttDstTable1 = _monsterCloseAttDstTable2 = 0; + _monsterProximityTable = _findBlockMonstersTable = _wallOfForceDsY = _wallOfForceDsNumW = 0; + _wallOfForceDsNumH = _wallOfForceShpId = _wllFlagPreset = _teleporterShapeCoords = 0; + _monsterCloseAttUnkTable = _monsterFrmOffsTable1 = _monsterFrmOffsTable2 = 0; + _monsterDirChangeTable = _portalSeq = 0; + _wallOfForceDsX = 0; + _expObjectAnimTbl1Size = _expObjectAnimTbl2Size = _expObjectAnimTbl3Size = 0; + _wllFlagPresetSize = _scriptTimersCount = _buttonList1Size = _buttonList2Size = 0; + _buttonList3Size = _buttonList4Size = _buttonList5Size = _buttonList6Size = 0; + _buttonList7Size = _buttonList8Size = 0; + _inventorySlotsY = _mnDef = 0; + _transferStringsScummVM = 0; + _buttonDefs = 0; + _npcPreset = 0; + _chargenStatStrings = _chargenRaceSexStrings = _chargenClassStrings = 0; + _chargenAlignmentStrings = _pryDoorStrings = _warningStrings = _ripItemStrings = 0; + _cursedString = _enchantedString = _magicObjectStrings = _magicObjectString5 = 0; + _patternSuffix = _patternGrFix1 = _patternGrFix2 = _validateArmorString = 0; + _validateCursedString = _validateNoDropString = _potionStrings = _wandStrings = 0; + _itemMisuseStrings = _suffixStringsRings = _suffixStringsPotions = 0; + _suffixStringsWands = _takenStrings = _potionEffectStrings = _yesNoStrings = 0; + _npcMaxStrings = _okStrings = _npcJoinStrings = _cancelStrings = 0; + _abortStrings = _saveLoadStrings = _mnWord = _mnPrompt = _bookNumbers = 0; + _mageSpellList = _clericSpellList = _spellNames = _magicStrings1 = 0; + _magicStrings2 = _magicStrings3 = _magicStrings4 = _magicStrings6 = 0; + _magicStrings7 = _magicStrings8 = _saveNamePatterns = 0; + _spellAnimBuffer = 0; + _sparkEffectDefSteps = _sparkEffectDefSubSteps = _sparkEffectDefShift = 0; + _sparkEffectDefAdd = _sparkEffectDefX = _sparkEffectDefY = _sparkEffectOfShift = 0; + _sparkEffectOfX = _sparkEffectOfY = _magicFlightObjectProperties = 0; + _turnUndeadEffect = _burningHandsDest = _coneOfColdGfxTbl = 0; + _sparkEffectOfFlags1 = _sparkEffectOfFlags2 = 0; + _coneOfColdDest1 = _coneOfColdDest2 = _coneOfColdDest3 = _coneOfColdDest4 = 0; + _coneOfColdGfxTblSize = 0; + _menuButtonDefs = 0; + _updateCharNum = 0; + _menuStringsMain = _menuStringsSaveLoad = _menuStringsOnOff = _menuStringsSpells = 0; + _menuStringsRest = _menuStringsDrop = _menuStringsExit = _menuStringsStarve = 0; + _menuStringsScribe = _menuStringsDrop2 = _menuStringsHead = _menuStringsPoison = 0; + _menuStringsMgc = _menuStringsPrefs = _menuStringsRest2 = _menuStringsRest3 = 0; + _menuStringsRest4 = _menuStringsDefeat = _menuStringsTransfer = _menuStringsSpec = 0; + _menuStringsSpellNo = _menuYesNoStrings = _2431Strings = _katakanaLines = _katakanaSelectStrings = 0; + _errorSlotEmptyString = _errorSlotNoNameString = _menuOkString = 0; + _spellLevelsMage = _spellLevelsCleric = _numSpellsCleric = _numSpellsWisAdj = _numSpellsPal = _numSpellsMage = 0; + _mnNumWord = _numSpells = _mageSpellListSize = _spellLevelsMageSize = _spellLevelsClericSize = 0; + _inventorySlotsX = _slotValidationFlags = _encodeMonsterShpTable = 0; + _cgaMappingDefault = _cgaMappingAlt = _cgaMappingInv = _cgaLevelMappingIndex = _cgaMappingItemsL = _cgaMappingItemsS = _cgaMappingThrown = _cgaMappingIcons = _cgaMappingDeco = 0; + memset(_cgaMappingLevel, 0, sizeof(_cgaMappingLevel)); + memset(_expRequirementTables, 0, sizeof(_expRequirementTables)); + memset(_saveThrowTables, 0, sizeof(_saveThrowTables)); + memset(_doorType, 0, sizeof(_doorType)); + memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); + memset(_scriptTimers, 0, sizeof(_scriptTimers)); + memset(_monsterBlockPosArray, 0, sizeof(_monsterBlockPosArray)); + memset(_foundMonstersArray, 0, sizeof(_foundMonstersArray)); + +#define DWM0 _dscWallMapping.push_back(0) +#define DWM(x) _dscWallMapping.push_back(&_sceneDrawVar##x) + DWM0; DWM0; DWM(Down); DWM(Right); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM(Down); DWM(Left); + DWM0; DWM0; DWM(Down); DWM(Right); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM(Down); DWM(Left); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM0; DWM(Right); + DWM(Down); DWM0; DWM0; DWM(Left); +#undef DWM +#undef DWM0 +} + +EoBCoreEngine::~EoBCoreEngine() { + releaseItemsAndDecorationsShapes(); + releaseTempData(); + + if (_faceShapes) { + for (int i = 0; i < 44; i++) { + if (_characters) { + for (int ii = 0; ii < 6; ii++) { + if (_characters[ii].faceShape == _faceShapes[i]) + _characters[ii].faceShape = 0; + } + } + delete[] _faceShapes[i]; + _faceShapes[i] = 0; + } + delete[] _faceShapes; + } + + if (_characters) { + for (int i = 0; i < 6; i++) + delete[] _characters[i].faceShape; + } + + delete[] _characters; + delete[] _items; + delete[] _itemTypes; + if (_itemNames) { + for (int i = 0; i < 130; i++) + delete[] _itemNames[i]; + } + delete[] _itemNames; + delete[] _flyingObjects; + + delete[] _monsterFlashOverlay; + delete[] _monsterStoneOverlay; + delete[] _monsters; + + if (_monsterDecorations) { + releaseMonsterShapes(0, 36); + delete[] _monsterShapes; + delete[] _monsterDecorations; + + for (int i = 0; i < 24; i++) + delete[] _monsterPalettes[i]; + delete[] _monsterPalettes; + } + + delete[] _monsterProps; + + if (_doorSwitches) { + releaseDoorShapes(); + delete[] _doorSwitches; + } + + releaseDecorations(); + delete[] _levelDecorationRects; + _dscWallMapping.clear(); + + delete[] _greenFadingTable; + delete[] _blueFadingTable; + delete[] _lightBlueFadingTable; + delete[] _blackFadingTable; + delete[] _greyFadingTable; + + delete[] _spells; + delete[] _spellAnimBuffer; + delete[] _wallsOfForce; + delete[] _buttonDefs; + + delete _gui; + _gui = 0; + delete _screen; + _screen = 0; + + delete[] _menuDefs; + _menuDefs = 0; + + delete _inf; + _inf = 0; + delete _timer; + _timer = 0; + delete _debugger; + _debugger = 0; + delete _txt; + _txt = 0; +} + +void EoBCoreEngine::initKeymap() { +#ifdef ENABLE_KEYMAPPER + Common::Keymapper *const mapper = _eventMan->getKeymapper(); + + // Do not try to recreate same keymap over again + if (mapper->getKeymap(kKeymapName) != 0) + return; + + Common::Keymap *const engineKeyMap = new Common::Keymap(kKeymapName); + + const Common::KeyActionEntry keyActionEntries[] = { + { Common::KeyState(Common::KEYCODE_UP), "MVF", _("Move Forward") }, + { Common::KeyState(Common::KEYCODE_DOWN), "MVB", _("Move Back") }, + { Common::KeyState(Common::KEYCODE_LEFT), "MVL", _("Move Left") }, + { Common::KeyState(Common::KEYCODE_RIGHT), "MVR", _("Move Right") }, + { Common::KeyState(Common::KEYCODE_HOME), "TL", _("Turn Left") }, + { Common::KeyState(Common::KEYCODE_PAGEUP), "TR", _("Turn Right") }, + { Common::KeyState(Common::KEYCODE_i), "INV", _("Open/Close Inventory") }, + { Common::KeyState(Common::KEYCODE_p), "SCE", _("Switch Inventory/Character screen") }, + { Common::KeyState(Common::KEYCODE_c), "CMP", _("Camp") }, + { Common::KeyState(Common::KEYCODE_SPACE), "CSP", _("Cast Spell") }, + // TODO: Spell cursor, but this needs more thought, since different + // game versions use different keycodes. + { Common::KeyState(Common::KEYCODE_1), "SL1", _("Spell Level 1") }, + { Common::KeyState(Common::KEYCODE_2), "SL2", _("Spell Level 2") }, + { Common::KeyState(Common::KEYCODE_3), "SL3", _("Spell Level 3") }, + { Common::KeyState(Common::KEYCODE_4), "SL4", _("Spell Level 4") }, + { Common::KeyState(Common::KEYCODE_5), "SL5", _("Spell Level 5") } + }; + + for (uint i = 0; i < ARRAYSIZE(keyActionEntries); ++i) { + Common::Action *const act = new Common::Action(engineKeyMap, keyActionEntries[i].id, keyActionEntries[i].description); + act->addKeyEvent(keyActionEntries[i].ks); + } + + if (_flags.gameID == GI_EOB2) { + Common::Action *const act = new Common::Action(engineKeyMap, "SL6", _("Spell Level 6")); + act->addKeyEvent(Common::KeyState(Common::KEYCODE_6)); + } + + mapper->addGameKeymap(engineKeyMap); +#endif +} + +Common::Error EoBCoreEngine::init() { + // In EOB the timer proc is directly invoked via interrupt 0x1C, 18.2 times per second. + // This makes a tick length of 54.94. + _tickLength = 55; + + if (ConfMan.hasKey("render_mode")) + _configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + + _enableHiResDithering = (_configRenderMode == Common::kRenderEGA && _flags.useHiRes); + + _screen = new Screen_EoB(this, _system); + assert(_screen); + _screen->setResolution(); + + _res = new Resource(this); + assert(_res); + _res->reset(); + + _staticres = new StaticResource(this); + assert(_staticres); + if (!_staticres->init()) + error("_staticres->init() failed"); + + // SoundTowns_Darkmoon requires initialized _staticres + if (_flags.platform == Common::kPlatformDOS) { + //MidiDriverType midiDriver = MidiDriver::detectDevice(MDT_PCSPK | MDT_ADLIB); + _sound = new SoundAdLibPC(this, _mixer); + } else if (_flags.platform == Common::kPlatformFMTowns) { + _sound = new SoundTowns_Darkmoon(this, _mixer); + } else if (_flags.platform == Common::kPlatformPC98) { + + } + + assert(_sound); + _sound->init(); + + // Setup volume settings (and read in all ConfigManager settings) + syncSoundSettings(); + + if (!_screen->init()) + error("screen()->init() failed"); + + if (ConfMan.hasKey("save_slot")) { + _gameToLoad = ConfMan.getInt("save_slot"); + if (!saveFileLoadable(_gameToLoad)) + _gameToLoad = -1; + } + + setupKeyMap(); + + _gui = new GUI_EoB(this); + assert(_gui); + _txt = new TextDisplayer_rpg(this, _screen); + assert(_txt); + _inf = new EoBInfProcessor(this, _screen); + assert(_inf); + _debugger = new Debugger_EoB(this); + assert(_debugger); + + _screen->loadFont(Screen::FID_6_FNT, "FONT6.FNT"); + _screen->loadFont(Screen::FID_8_FNT, "FONT8.FNT"); + + Common::Error err = KyraRpgEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initButtonData(); + initMenus(); + initStaticResource(); + initSpells(); + + _timer = new TimerManager(this, _system); + assert(_timer); + setupTimers(); + + _wllVmpMap[1] = 1; + _wllVmpMap[2] = 2; + memset(&_wllVmpMap[3], 3, 20); + _wllVmpMap[23] = 4; + _wllVmpMap[24] = 5; + + memcpy(_wllWallFlags, _wllFlagPreset, _wllFlagPresetSize); + + memset(&_specialWallTypes[3], 1, 5); + memset(&_specialWallTypes[13], 1, 5); + _specialWallTypes[8] = _specialWallTypes[18] = 6; + + memset(&_wllShapeMap[3], -1, 5); + memset(&_wllShapeMap[13], -1, 5); + + _wllVcnOffset = (_flags.platform == Common::kPlatformFMTowns) ? 0 : 16; + int bpp = (_flags.platform == Common::kPlatformFMTowns) ? 2 : 1; + + _greenFadingTable = new uint8[256 * bpp]; + _blueFadingTable = new uint8[256 * bpp]; + _lightBlueFadingTable = new uint8[256 * bpp]; + _blackFadingTable = new uint8[256 * bpp]; + _greyFadingTable = new uint8[256 * bpp]; + + _monsters = new EoBMonsterInPlay[30]; + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + _characters = new EoBCharacter[6]; + memset(_characters, 0, sizeof(EoBCharacter) * 6); + + _items = new EoBItem[600]; + memset(_items, 0, sizeof(EoBItem) * 600); + + _itemNames = new char*[130]; + for (int i = 0; i < 130; i++) { + _itemNames[i] = new char[35]; + memset(_itemNames[i], 0, 35); + } + + _flyingObjects = new EoBFlyingObject[_numFlyingObjects]; + _flyingObjectsPtr = _flyingObjects; + memset(_flyingObjects, 0, _numFlyingObjects * sizeof(EoBFlyingObject)); + + int bufferSize = _flags.useHiColorMode ? 8192 : 4096; + _spellAnimBuffer = new uint8[bufferSize]; + memset(_spellAnimBuffer, 0, bufferSize); + + _wallsOfForce = new WallOfForce[5]; + memset(_wallsOfForce, 0, 5 * sizeof(WallOfForce)); + + memset(_doorType, 0, sizeof(_doorType)); + memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); + + _monsterShapes = new uint8*[36]; + memset(_monsterShapes, 0, 36 * sizeof(uint8 *)); + _monsterDecorations = new SpriteDecoration[36]; + memset(_monsterDecorations, 0, 36 * sizeof(SpriteDecoration)); + _monsterPalettes = new uint8*[24]; + for (int i = 0; i < 24; i++) + _monsterPalettes[i] = new uint8[16]; + + _doorSwitches = new SpriteDecoration[6]; + memset(_doorSwitches, 0, 6 * sizeof(SpriteDecoration)); + + _monsterFlashOverlay = new uint8[16]; + _monsterStoneOverlay = new uint8[16]; + memset(_monsterFlashOverlay, (_configRenderMode == Common::kRenderCGA) ? 0xFF : 0x0F, 16 * sizeof(uint8)); + memset(_monsterStoneOverlay, 0x0D, 16 * sizeof(uint8)); + _monsterFlashOverlay[0] = _monsterStoneOverlay[0] = 0; + + // Prevent autosave on game startup + _lastAutosave = _system->getMillis(); + +#ifdef ENABLE_KEYMAPPER + _eventMan->getKeymapper()->pushKeymap(kKeymapName, true); +#endif + + return Common::kNoError; +} + +Common::Error EoBCoreEngine::go() { + _debugger->initialize(); + _txt->removePageBreakFlag(); + _screen->setFont(Screen::FID_8_FNT); + loadItemsAndDecorationsShapes(); + _screen->setMouseCursor(0, 0, _itemIconShapes[0]); + + // Import original save game files (especially the "Quick Start Party") + if (ConfMan.getBool("importOrigSaves")) { + importOriginalSaveFile(-1); + ConfMan.setBool("importOrigSaves", false); + ConfMan.flushToDisk(); + } + + loadItemDefs(); + int action = 0; + + for (bool repeatLoop = true; repeatLoop; repeatLoop ^= true) { + action = 0; + + if (_gameToLoad != -1) { + _sound->selectAudioResourceSet(kMusicIngame); + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d on startup", _gameToLoad); + startupLoad(); + _gameToLoad = -1; + } else { + _screen->showMouse(); + action = mainMenu(); + } + + _sound->selectAudioResourceSet(kMusicIngame); + + if (action == -1) { + // load game + repeatLoop = _gui->runLoadMenu(72, 14); + if (repeatLoop && !shouldQuit()) + startupLoad(); + } else if (action == -2) { + // new game + repeatLoop = startCharacterGeneration(); + if (repeatLoop && !shouldQuit()) + startupNew(); + } else if (action == -3) { + // transfer party + repeatLoop = startPartyTransfer(); + if (repeatLoop && !shouldQuit()) + startupNew(); + } + } + + if (!shouldQuit() && action >= -3) { + runLoop(); + + if (_playFinale) { + // make final save for party transfer + saveGameStateIntern(-1, 0, 0); + _sound->selectAudioResourceSet(kMusicFinale); + seq_playFinale(); + } + } + + return Common::kNoError; +} + +void EoBCoreEngine::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + ConfMan.registerDefault("hpbargraphs", true); + ConfMan.registerDefault("mousebtswap", false); + ConfMan.registerDefault("importOrigSaves", true); +} + +void EoBCoreEngine::readSettings() { + _configHpBarGraphs = ConfMan.getBool("hpbargraphs"); + _configMouseBtSwap = ConfMan.getBool("mousebtswap"); + _configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1; + _configMusic = _configSounds ? 1 : 0; + + if (_sound) + _sound->enableSFX(_configSounds); +} + +void EoBCoreEngine::writeSettings() { + ConfMan.setBool("hpbargraphs", _configHpBarGraphs); + ConfMan.setBool("mousebtswap", _configMouseBtSwap); + ConfMan.setBool("sfx_mute", _configSounds == 0); + + if (_sound) { + if (!_configSounds) + _sound->haltTrack(); + _sound->enableMusic(_configSounds ? 1 : 0); + _sound->enableSFX(_configSounds); + } + + ConfMan.flushToDisk(); +} + +void EoBCoreEngine::startupNew() { + gui_setPlayFieldButtons(); + _screen->_curPage = 0; + gui_drawPlayField(false); + _screen->_curPage = 0; + gui_drawAllCharPortraitsWithStats(); + drawScene(1); + _updateFlags = 0; + _updateCharNum = 0; +} + +void EoBCoreEngine::runLoop() { + _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); + _flashShapeTimer = 0; + _drawSceneTimer = _system->getMillis(); + + _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + + _runFlag = true; + + while (!shouldQuit() && _runFlag) { + checkPartyStatus(true); + checkInput(_activeButtons, true, 0); + removeInputTop(); + + if (!_runFlag) + break; + + _timer->update(); + updateScriptTimers(); + updateWallOfForceTimers(); + + if (_sceneUpdateRequired) + drawScene(1); + + if (_envAudioTimer >= _system->getMillis() || (_flags.gameID == GI_EOB1 && (_currentLevel == 0 || _currentLevel > 3))) + continue; + + _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); + snd_processEnvironmentalSoundEffect(_flags.gameID == GI_EOB1 ? 30 : (rollDice(1, 2, -1) ? 27 : 28), _currentBlock + rollDice(1, 12, -1)); + updateEnvironmentalSfx(0); + turnUndeadAuto(); + } +} + +bool EoBCoreEngine::checkPartyStatus(bool handleDeath) { + int numChars = 0; + for (int i = 0; i < 6; i++) + numChars += testCharacter(i, 13); + + if (numChars) + return false; + + if (!handleDeath) + return true; + + gui_drawAllCharPortraitsWithStats(); + + if (checkPartyStatusExtra()) { + _screen->setFont(Screen::FID_8_FNT); + gui_updateControls(); + if (_gui->runLoadMenu(0, 0)) { + _screen->setFont(Screen::FID_6_FNT); + return true; + } + } + + quitGame(); + return false; +} + +void EoBCoreEngine::loadItemsAndDecorationsShapes() { + releaseItemsAndDecorationsShapes(); + int div = (_flags.gameID == GI_EOB1) ? 3 : 8; + int mul = (_flags.gameID == GI_EOB1) ? 64 : 24; + int size = 0; + + _largeItemShapes = new const uint8*[_numLargeItemShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numLargeItemShapes; i++) + _largeItemShapes[i] = _staticres->loadRawData(kEoB2LargeItemsShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEML1", 5, 3); + for (int i = 0; i < _numLargeItemShapes; i++) + _largeItemShapes[i] = _screen->encodeShape((i / div) << 3, (i % div) * mul, 8, 24, false, _cgaMappingItemsL); + } + + _smallItemShapes = new const uint8*[_numSmallItemShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numSmallItemShapes; i++) + _smallItemShapes[i] = _staticres->loadRawData(kEoB2SmallItemsShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEMS1", 5, 3); + for (int i = 0; i < _numSmallItemShapes; i++) + _smallItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingItemsS); + } + + _thrownItemShapes = new const uint8*[_numThrownItemShapes]; + _spellShapes = new const uint8*[4]; + _firebeamShapes = new const uint8*[3]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numThrownItemShapes; i++) + _thrownItemShapes[i] = _staticres->loadRawData(kEoB2ThrownShapeData00 + i, size); + for (int i = 0; i < 4; i++) + _spellShapes[i] = _staticres->loadRawData(kEoB2SpellShapeData00 + i, size); + for (int i = 0; i < 3; i++) + _firebeamShapes[i] = _staticres->loadRawData(kEoB2FirebeamShapeData00 + i, size); + _redSplatShape = _staticres->loadRawData(kEoB2RedSplatShapeData, size); + _greenSplatShape = _staticres->loadRawData(kEoB2GreenSplatShapeData, size); + } else { + _screen->loadShapeSetBitmap("THROWN", 5, 3); + for (int i = 0; i < _numThrownItemShapes; i++) + _thrownItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingThrown); + for (int i = 0; i < 4; i++) + _spellShapes[i] = _screen->encodeShape(8, i << 5, 6, 32, false, _cgaMappingThrown); + + _firebeamShapes[0] = _screen->encodeShape(16, 0, 4, 24, false, _cgaMappingThrown); + _firebeamShapes[1] = _screen->encodeShape(16, 24, 4, 24, false, _cgaMappingThrown); + _firebeamShapes[2] = _screen->encodeShape(16, 48, 3, 24, false, _cgaMappingThrown); + _redSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 144 : 72, 5, 24, false, _cgaMappingThrown); + _greenSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 168 : 96, 5, 16, false, _cgaMappingThrown); + } + + _itemIconShapes = new const uint8*[_numItemIconShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _staticres->loadRawData(kEoB2ItemIconShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEMICN", 5, 3); + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _screen->encodeShape((i % 0x14) << 1, (i / 0x14) << 4, 2, 0x10, false, _cgaMappingIcons); + } + + _teleporterShapes = new const uint8*[6]; + _sparkShapes = new const uint8*[3]; + _compassShapes = new const uint8*[12]; + if (_flags.gameID == GI_EOB2) + _wallOfForceShapes = new const uint8*[6]; + + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + _lightningColumnShape = _staticres->loadRawData(kEoB2LightningColumnShapeData, size); + for (int i = 0; i < 6; i++) + _wallOfForceShapes[i] = _staticres->loadRawData(kEoB2WallOfForceShapeData00 + i, size); + for (int i = 0; i < 6; i++) + _teleporterShapes[i] = _staticres->loadRawData(kEoB2TeleporterShapeData00 + i, size); + for (int i = 0; i < 3; i++) + _sparkShapes[i] = _staticres->loadRawData(kEoB2SparkShapeData00 + i, size); + for (int i = 0; i < 12; i++) + _compassShapes[i] = _staticres->loadRawData(kEoB2CompassShapeData00 + i, size); + + _deadCharShape = _staticres->loadRawData(kEoB2DeadCharShapeData, size); + _disabledCharGrid = _staticres->loadRawData(kEoB2DisabledCharGridShapeData, size); + _blackBoxSmallGrid = _staticres->loadRawData(kEoB2SmallGridShapeData, size); + _weaponSlotGrid = _staticres->loadRawData(kEoB2WeaponSlotGridShapeData, size); + _blackBoxWideGrid = _staticres->loadRawData(kEoB2WideGridShapeData, size); + + } else { + _screen->loadShapeSetBitmap("DECORATE", 5, 3); + if (_flags.gameID == GI_EOB2) { + _lightningColumnShape = _screen->encodeShape(18, 88, 4, 64); + for (int i = 0; i < 6; i++) + _wallOfForceShapes[i] = _screen->encodeShape(_wallOfForceShapeDefs[(i << 2)], _wallOfForceShapeDefs[(i << 2) + 1], _wallOfForceShapeDefs[(i << 2) + 2], _wallOfForceShapeDefs[(i << 2) + 3]); + } + + for (int i = 0; i < 6; i++) + _teleporterShapes[i] = _screen->encodeShape(_teleporterShapeDefs[(i << 2)], _teleporterShapeDefs[(i << 2) + 1], _teleporterShapeDefs[(i << 2) + 2], _teleporterShapeDefs[(i << 2) + 3], false, _cgaMappingDefault); + + _sparkShapes[0] = _screen->encodeShape(29, 0, 2, 16, false, _cgaMappingDeco); + _sparkShapes[1] = _screen->encodeShape(31, 0, 2, 16, false, _cgaMappingDeco); + _sparkShapes[2] = _screen->encodeShape(33, 0, 2, 16, false, _cgaMappingDeco); + _deadCharShape = _screen->encodeShape(0, 88, 4, 32, false, _cgaMappingDeco); + _disabledCharGrid = _screen->encodeShape(4, 88, 4, 32, false, _cgaMappingDeco); + _blackBoxSmallGrid = _screen->encodeShape(9, 88, 2, 8, false, _cgaMappingDeco); + _weaponSlotGrid = _screen->encodeShape(8, 88, 4, 16, false, _cgaMappingDeco); + _blackBoxWideGrid = _screen->encodeShape(8, 104, 4, 8, false, _cgaMappingDeco); + + static const uint8 dHeight[] = { 17, 10, 10 }; + static const uint8 dY[] = { 120, 137, 147 }; + + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 4; x++) + _compassShapes[(y << 2) + x] = _screen->encodeShape(x * 3, dY[y], 3, dHeight[y], false, _cgaMappingDeco); + } + } +} + +void EoBCoreEngine::releaseItemsAndDecorationsShapes() { + if (_flags.platform != Common::kPlatformFMTowns || _flags.gameID != GI_EOB2) { + if (_largeItemShapes) { + for (int i = 0; i < _numLargeItemShapes; i++) { + if (_largeItemShapes[i]) + delete[] _largeItemShapes[i]; + } + } + + if (_smallItemShapes) { + for (int i = 0; i < _numSmallItemShapes; i++) { + if (_smallItemShapes[i]) + delete[] _smallItemShapes[i]; + } + } + + if (_thrownItemShapes) { + for (int i = 0; i < _numThrownItemShapes; i++) { + if (_thrownItemShapes[i]) + delete[] _thrownItemShapes[i]; + } + } + + if (_spellShapes) { + for (int i = 0; i < 4; i++) { + if (_spellShapes[i]) + delete[] _spellShapes[i]; + } + } + + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) { + if (_itemIconShapes[i]) + delete[] _itemIconShapes[i]; + } + } + + if (_sparkShapes) { + for (int i = 0; i < 3; i++) { + if (_sparkShapes[i]) + delete[] _sparkShapes[i]; + } + } + + if (_wallOfForceShapes) { + for (int i = 0; i < 6; i++) { + if (_wallOfForceShapes[i]) + delete[] _wallOfForceShapes[i]; + } + } + + if (_teleporterShapes) { + for (int i = 0; i < 6; i++) { + if (_teleporterShapes[i]) + delete[] _teleporterShapes[i]; + } + } + + if (_compassShapes) { + for (int i = 0; i < 12; i++) { + if (_compassShapes[i]) + delete[] _compassShapes[i]; + } + } + + if (_firebeamShapes) { + for (int i = 0; i < 3; i++) { + if (_firebeamShapes[i]) + delete[] _firebeamShapes[i]; + } + } + + delete[] _redSplatShape; + delete[] _greenSplatShape; + delete[] _deadCharShape; + delete[] _disabledCharGrid; + delete[] _blackBoxSmallGrid; + delete[] _weaponSlotGrid; + delete[] _blackBoxWideGrid; + delete[] _lightningColumnShape; + } + + delete[] _largeItemShapes; + delete[] _smallItemShapes; + delete[] _thrownItemShapes; + delete[] _spellShapes; + delete[] _itemIconShapes; + delete[] _sparkShapes; + delete[] _wallOfForceShapes; + delete[] _teleporterShapes; + delete[] _compassShapes; + delete[] _firebeamShapes; +} + +void EoBCoreEngine::setHandItem(Item itemIndex) { + if (itemIndex == -1) { + if (_flags.platform == Common::kPlatformFMTowns) + _screen->setMouseCursor(8, 8, _itemIconShapes[37], 0); + return; + } + + if (_screen->curDimIndex() == 7 && itemIndex) { + printFullItemName(itemIndex); + _txt->printMessage(_takenStrings[0]); + } + + _itemInHand = itemIndex; + int icon = _items[_itemInHand].icon; + const uint8 *shp = _itemIconShapes[icon]; + const uint8 *ovl = 0; + + if (icon && (_items[_itemInHand].flags & 0x80) && (_partyEffectFlags & 2)) + ovl = _flags.gameID == GI_EOB1 ? ((_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icon << 4]) : _screen->generateShapeOverlay(shp, _lightBlueFadingTable); + + int mouseOffs = itemIndex ? 8 : 0; + _screen->setMouseCursor(mouseOffs, mouseOffs, shp, ovl); + + if (_flags.useHiColorMode) { + _screen->setFadeTable(_greyFadingTable); + _screen->setShapeFadingLevel(0); + } +} + +int EoBCoreEngine::getDexterityArmorClassModifier(int dexterity) { + static const int8 mod[] = { 5, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -4, -4, -5, -5, -5, -6, -6 }; + return mod[dexterity]; +} + +int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex) { + EoBCharacter *c = &_characters[charIndex]; + int m = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur); + + int h = 0; + + for (int i = 0; i < 3; i++) { + if (!(levelIndex & (1 << i))) + continue; + + int d = getCharacterClassType(c->cClass, i); + + if (c->level[i] <= _hpIncrPerLevel[6 + i]) + h += rollDice(1, (d >= 0) ? _hpIncrPerLevel[d] : 0); + else + h += _hpIncrPerLevel[12 + i]; + + h += m; + } + + h /= _numLevelsPerClass[c->cClass]; + + if (h < 1) + h = 1; + + return h; +} + +int EoBCoreEngine::getClassAndConstHitpointsModifier(int cclass, int constitution) { + int res = _hpConstModifiers[constitution]; + // This also applies to EOB1 despite being coded differently there + if (res <= 2 || (_classModifierFlags[cclass] & 0x31)) + return res; + + return 2; +} + +int EoBCoreEngine::getCharacterClassType(int cclass, int levelIndex) { + return _characterClassType[cclass * 3 + levelIndex]; +} + +int EoBCoreEngine::getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode) { + int s = _hpIncrPerLevel[6 + hpModifier] > level ? level : _hpIncrPerLevel[6 + hpModifier]; + int res = s; + + if (!mode) + res *= (hpModifier >= 0 ? _hpIncrPerLevel[hpModifier] : 0); + + if (level > s) { + s = level - s; + res += (s * _hpIncrPerLevel[12 + hpModifier]); + } + + if (!mode || (constModifier > 0)) + res += (level * constModifier); + + return res; +} + +Common::String EoBCoreEngine::getCharStrength(int str, int strExt) { + if (strExt) { + if (strExt == 100) + strExt = 0; + _strenghtStr = Common::String::format("%d/%02d", str, strExt); + } else { + _strenghtStr = Common::String::format("%d", str); + } + + return _strenghtStr; +} + +int EoBCoreEngine::testCharacter(int16 index, int flags) { + if (index == -1) + return 0; + + EoBCharacter *c = &_characters[index]; + int res = 1; + + if (flags & 1) + res &= (c->flags & 1); + + if (flags & 2) + res &= ((c->hitPointsCur <= -10) || (c->flags & 8)) ? 0 : 1; + + if (flags & 4) + res &= ((c->hitPointsCur <= 0) || (c->flags & 8)) ? 0 : 1; + + if (flags & 8) + res &= (c->flags & 12) ? 0 : 1; + + if (flags & 0x20) + res &= (c->flags & 4) ? 0 : 1; + + if (flags & 0x10) + res &= (c->flags & 2) ? 0 : 1; + + if (flags & 0x40) + res &= (c->food <= 0) ? 0 : 1; + + return res; +} + +int EoBCoreEngine::getNextValidCharIndex(int curCharIndex, int searchStep) { + do { + curCharIndex += searchStep; + if (curCharIndex < 0) + curCharIndex = 5; + if (curCharIndex > 5) + curCharIndex = 0; + } while (!testCharacter(curCharIndex, 1)); + + return curCharIndex; +} + +void EoBCoreEngine::recalcArmorClass(int index) { + EoBCharacter *c = &_characters[index]; + int acm = getDexterityArmorClassModifier(c->dexterityCur); + c->armorClass = 10 + acm; + + static uint8 slot[] = { 17, 0, 1, 18 }; + for (int i = 0; i < 4; i++) { + int itm = c->inventory[slot[i]]; + if (!itm) + continue; + + if (i == 2) { + if (!validateWeaponSlotItem(index, 1)) + continue; + } + + int tp = _items[itm].type; + + if (!(_itemTypes[tp].allowedClasses & _classModifierFlags[c->cClass]) || (_itemTypes[tp].extraProperties & 0x7F) || (i >= 1 && i <= 2 && tp != 27 && !(_flags.gameID == GI_EOB2 && tp == 57))) + continue; + + c->armorClass += _itemTypes[tp].armorClass; + c->armorClass -= _items[itm].value; + } + + if (!_items[c->inventory[17]].value) { + int8 m1 = 0; + int8 m2 = 0; + + if (c->inventory[25]) { + if (!(_itemTypes[_items[c->inventory[25]].type].extraProperties & 0x7F)) + m1 = _items[c->inventory[25]].value; + } + + if (c->inventory[26]) { + if (!(_itemTypes[_items[c->inventory[26]].type].extraProperties & 0x7F)) + m2 = _items[c->inventory[26]].value; + } + + c->armorClass -= MAX(m1, m2); + } + + if (c->effectsRemainder[0] > 0) { + if (c->armorClass <= (acm + 6)) + c->effectsRemainder[0] = 0; + else + c->armorClass = (acm + 6); + } + + // shield + if ((c->effectFlags & 8) && (c->armorClass > 4)) + c->armorClass = 4; + + // magical vestment + if (c->effectFlags & 0x4000) { + int8 m1 = 5; + + if (getClericPaladinLevel(index) > 5) + m1 += ((getClericPaladinLevel(index) - 5) / 3); + + if (c->armorClass > m1) + c->armorClass = m1; + } + + if (c->armorClass < -10) + c->armorClass = -10; +} + +int EoBCoreEngine::validateWeaponSlotItem(int index, int slot) { + EoBCharacter *c = &_characters[index]; + int itm1 = c->inventory[0]; + int r = itemUsableByCharacter(index, itm1); + int tp1 = _items[itm1].type; + + if (!slot) + return (!itm1 || r) ? 1 : 0; + + int itm2 = c->inventory[1]; + r = itemUsableByCharacter(index, itm2); + int tp2 = _items[itm2].type; + + if (itm1 && _itemTypes[tp1].requiredHands == 2) + return 0; + + if (!itm2) + return 1; + + int f = (_itemTypes[tp2].extraProperties & 0x7F); + if (f <= 0 || f > 3) + return r; + + if (_itemTypes[tp2].requiredHands) + return 0; + + return r; +} + +int EoBCoreEngine::getClericPaladinLevel(int index) { + if (_castScrollSlot) + return 9; + + if (index == -1) + return (_currentLevel < 7) ? 5 : 9; + + int l = getCharacterLevelIndex(2, _characters[index].cClass); + if (l > -1) + return _characters[index].level[l]; + + l = getCharacterLevelIndex(4, _characters[index].cClass); + if (l > -1) { + if (_characters[index].level[l] > 8) + return _characters[index].level[l] - 8; + } + + return 1; +} + +int EoBCoreEngine::getMageLevel(int index) { + if (_castScrollSlot) + return 9; + + if (index == -1) + return (_currentLevel < 7) ? 5 : 9; + + int l = getCharacterLevelIndex(1, _characters[index].cClass); + return (l > -1) ? _characters[index].level[l] : 1; +} + +int EoBCoreEngine::getCharacterLevelIndex(int type, int cClass) { + if (getCharacterClassType(cClass, 0) == type) + return 0; + + if (getCharacterClassType(cClass, 1) == type) + return 1; + + if (getCharacterClassType(cClass, 2) == type) + return 2; + + return -1; +} + +int EoBCoreEngine::countCharactersWithSpecificItems(int16 itemType, int16 itemValue) { + int res = 0; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (checkInventoryForItem(i, itemType, itemValue) != -1) + res++; + } + return res; +} + +int EoBCoreEngine::checkInventoryForItem(int character, int16 itemType, int16 itemValue) { + if (character < 0) + return -1; + + for (int i = 0; i < 27; i++) { + uint16 inv = _characters[character].inventory[i]; + if (!inv) + continue; + if (_items[inv].type != itemType && itemType != -1) + continue; + if (_items[inv].value == itemValue || itemValue == -1) + return i; + } + return -1; +} + +void EoBCoreEngine::modifyCharacterHitpoints(int character, int16 points) { + if (!testCharacter(character, 3)) + return; + + EoBCharacter *c = &_characters[character]; + c->hitPointsCur += points; + if (c->hitPointsCur > c->hitPointsMax) + c->hitPointsCur = c->hitPointsMax; + + gui_drawHitpoints(character); + gui_drawCharPortraitWithStats(character); +} + +void EoBCoreEngine::neutralizePoison(int character) { + _characters[character].flags &= ~2; + _characters[character].effectFlags &= ~0x2000; + deleteCharEventTimer(character, -34); + gui_drawCharPortraitWithStats(character); +} + +void EoBCoreEngine::npcSequence(int npcIndex) { + _screen->loadShapeSetBitmap("OUTTAKE", 5, 3); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK); + + drawNpcScene(npcIndex); + + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + delete s; + + gui_drawBox(0, 121, 320, 79, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _txt->setupField(9, true); + _txt->resetPageBreakString(); + + runNpcDialogue(npcIndex); + + _txt->removePageBreakFlag(); + gui_restorePlayField(); +} + +void EoBCoreEngine::initNpc(int npcIndex) { + EoBCharacter *c = _characters; + int i = 0; + for (; i < 6; i++) { + if (!(_characters[i].flags & 1)) { + c = &_characters[i]; + break; + } + } + + delete[] c->faceShape; + memcpy(c, &_npcPreset[npcIndex], sizeof(EoBCharacter)); + recalcArmorClass(i); + + for (i = 0; i < 25; i++) { + if (!c->inventory[i]) + continue; + c->inventory[i] = duplicateItem(c->inventory[i]); + } + + _screen->loadShapeSetBitmap(_flags.gameID == GI_EOB2 ? "OUTPORTS" : "OUTTAKE", 3, 3); + _screen->_curPage = 2; + c->faceShape = _screen->encodeShape(npcIndex << 2, _flags.gameID == GI_EOB2 ? 0 : 160, 4, 32, true, _cgaMappingDefault); + _screen->_curPage = 0; +} + +int EoBCoreEngine::npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId) { + gui_drawDialogueBox(); + _txt->printDialogueText(queryJoinTextId, 0); + + int r = runDialogue(-1, 2, _yesNoStrings[0], _yesNoStrings[1]) - 1; + if (r == 0) { + if (confirmJoinTextId == -1) { + Common::String tmp = Common::String::format(_npcJoinStrings[0], _npcPreset[npcIndex].name); + _txt->printDialogueText(tmp.c_str(), true); + } else { + _txt->printDialogueText(confirmJoinTextId, _okStrings[0]); + } + + if (prepareForNewPartyMember(33, npcIndex + 1)) + initNpc(npcIndex); + + } else if (r == 1) { + _txt->printDialogueText(noJoinTextId, _okStrings[0]); + } + + return r ^ 1; +} + +int EoBCoreEngine::prepareForNewPartyMember(int16 itemType, int16 itemValue) { + int numChars = 0; + for (int i = 0; i < 6; i++) + numChars += (_characters[i].flags & 1); + + if (numChars < 6) { + deletePartyItems(itemType, itemValue); + } else { + gui_drawDialogueBox(); + _screen->set16bitShadingLevel(4); + _txt->printDialogueText(_npcMaxStrings[0]); + _screen->set16bitShadingLevel(0); + int r = runDialogue(-1, 7, _characters[0].name, _characters[1].name, _characters[2].name, _characters[3].name, + _characters[4].name, _characters[5].name, _abortStrings[0]) - 1; + + if (r == 6) + return 0; + + deletePartyItems(itemType, itemValue); + removeCharacterFromParty(r); + } + + return 1; +} + +void EoBCoreEngine::dropCharacter(int charIndex) { + if (!testCharacter(charIndex, 1)) + return; + + removeCharacterFromParty(charIndex); + + if (charIndex < 5) + exchangeCharacters(charIndex, testCharacter(5, 1) ? 5 : 4); + + gui_processCharPortraitClick(0); + gui_setPlayFieldButtons(); + setupCharacterTimers(); +} + +void EoBCoreEngine::removeCharacterFromParty(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + c->flags = 0; + + for (int i = 0; i < 27; i++) { + if (i == 16 || !c->inventory[i]) + continue; + + setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, c->inventory[i], _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + c->inventory[i] = 0; + } + + while (c->inventory[16]) + setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, getQueuedItem(&c->inventory[16], 0, -1), _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + + c->inventory[16] = 0; + + if (_updateCharNum == charIndex) + _updateCharNum = 0; + + setupCharacterTimers(); +} + +void EoBCoreEngine::exchangeCharacters(int charIndex1, int charIndex2) { + EoBCharacter temp; + memcpy(&temp, &_characters[charIndex1], sizeof(EoBCharacter)); + memcpy(&_characters[charIndex1], &_characters[charIndex2], sizeof(EoBCharacter)); + memcpy(&_characters[charIndex2], &temp, sizeof(EoBCharacter)); +} + +void EoBCoreEngine::increasePartyExperience(int16 points) { + int cnt = 0; + for (int i = 0; i < 6; i++) { + if (testCharacter(i, 3)) + cnt++; + } + + if (cnt <= 0) + return; + + points /= cnt; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + increaseCharacterExperience(i, points); + } +} + +void EoBCoreEngine::increaseCharacterExperience(int charIndex, int32 points) { + int cl = _characters[charIndex].cClass; + points /= _numLevelsPerClass[cl]; + + for (int i = 0; i < 3; i++) { + if (getCharacterClassType(cl, i) == -1) + continue; + _characters[charIndex].experience[i] += points; + + uint32 er = getRequiredExperience(cl, i, _characters[charIndex].level[i] + 1); + if (er == 0xFFFFFFFF) + continue; + + if (_characters[charIndex].experience[i] >= er) + increaseCharacterLevel(charIndex, i); + } +} + +uint32 EoBCoreEngine::getRequiredExperience(int cClass, int levelIndex, int level) { + cClass = getCharacterClassType(cClass, levelIndex); + if (cClass == -1) + return 0xFFFFFFFF; + + const uint32 *tbl = _expRequirementTables[cClass]; + return tbl[level - 1]; +} + +void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) { + _characters[charIndex].level[levelIndex]++; + int hpInc = generateCharacterHitpointsByLevel(charIndex, 1 << levelIndex); + _characters[charIndex].hitPointsCur += hpInc; + _characters[charIndex].hitPointsMax += hpInc; + + gui_drawCharPortraitWithStats(charIndex); + _txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name); + snd_playSoundEffect(23); +} + +void EoBCoreEngine::setWeaponSlotStatus(int charIndex, int mode, int slot) { + if (mode == 0 || mode == 2) + _characters[charIndex].disabledSlots ^= (1 << slot); + else if (mode != 1) + return; + + _characters[charIndex].slotStatus[slot] = 0; + gui_drawCharPortraitWithStats(charIndex); +} + +void EoBCoreEngine::setupDialogueButtons(int presetfirst, int numStr, va_list &args) { + _dialogueNumButtons = numStr; + _dialogueHighlightedButton = 0; + + Screen::FontId of = _screen->setFont((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : _screen->_currentFont); + + for (int i = 0; i < numStr; i++) { + const char *s = va_arg(args, const char *); + if (s) + _dialogueButtonString[i] = s; + else + _dialogueNumButtons = numStr = i; + } + + static const uint16 prsX[] = { 59, 166, 4, 112, 220, 4, 112, 220, 4, 112, 220, 4, 112, 220 }; + static const uint8 prsY[] = { 0, 0, 0, 0, 0, 12, 12, 12, 24, 24, 24, 36, 36, 36 }; + + const ScreenDim *dm = screen()->_curDim; + int yOffs = (_txt->lineCount() + 1) * _screen->getFontHeight() + dm->sy + 4; + + _dialogueButtonPosX = &prsX[presetfirst]; + _dialogueButtonPosY = &prsY[presetfirst]; + _dialogueButtonYoffs = yOffs; + + drawDialogueButtons(); + + _screen->setFont(of); + + if (!shouldQuit()) + removeInputTop(); +} + +void EoBCoreEngine::initDialogueSequence() { + _npcSequenceSub = -1; + _txt->setWaitButtonMode(0); + _dialogueField = true; + + _dialogueLastBitmap[0] = 0; + + _txt->resetPageBreakString(); + gui_updateControls(); + //_allowSkip = true; + + // WORKAROUND for bug in the original code (all platforms). Sequence sound would be terminated prematurely. + if (_flags.gameID == GI_EOB2 && _currentLevel == 2 && _currentBlock == 654) + _sound->stopAllSoundEffects(); + else + snd_stopSound(); + + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + _txt->setupField(9, 0); + delete s; +} + +void EoBCoreEngine::restoreAfterDialogueSequence() { + _txt->allowPageBreak(false); + _dialogueField = false; + + _dialogueLastBitmap[0] = 0; + + gui_restorePlayField(); + //_allowSkip = false; + _screen->setScreenDim(7); + + if (_flags.gameID == GI_EOB2) + snd_playSoundEffect(2); + + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags) { + static const uint8 frameX[] = { 1, 0 }; + static const uint8 frameY[] = { 8, 0 }; + static const uint8 frameW[] = { 20, 40 }; + static const uint8 frameH[] = { 96, 121 }; + + int page = ((flags & 2) || destRect) ? 0 : 6; + + if (scumm_stricmp(_dialogueLastBitmap, file)) { + _screen->clearPage(2); + if (!destRect) { + if (!(flags & 1)) { + _screen->loadEoBBitmap("BORDER", 0, 3, 3, 2); + _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, page, Screen::CR_NO_P_CHECK); + } else { + _screen->copyRegion(0, 0, 0, 0, 184, 121, 0, page, Screen::CR_NO_P_CHECK); + } + + if (!page) + _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, 6, Screen::CR_NO_P_CHECK); + } + + _screen->loadEoBBitmap(file, 0, 3, 3, 2); + strcpy(_dialogueLastBitmap, file); + } + + if (flags & 2) + _screen->crossFadeRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page); + else + _screen->copyRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page, Screen::CR_NO_P_CHECK); + + if (page == 6) + _screen->copyRegion(0, 0, 0, 0, 184, 121, 6, 0, Screen::CR_NO_P_CHECK); + + _screen->updateScreen(); +} + +int EoBCoreEngine::runDialogue(int dialogueTextId, int numStr, ...) { + if (dialogueTextId != -1) + txt()->printDialogueText(dialogueTextId, 0); + + va_list args; + va_start(args, numStr); + if (numStr > 2) + setupDialogueButtons(2, numStr, args); + else + setupDialogueButtons(0, numStr, args); + va_end(args); + + int res = 0; + while (res == 0 && !shouldQuit()) + res = processDialogue(); + + gui_drawDialogueBox(); + + return res; +} + +void EoBCoreEngine::restParty_displayWarning(const char *str) { + int od = _screen->curDimIndex(); + _screen->setScreenDim(7); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setCurPage(0); + + _txt->printMessage(Common::String::format("\r%s\r", str).c_str()); + + _screen->setFont(of); + _screen->setScreenDim(od); +} + +bool EoBCoreEngine::restParty_updateMonsters() { + bool sfxEnabled = _sound->sfxEnabled(); + bool musicEnabled = _sound->musicEnabled(); + _sound->enableSFX(false); + _sound->enableMusic(false); + + for (int i = 0; i < 5; i++) { + _partyResting = true; + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + int od = _screen->curDimIndex(); + _screen->setScreenDim(7); + updateMonsters(0); + updateMonsters(1); + timerProcessFlyingObjects(0); + _screen->setScreenDim(od); + _screen->setFont(of); + _partyResting = false; + + for (int ii = 0; ii < 30; ii++) { + if (_monsters[ii].mode == 8) + continue; + if (getBlockDistance(_currentBlock, _monsters[ii].block) >= 2) + continue; + + restParty_displayWarning(_menuStringsRest4[0]); + _sound->enableSFX(sfxEnabled); + _sound->enableMusic(musicEnabled); + return true; + } + } + + _sound->enableSFX(sfxEnabled); + _sound->enableMusic(musicEnabled); + return false; +} + +int EoBCoreEngine::restParty_getCharacterWithLowestHp() { + int lhp = 900; + int res = -1; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) + continue; + if (_characters[i].hitPointsCur < lhp) { + lhp = _characters[i].hitPointsCur; + res = i; + } + } + + return res + 1; +} + +bool EoBCoreEngine::restParty_checkHealSpells(int charIndex) { + static const uint8 eob1healSpells[] = { 2, 15, 20 }; + static const uint8 eob2healSpells[] = { 3, 16, 20 }; + const uint8 *spells = _flags.gameID == GI_EOB1 ? eob1healSpells : eob2healSpells; + const int8 *list = _characters[charIndex].clericSpells; + + for (int i = 0; i < 80; i++) { + int s = list[i] < 0 ? -list[i] : list[i]; + if (s == spells[0] || s == spells[1] || s == spells[2]) + return true; + } + + return false; +} + +bool EoBCoreEngine::restParty_checkSpellsToLearn() { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x43)) + continue; + + if ((getCharacterLevelIndex(2, _characters[i].cClass) != -1 || getCharacterLevelIndex(4, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 30, -1) != -1)) { + for (int ii = 0; ii < 80; ii++) { + if (_characters[i].clericSpells[ii] < 0) + return true; + } + } + + if ((getCharacterLevelIndex(1, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 29, -1) != -1)) { + for (int ii = 0; ii < 80; ii++) { + if (_characters[i].mageSpells[ii] < 0) + return true; + } + } + } + + return false; +} + +bool EoBCoreEngine::restParty_extraAbortCondition() { + return false; +} + +void EoBCoreEngine::delay(uint32 millis, bool, bool) { + while (millis && !shouldQuit() && !(_allowSkip && skipFlag())) { + updateInput(); + uint32 step = MIN<uint32>(millis, (_tickLength / 5)); + _system->delayMillis(step); + millis -= step; + } +} + +void EoBCoreEngine::displayParchment(int id) { + _txt->setWaitButtonMode(1); + _txt->resetPageBreakString(); + gui_updateControls(); + + if (id >= 0) { + // display text + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + _screen->set16bitShadingLevel(4); + gui_drawBox(0, 0, 176, 175, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->set16bitShadingLevel(0); + _txt->setupField(12, 1); + if (_flags.gameID == GI_EOB2) + id++; + _txt->printDialogueText(id, _okStrings[0]); + + } else { + // display bitmap + id = -id - 1; + static const uint8 x[] = { 0, 20, 0 }; + static const uint8 y[] = { 0, 0, 96 }; + drawSequenceBitmap("MAP", 0, x[id], y[id], 0); + + removeInputTop(); + while (!shouldQuit()) { + delay(_tickLength); + if (checkInput(0, false, 0) & 0xFF) + break; + removeInputTop(); + } + removeInputTop(); + } + + restoreAfterDialogueSequence(); +} + +int EoBCoreEngine::countResurrectionCandidates() { + _rrCount = 0; + memset(_rrNames, 0, 10 * sizeof(const char *)); + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (_characters[i].hitPointsCur != -10) + continue; + + _rrNames[_rrCount] = _characters[i].name; + _rrId[_rrCount++] = i; + } + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + + for (int ii = 0; ii < 27; ii++) { + uint16 inv = _characters[i].inventory[ii]; + if (!inv) + continue; + + if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[inv].type].extraProperties & 0x7F) != 8)) || (_flags.gameID == GI_EOB2 && _items[inv].type != 33)) + continue; + + _rrNames[_rrCount] = _npcPreset[_items[inv].value - 1].name; + _rrId[_rrCount++] = -_items[inv].value; + } + } + + if (_itemInHand > 0) { + if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[_itemInHand].type].extraProperties & 0x7F) == 8)) || (_flags.gameID == GI_EOB2 && _items[_itemInHand].type == 33)) { + _rrNames[_rrCount] = _npcPreset[_items[_itemInHand].value - 1].name; + _rrId[_rrCount++] = -_items[_itemInHand].value; + } + } + + return _rrCount; +} + +void EoBCoreEngine::seq_portal() { + uint8 *shapes1[5]; + uint8 *shapes2[5]; + uint8 *shapes3[5]; + uint8 *shape0; + + _screen->loadShapeSetBitmap("PORTALA", 5, 3); + + for (int i = 0; i < 5; i++) { + shapes1[i] = _screen->encodeShape(i * 3, 0, 3, 75, false, _cgaMappingDefault); + shapes2[i] = _screen->encodeShape(i * 3, 80, 3, 75, false, _cgaMappingDefault); + shapes3[i] = _screen->encodeShape(15, i * 18, 15, 18, false, _cgaMappingDefault); + } + + shape0 = _screen->encodeShape(30, 0, 8, 77, false, _cgaMappingDefault); + _screen->loadEoBBitmap("PORTALB", _cgaMappingDefault, 5, 3, 2); + + snd_playSoundEffect(33); + snd_playSoundEffect(19); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 2, 5, Screen::CR_NO_P_CHECK); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 0, 2, Screen::CR_NO_P_CHECK); + _screen->drawShape(2, shapes3[0], 28, 9, 0); + _screen->drawShape(2, shapes1[0], 34, 28, 0); + _screen->drawShape(2, shapes2[0], 120, 28, 0); + _screen->drawShape(2, shape0, 56, 27, 0); + _screen->crossFadeRegion(24, 0, 24, 0, 144, 104, 2, 0); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 5, 2, Screen::CR_NO_P_CHECK); + delay(30 * _tickLength); + + for (const int8 *pos = _portalSeq; *pos > -1 && !shouldQuit();) { + int s = *pos++; + _screen->drawShape(0, shapes3[s], 28, 9, 0); + _screen->drawShape(0, shapes1[s], 34, 28, 0); + _screen->drawShape(0, shapes2[s], 120, 28, 0); + + if ((s == 1) && (pos >= _portalSeq + 3)) { + if (*(pos - 3) == 0) { + snd_playSoundEffect(24); + snd_playSoundEffect(86); + } + } + + s = *pos++; + if (s == 0) { + _screen->drawShape(0, shape0, 56, 27, 0); + } else { + s--; + _screen->copyRegion((s % 5) << 6, s / 5 * 77, 56, 27, 64, 77, 2, 0, Screen::CR_NO_P_CHECK); + } + + if (s == 1) + snd_playSoundEffect(31); + else if (s == 3) { + if (*(pos - 2) == 3) + snd_playSoundEffect(90); + } + + _screen->updateScreen(); + delay(2 * _tickLength); + } + + delete[] shape0; + for (int i = 0; i < 5; i++) { + delete[] shapes1[i]; + delete[] shapes2[i]; + delete[] shapes3[i]; + } +} + +bool EoBCoreEngine::checkPassword() { + char answ[20]; + Screen::FontId of = _screen->setFont(Screen::FID_8_FNT); + _screen->copyPage(0, 10); + + _screen->setScreenDim(13); + gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); + gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->modifyScreenDim(13, _screen->_curDim->sx + 1, _screen->_curDim->sy + 2, _screen->_curDim->w - 2, _screen->_curDim->h - 16); + + for (int i = 0; i < 3; i++) { + _screen->fillRect(_screen->_curDim->sx << 3, _screen->_curDim->sy, ((_screen->_curDim->sx + _screen->_curDim->w) << 3) - 1, (_screen->_curDim->sy + _screen->_curDim->h) - 1, guiSettings()->colors.fill); + int c = rollDice(1, _mnNumWord - 1, -1); + const uint8 *shp = (_mnDef[c << 2] < _numLargeItemShapes) ? _largeItemShapes[_mnDef[c << 2]] : (_mnDef[c << 2] < 15 ? 0 : _smallItemShapes[_mnDef[c << 2] - 15]); + assert(shp); + _screen->drawShape(0, shp, 100, 2, 13); + _screen->printShadedText(Common::String::format(_mnPrompt[0], _mnDef[(c << 2) + 1], _mnDef[(c << 2) + 2]).c_str(), (_screen->_curDim->sx + 1) << 3, _screen->_curDim->sy, _screen->_curDim->unk8, guiSettings()->colors.fill); + memset(answ, 0, 20); + gui_drawBox(76, 100, 133, 14, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); + gui_drawBox(77, 101, 131, 12, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); + if (_gui->getTextInput(answ, 10, 103, 15, _screen->_curDim->unk8, guiSettings()->colors.fill, 8) < 0) + i = 3; + if (!scumm_stricmp(_mnWord[c], answ)) + break; + else if (i == 2) + return false; + } + + _screen->modifyScreenDim(13, _screen->_curDim->sx - 1, _screen->_curDim->sy - 2, _screen->_curDim->w + 2, _screen->_curDim->h + 16); + _screen->setFont(of); + _screen->copyPage(10, 0); + return true; +} + +Common::String EoBCoreEngine::convertAsciiToSjis(Common::String str) { + if (_flags.platform != Common::kPlatformFMTowns) + return str; + + Common::String n; + const char *src = str.c_str(); + int pos = 0; + for (uint32 i = 0; i < str.size(); ++i) { + if (src[i] & 0x80) { + n.insertChar(src[i++], pos++); + n.insertChar(src[i], pos++); + } else if (src[i] >= 32 && src[i] <= 64) { + n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2], pos++); + n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2 + 1], pos++); + } else if ((src[i] >= 97 && src[i] <= 122) || (src[i] >= 65 && src[i] <= 90)) { + char c = (src[i] >= 97) ? src[i] - 97 : src[i] - 65; + n.insertChar(_ascii2SjisTables2[0][c * 2], pos++); + n.insertChar(_ascii2SjisTables2[0][c * 2 + 1], pos++); + } + } + + return n; +} + +void EoBCoreEngine::useSlotWeapon(int charIndex, int slotIndex, Item item) { + EoBCharacter *c = &_characters[charIndex]; + int tp = item ? _items[item].type : 0; + + if (c->effectFlags & 0x40) + removeCharacterEffect(10, charIndex, 1); // remove invisibility effect + + int ep = _itemTypes[tp].extraProperties & 0x7F; + int8 inflict = 0; + + if (ep == 1) { + inflict = closeDistanceAttack(charIndex, item); + if (!inflict) + inflict = -1; + snd_playSoundEffect(32); + } else if (ep == 2) { + inflict = thrownAttack(charIndex, slotIndex, item); + } else if (ep == 3) { + inflict = projectileWeaponAttack(charIndex, item); + gui_drawCharPortraitWithStats(charIndex); + } + + if (inflict > 0) { + if (_items[item].flags & 8) { + c->hitPointsCur += inflict; + gui_drawCharPortraitWithStats(charIndex); + } + + if (_items[item].flags & 0x10) + c->inventory[slotIndex] = 0; + + inflictMonsterDamage(&_monsters[_dstMonsterIndex], inflict, true); + } + + c->disabledSlots ^= (1 << slotIndex); + c->slotStatus[slotIndex] = inflict; + + gui_drawCharPortraitWithStats(charIndex); + setCharEventTimer(charIndex, 18, inflict >= -2 ? slotIndex + 2 : slotIndex, 1); +} + +int EoBCoreEngine::closeDistanceAttack(int charIndex, Item item) { + if (charIndex > 1) + return -3; + + uint16 d = calcNewBlockPosition(_currentBlock, _currentDirection); + int r = getClosestMonster(charIndex, d); + + if (r == -1) { + uint8 w = _specialWallTypes[_levelBlockProperties[d].walls[_sceneDrawVarDown]]; + if (w == 0xFF) { + if (_flags.gameID == GI_EOB1) { + _levelBlockProperties[d].walls[_sceneDrawVarDown]++; + _levelBlockProperties[d].walls[_sceneDrawVarDown ^ 2]++; + + } else { + for (int i = 0; i < 4; i++) { + if (_specialWallTypes[_levelBlockProperties[d].walls[i]] == 0xFF) + _levelBlockProperties[d].walls[i]++; + } + } + _sceneUpdateRequired = true; + + } else if ((_flags.gameID == GI_EOB1) || (_flags.gameID == GI_EOB2 && w != 8 && w != 9)) { + return -1; + } + + return (_flags.gameID == GI_EOB2 && ((_itemTypes[_items[item].type].allowedClasses & 4) || !item)) ? -5 : -2; + + } else { + if (_monsters[r].flags & 0x20) { + killMonster(&_monsters[r], 1); + _txt->printMessage(_monsterDustStrings[0]); + return -2; + } + + if (!characterAttackHitTest(charIndex, r, item, 1)) + return -1; + + uint16 flg = 0x100; + + if (isMagicEffectItem(item)) + flg |= 1; + + _dstMonsterIndex = r; + return calcMonsterDamage(&_monsters[r], charIndex, item, 1, flg, 5, 3); + } + + return 0; +} + +int EoBCoreEngine::thrownAttack(int charIndex, int slotIndex, Item item) { + int d = charIndex > 3 ? charIndex - 2 : charIndex; + if (!launchObject(charIndex, item, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + d], _currentDirection, _items[item].type)) + return 0; + + snd_playSoundEffect(11); + _characters[charIndex].inventory[slotIndex] = 0; + reloadWeaponSlot(charIndex, slotIndex, -1, 0); + _sceneUpdateRequired = true; + return 0; +} + +int EoBCoreEngine::projectileWeaponAttack(int charIndex, Item item) { + int tp = _items[item].type; + + if (_flags.gameID == GI_EOB1) + assert(tp >= 7); + + int t = _projectileWeaponAmmoTypes[_flags.gameID == GI_EOB1 ? tp - 7 : tp]; + Item ammoItem = 0; + + if (t == 16) { + if (_characters[charIndex].inventory[0] && _items[_characters[charIndex].inventory[0]].type == 16) + SWAP(ammoItem, _characters[charIndex].inventory[0]); + else if (_characters[charIndex].inventory[1] && _items[_characters[charIndex].inventory[1]].type == 16) + SWAP(ammoItem, _characters[charIndex].inventory[1]); + else if (_characters[charIndex].inventory[16]) + ammoItem = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1); + + } else { + for (int i = 0; i < 27; i++) { + if (_items[_characters[charIndex].inventory[i]].type == t) { + SWAP(ammoItem, _characters[charIndex].inventory[i]); + if (i < 2) + gui_drawCharPortraitWithStats(charIndex); + break; + } + } + } + + if (!ammoItem) + return -4; + + int c = charIndex; + if (c > 3) + c -= 2; + + if (launchObject(charIndex, ammoItem, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + c], _currentDirection, tp)) { + snd_playSoundEffect(tp == 7 ? 26 : 11); + _sceneUpdateRequired = true; + } + + return 0; +} + +void EoBCoreEngine::inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience) { + m->hitPointsCur -= damage; + m->flags = (m->flags & 0xF7) | 1; + + if (_monsterProps[m->type].capsFlags & 0x2000) { + explodeMonster(m); + checkSceneUpdateNeed(m->block); + m->hitPointsCur = 0; + } else { + if (checkSceneUpdateNeed(m->block)) { + m->flags |= 2; + if (_preventMonsterFlash) + return; + flashMonsterShape(m); + } + } + + if (m->hitPointsCur <= 0) + killMonster(m, giveExperience); + else if (getBlockDistance(m->block, _currentBlock) < 4) + m->dest = _currentBlock; +} + +void EoBCoreEngine::calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { + int dmg = calcMonsterDamage(m, times, pips, offs, flags, savingThrowType, savingThrowEffect); + if (dmg > 0) + inflictMonsterDamage(m, dmg, flags & 0x800 ? true : false); +} + +void EoBCoreEngine::calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { + int dmg = calcCharacterDamage(charIndex, times, itemOrPips, useStrModifierOrBase, flags, savingThrowType, savingThrowEffect); + if (dmg) + inflictCharacterDamage(charIndex, dmg); +} + +int EoBCoreEngine::calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { + int s = (flags & 0x100) ? calcDamageModifers(times, 0, itemOrPips, _items[itemOrPips].type, useStrModifierOrBase) : rollDice(times, itemOrPips, useStrModifierOrBase); + EoBCharacter *c = &_characters[charIndex]; + + if (savingThrowType != 5) { + if (trySavingThrow(c, _charClassModifier[c->cClass], c->level[0], savingThrowType, c->raceSex >> 1 /*fix bug in original code by adding a right shift*/)) + s = savingThrowReduceDamage(savingThrowEffect, s); + } + + if ((flags & 0x110) == 0x110) { + if (!calcDamageCheckItemType(_items[itemOrPips].type)) + s = 1; + } + + if (flags & 4) { + if (checkInventoryForRings(charIndex, 3)) + s = 0; + } + + if (flags & 0x400) { + if (c->effectFlags & 0x2000) + s = 0; + else + _txt->printMessage(_characterStatusStrings8[0], -1, c->name); + } + + return s; +} + +void EoBCoreEngine::inflictCharacterDamage(int charIndex, int damage) { + EoBCharacter *c = &_characters[charIndex]; + if (!testCharacter(charIndex, 3)) + return; + + if (c->effectsRemainder[3]) + c->effectsRemainder[3] = (damage < c->effectsRemainder[3]) ? (c->effectsRemainder[3] - damage) : 0; + + c->hitPointsCur -= damage; + c->damageTaken = damage; + + if (c->hitPointsCur > -10) { + snd_playSoundEffect(21); + } else { + c->hitPointsCur = -10; + c->flags &= 1; + c->food = 0; + removeAllCharacterEffects(charIndex); + snd_playSoundEffect(22); + } + + if (c->effectsRemainder[0]) { + c->effectsRemainder[0] = (damage < c->effectsRemainder[0]) ? (c->effectsRemainder[0] - damage) : 0; + if (!c->effectsRemainder[0]) + removeCharacterEffect(1, charIndex, 1); + } + + if (_currentControlMode) + gui_drawFaceShape(charIndex); + else + gui_drawCharPortraitWithStats(charIndex); + + if (c->hitPointsCur <= 0 && _updateFlags == 1 && charIndex == _openBookChar) { + Button b; + clickedSpellbookAbort(&b); + } + + setCharEventTimer(charIndex, 18, 6, 1); +} + +bool EoBCoreEngine::characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType) { + if (charIndex < 0) + return true; + + int p = item ? (_flags.gameID == GI_EOB1 ? _items[item].type : (_itemTypes[_items[item].type].extraProperties & 0x7F)) : 0; + + if (_monsters[monsterIndex].flags & 0x20) + return true;// EOB 2 only ? + + int t = _monsters[monsterIndex].type; + int d = (p < 1 || p > 3) ? 0 : _items[item].value; + + if (_flags.gameID == GI_EOB2) { + if ((p > 0 && p < 4) || !item) { + if (((_monsterProps[t].immunityFlags & 0x200) && (d <= 0)) || ((_monsterProps[t].immunityFlags & 0x1000) && (d <= 1))) + return false; + } + } + + d += (attackType ? getStrHitChanceModifier(charIndex) : getDexHitChanceModifier(charIndex)); + + int m = getMonsterAcHitChanceModifier(charIndex, _monsterProps[t].armorClass) - d; + int s = rollDice(1, 20); + + _monsters[monsterIndex].flags |= 1; + + if (_flags.gameID == GI_EOB1) { + if (_partyEffectFlags & 0x30) + s++; + if (_characters[charIndex].effectFlags & 0x40) + s++; + } else if ((_partyEffectFlags & 0x8400) || (_characters[charIndex].effectFlags & 0x1000)) { + s++; + } + + s = CLIP(s, 1, 20); + + return s >= m; +} + +bool EoBCoreEngine::monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex) { + int tp = m->type; + EoBMonsterProperty *p = &_monsterProps[tp]; + + int r = rollDice(1, 20); + if (r != 20) { + // Prot from evil + if (_characters[charIndex].effectFlags & 0x800) + r -= 2; + // blur + if (_characters[charIndex].effectFlags & 0x10) + r -= 2; + // prayer + if (_partyEffectFlags & 0x8000) + r--; + } + + return ((r == 20) || (r >= (p->hitChance - _characters[charIndex].armorClass))); +} + +bool EoBCoreEngine::flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex) { + if (fo->attackerId != -1) { + if (!characterAttackHitTest(fo->attackerId, monsterIndex, fo->item, 0)) + return false; + } + calcAndInflictMonsterDamage(&_monsters[monsterIndex], fo->attackerId, fo->item, 0, (fo->attackerId == -1) ? 0x110 : 0x910, 5, 3); + return true; +} + +bool EoBCoreEngine::flyingObjectPartyHit(EoBFlyingObject *fo) { + int ps = _dscItemPosIndex[(_currentDirection << 2) + (_items[fo->item].pos & 3)]; + bool res = false; + + bool b = ((_currentDirection == fo->direction || _currentDirection == (fo->direction ^ 2)) && ps > 2); + int s = ps << 1; + if (ps > 2) + s += rollDice(1, 2, -1); + + static const int8 charId[] = { 0, -1, 1, -1, 2, 4, 3, 5 }; + + for (int i = 0; i < 2; i++) { + int c = charId[s]; + s ^= 1; + if (!testCharacter(c, 3)) + continue; + calcAndInflictCharacterDamage(c, -1, fo->item, 0, 0x110, 5, 3); + res = true; + if (ps < 2 || b == 0) + break; + } + + return res; +} + +void EoBCoreEngine::monsterCloseAttack(EoBMonsterInPlay *m) { + int first = _monsterCloseAttDstTable1[(_currentDirection << 2) + m->dir] * 12; + int v = (m->pos == 4) ? rollDice(1, 2, -1) : _monsterCloseAttChkTable2[(m->dir << 2) + m->pos]; + if (!v) + first += 6; + + int last = first + 6; + for (int i = first; i < last; i++) { + int c = _monsterCloseAttDstTable2[i]; + if (!testCharacter(c, 3)) + continue; + + // Character Invisibility + if ((_characters[c].effectFlags & 0x140) && (rollDice(1, 20) >= 5)) + continue; + + int dmg = 0; + for (int ii = 0; ii < _monsterProps[m->type].attacksPerRound; ii++) { + if (!monsterAttackHitTest(m, c)) + continue; + dmg += rollDice(_monsterProps[m->type].dmgDc[ii].times, _monsterProps[m->type].dmgDc[ii].pips, _monsterProps[m->type].dmgDc[ii].base); + if (_characters[c].effectsRemainder[1]) { + if (--_characters[c].effectsRemainder[1]) + dmg = 0; + } + } + + if (dmg > 0) { + if ((_monsterProps[m->type].capsFlags & 0x80) && rollDice(1, 4, -1) != 3) { + int slot = rollDice(1, 27, -1); + for (int iii = 0; iii < 27; iii++) { + Item itm = _characters[c].inventory[slot]; + if (!itm || !(_itemTypes[_items[itm].type].extraProperties & 0x80)) { + if (++slot == 27) + slot = 0; + continue; + } + + _characters[c].inventory[slot] = 0; + _txt->printMessage(_ripItemStrings[(_characters[c].raceSex & 1) ^ 1], -1, _characters[c].name); + printFullItemName(itm); + _txt->printMessage(_ripItemStrings[2]); + break; + } + gui_drawCharPortraitWithStats(c); + } + + inflictCharacterDamage(c, dmg); + + if (_monsterProps[m->type].capsFlags & 0x10) { + statusAttack(c, 2, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 3 : 2], 0, 1, 8, 1); + _characters[c].effectFlags &= ~0x2000; + } + + if (_monsterProps[m->type].capsFlags & 0x20) + statusAttack(c, 4, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 4 : 3], 2, 5, 9, 1); + + if (_monsterProps[m->type].capsFlags & 0x8000) + statusAttack(c, 8, _monsterSpecAttStrings[4], 2, 0, 0, 1); + + } + + if (!(_monsterProps[m->type].capsFlags & 0x4000)) + return; + } +} + +void EoBCoreEngine::monsterSpellCast(EoBMonsterInPlay *m, int type) { + launchMagicObject(-1, type, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_spells[_magicFlightObjectProperties[type << 2]].sound, m->block); +} + +void EoBCoreEngine::statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh) { + EoBCharacter *c = &_characters[charIndex]; + if ((c->flags & attackStatusFlags) && noRefresh) + return; + if (!testCharacter(charIndex, 3)) + return; + + if (savingThrowType != 5 && specialAttackSavingThrow(charIndex, savingThrowType)) + return; + + if (attackStatusFlags & 8) { + removeAllCharacterEffects(charIndex); + c->flags = (c->flags & 1) | 8; + } else { + c->flags |= attackStatusFlags; + } + + if ((attackStatusFlags & 0x0C) && (_openBookChar == charIndex) && _updateFlags) { + Button b; + clickedSpellbookAbort(&b); + } + + if (effectDuration) + setCharEventTimer(charIndex, effectDuration * 546, restoreEvent, 1); + + gui_drawCharPortraitWithStats(charIndex); + _txt->printMessage(_characterStatusStrings13[0], -1, c->name, attackStatusString); +} + +int EoBCoreEngine::calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { + int s = flags & 0x100 ? calcDamageModifers(times, m, pips, _items[pips].type, offs) : rollDice(times, pips, offs); + EoBMonsterProperty *p = &_monsterProps[m->type]; + + if (savingThrowType != 5) { + if (trySavingThrow(m, 0, p->level, savingThrowType, 6)) + s = savingThrowReduceDamage(savingThrowEffect, s); + } + + if ((flags & 0x110) == 0x110) { + if (!calcDamageCheckItemType(_items[pips].type)) + s = 1; + } + + if ((flags & 0x100) && (!(_itemTypes[_items[pips].type].allowedClasses & 4 /* bug in original code ??*/)) + && ((_flags.gameID == GI_EOB2 && (p->immunityFlags & 0x100)) || (_flags.gameID == GI_EOB1 && (p->capsFlags & 4)))) + s >>= 1; + + if (p->immunityFlags & 0x2000) { + if (flags & 0x100) { + if (_items[pips].value < 3) + s >>= 2; + if (_items[pips].value == 3) + s >>= 1; + if (s == 0) + s = _items[pips].value; + + } else { + s >>= 1; + } + } + + if (flags & 1) { + if (tryMonsterAttackEvasion(m)) + s = 0; + } + + if (_flags.gameID == GI_EOB1) + return s; + + static const uint16 damageImmunityFlags[] = { 0x01, 0x10, 0x02, 0x20, 0x80, 0x400, 0x20, 0x800, 0x40, 0x80, 0x400, 0x40 }; + for (int i = 0; i < 12; i += 2) { + if ((flags & damageImmunityFlags[i]) && (p->immunityFlags & damageImmunityFlags[i + 1])) + s = 0; + } + + return s; +} + +int EoBCoreEngine::calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier) { + int s = (useStrModifier && (charIndex != -1)) ? getStrDamageModifier(charIndex) : 0; + if (item) { + EoBItemType *p = &_itemTypes[itemType]; + int t = m ? m->type : 0; + s += ((m && (_monsterProps[t].capsFlags & 1)) ? rollDice(p->dmgNumDiceL, p->dmgNumPipsL, p->dmgIncS /* bug in original code ? */) : + rollDice(p->dmgNumDiceS, p->dmgNumPipsS, p->dmgIncS)); + s += _items[item].value; + } else { + s += rollDice(1, 2); + } + + return (s < 0) ? 0 : s; +} + +bool EoBCoreEngine::trySavingThrow(void *target, int hpModifier, int level, int type, int race) { + static const int8 constMod[] = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5 }; + + if (type == 5) + return false; + + int s = getSaveThrowModifier(hpModifier, level, type); + if (((race == 3 || race == 5) && (type == 4 || type == 1 || type == 0)) || (race == 4 && (type == 4 || type == 1))) { + EoBCharacter *c = (EoBCharacter *)target; + s -= constMod[c->constitutionCur]; + } + + return rollDice(1, 20) >= s; +} + +bool EoBCoreEngine::specialAttackSavingThrow(int charIndex, int type) { + return trySavingThrow(&_characters[charIndex], _charClassModifier[_characters[charIndex].cClass], _characters[charIndex].level[0], type, _characters[charIndex].raceSex >> 1); +} + +int EoBCoreEngine::getSaveThrowModifier(int hpModifier, int level, int type) { + const uint8 *tbl = _saveThrowTables[hpModifier]; + if (_saveThrowLevelIndex[hpModifier] < level) + level = _saveThrowLevelIndex[hpModifier]; + level /= _saveThrowModDiv[hpModifier]; + level += (_saveThrowModExt[hpModifier] * type); + + return tbl[level]; +} + +bool EoBCoreEngine::calcDamageCheckItemType(int itemType) { + itemType = _itemTypes[itemType].extraProperties & 0x7F; + return (itemType == 2 || itemType == 3) ? true : false; +} + +int EoBCoreEngine::savingThrowReduceDamage(int savingThrowEffect, int damage) { + if (savingThrowEffect == 3) + return 0; + + if (savingThrowEffect == 0 || savingThrowEffect == 1) + return damage >> 1; + + return damage; +} + +bool EoBCoreEngine::tryMonsterAttackEvasion(EoBMonsterInPlay *m) { + return rollDice(1, 100) < _monsterProps[m->type].dmgModifierEvade ? true : false; +} + +int EoBCoreEngine::getStrHitChanceModifier(int charIndex) { + static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; + static const int8 strExtMod[] = { 1, 2, 2, 2, 3 }; + static const int8 strMod[] = { -4, -3, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 4, 4, 5, 6, 7 }; + + int r = strMod[_characters[charIndex].strengthCur - 1]; + if (_characters[charIndex].strengthExtCur) { + for (int i = 0; i < 5; i++) { + if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) + r = strExtMod[i]; + } + } + + return r; +} + +int EoBCoreEngine::getStrDamageModifier(int charIndex) { + static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; + static const int8 strExtMod[] = { 3, 3, 4, 5, 6 }; + static const int8 strMod[] = { -3, -2, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 7, 8, 9, 10, 11, 12, 14 }; + + int r = strMod[_characters[charIndex].strengthCur - 1]; + if (_characters[charIndex].strengthExtCur) { + for (int i = 0; i < 5; i++) { + if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) + r = strExtMod[i]; + } + } + + return r; +} + +int EoBCoreEngine::getDexHitChanceModifier(int charIndex) { + static const int8 dexMod[] = { -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 4 }; + return dexMod[_characters[charIndex].dexterityCur - 1]; +} + +int EoBCoreEngine::getMonsterAcHitChanceModifier(int charIndex, int monsterAc) { + int l = _characters[charIndex].level[0] - 1; + int cm = _charClassModifier[_characters[charIndex].cClass]; + + return (20 - ((l / _monsterAcHitChanceTable1[cm]) * _monsterAcHitChanceTable2[cm])) - monsterAc; +} + +void EoBCoreEngine::explodeMonster(EoBMonsterInPlay *m) { + m->flags |= 2; + if (getBlockDistance(m->block, _currentBlock) < 2) { + explodeObject(0, _currentBlock, 2); + for (int i = 0; i < 6; i++) + calcAndInflictCharacterDamage(i, 6, 6, 0, 8, 1, 0); + } else { + explodeObject(0, m->block, 2); + } + m->flags &= ~2; +} + +void EoBCoreEngine::snd_playSong(int track) { + _sound->playTrack(track); +} + +void EoBCoreEngine::snd_playSoundEffect(int track, int volume) { + if ((track < 1) || (_flags.gameID == GI_EOB2 && track > 119) || shouldQuit()) + return; + + _sound->playSoundEffect(track, volume); +} + +void EoBCoreEngine::snd_stopSound() { + _sound->haltTrack(); + _sound->stopAllSoundEffects(); +} + +void EoBCoreEngine::snd_fadeOut() { + _sound->beginFadeOut(); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h new file mode 100644 index 0000000000..9e0fdf08b7 --- /dev/null +++ b/engines/kyra/engine/eobcommon.h @@ -0,0 +1,1185 @@ +/* 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 KYRA_EOBCOMMON_H +#define KYRA_EOBCOMMON_H + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) +#include "kyra/engine/kyra_rpg.h" +#endif // (ENABLE_EOB || ENABLE_LOL) + +#ifdef ENABLE_EOB + +namespace Kyra { + +struct DarkMoonShapeDef { + int16 index; + uint8 x, y, w, h; +}; + +struct CreatePartyModButton { + uint8 encodeLabelX; + uint8 encodeLabelY; + uint8 labelW; + uint8 labelH; + uint8 labelX; + uint8 labelY; + uint8 bodyIndex; + uint8 destX; + uint8 destY; +}; + +struct EoBRect8 { + uint8 x; + uint8 y; + uint8 w; + uint8 h; +}; + +struct EoBChargenButtonDef { + uint8 x; + uint8 y; + uint8 w; + uint8 h; + uint8 keyCode; +}; + +struct EoBGuiButtonDef { + uint16 keyCode; + uint16 keyCode2; + uint16 flags; + uint16 x; + uint8 y; + uint16 w; + uint8 h; + uint16 arg; +}; + +struct EoBCharacter { + uint8 id; + uint8 flags; + char name[21]; + int8 strengthCur; + int8 strengthMax; + int8 strengthExtCur; + int8 strengthExtMax; + int8 intelligenceCur; + int8 intelligenceMax; + int8 wisdomCur; + int8 wisdomMax; + int8 dexterityCur; + int8 dexterityMax; + int8 constitutionCur; + int8 constitutionMax; + int8 charismaCur; + int8 charismaMax; + int16 hitPointsCur; + int16 hitPointsMax; + int8 armorClass; + uint8 disabledSlots; + uint8 raceSex; + uint8 cClass; + uint8 alignment; + int8 portrait; + uint8 food; + uint8 level[3]; + uint32 experience[3]; + uint8 *faceShape; + + int8 mageSpells[80]; + int8 clericSpells[80]; + uint32 mageSpellsAvailableFlags; + + Item inventory[27]; + uint32 timers[10]; + int8 events[10]; + uint8 effectsRemainder[4]; + uint32 effectFlags; + uint8 damageTaken; + int8 slotStatus[5]; +}; + +struct EoBItem { + uint8 nameUnid; + uint8 nameId; + uint8 flags; + int8 icon; + int8 type; + int8 pos; + int16 block; + Item next; + Item prev; + uint8 level; + int8 value; +}; + +struct EoBItemType { + uint16 invFlags; + uint16 handFlags; + int8 armorClass; + int8 allowedClasses; + int8 requiredHands; + int8 dmgNumDiceS; + int8 dmgNumPipsS; + int8 dmgIncS; + int8 dmgNumDiceL; + int8 dmgNumPipsL; + int8 dmgIncL; + uint8 unk1; + uint16 extraProperties; +}; + +struct SpriteDecoration { + uint8 *shp; + int16 x; + int16 y; +}; + +struct EoBMonsterProperty { + int8 armorClass; + int8 hitChance; + int8 level; + uint8 hpDcTimes; + uint8 hpDcPips; + uint8 hpDcBase; + uint8 attacksPerRound; + struct DmgDc { + uint8 times; + uint8 pips; + int8 base; + } dmgDc[3]; + uint16 immunityFlags; + uint32 capsFlags; + uint32 typeFlags; + int32 experience; + + uint8 u30; + int8 sound1; + int8 sound2; + uint8 numRemoteAttacks; + uint8 remoteWeaponChangeMode; + uint8 numRemoteWeapons; + + int8 remoteWeapons[5]; + + int8 tuResist; + uint8 dmgModifierEvade; + + uint8 decorations[3]; +}; + +struct EoBMonsterInPlay { + uint8 type; + uint8 unit; + uint16 block; + uint8 pos; + int8 dir; + uint8 animStep; + uint8 shpIndex; + int8 mode; + int8 f_9; + int8 curAttackFrame; + int8 spellStatusLeft; + int16 hitPointsMax; + int16 hitPointsCur; + uint16 dest; + uint16 randItem; + uint16 fixedItem; + uint8 flags; + uint8 idleAnimState; + uint8 curRemoteWeapon; + uint8 numRemoteAttacks; + int8 palette; + uint8 directionChanged; + uint8 stepsTillRemoteAttack; + uint8 sub; +}; + +struct ScriptTimer { + uint16 func; + uint16 ticks; + uint32 next; +}; + +struct EoBMenuDef { + int8 titleStrId; + uint8 dim; + uint8 firstButtonStrId; + int8 numButtons; + int8 titleCol; +}; +struct EoBMenuButtonDef { + int8 labelId; + int16 x; + int8 y; + uint8 width; + uint8 height; + int16 keyCode; + int16 flags; +}; + +class EoBInfProcessor; + +class EoBCoreEngine : public KyraRpgEngine { +friend class TextDisplayer_rpg; +friend class GUI_EoB; +friend class Debugger_EoB; +friend class EoBInfProcessor; +friend class DarkmoonSequenceHelper; +friend class CharacterGenerator; +friend class TransferPartyWiz; +public: + EoBCoreEngine(OSystem *system, const GameFlags &flags); + virtual ~EoBCoreEngine(); + + virtual void initKeymap(); + + Screen *screen() { return _screen; } + GUI *gui() const { return _gui; } + +protected: + // Startup + virtual Common::Error init(); + Common::Error go(); + + // Main Menu, Intro, Finale + virtual int mainMenu() = 0; + virtual void seq_xdeath() {} + virtual void seq_playFinale() = 0; + bool _playFinale; + + //Init, config + void loadItemsAndDecorationsShapes(); + void releaseItemsAndDecorationsShapes(); + + void initButtonData(); + void initMenus(); + void initStaticResource(); + virtual void initSpells(); + + void registerDefaultSettings(); + void readSettings(); + void writeSettings(); + + const uint8 **_largeItemShapes; + const uint8 **_smallItemShapes; + const uint8 **_thrownItemShapes; + const int _numLargeItemShapes; + const int _numSmallItemShapes; + const int _numThrownItemShapes; + const int _numItemIconShapes; + + const uint8 **_spellShapes; + const uint8 **_firebeamShapes; + const uint8 *_redSplatShape; + const uint8 *_greenSplatShape; + const uint8 **_wallOfForceShapes; + const uint8 **_teleporterShapes; + const uint8 **_sparkShapes; + const uint8 *_deadCharShape; + const uint8 *_disabledCharGrid; + const uint8 *_blackBoxSmallGrid; + const uint8 *_weaponSlotGrid; + const uint8 *_blackBoxWideGrid; + const uint8 *_lightningColumnShape; + + uint8 *_itemsOverlay; + static const uint8 _itemsOverlayCGA[]; + + static const uint8 _teleporterShapeDefs[]; + static const uint8 _wallOfForceShapeDefs[]; + + const char *const *_mainMenuStrings; + + // Main loop + virtual void startupNew(); + virtual void startupLoad() = 0; + void runLoop(); + void update() { screen()->updateScreen(); } + bool checkPartyStatus(bool handleDeath); + + bool _runFlag; + + // Character generation / party transfer + bool startCharacterGeneration(); + bool startPartyTransfer(); + + uint8 **_faceShapes; + + static const int8 _characterClassType[]; + static const uint8 _hpIncrPerLevel[]; + static const uint8 _numLevelsPerClass[]; + static const int16 _hpConstModifiers[]; + static const uint8 _charClassModifier[]; + + const uint8 *_classModifierFlags; + + // timers + void setupTimers(); + void setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer); + void deleteCharEventTimer(int charIndex, int evnt); + void setupCharacterTimers(); + void advanceTimers(uint32 millis); + + void timerProcessMonsters(int timerNum); + void timerSpecialCharacterUpdate(int timerNum); + void timerProcessFlyingObjects(int timerNum); + void timerProcessCharacterExchange(int timerNum); + void timerUpdateTeleporters(int timerNum); + void timerUpdateFoodStatus(int timerNum); + void timerUpdateMonsterIdleAnim(int timerNum); + + uint8 getClock2Timer(int index) { return index < _numClock2Timers ? _clock2Timers[index] : 0; } + uint8 getNumClock2Timers() { return _numClock2Timers; } + + static const uint8 _clock2Timers[]; + static const uint8 _numClock2Timers; + + int32 _restPartyElapsedTime; + + // Mouse + void setHandItem(Item itemIndex); + + // Characters + int getDexterityArmorClassModifier(int dexterity); + int generateCharacterHitpointsByLevel(int charIndex, int levelIndex); + int getClassAndConstHitpointsModifier(int cclass, int constitution); + int getCharacterClassType(int cclass, int levelIndex); + int getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode); + Common::String getCharStrength(int str, int strExt); + int testCharacter(int16 index, int flags); + int getNextValidCharIndex(int curCharIndex, int searchStep); + + void recalcArmorClass(int index); + int validateWeaponSlotItem(int index, int slot); + + int getClericPaladinLevel(int index); + int getMageLevel(int index); + int getCharacterLevelIndex(int type, int cClass); + + int countCharactersWithSpecificItems(int16 itemType, int16 itemValue); + int checkInventoryForItem(int character, int16 itemType, int16 itemValue); + void modifyCharacterHitpoints(int character, int16 points); + void neutralizePoison(int character); + + void npcSequence(int npcIndex); + virtual void drawNpcScene(int npcIndex) = 0; + virtual void runNpcDialogue(int npcIndex) = 0; + void initNpc(int npcIndex); + int npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId); + int prepareForNewPartyMember(int16 itemType, int16 itemValue); + void dropCharacter(int charIndex); + void removeCharacterFromParty(int charIndex); + void exchangeCharacters(int charIndex1, int charIndex2); + + void increasePartyExperience(int16 points); + void increaseCharacterExperience(int charIndex, int32 points); + uint32 getRequiredExperience(int cClass, int levelIndex, int level); + void increaseCharacterLevel(int charIndex, int levelIndex); + + void setWeaponSlotStatus(int charIndex, int mode, int slot); + + EoBCharacter *_characters; + Common::String _strenghtStr; + int _castScrollSlot; + int _exchangeCharacterId; + + const char *const *_levelGainStrings; + const uint32 *_expRequirementTables[6]; + + const uint8 *_saveThrowTables[6]; + const uint8 *_saveThrowLevelIndex; + const uint8 *_saveThrowModDiv; + const uint8 *_saveThrowModExt; + + const EoBCharacter *_npcPreset; + int _npcSequenceSub; + bool _partyResting; + bool _loading; + + // Items + void loadItemDefs(); + Item duplicateItem(Item itemIndex); + void setItemPosition(Item *itemQueue, int block, Item item, int pos); + Item createItemOnCurrentBlock(Item itemIndex); + void createInventoryItem(EoBCharacter *c, Item itemIndex, int16 itemValue, int preferedInventorySlot); + int deleteInventoryItem(int charIndex, int slot); + void deleteBlockItem(uint16 block, int type); + int validateInventorySlotForItem(Item item, int charIndex, int slot); + int stripPartyItems(int16 itemType, int16 itemValue, int handleValueMode, int numItems); + bool deletePartyItems(int16 itemType, int16 itemValue); + virtual void updateUsedCharacterHandItem(int charIndex, int slot) = 0; + int itemUsableByCharacter(int charIndex, Item item); + int countQueuedItems(Item itemQueue, int16 id, int16 type, int count, int includeFlyingItems); + int getQueuedItem(Item *items, int pos, int id); + void printFullItemName(Item item); + void identifyQueuedItems(Item itemQueue); + void drawItemIconShape(int pageNum, Item itemId, int x, int y); + bool isMagicEffectItem(Item itemIndex); + bool checkInventoryForRings(int charIndex, int itemValue); + void eatItemInHand(int charIndex); + + bool launchObject(int charIndex, Item item, uint16 startBlock, int startPos, int dir, int type); + void launchMagicObject(int charIndex, int type, uint16 startBlock, int startPos, int dir); + bool updateObjectFlight(EoBFlyingObject *fo, int block, int pos); + bool updateFlyingObjectHitTest(EoBFlyingObject *fo, int block, int pos); + void explodeObject(EoBFlyingObject *fo, int block, Item item); + void endObjectFlight(EoBFlyingObject *fo); + void checkFlyingObjects(); + + void reloadWeaponSlot(int charIndex, int slotIndex, int itemType, int arrowOrDagger); + + EoBItem *_items; + uint16 _numItems; + EoBItemType *_itemTypes; + char **_itemNames; + uint16 _numItemNames; + uint32 _partyEffectFlags; + Item _lastUsedItem; + + const uint16 *_slotValidationFlags; + const int8 *_projectileWeaponAmmoTypes; + const uint8 *_wandTypes; + + EoBFlyingObject *_flyingObjects; + const uint8 *_drawObjPosIndex; + const uint8 *_flightObjFlipIndex; + const int8 *_flightObjShpMap; + const int8 *_flightObjSclIndex; + + const uint8 *_expObjectTlMode; + const uint8 *_expObjectTblIndex; + const uint8 *_expObjectShpStart; + const uint8 *_expObjectAnimTbl1; + int _expObjectAnimTbl1Size; + const uint8 *_expObjectAnimTbl2; + int _expObjectAnimTbl2Size; + const uint8 *_expObjectAnimTbl3; + int _expObjectAnimTbl3Size; + + const char *const *_ascii2SjisTables; + const char *const *_ascii2SjisTables2; + + // Monsters + void loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex); + void releaseMonsterShapes(int first, int num); + uint8 *loadTownsShape(Common::SeekableReadStream *stream); + virtual void generateMonsterPalettes(const char *file, int16 monsterIndex) {} + virtual void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) {} + const uint8 *loadMonsterProperties(const uint8 *data); + const uint8 *loadActiveMonsterData(const uint8 *data, int level); + void initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem); + void placeMonster(EoBMonsterInPlay *m, uint16 block, int dir); + virtual void replaceMonster(int b, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) = 0; + void killMonster(EoBMonsterInPlay *m, bool giveExperience); + virtual bool killMonsterExtra(EoBMonsterInPlay *m) = 0; + int countSpecificMonsters(int type); + void updateAttackingMonsterFlags(); + + const int8 *getMonstersOnBlockPositions(uint16 block); + int getClosestMonster(int charIndex, int block); + + bool blockHasMonsters(uint16 block); + bool isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4); + const int16 *findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent); + + void drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl = 0); + void drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex); + void flashMonsterShape(EoBMonsterInPlay *m); + void updateAllMonsterShapes(); + void drawBlockItems(int index); + void drawDoor(int index); + virtual void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) = 0; + void drawMonsters(int index); + void drawWallOfForce(int index); + void drawFlyingObjects(int index); + void drawTeleporter(int index); + + void updateMonsters(int unit); + void updateMonsterDest(EoBMonsterInPlay *m); + void updateMonsterAttackMode(EoBMonsterInPlay *m); + void updateAllMonsterDests(); + void turnFriendlyMonstersHostile(); + int getNextMonsterDirection(int curBlock, int destBlock); + int getNextMonsterPos(EoBMonsterInPlay *m, int block); + int findFreeMonsterPos(int block, int size); + void updateMoveMonster(EoBMonsterInPlay *m); + bool updateMonsterTryDistanceAttack(EoBMonsterInPlay *m); + bool updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block); + void walkMonster(EoBMonsterInPlay *m, int destBlock); + bool walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction); + void updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps); + void updateMonstersStraying(EoBMonsterInPlay *m, int a); + void updateMonstersSpellStatus(EoBMonsterInPlay *m); + void setBlockMonsterDirection(int block, int dir); + + uint8 *_monsterFlashOverlay; + uint8 *_monsterStoneOverlay; + + SpriteDecoration *_monsterDecorations; + EoBMonsterProperty *_monsterProps; + + EoBMonsterInPlay *_monsters; + + const int8 *_monsterStepTable0; + const int8 *_monsterStepTable1; + const int8 *_monsterStepTable2; + const int8 *_monsterStepTable3; + const uint8 *_monsterCloseAttPosTable1; + const uint8 *_monsterCloseAttPosTable2; + const int8 *_monsterCloseAttUnkTable; + const uint8 *_monsterCloseAttChkTable1; + const uint8 *_monsterCloseAttChkTable2; + const uint8 *_monsterCloseAttDstTable1; + const uint8 *_monsterCloseAttDstTable2; + + const uint8 *_monsterProximityTable; + const uint8 *_findBlockMonstersTable; + const char *const *_monsterDustStrings; + + const uint8 *_enemyMageSpellList; + const uint8 *_enemyMageSfx; + const uint8 *_beholderSpellList; + const uint8 *_beholderSfx; + const char *const *_monsterSpecAttStrings; + + const int8 *_monsterFrmOffsTable1; + const int8 *_monsterFrmOffsTable2; + + const uint16 *_encodeMonsterShpTable; + const uint8 _teleporterWallId; + + const int16 *_wallOfForceDsX; + const uint8 *_wallOfForceDsY; + const uint8 *_wallOfForceDsNumW; + const uint8 *_wallOfForceDsNumH; + const uint8 *_wallOfForceShpId; + + const int8 *_monsterDirChangeTable; + + // Level + void loadLevel(int level, int sub); + void readLevelFileData(int level); + Common::String initLevelData(int sub); + void addLevelItems(); + void loadVcnData(const char *file, const uint8 *cgaMapping); + void loadBlockProperties(const char *mazFile); + const uint8 *getBlockFileData(int levelIndex = 0); + Common::String getBlockFileName(int levelIndex, int sub); + const uint8 *getBlockFileData(const char *mazFile); + void loadDecorations(const char *cpsFile, const char *decFile); + void assignWallsAndDecorations(int wallIndex, int vmpIndex, int decDataIndex, int specialType, int flags); + void releaseDecorations(); + void releaseDoorShapes(); + void toggleWallState(int wall, int flags); + virtual void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) = 0; + virtual const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) = 0; + + void drawScene(int refresh); + void drawSceneShapes(int start = 0); + void drawDecorations(int index); + + int calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction); + void notifyBlockNotPassable(); + void moveParty(uint16 block); + + int clickedDoorSwitch(uint16 block, uint16 direction); + int clickedDoorPry(uint16 block, uint16 direction); + int clickedDoorNoPry(uint16 block, uint16 direction); + int clickedNiche(uint16 block, uint16 direction); + + int specialWallAction(int block, int direction); + + void openDoor(int block); + void closeDoor(int block); + + int16 _doorType[2]; + int16 _noDoorSwitch[2]; + + EoBRect8 *_levelDecorationRects; + SpriteDecoration *_doorSwitches; + + int8 _currentSub; + Common::String _curGfxFile; + Common::String _curBlockFile; + + uint32 _drawSceneTimer; + uint32 _flashShapeTimer; + uint32 _envAudioTimer; + uint16 _teleporterPulse; + + Common::Array<const int16 *> _dscWallMapping; + const int16 *_dscShapeCoords; + + const uint8 *_dscItemPosIndex; + const int16 *_dscItemShpX; + const uint8 *_dscItemScaleIndex; + const uint8 *_dscItemTileIndex; + const uint8 *_dscItemShapeMap; + + const uint8 *_dscDoorScaleMult1; + const uint8 *_dscDoorScaleMult2; + const uint8 *_dscDoorScaleMult3; + const uint8 *_dscDoorY1; + const uint8 *_dscDoorXE; + + uint8 *_greenFadingTable; + uint8 *_blueFadingTable; + uint8 *_lightBlueFadingTable; + uint8 *_blackFadingTable; + uint8 *_greyFadingTable; + + const uint8 *_wllFlagPreset; + int _wllFlagPresetSize; + const uint8 *_teleporterShapeCoords; + const int8 *_portalSeq; + + // Script + void runLevelScript(int block, int flags); + void setScriptFlags(uint32 flags); + void clearScriptFlags(uint32 flags); + bool checkScriptFlags(uint32 flags); + + const uint8 *initScriptTimers(const uint8 *pos); + void updateScriptTimers(); + virtual void updateScriptTimersExtra() {} + + EoBInfProcessor *_inf; + int _stepCounter; + int _stepsUntilScriptCall; + ScriptTimer _scriptTimers[5]; + int _scriptTimersCount; + uint8 _scriptTimersMode; + + // Gui + void gui_drawPlayField(bool refresh); + void gui_restorePlayField(); + void gui_drawAllCharPortraitsWithStats(); + void gui_drawCharPortraitWithStats(int index); + void gui_drawFaceShape(int index); + void gui_drawWeaponSlot(int charIndex, int slot); + void gui_drawWeaponSlotStatus(int x, int y, int status); + void gui_drawHitpoints(int index); + void gui_drawFoodStatusGraph(int index); + void gui_drawHorizontalBarGraph(int x, int y, int w, int h, int32 curVal, int32 maxVal, int col1, int col2); + void gui_drawCharPortraitStatusFrame(int index); + void gui_drawInventoryItem(int slot, int special, int pageNum); + void gui_drawCompass(bool force); + void gui_drawDialogueBox(); + void gui_drawSpellbook(); + void gui_drawSpellbookScrollArrow(int x, int y, int direction); + void gui_updateSlotAfterScrollUse(); + void gui_updateControls(); + void gui_toggleButtons(); + void gui_setPlayFieldButtons(); + void gui_setInventoryButtons(); + void gui_setStatsListButtons(); + void gui_setSwapCharacterButtons(); + void gui_setCastOnWhomButtons(); + void gui_initButton(int index, int x = -1, int y = -1, int val = -1); + Button *gui_getButton(Button *buttonList, int index); + + int clickedInventoryNextPage(Button *button); + int clickedPortraitRestore(Button *button); + int clickedCharPortraitDefault(Button *button); + int clickedCamp(Button *button); + int clickedSceneDropPickupItem(Button *button); + int clickedCharPortrait2(Button *button); + int clickedWeaponSlot(Button *button); + int clickedCharNameLabelRight(Button *button); + int clickedInventorySlot(Button *button); + int clickedEatItem(Button *button); + int clickedInventoryPrevChar(Button *button); + int clickedInventoryNextChar(Button *button); + int clickedSpellbookTab(Button *button); + int clickedSpellbookList(Button *button); + int clickedCastSpellOnCharacter(Button *button); + int clickedUpArrow(Button *button); + int clickedDownArrow(Button *button); + int clickedLeftArrow(Button *button); + int clickedRightArrow(Button *button); + int clickedTurnLeftArrow(Button *button); + int clickedTurnRightArrow(Button *button); + int clickedAbortCharSwitch(Button *button); + int clickedSceneThrowItem(Button *button); + int clickedSceneSpecial(Button *button); + int clickedSpellbookAbort(Button *button); + int clickedSpellbookScroll(Button *button); + int clickedUnk(Button *button); + + void gui_processCharPortraitClick(int index); + void gui_processWeaponSlotClickLeft(int charIndex, int slotIndex); + void gui_processWeaponSlotClickRight(int charIndex, int slotIndex); + void gui_processInventorySlotClick(int slot); + + static const uint8 _buttonList1[]; + int _buttonList1Size; + static const uint8 _buttonList2[]; + int _buttonList2Size; + static const uint8 _buttonList3[]; + int _buttonList3Size; + static const uint8 _buttonList4[]; + int _buttonList4Size; + static const uint8 _buttonList5[]; + int _buttonList5Size; + static const uint8 _buttonList6[]; + int _buttonList6Size; + static const uint8 _buttonList7[]; + int _buttonList7Size; + static const uint8 _buttonList8[]; + int _buttonList8Size; + + EoBGuiButtonDef *_buttonDefs; + + const char *const *_characterGuiStringsHp; + const char *const *_characterGuiStringsWp; + const char *const *_characterGuiStringsWr; + const char *const *_characterGuiStringsSt; + const char *const *_characterGuiStringsIn; + + const char *const *_characterStatusStrings7; + const char *const *_characterStatusStrings8; + const char *const *_characterStatusStrings9; + const char *const *_characterStatusStrings12; + const char *const *_characterStatusStrings13; + + const uint16 *_inventorySlotsX; + const uint8 *_inventorySlotsY; + const uint8 **_compassShapes; + uint8 _charExchangeSwap; + bool _configHpBarGraphs; + bool _configMouseBtSwap; + + Graphics::Surface _thumbNail; + + // text + void setupDialogueButtons(int presetfirst, int numStr, va_list &args); + void initDialogueSequence(); + void restoreAfterDialogueSequence(); + void drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags); + int runDialogue(int dialogueTextId, int numStr, ...); + + char _dialogueLastBitmap[13]; + int _moveCounter; + + const char *const *_chargenStatStrings; + const char *const *_chargenRaceSexStrings; + const char *const *_chargenClassStrings; + const char *const *_chargenAlignmentStrings; + + const char *const *_pryDoorStrings; + const char *const *_warningStrings; + + const char *const *_ripItemStrings; + const char *const *_cursedString; + const char *const *_enchantedString; + const char *const *_magicObjectStrings; + const char *const *_magicObjectString5; + const char *const *_patternSuffix; + const char *const *_patternGrFix1; + const char *const *_patternGrFix2; + const char *const *_validateArmorString; + const char *const *_validateCursedString; + const char *const *_validateNoDropString; + const char *const *_potionStrings; + const char *const *_wandStrings; + const char *const *_itemMisuseStrings; + + const char *const *_suffixStringsRings; + const char *const *_suffixStringsPotions; + const char *const *_suffixStringsWands; + + const char *const *_takenStrings; + const char *const *_potionEffectStrings; + + const char *const *_yesNoStrings; + const char *const *_npcMaxStrings; + const char *const *_okStrings; + const char *const *_npcJoinStrings; + const char *const *_cancelStrings; + const char *const *_abortStrings; + + // Rest party + void restParty_displayWarning(const char *str); + bool restParty_updateMonsters(); + int restParty_getCharacterWithLowestHp(); + bool restParty_checkHealSpells(int charIndex); + bool restParty_checkSpellsToLearn(); + virtual void restParty_npc() {} + virtual bool restParty_extraAbortCondition(); + + // misc + void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false); + + void displayParchment(int id); + int countResurrectionCandidates(); + + void seq_portal(); + bool checkPassword(); + + Common::String convertAsciiToSjis(Common::String str); + + virtual int resurrectionSelectDialogue() = 0; + virtual void useHorn(int charIndex, int weaponSlot) {} + virtual bool checkPartyStatusExtra() = 0; + virtual void drawLightningColumn() {} + virtual int charSelectDialogue() { return -1; } + virtual void characterLevelGain(int charIndex) {} + + Common::Error loadGameState(int slot); + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + + const uint8 *_cgaMappingDefault; + const uint8 *_cgaMappingAlt; + const uint8 *_cgaMappingInv; + const uint8 *_cgaMappingItemsL; + const uint8 *_cgaMappingItemsS; + const uint8 *_cgaMappingThrown; + const uint8 *_cgaMappingIcons; + const uint8 *_cgaMappingDeco; + const uint8 *_cgaMappingLevel[5]; + const uint8 *_cgaLevelMappingIndex; + + bool _enableHiResDithering; + + // Default parameters will import all present original save files and push them to the top of the save dialog. + bool importOriginalSaveFile(int destSlot, const char *sourceFile = 0); + Common::String readOriginalSaveFile(Common::String &file); + bool saveAsOriginalSaveFile(int slot = -1); + + void *generateMonsterTempData(LevelTempData *tmp); + void restoreMonsterTempData(LevelTempData *tmp); + void releaseMonsterTempData(LevelTempData *tmp); + void *generateWallOfForceTempData(LevelTempData *tmp); + void restoreWallOfForceTempData(LevelTempData *tmp); + void releaseWallOfForceTempData(LevelTempData *tmp); + + const char *const *_saveLoadStrings; + + const uint8 *_mnDef; + const char *const *_mnWord; + const char *const *_mnPrompt; + int _mnNumWord; + + int _rrCount; + const char *_rrNames[10]; + int8 _rrId[10]; + + bool _allowSkip; + bool _allowImport; + + Screen_EoB *_screen; + GUI_EoB *_gui; + + // fight + void useSlotWeapon(int charIndex, int slotIndex, Item item); + int closeDistanceAttack(int charIndex, Item item); + int thrownAttack(int charIndex, int slotIndex, Item item); + int projectileWeaponAttack(int charIndex, Item item); + + void inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience); + void calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); + void calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect); + int calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int damageType); + void inflictCharacterDamage(int charIndex, int damage); + + bool characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType); + bool monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex); + bool flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex); + bool flyingObjectPartyHit(EoBFlyingObject *fo); + + void monsterCloseAttack(EoBMonsterInPlay *m); + void monsterSpellCast(EoBMonsterInPlay *m, int type); + void statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh); + + int calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); + int calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier); + bool trySavingThrow(void *target, int hpModifier, int level, int type, int race); + bool specialAttackSavingThrow(int charIndex, int type); + int getSaveThrowModifier(int hpModifier, int level, int type); + bool calcDamageCheckItemType(int itemType); + int savingThrowReduceDamage(int savingThrowEffect, int damage); + bool tryMonsterAttackEvasion(EoBMonsterInPlay *m); + int getStrHitChanceModifier(int charIndex); + int getStrDamageModifier(int charIndex); + int getDexHitChanceModifier(int charIndex); + int getMonsterAcHitChanceModifier(int charIndex, int monsterAc); + void explodeMonster(EoBMonsterInPlay *m); + + int _dstMonsterIndex; + bool _preventMonsterFlash; + int16 _foundMonstersArray[5]; + int8 _monsterBlockPosArray[6]; + const uint8 *_monsterAcHitChanceTable1; + const uint8 *_monsterAcHitChanceTable2; + + // magic + void useMagicBookOrSymbol(int charIndex, int type); + void useMagicScroll(int charIndex, int type, int weaponSlot); + void usePotion(int charIndex, int weaponSlot); + void useWand(int charIndex, int weaponSlot); + + virtual void turnUndeadAuto() {} + virtual void turnUndeadAutoHit() {} + + void castSpell(int spell, int weaponSlot); + void removeCharacterEffect(int spell, int charIndex, int showWarning); + void removeAllCharacterEffects(int charIndex); + void castOnWhomDialogue(); + void startSpell(int spell); + + void sparkEffectDefensive(int charIndex); + void sparkEffectOffensive(); + void setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer); + void sortCharacterSpellList(int charIndex); + + bool magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level); + bool magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod); + bool turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel); + void causeWounds(int dcTimes, int dcPips, int dcOffs); + + int getMagicWeaponSlot(int charIndex); + int createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNum, int dmgPips, int dmgInc, int extraProps); + Item createMagicWeaponItem(int flags, int icon, int value, int type); + void removeMagicWeaponItem(Item item); + + void updateWallOfForceTimers(); + void destroyWallOfForce(int index); + + int findSingleSpellTarget(int dist); + + int findFirstCharacterSpellTarget(); + int findNextCharacterSpellTarget(int curCharIndex); + int charDeathSavingThrow(int charIndex, int div); + + void printWarning(const char *str); + void printNoEffectWarning(); + + void spellCallback_start_empty() {} + bool spellCallback_end_empty(void *) { return true; } + void spellCallback_start_armor(); + void spellCallback_start_burningHands(); + void spellCallback_start_detectMagic(); + bool spellCallback_end_detectMagic(void *); + void spellCallback_start_magicMissile(); + bool spellCallback_end_magicMissile(void *obj); + void spellCallback_start_shockingGrasp(); + bool spellCallback_end_shockingGraspFlameBlade(void *obj); + void spellCallback_start_improvedIdentify(); + void spellCallback_start_melfsAcidArrow(); + bool spellCallback_end_melfsAcidArrow(void *obj); + void spellCallback_start_dispelMagic(); + void spellCallback_start_fireball(); + bool spellCallback_end_fireball(void *obj); + void spellCallback_start_flameArrow(); + bool spellCallback_end_flameArrow(void *obj); + void spellCallback_start_holdPerson(); + bool spellCallback_end_holdPerson(void *obj); + void spellCallback_start_lightningBolt(); + bool spellCallback_end_lightningBolt(void *obj); + void spellCallback_start_vampiricTouch(); + bool spellCallback_end_vampiricTouch(void *obj); + void spellCallback_start_fear(); + void spellCallback_start_iceStorm(); + bool spellCallback_end_iceStorm(void *obj); + void spellCallback_start_stoneSkin(); + void spellCallback_start_removeCurse(); + void spellCallback_start_coneOfCold(); + void spellCallback_start_holdMonster(); + bool spellCallback_end_holdMonster(void *obj); + void spellCallback_start_wallOfForce(); + void spellCallback_start_disintegrate(); + void spellCallback_start_fleshToStone(); + void spellCallback_start_stoneToFlesh(); + void spellCallback_start_trueSeeing(); + bool spellCallback_end_trueSeeing(void *); + void spellCallback_start_slayLiving(); + void spellCallback_start_powerWordStun(); + void spellCallback_start_causeLightWounds(); + void spellCallback_start_cureLightWounds(); + void spellCallback_start_aid(); + bool spellCallback_end_aid(void *obj); + void spellCallback_start_flameBlade(); + void spellCallback_start_slowPoison(); + bool spellCallback_end_slowPoison(void *obj); + void spellCallback_start_createFood(); + void spellCallback_start_removeParalysis(); + void spellCallback_start_causeSeriousWounds(); + void spellCallback_start_cureSeriousWounds(); + void spellCallback_start_neutralizePoison(); + void spellCallback_start_causeCriticalWounds(); + void spellCallback_start_cureCriticalWounds(); + void spellCallback_start_flameStrike(); + bool spellCallback_end_flameStrike(void *obj); + void spellCallback_start_raiseDead(); + void spellCallback_start_harm(); + void spellCallback_start_heal(); + void spellCallback_start_layOnHands(); + void spellCallback_start_turnUndead(); + bool spellCallback_end_monster_lightningBolt(void *obj); + bool spellCallback_end_monster_fireball1(void *obj); + bool spellCallback_end_monster_fireball2(void *obj); + bool spellCallback_end_monster_deathSpell(void *obj); + bool spellCallback_end_monster_disintegrate(void *obj); + bool spellCallback_end_monster_causeCriticalWounds(void *obj); + bool spellCallback_end_monster_fleshToStone(void *obj); + + int8 _openBookSpellLevel; + int8 _openBookSpellSelectedItem; + int8 _openBookSpellListOffset; + uint8 _openBookChar; + uint8 _openBookType; + uint8 _openBookCharBackup; + uint8 _openBookTypeBackup; + uint8 _openBookCasterLevel; + const char *const *_openBookSpellList; + int8 *_openBookAvailableSpells; + uint8 _activeSpellCharId; + uint8 _activeSpellCharacterPos; + int _activeSpell; + int _characterSpellTarget; + bool _returnAfterSpellCallback; + + typedef void (EoBCoreEngine::*SpellStartCallback)(); + typedef bool (EoBCoreEngine::*SpellEndCallback)(void *obj); + + struct EoBSpell { + const char *name; + SpellStartCallback startCallback; + uint16 flags; + const uint16 *timingPara; + SpellEndCallback endCallback; + uint8 sound; + uint32 effectFlags; + uint16 damageFlags; + }; + + EoBSpell *_spells; + int _numSpells; + + struct WallOfForce { + uint16 block; + uint32 duration; + }; + + WallOfForce *_wallsOfForce; + + const char *const *_bookNumbers; + const char *const *_mageSpellList; + int _mageSpellListSize; + int _clericSpellOffset; + const char *const *_clericSpellList; + const char *const *_spellNames; + const char *const *_magicStrings1; + const char *const *_magicStrings2; + const char *const *_magicStrings3; + const char *const *_magicStrings4; + const char *const *_magicStrings6; + const char *const *_magicStrings7; + const char *const *_magicStrings8; + + uint8 *_spellAnimBuffer; + + const uint8 *_sparkEffectDefSteps; + const uint8 *_sparkEffectDefSubSteps; + const uint8 *_sparkEffectDefShift; + const uint8 *_sparkEffectDefAdd; + const uint8 *_sparkEffectDefX; + const uint8 *_sparkEffectDefY; + const uint32 *_sparkEffectOfFlags1; + const uint32 *_sparkEffectOfFlags2; + const uint8 *_sparkEffectOfShift; + const uint8 *_sparkEffectOfX; + const uint8 *_sparkEffectOfY; + + const uint8 *_magicFlightObjectProperties; + const uint8 *_turnUndeadEffect; + const uint8 *_burningHandsDest; + const int8 *_coneOfColdDest1; + const int8 *_coneOfColdDest2; + const int8 *_coneOfColdDest3; + const int8 *_coneOfColdDest4; + const uint8 *_coneOfColdGfxTbl; + int _coneOfColdGfxTblSize; + + // Menu + EoBMenuDef *_menuDefs; + const EoBMenuButtonDef *_menuButtonDefs; + + bool _configMouse; + bool _config2431; + + const char *const *_menuStringsMain; + const char *const *_menuStringsSaveLoad; + const char *const *_menuStringsOnOff; + const char *const *_menuStringsSpells; + const char *const *_menuStringsRest; + const char *const *_menuStringsDrop; + const char *const *_menuStringsExit; + const char *const *_menuStringsStarve; + const char *const *_menuStringsScribe; + const char *const *_menuStringsDrop2; + const char *const *_menuStringsHead; + const char *const *_menuStringsPoison; + const char *const *_menuStringsMgc; + const char *const *_menuStringsPrefs; + const char *const *_menuStringsRest2; + const char *const *_menuStringsRest3; + const char *const *_menuStringsRest4; + const char *const *_menuStringsDefeat; + const char *_errorSlotEmptyString; + const char *_errorSlotNoNameString; + const char *_menuOkString; + const char *const *_2431Strings; + const char *const *_katakanaLines; + const char *const *_katakanaSelectStrings; + const char *const *_menuStringsTransfer; + const char *const *_transferStringsScummVM; + const char *const *_menuStringsSpec; + const char *const *_menuStringsSpellNo; + const char *const *_menuYesNoStrings; + const char *const *_saveNamePatterns; + + const uint8 *_spellLevelsMage; + int _spellLevelsMageSize; + const uint8 *_spellLevelsCleric; + int _spellLevelsClericSize; + const uint8 *_numSpellsCleric; + const uint8 *_numSpellsWisAdj; + const uint8 *_numSpellsPal; + const uint8 *_numSpellsMage; + + // sound + void snd_playSong(int id); + void snd_playSoundEffect(int id, int volume=0xFF); + void snd_stopSound(); + void snd_fadeOut(); + + // keymap + static const char *const kKeymapName; +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB + +#endif diff --git a/engines/kyra/engine/item.h b/engines/kyra/engine/item.h new file mode 100644 index 0000000000..cf06aad8ba --- /dev/null +++ b/engines/kyra/engine/item.h @@ -0,0 +1,41 @@ +/* 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 KYRA_ITEM_H +#define KYRA_ITEM_H + +#include "common/scummsys.h" + +namespace Kyra { + +typedef int16 Item; + +enum { + /** + * Constant for invalid item. + */ + kItemNone = -1 +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/items_eob.cpp b/engines/kyra/engine/items_eob.cpp new file mode 100644 index 0000000000..54d3d5090b --- /dev/null +++ b/engines/kyra/engine/items_eob.cpp @@ -0,0 +1,732 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +void EoBCoreEngine::loadItemDefs() { + Common::SeekableReadStream *s = _res->createReadStream("item.dat"); + memset(_items, 0, sizeof(EoBItem) * 600); + _numItems = s->readUint16LE(); + + for (int i = 0; i < 600; i++) + _items[i].block = -1; + + for (int i = 0; i < _numItems; i++) { + _items[i].nameUnid = s->readByte(); + _items[i].nameId = s->readByte(); + _items[i].flags = s->readByte(); + _items[i].icon = s->readSByte(); + _items[i].type = s->readSByte(); + _items[i].pos = s->readSByte(); + _items[i].block = s->readSint16LE(); + _items[i].next = s->readSint16LE(); + _items[i].prev = s->readSint16LE(); + _items[i].level = s->readSByte(); + _items[i].value = s->readSByte(); + } + + _numItemNames = s->readUint16LE(); + for (int i = 0; i < _numItemNames; i++) + s->read(_itemNames[i], 35); + + delete s; + + s = _res->createReadStream("itemtype.dat"); + uint16 numTypes = s->readUint16LE(); + + delete[] _itemTypes; + _itemTypes = new EoBItemType[65]; + memset(_itemTypes, 0, sizeof(EoBItemType) * 65); + + for (int i = 0; i < numTypes; i++) { + _itemTypes[i].invFlags = s->readUint16LE(); + _itemTypes[i].handFlags = s->readUint16LE(); + _itemTypes[i].armorClass = s->readSByte(); + _itemTypes[i].allowedClasses = s->readSByte(); + _itemTypes[i].requiredHands = s->readSByte(); + _itemTypes[i].dmgNumDiceS = s->readSByte(); + _itemTypes[i].dmgNumPipsS = s->readSByte(); + _itemTypes[i].dmgIncS = s->readSByte(); + _itemTypes[i].dmgNumDiceL = s->readSByte(); + _itemTypes[i].dmgNumPipsL = s->readSByte(); + _itemTypes[i].dmgIncL = s->readSByte(); + _itemTypes[i].unk1 = s->readByte(); + _itemTypes[i].extraProperties = s->readUint16LE(); + } + + delete s; +} + +Kyra::Item EoBCoreEngine::duplicateItem(Item itemIndex) { + EoBItem *itm = &_items[itemIndex]; + + if (itm->block == -1) + return 0; + + Item i = 1; + bool foundSlot = false; + + for (; i < 600; i++) { + if (_items[i].block == -1) { + foundSlot = true; + break; + } + } + + if (!foundSlot) + return 0; + + memcpy(&_items[i], itm, sizeof(EoBItem)); + return i; +} + +Item EoBCoreEngine::createItemOnCurrentBlock(Item itemIndex) { + Item itm = duplicateItem(itemIndex); + setItemPosition((Item *)&_levelBlockProperties[_currentBlock].drawObjects, _currentBlock, itm, _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + return itm; +} + +void EoBCoreEngine::setItemPosition(Item *itemQueue, int block, Item item, int pos) { + if (!item) + return; + + EoBItem *itm = &_items[item]; + itm->pos = pos; + itm->block = block; + itm->level = block < 0 ? 0xFF : _currentLevel; + + if (!*itemQueue) { + *itemQueue = itm->next = itm->prev = item; + } else { + EoBItem *itmQ = &_items[*itemQueue]; + EoBItem *itmQN = &_items[itmQ->next]; + itm->prev = itmQN->prev; + itm->next = itmQ->next; + *itemQueue = itmQN->prev = itmQ->next = item; + } +} + +void EoBCoreEngine::createInventoryItem(EoBCharacter *c, Item itemIndex, int16 itemValue, int preferedInventorySlot) { + if (itemIndex <= 0) + return; + + itemIndex = duplicateItem(itemIndex); + _items[itemIndex].flags |= 0x40; + + if (itemValue != -1) + _items[itemIndex].value = itemValue; + + if (itemValue && ((_itemTypes[_items[itemIndex].type].extraProperties & 0x7F) < 4)) + _items[itemIndex].flags |= 0x80; + + if (c->inventory[preferedInventorySlot]) { + for (int i = 2; i < 16; i++) { + if (!c->inventory[i]) { + c->inventory[i] = itemIndex; + return; + } + } + } else { + c->inventory[preferedInventorySlot] = itemIndex; + } +} + +int EoBCoreEngine::deleteInventoryItem(int charIndex, int slot) { + int itm = (slot == -1) ? _itemInHand : _characters[charIndex].inventory[slot]; + _items[itm].block = -1; + + if (slot == -1) { + setHandItem(0); + } else { + _characters[charIndex].inventory[slot] = 0; + + if (_currentControlMode == 1) + gui_drawInventoryItem(slot, 1, 0); + + if (_currentControlMode == 0) + gui_drawCharPortraitWithStats(charIndex); + } + + return _items[itm].value; +} + +void EoBCoreEngine::deleteBlockItem(uint16 block, int type) { + uint16 itm = _levelBlockProperties[block].drawObjects; + if (!itm) + return; + + _levelBlockProperties[block].drawObjects = 0; + + for (uint16 i2 = itm, i = 0; itm != i2 || !i; i++) { + if (type == _items[itm].type || type == -1) { + _items[itm].block = -1; + _items[itm].level = 0; + uint16 i3 = itm; + itm = _items[itm].prev; + _items[i3].prev = _items[i3].next = 0; + } else { + uint16 i3 = itm; + itm = _items[itm].prev; + _items[i3].prev = _items[i3].next = 0; + setItemPosition((Item *)&_levelBlockProperties[block].drawObjects, block, i3, _items[i3].pos); + } + } +} + +int EoBCoreEngine::validateInventorySlotForItem(Item item, int charIndex, int slot) { + if (item < 0) + return 0; + + if (slot == 17 && item && !itemUsableByCharacter(charIndex, item)) { + _txt->printMessage(_validateArmorString[0], -1, _characters[charIndex].name); + return 0; + } + + int itm = _characters[charIndex].inventory[slot]; + int ex = _itemTypes[_items[itm].type].extraProperties & 0x7F; + + if (_items[itm].flags & 0x20 && (_flags.gameID == GI_EOB1 || slot < 2)) { + if (_flags.gameID == GI_EOB2 && ex > 0 && ex < 4) + _txt->printMessage(_validateCursedString[0], -1, _characters[charIndex].name); + return 0; + } + + uint16 v = item ? _itemTypes[_items[item].type].invFlags : 0xFFFF; + if (v & _slotValidationFlags[slot]) + return 1; + + _txt->printMessage(_validateNoDropString[0]); + return 0; +} + +int EoBCoreEngine::stripPartyItems(int16 itemType, int16 itemValue, int handleValueMode, int numItems) { + int itemsLeft = numItems; + + for (bool runloop = true; runloop && itemsLeft;) { + runloop = false; + for (int i = 0; i < 6 && itemsLeft; i++) { + if (!testCharacter(i, 1)) + continue; + + for (int ii = 0; ii < 27 && itemsLeft; ii++) { + if (ii == 16) + continue; + + Item itm = _characters[i].inventory[ii]; + if ((_items[itm].type == itemType) && ((handleValueMode == -1 && _items[itm].value <= itemValue) || (handleValueMode == 0 && _items[itm].value == itemValue) || (handleValueMode == 1 && _items[itm].value >= itemValue))) { + _characters[i].inventory[ii] = 0; + _items[itm].block = -1; + itemsLeft--; + runloop = true; + } + } + } + } + + return numItems - itemsLeft; +} + +bool EoBCoreEngine::deletePartyItems(int16 itemType, int16 itemValue) { + bool res = false; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + + EoBCharacter *c = &_characters[i]; + for (int slot = checkInventoryForItem(i, itemType, itemValue); slot != -1; slot = checkInventoryForItem(i, itemType, itemValue)) { + int itm = c->inventory[slot]; + _items[itm].block = -1; + c->inventory[slot] = 0; + res = true; + + if (!_dialogueField) { + if (_currentControlMode == 0 && slot < 2 && i < 5) + gui_drawWeaponSlot(i, slot); + + if (_currentControlMode == 1 && i == _updateCharNum) + gui_drawInventoryItem(slot, 1, 0); + } + } + } + + if (_itemInHand > 0) { + if ((itemType == -1 || itemType == _items[_itemInHand].type) && (itemValue == -1 || itemValue == _items[_itemInHand].value)) { + _items[_itemInHand].block = -1; + setHandItem(0); + res = true; + } + } + + return res; +} + +int EoBCoreEngine::itemUsableByCharacter(int charIndex, Item item) { + if (!item) + return 1; + + return (_itemTypes[_items[item].type].allowedClasses & _classModifierFlags[_characters[charIndex].cClass]); +} + +int EoBCoreEngine::countQueuedItems(Item itemQueue, int16 id, int16 type, int count, int includeFlyingItems) { + uint16 o1 = itemQueue; + uint16 o2 = o1; + + if (!o1) + return 0; + + int res = 0; + + for (bool forceLoop = true; o1 != o2 || forceLoop; o1 = _items[o1].prev) { + EoBItem *itm = &_items[o1]; + forceLoop = false; + if (id != -1 || type != -1) { + if (((id != -1) || (id == -1 && type != itm->type)) && ((type != -1) || (type == -1 && id != o1))) + continue; + } + + if (!includeFlyingItems) { + if (itm->pos > 3 && itm->pos < 8) + continue; + } + + if (!count) + return o1; + + res++; + } + + return res; +} + +int EoBCoreEngine::getQueuedItem(Item *items, int pos, int id) { + Item o1 = *items; + Item o2 = o1; + + if (!o1) + return 0; + + EoBItem *itm = &_items[o1]; + + for (bool forceLoop = true; o1 != o2 || forceLoop; o1 = itm->prev) { + itm = &_items[o1]; + forceLoop = false; + if ((id != -1 || (id == -1 && itm->pos != pos)) && id != o1) + continue; + + Item n = itm->next; + Item p = itm->prev; + _items[n].prev = p; + _items[p].next = n; + itm->next = itm->prev = itm->block = 0; + itm->level = 0; + if (o1 == *items) + *items = p; + if (o1 == *items) + *items = 0; + + return o1; + } + + return 0; +} + +void EoBCoreEngine::printFullItemName(Item item) { + EoBItem *itm = &_items[item]; + const char *nameUnid = _itemNames[itm->nameUnid]; + const char *nameId = _itemNames[itm->nameId]; + uint8 f = _itemTypes[itm->type].extraProperties & 0x7F; + int8 v = itm->value; + + const char *tstr2 = 0; + const char *tstr3 = 0; + + bool correctSuffixCase = false; + + Common::String tmpString; + + if ((itm->flags & 0x40) && !strlen(nameId)) { + switch (f) { + case 0: + case 1: + case 2: + case 3: + if (v == 0) + tmpString = nameUnid; + else if (v < 0) + tmpString = _flags.gameID == GI_EOB1 ? Common::String::format(_cursedString[0], nameUnid, v) : Common::String::format(_cursedString[0], v, nameUnid); + else + tmpString = _flags.gameID == GI_EOB1 ? Common::String::format(_enchantedString[0], nameUnid, v) : Common::String::format(_enchantedString[0], v, nameUnid); + break; + + case 9: + tstr2 = _magicObjectStrings[0]; + tstr3 = _spells[v].name; + correctSuffixCase = true; + break; + + case 10: + tstr2 = _magicObjectStrings[1]; + tstr3 = _spells[_flags.gameID == GI_EOB1 ? (_clericSpellOffset + v) : v].name; + correctSuffixCase = true; + break; + + case 14: + tstr2 = _magicObjectStrings[3]; + if (_flags.gameID == GI_EOB1) + v--; + tstr3 = _suffixStringsPotions[v]; + break; + + case 16: + tstr2 = _magicObjectStrings[2]; + tstr3 = _suffixStringsRings[v]; + break; + + case 18: + if (_flags.gameID == GI_EOB2 && v == 5) { + if (_flags.lang == Common::DE_DEU) + tstr2 = _magicObjectString5[0]; + else + tstr3 = _magicObjectString5[0]; + correctSuffixCase = true; + } else { + tstr2 = _magicObjectStrings[4]; + } + tstr3 = _suffixStringsWands[v]; + break; + + default: + tmpString = nameUnid; + break; + } + + + if (tstr3) { + if (!tstr2) { + tmpString = tstr3; + } else { + if (correctSuffixCase) { + if (tstr2 == _magicObjectString5[0]) + tmpString = Common::String::format(_patternGrFix2[0], tstr2, tstr3); + else + tmpString = Common::String::format(_patternGrFix1[0], tstr2, tstr3); + } else { + tmpString = Common::String::format(_patternSuffix[0], tstr2, tstr3); + } + } + } + } else { + tmpString = (itm->flags & 0x40) ? nameId : nameUnid; + } + + _txt->printMessage(convertAsciiToSjis(tmpString).c_str()); +} + +void EoBCoreEngine::identifyQueuedItems(Item itemQueue) { + if (!itemQueue) + return; + + Item first = itemQueue; + do { + _items[itemQueue].flags |= 0x40; + itemQueue = _items[itemQueue].prev; + + } while (first != itemQueue); +} + +void EoBCoreEngine::drawItemIconShape(int pageNum, Item itemId, int x, int y) { + int icn = _items[itemId].icon; + bool applyBluePal = ((_partyEffectFlags & 2) && (_items[itemId].flags & 0x80)) ? true : false; + const uint8 *ovl = 0; + + if (applyBluePal) { + if (_flags.gameID == GI_EOB1) { + ovl = (_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icn << 4]; + } else { + _screen->setFadeTable(_lightBlueFadingTable); + _screen->setShapeFadingLevel(1); + } + } + + _screen->drawShape(pageNum, _itemIconShapes[icn], x, y, 0, ovl ? 2 : 0, ovl); + + if (applyBluePal) { + _screen->setFadeTable(_greyFadingTable); + _screen->setShapeFadingLevel(0); + } +} + +bool EoBCoreEngine::isMagicEffectItem(Item itemIndex) { + return (itemIndex > 10 && itemIndex < 18); +} + +bool EoBCoreEngine::checkInventoryForRings(int charIndex, int itemValue) { + for (int i = 25; i <= 26; i++) { + int itm = _characters[charIndex].inventory[i]; + if (itm && _items[itm].type == 47 && _items[itm].value == itemValue) + return true; + } + return false; +} + +void EoBCoreEngine::eatItemInHand(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + if (!testCharacter(charIndex, 5)) { + _txt->printMessage(_warningStrings[1], -1, c->name); + } else if (_itemInHand && _items[_itemInHand].type != 31 && !(_flags.gameID == GI_EOB1 && _items[_itemInHand].type == 49)) { + _txt->printMessage(_warningStrings[_flags.gameID == GI_EOB1 ? 2 : 3]); + } else if (_items[_itemInHand].value == -1) { + printWarning(_warningStrings[2]); + } else { + c->food += _items[_itemInHand].value; + if (c->food > 100) + c->food = 100; + + _items[_itemInHand].block = -1; + setHandItem(0); + gui_drawFoodStatusGraph(charIndex); + _screen->updateScreen(); + snd_playSoundEffect(9); + } +} + +bool EoBCoreEngine::launchObject(int charIndex, Item item, uint16 startBlock, int startPos, int dir, int type) { + EoBFlyingObject *t = _flyingObjects; + int slot = 0; + for (; slot < 10; slot++) { + if (!t->enable) + break; + t++; + } + + if (slot == 10) + return false; + + setItemPosition((Item *)&_levelBlockProperties[startBlock].drawObjects, startBlock, item, startPos | 4); + + t->enable = 1; + t->starting = 1; + t->flags = 0; + t->direction = dir; + t->distance = 12; + t->curBlock = startBlock; + t->curPos = startPos; + t->item = item; + t->objectType = type; + t->attackerId = charIndex; + t->callBackIndex = 0; + + snd_playSoundEffect(type == 7 ? 26 : 11); + return true; +} + +void EoBCoreEngine::launchMagicObject(int charIndex, int type, uint16 startBlock, int startPos, int dir) { + EoBFlyingObject *t = _flyingObjects; + int slot = 0; + for (; slot < 10; slot++) { + if (!t->enable) + break; + t++; + } + + if (slot == 10) + return; + + t->enable = 2; + t->starting = 1; + t->flags = _magicFlightObjectProperties[(type << 2) + 2]; + t->direction = dir; + t->distance = _magicFlightObjectProperties[(type << 2) + 1]; + t->curBlock = startBlock; + t->curPos = startPos; + t->item = type; + t->objectType = _magicFlightObjectProperties[(type << 2) + 3]; + t->attackerId = charIndex; + t->callBackIndex = _magicFlightObjectProperties[type << 2]; + _sceneUpdateRequired = true; +} + +bool EoBCoreEngine::updateObjectFlight(EoBFlyingObject *fo, int block, int pos) { + uint8 wallFlags = _wllWallFlags[_levelBlockProperties[block].walls[fo->direction ^ 2]]; + if (fo->enable == 1) { + if ((wallFlags & 1) || (fo->starting) || ((wallFlags & 2) && (_dscItemShapeMap[_items[fo->item].icon] >= 15))) { + getQueuedItem((Item *)&_levelBlockProperties[fo->curBlock].drawObjects, 0, fo->item); + setItemPosition((Item *)&_levelBlockProperties[block].drawObjects, block, fo->item, pos | 4); + fo->curBlock = block; + fo->curPos = pos; + fo->distance--; + return true; + + } else { + _clickedSpecialFlag = 0x10; + specialWallAction(block, fo->direction); + return false; + } + + } else { + if (!(wallFlags & 1) && (fo->curBlock != block)) + return false; + fo->curBlock = block; + fo->curPos = pos; + if (fo->distance != 255) + fo->distance--; + } + return true; +} + +bool EoBCoreEngine::updateFlyingObjectHitTest(EoBFlyingObject *fo, int block, int pos) { + if (fo->starting && (fo->curBlock != _currentBlock || fo->attackerId >= 0) && (!blockHasMonsters(fo->curBlock) || fo->attackerId < 0)) + return false; + + if (fo->enable == 2) { + if (fo->callBackIndex) + return (this->*_spells[fo->callBackIndex].endCallback)(fo); + } + + if (blockHasMonsters(block)) { + for (int i = 0; i < 30; i++) { + if (!isMonsterOnPos(&_monsters[i], block, pos, 1)) + continue; + if (flyingObjectMonsterHit(fo, i)) + return true; + } + + } else if (block == _currentBlock) { + return flyingObjectPartyHit(fo); + } + + return false; +} + +void EoBCoreEngine::explodeObject(EoBFlyingObject *fo, int block, Item item) { + if (_partyResting) { + snd_processEnvironmentalSoundEffect(35, _currentBlock); + return; + } + + const uint8 *table = (_expObjectTblIndex[item] == 0) ? _expObjectAnimTbl1 : ((_expObjectTblIndex[item] == 1) ? _expObjectAnimTbl2 : _expObjectAnimTbl3); + int tableSize = (_expObjectTblIndex[item] == 0) ? _expObjectAnimTbl1Size : ((_expObjectTblIndex[item] == 1) ? _expObjectAnimTbl2Size : _expObjectAnimTbl3Size); + + int tl = 0; + for (; tl < 18; tl++) { + if (_visibleBlockIndex[tl] == block) + break; + } + + if (tl == 18) + return; + + int b = _expObjectTlMode ? _expObjectTlMode[tl] : 2; + + uint8 fdr = fo ? fo->direction : 0; + if (b == 0 || (b == 1 && (fdr & 1) == (_currentDirection & 1))) { + snd_processEnvironmentalSoundEffect(35, _currentBlock); + return; + } + + uint8 dm = _dscDimMap[tl]; + int16 x1 = 0; + int16 x2 = 0; + + setLevelShapesDim(tl, x1, x2, 5); + + if (x2 < x1) + return; + + if (fo) + fo->enable = 0; + + drawScene(1); + + if (fo) + fo->enable = 2; + + _screen->fillRect(0, 0, 176, 120, 0, 2); + uint8 col = _screen->getPagePixel(2, 0, 0); + drawSceneShapes(_expObjectShpStart[dm]); + + setLevelShapesDim(tl, x1, x2, 5); + _screen->updateScreen(); + + _screen->setGfxParameters(_dscShapeCoords[(tl * 5 + 4) << 1] + 88, 48, col); + snd_processEnvironmentalSoundEffect(35, _currentBlock); + + disableSysTimer(2); + if (dm == 0) { + _screen->drawExplosion(3, 147, 35, 20, 7, table, tableSize); + } else if (dm == 1) { + _screen->drawExplosion(2, 147, 35, 20, 7, table, tableSize); + } else if (dm == 2) { + _screen->drawExplosion(1, 147, 35, 20, 7, table, tableSize); + } else if (dm == 3) { + _screen->drawExplosion(0, 460, 50, 20, 4, table, tableSize); + } + enableSysTimer(2); +} + +void EoBCoreEngine::endObjectFlight(EoBFlyingObject *fo) { + if (fo->enable == 1) { + _items[fo->item].pos &= 3; + runLevelScript(fo->curBlock, 4); + updateEnvironmentalSfx(18); + } + memset(fo, 0, sizeof(EoBFlyingObject)); +} + +void EoBCoreEngine::checkFlyingObjects() { + if (!_runFlag) + return; + + for (int i = 0; i < 10; i++) { + EoBFlyingObject *fo = &_flyingObjects[i]; + if (!fo->enable) + continue; + if (updateFlyingObjectHitTest(fo, fo->curBlock, fo->curPos)) + endObjectFlight(fo); + } +} + +void EoBCoreEngine::reloadWeaponSlot(int charIndex, int slotIndex, int itemType, int arrowOrDagger) { + if (arrowOrDagger && _characters[charIndex].inventory[16]) { + _characters[charIndex].inventory[slotIndex] = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1); + } else { + for (int i = 24; i >= 22; i--) { + if (!_characters[charIndex].inventory[i]) + continue; + if (_items[_characters[charIndex].inventory[i]].type == itemType && itemType != -1) + continue; + _characters[charIndex].inventory[slotIndex] = _characters[charIndex].inventory[i]; + _characters[charIndex].inventory[i] = 0; + return; + } + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/items_hof.cpp b/engines/kyra/engine/items_hof.cpp new file mode 100644 index 0000000000..dd53882cb9 --- /dev/null +++ b/engines/kyra/engine/items_hof.cpp @@ -0,0 +1,424 @@ +/* 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 "kyra/engine/kyra_hof.h" + +#include "common/system.h" + +namespace Kyra { + +int KyraEngine_HoF::checkItemCollision(int x, int y) { + int itemPos = -1, yPos = -1; + + for (int i = 0; i < 30; ++i) { + const ItemDefinition &curItem = _itemList[i]; + + if (curItem.id == kItemNone || curItem.sceneId != _mainCharacter.sceneId) + continue; + + int itemX1 = curItem.x - 8 - 3; + int itemX2 = curItem.x + 7 + 3; + + if (x < itemX1 || x > itemX2) + continue; + + int itemY1 = curItem.y - _itemHtDat[curItem.id] - 3; + int itemY2 = curItem.y + 3; + + if (y < itemY1 || y > itemY2) + continue; + + if (curItem.y >= yPos) { + itemPos = i; + yPos = curItem.y; + } + } + + return itemPos; +} + +void KyraEngine_HoF::updateWaterFlasks() { + for (int i = 22; i < 24; i++) { + if (_itemInHand == i) + setHandItem(i - 1); + + for (int ii = 0; ii < 20; ii++) { + if (_mainCharacter.inventory[ii] == i) { + _mainCharacter.inventory[ii]--; + if (ii < 10) { + clearInventorySlot(ii, 0); + _screen->drawShape(0, getShapePtr(i + 63), _inventoryX[ii], _inventoryY[ii], 0, 0); + } + } + } + + for (int ii = 0; ii < 30; ii++) { + if (_itemList[ii].id == i) + _itemList[ii].id--; + } + } +} + +bool KyraEngine_HoF::dropItem(int unk1, Item item, int x, int y, int unk2) { + if (_mouseState <= -1) + return false; + + bool success = processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2); + if (!success) { + snd_playSoundEffect(0x0D); + if (countAllItems() >= 30) + showMessageFromCCode(5, 0x84, 0); + } + + return success; +} + +bool KyraEngine_HoF::processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2) { + int itemPos = checkItemCollision(x, y); + + if (unk1) + itemPos = -1; + + if (itemPos >= 0) { + exchangeMouseItem(itemPos); + return false; + } + + int freeItemSlot = -1; + + if (unk1 != 3) { + for (int i = 0; i < 30; ++i) { + if (_itemList[i].id == kItemNone) { + freeItemSlot = i; + break; + } + } + } + + if (freeItemSlot == -1) + return false; + + if (sceneId != _mainCharacter.sceneId) { + _itemList[freeItemSlot].x = x; + _itemList[freeItemSlot].y = y; + _itemList[freeItemSlot].id = item; + _itemList[freeItemSlot].sceneId = sceneId; + return true; + } + + int itemHeight = _itemHtDat[item]; + + // no idea why it's '&&' here and not single checks for x and y + if (x == -1 && y == -1) { + x = _rnd.getRandomNumberRng(0x10, 0x130); + y = _rnd.getRandomNumberRng(0x10, 0x87); + } + + int posX = x, posY = y; + int itemX = -1, itemY = -1; + bool needRepositioning = true; + + while (needRepositioning) { + if ((_screen->getDrawLayer(posX, posY) <= 1 && _screen->getDrawLayer2(posX, posY, itemHeight) <= 1 && isDropable(posX, posY)) || posY == 136) { + int posX2 = posX, posX3 = posX; + bool repositioning = true; + + while (repositioning) { + if (isDropable(posX3, posY) && _screen->getDrawLayer(posX3, posY) < 7 && checkItemCollision(posX3, posY) == -1) { + itemX = posX3; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (isDropable(posX2, posY) && _screen->getDrawLayer(posX2, posY) < 7 && checkItemCollision(posX2, posY) == -1) { + itemX = posX2; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (repositioning) { + posX3 = MAX(posX3 - 2, 16); + posX2 = MIN(posX2 + 2, 304); + + if (posX3 <= 16 && posX2 >= 304) + repositioning = false; + } + } + } + + if (posY == 136) + needRepositioning = false; + else + posY = MIN(posY + 2, 136); + } + + if (itemX == -1 || itemY == -1) + return false; + + if (unk1 == 3) { + _itemList[freeItemSlot].x = itemX; + _itemList[freeItemSlot].y = itemY; + return true; + } else if (unk1 == 2) { + itemDropDown(x, y, itemX, itemY, freeItemSlot, item); + } + + if (!unk1) + removeHandItem(); + + itemDropDown(x, y, itemX, itemY, freeItemSlot, item); + + if (!unk1 && unk2) { + int itemStr = 3; + if (_lang == 1) + itemStr = getItemCommandStringDrop(item); + updateCommandLineEx(item+54, itemStr, 0xD6); + } + + return true; +} + +void KyraEngine_HoF::itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item) { + uint8 *itemShape = getShapePtr(item + 64); + + if (startX == dstX && startY == dstY) { + if (_layerFlagTable[_screen->getLayer(dstX, dstY)] && item != 13) { + updateCharFacing(); + snd_playSoundEffect(0x2D); + removeHandItem(); + objectChat(getTableString(0xFF, _cCodeBuffer, 1), 0, 0x83, 0xFF); + } else { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C); + addItemToAnimList(itemSlot); + } + } else { + _screen->hideMouse(); + + if (startY <= dstY) { + int speed = 2; + int curY = startY; + int curX = startX - 8; + + backUpGfxRect24x24(curX, curY-16); + while (curY < dstY) { + restoreGfxRect24x24(curX, curY-16); + + curY = MIN(curY + speed, dstY); + ++speed; + + backUpGfxRect24x24(curX, curY-16); + uint32 endDelay = _system->getMillis() + _tickLength; + + _screen->drawShape(0, itemShape, curX, curY-16, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay, false, true); + } + + if (dstX != dstY || (dstY - startY > 16)) { + snd_playSoundEffect(0x69); + speed = MAX(speed, 6); + int speedX = ((dstX - startX) << 4) / speed; + int origSpeed = speed; + speed >>= 1; + + if (dstY - startY <= 8) + speed >>= 1; + + speed = -speed; + + curX = startX << 4; + + int x = 0, y = 0; + while (--origSpeed) { + x = (curX >> 4) - 8; + y = curY - 16; + + restoreGfxRect24x24(x, y); + curY = MIN(curY + speed, dstY); + curX += speedX; + ++speed; + + x = (curX >> 4) - 8; + y = curY - 16; + backUpGfxRect24x24(x, y); + + uint16 endDelay = _system->getMillis() + _tickLength; + _screen->drawShape(0, itemShape, x, y, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay, false, true); + } + + restoreGfxRect24x24(x, y); + } else { + restoreGfxRect24x24(curX, curY-16); + } + } + + if (_layerFlagTable[_screen->getLayer(dstX, dstY)] && item != 13) { + updateCharFacing(); + snd_playSoundEffect(0x2D); + removeHandItem(); + _screen->showMouse(); + objectChat(getTableString(0xFF, _cCodeBuffer, 1), 0, 0x83, 0xFF); + } else { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C); + addItemToAnimList(itemSlot); + _screen->showMouse(); + } + } +} + +void KyraEngine_HoF::exchangeMouseItem(int itemPos) { + deleteItemAnimEntry(itemPos); + + int itemId = _itemList[itemPos].id; + _itemList[itemPos].id = _itemInHand; + _itemInHand = itemId; + + addItemToAnimList(itemPos); + snd_playSoundEffect(0x0B); + setMouseCursor(_itemInHand); + int str2 = 7; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateCommandLineEx(itemId + 54, str2, 0xD6); + + runSceneScript6(); +} + +bool KyraEngine_HoF::pickUpItem(int x, int y) { + int itemPos = checkItemCollision(x, y); + + if (itemPos <= -1) + return false; + + if (_itemInHand >= 0) { + exchangeMouseItem(itemPos); + } else { + deleteItemAnimEntry(itemPos); + int itemId = _itemList[itemPos].id; + _itemList[itemPos].id = kItemNone; + snd_playSoundEffect(0x0B); + setMouseCursor(itemId); + int str2 = 7; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateCommandLineEx(itemId + 54, str2, 0xD6); + _itemInHand = itemId; + + runSceneScript6(); + } + + return true; +} + +bool KyraEngine_HoF::isDropable(int x, int y) { + if (x < 14 || x > 304 || y < 14 || y > 136) + return false; + + x -= 8; + y -= 1; + + for (int xpos = x; xpos < x + 16; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return false; + } + + return true; +} + +int KyraEngine_HoF::getItemCommandStringDrop(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int dropStringIds[] = { + 0x2D, 0x103, 0x003, 0x106 + }; + assert(stringId < ARRAYSIZE(dropStringIds)); + + return dropStringIds[stringId]; +} + +int KyraEngine_HoF::getItemCommandStringPickUp(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int pickUpStringIds[] = { + 0x02B, 0x102, 0x007, 0x105 + }; + assert(stringId < ARRAYSIZE(pickUpStringIds)); + + return pickUpStringIds[stringId]; +} + +int KyraEngine_HoF::getItemCommandStringInv(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int pickUpStringIds[] = { + 0x02C, 0x104, 0x008, 0x107 + }; + assert(stringId < ARRAYSIZE(pickUpStringIds)); + + return pickUpStringIds[stringId]; +} + +bool KyraEngine_HoF::itemIsFlask(Item item) { + for (int i = 0; _flaskTable[i] != kItemNone; ++i) { + if (_flaskTable[i] == item) + return true; + } + + return false; +} + +void KyraEngine_HoF::setMouseCursor(Item item) { + int shape = 0; + int hotX = 1; + int hotY = 1; + + if (item != kItemNone) { + hotX = 8; + hotY = 15; + shape = item+64; + } + + _screen->setMouseCursor(hotX, hotY, getShapePtr(shape)); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_lok.cpp b/engines/kyra/engine/items_lok.cpp new file mode 100644 index 0000000000..5927ba0060 --- /dev/null +++ b/engines/kyra/engine/items_lok.cpp @@ -0,0 +1,960 @@ +/* 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 "kyra/engine/kyra_lok.h" +#include "kyra/graphics/animator_lok.h" + +#include "common/system.h" + +namespace Kyra { + +int KyraEngine_LoK::findDuplicateItemShape(int shape) { + static const uint8 dupTable[] = { + 0x48, 0x46, 0x49, 0x47, 0x4A, 0x46, 0x4B, 0x47, + 0x4C, 0x46, 0x4D, 0x47, 0x5B, 0x5A, 0x5C, 0x5A, + 0x5D, 0x5A, 0x5E, 0x5A, 0xFF, 0xFF + }; + + int i = 0; + + while (dupTable[i] != 0xFF) { + if (dupTable[i] == shape) + return dupTable[i + 1]; + i += 2; + } + return -1; +} + +void KyraEngine_LoK::addToNoDropRects(int x, int y, int w, int h) { + for (int rect = 0; rect < ARRAYSIZE(_noDropRects); ++rect) { + if (_noDropRects[rect].top == -1) { + _noDropRects[rect].left = x; + _noDropRects[rect].top = y; + _noDropRects[rect].right = x + w; + _noDropRects[rect].bottom = y + h; + break; + } + } +} + +void KyraEngine_LoK::clearNoDropRects() { + memset(_noDropRects, -1, sizeof(_noDropRects)); +} + +byte KyraEngine_LoK::findFreeItemInScene(int scene) { + assert(scene < _roomTableSize); + Room *room = &_roomTable[scene]; + + for (int i = 0; i < 12; ++i) { + if (room->itemsTable[i] == kItemNone) + return i; + } + + return 0xFF; +} + +byte KyraEngine_LoK::findItemAtPos(int x, int y) { + assert(_currentCharacter->sceneId < _roomTableSize); + const int8 *itemsTable = _roomTable[_currentCharacter->sceneId].itemsTable; + const uint16 *xposOffset = _roomTable[_currentCharacter->sceneId].itemsXPos; + const uint8 *yposOffset = _roomTable[_currentCharacter->sceneId].itemsYPos; + + int highestYPos = -1; + Item returnValue = kItemNone; + + for (int i = 0; i < 12; ++i) { + if (*itemsTable != kItemNone) { + int xpos = *xposOffset - 11; + int xpos2 = *xposOffset + 10; + if (x > xpos && x < xpos2) { + assert(*itemsTable >= 0); + int itemHeight = _itemHtDat[*itemsTable]; + int ypos = *yposOffset + 3; + int ypos2 = ypos - itemHeight - 3; + + if (y > ypos2 && ypos > y) { + if (highestYPos <= ypos) { + returnValue = i; + highestYPos = ypos; + } + } + } + } + + ++xposOffset; + ++yposOffset; + ++itemsTable; + } + + return returnValue; +} + +void KyraEngine_LoK::placeItemInGenericMapScene(int item, int index) { + static const uint16 itemMapSceneMinTable[] = { + 0x0000, 0x0011, 0x006D, 0x0025, 0x00C7, 0x0000 + }; + static const uint16 itemMapSceneMaxTable[] = { + 0x0010, 0x0024, 0x00C6, 0x006C, 0x00F5, 0x0000 + }; + + int minValue = itemMapSceneMinTable[index]; + int maxValue = itemMapSceneMaxTable[index]; + + while (true) { + int room = _rnd.getRandomNumberRng(minValue, maxValue); + assert(room < _roomTableSize); + int nameIndex = _roomTable[room].nameIndex; + bool placeItem = false; + + switch (nameIndex) { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 11: + case 12: case 16: case 17: case 20: + case 22: case 23: case 25: case 26: + case 27: case 31: case 33: case 34: + case 36: case 37: case 58: case 59: + case 60: case 61: case 83: case 84: + case 85: case 104: case 105: case 106: + placeItem = true; + break; + + case 51: + if (room != 46) + placeItem = true; + break; + + default: + break; + } + + if (placeItem) { + Room *roomPtr = &_roomTable[room]; + if (roomPtr->northExit == 0xFFFF && roomPtr->eastExit == 0xFFFF && roomPtr->southExit == 0xFFFF && roomPtr->westExit == 0xFFFF) + placeItem = false; + else if (_currentCharacter->sceneId == room) + placeItem = false; + } + + if (placeItem) { + if (!processItemDrop(room, item, -1, -1, 2, 0)) + continue; + break; + } + } +} + +void KyraEngine_LoK::setHandItem(Item item) { + setMouseItem(item); + _itemInHand = item; +} + +void KyraEngine_LoK::removeHandItem() { + _screen->setMouseCursor(1, 1, _shapes[0]); + _itemInHand = kItemNone; +} + +void KyraEngine_LoK::setMouseItem(Item item) { + if (item == kItemNone) + _screen->setMouseCursor(1, 1, _shapes[6]); + else + _screen->setMouseCursor(8, 15, _shapes[216 + item]); +} + +void KyraEngine_LoK::wipeDownMouseItem(int xpos, int ypos) { + if (_itemInHand == kItemNone) + return; + + xpos -= 8; + ypos -= 15; + _screen->hideMouse(); + backUpItemRect1(xpos, ypos); + int y = ypos; + int height = 16; + + while (height >= 0) { + restoreItemRect1(xpos, ypos); + _screen->setNewShapeHeight(_shapes[216 + _itemInHand], height); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], xpos, y, 0, 0); + _screen->updateScreen(); + y += 2; + height -= 2; + delayUntil(nextTime); + } + restoreItemRect1(xpos, ypos); + _screen->resetShapeHeight(_shapes[216 + _itemInHand]); + removeHandItem(); + _screen->showMouse(); +} + +void KyraEngine_LoK::setupSceneItems() { + uint16 sceneId = _currentCharacter->sceneId; + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + for (int i = 0; i < 12; ++i) { + uint8 item = currentRoom->itemsTable[i]; + if (item == 0xFF || !currentRoom->needInit[i]) + continue; + + int xpos = 0; + int ypos = 0; + + if (currentRoom->itemsXPos[i] == 0xFFFF) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + } else { + xpos = currentRoom->itemsXPos[i]; + ypos = currentRoom->itemsYPos[i]; + } + + _lastProcessedItem = i; + + int stop = 0; + while (!stop) { + stop = processItemDrop(sceneId, item, xpos, ypos, 3, 0); + if (!stop) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + if (countItemsInScene(sceneId) >= 12) + break; + } else { + currentRoom->needInit[i] = 0; + } + } + } +} + +int KyraEngine_LoK::countItemsInScene(uint16 sceneId) { + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int items = 0; + + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] != kItemNone) + ++items; + } + + return items; +} + +int KyraEngine_LoK::processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2) { + int freeItem = -1; + uint8 itemIndex = findItemAtPos(x, y); + if (unk1) + itemIndex = 0xFF; + + if (itemIndex != 0xFF) { + exchangeItemWithMouseItem(sceneId, itemIndex); + return 0; + } + + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + if (unk1 != 3) { + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] == kItemNone) { + freeItem = i; + break; + } + } + } else { + freeItem = _lastProcessedItem; + } + + if (freeItem == -1) + return 0; + + if (sceneId != _currentCharacter->sceneId) { + addItemToRoom(sceneId, item, freeItem, x, y); + return 1; + } + + int itemHeight = _itemHtDat[item]; + _lastProcessedItemHeight = itemHeight; + + if (x == -1) + x = _rnd.getRandomNumberRng(16, 304); + + if (y == -1) + y = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 135); + + int xpos = x; + int ypos = y; + int destY = -1; + int destX = -1; + int running = 1; + + while (running) { + if ((_northExitHeight & 0xFF) <= ypos) { + bool running2 = true; + + if (_screen->getDrawLayer(xpos, ypos) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + if (_screen->getDrawLayer2(xpos, ypos, itemHeight) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + if (!isDropable(xpos, ypos)) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + int xpos2 = xpos; + int xpos3 = xpos; + + while (running2) { + if (isDropable(xpos2, ypos)) { + if (_screen->getDrawLayer2(xpos2, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos2, ypos) == 0xFF) { + destX = xpos2; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (isDropable(xpos3, ypos)) { + if (_screen->getDrawLayer2(xpos3, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos3, ypos) == 0xFF) { + destX = xpos3; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (!running2) + continue; + + xpos2 -= 2; + if (xpos2 < 16) + xpos2 = 16; + + xpos3 += 2; + if (xpos3 > 304) + xpos3 = 304; + + if (xpos2 > 16) + continue; + if (xpos3 < 304) + continue; + running2 = false; + } + } + + if (((_northExitHeight >> 8) & 0xFF) == ypos) { + running = 0; + destY -= _rnd.getRandomNumberRng(0, 3); + + if ((_northExitHeight & 0xFF) < destY) + continue; + + destY = (_northExitHeight & 0xFF) + 1; + continue; + } + ypos += 2; + if (((_northExitHeight >> 8) & 0xFF) >= ypos) + continue; + ypos = (_northExitHeight >> 8) & 0xFF; + } + + if (destX == -1 || destY == -1) + return 0; + + if (unk1 == 3) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + return 1; + } + + if (unk1 == 2) + itemSpecialFX(x, y, item); + + if (unk1 == 0) + removeHandItem(); + + itemDropDown(x, y, destX, destY, freeItem, item); + + if (unk1 == 0 && unk2 != 0) { + assert(_itemList && _droppedList); + updateSentenceCommand(_itemList[getItemListIndex(item)], _droppedList[0], 179); + } + + return 1; +} + +void KyraEngine_LoK::exchangeItemWithMouseItem(uint16 sceneId, int itemIndex) { + _animator->animRemoveGameItem(itemIndex); + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int item = currentRoom->itemsTable[itemIndex]; + currentRoom->itemsTable[itemIndex] = _itemInHand; + _itemInHand = item; + _animator->animAddGameItem(itemIndex, sceneId); + snd_playSoundEffect(53); + + setMouseItem(_itemInHand); + assert(_itemList && _takenList); + if (_flags.platform == Common::kPlatformAmiga) + updateSentenceCommand(_itemList[getItemListIndex(_itemInHand)], _takenList[0], 179); + else + updateSentenceCommand(_itemList[getItemListIndex(_itemInHand)], _takenList[1], 179); + clickEventHandler2(); +} + +void KyraEngine_LoK::addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y) { + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + currentRoom->itemsTable[itemIndex] = item; + currentRoom->itemsXPos[itemIndex] = x; + currentRoom->itemsYPos[itemIndex] = y; + currentRoom->needInit[itemIndex] = 1; +} + +int KyraEngine_LoK::checkNoDropRects(int x, int y) { + if (_lastProcessedItemHeight < 1 || _lastProcessedItemHeight > 16) + _lastProcessedItemHeight = 16; + if (_noDropRects[0].left == -1) + return 0; + + for (int i = 0; i < ARRAYSIZE(_noDropRects); ++i) { + if (_noDropRects[i].left == -1) + break; + + int xpos = _noDropRects[i].left; + int ypos = _noDropRects[i].top; + int xpos2 = _noDropRects[i].right; + int ypos2 = _noDropRects[i].bottom; + + if (xpos > x + 16) + continue; + if (xpos2 <= x) + continue; + if (y < ypos) + continue; + if (ypos2 <= y - _lastProcessedItemHeight) + continue; + return 1; + } + + return 0; +} + +int KyraEngine_LoK::isDropable(int x, int y) { + x -= 8; + y -= 1; + + if (checkNoDropRects(x, y)) + return 0; + + for (int xpos = x; xpos < x + 16; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return 0; + } + return 1; +} + +void KyraEngine_LoK::itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item) { + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + if (x == destX && y == destY) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + return; + } + _screen->hideMouse(); + if (y <= destY) { + int tempY = y; + int addY = 2; + int drawX = x - 8; + int drawY = 0; + + backUpItemRect0(drawX, y - 16); + + while (tempY < destY) { + restoreItemRect0(drawX, tempY - 16); + tempY += addY; + if (tempY > destY) + tempY = destY; + ++addY; + drawY = tempY - 16; + backUpItemRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], drawX, drawY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + + bool skip = false; + if (x == destX) { + if (destY - y <= 16) + skip = true; + } + + if (!skip) { + snd_playSoundEffect(0x47); + if (addY < 6) + addY = 6; + + int xDiff = (destX - x) << 4; + xDiff /= addY; + int startAddY = addY; + addY >>= 1; + if (destY - y <= 8) + addY >>= 1; + addY = -addY; + int unkX = x << 4; + while (--startAddY) { + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + restoreItemRect0(drawX, drawY); + tempY += addY; + unkX += xDiff; + if (tempY > destY) + tempY = destY; + ++addY; + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + backUpItemRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], drawX, drawY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(drawX, drawY); + } else { + restoreItemRect0(drawX, tempY - 16); + } + } + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + _screen->showMouse(); +} + +void KyraEngine_LoK::dropItem(int unk1, int item, int x, int y, int unk2) { + if (processItemDrop(_currentCharacter->sceneId, item, x, y, unk1, unk2)) + return; + snd_playSoundEffect(54); + + // Old floppy versions don't print warning messages and don't have the necessary string resources. + // These versions will only play the warning sound effect. + if (_flags.isOldFloppy && !_noDropList) + return; + + assert(_noDropList); + + if (12 == countItemsInScene(_currentCharacter->sceneId)) + drawSentenceCommand(_noDropList[0], 6); + else + drawSentenceCommand(_noDropList[1], 6); +} + +void KyraEngine_LoK::itemSpecialFX(int x, int y, int item) { + if (item == 41) + itemSpecialFX1(x, y, item); + else + itemSpecialFX2(x, y, item); +} + +void KyraEngine_LoK::itemSpecialFX1(int x, int y, int item) { + uint8 *shape = _shapes[216 + item]; + x -= 8; + int startY = y; + y -= 15; + _screen->hideMouse(); + backUpItemRect0(x, y); + for (int i = 1; i <= 16; ++i) { + _screen->setNewShapeHeight(shape, i); + --startY; + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, shape, x, startY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(x, y); + _screen->showMouse(); +} + +void KyraEngine_LoK::itemSpecialFX2(int x, int y, int item) { + x -= 8; + y -= 15; + int yAdd = (int8)(((16 - _itemHtDat[item]) >> 1) & 0xFF); + backUpItemRect0(x, y); + if (item >= 80 && item <= 89) + snd_playSoundEffect(55); + + for (int i = 201; i <= 205; ++i) { + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + + for (int i = 204; i >= 201; --i) { + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], x, y, 0, 0); + _screen->drawShape(0, _shapes[i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(x, y); +} + +void KyraEngine_LoK::magicOutMouseItem(int animIndex, int itemPos) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + + if (itemPos == kItemNone) { + Common::Point mouse = getMousePos(); + x = mouse.x - 12; + y = mouse.y - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosY[itemPos] - 3; + } + + if (_itemInHand == kItemNone && itemPos == -1) + return; + + int tableIndex = 0, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } else { + tableIndex = -1; + } + + if (animIndex == 2) + snd_playSoundEffect(0x5E); + else + snd_playSoundEffect(0x37); + _screen->hideMouse(); + backUpItemRect1(x, y); + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + + if (itemPos != -1) { + restoreItemRect1(x, y); + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, 0); + backUpItemRect1(x, y); + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect1(x, y); + + if (itemPos == -1) { + _screen->setMouseCursor(1, 1, _shapes[0]); + _itemInHand = kItemNone; + } else { + _characterList[0].inventoryItems[itemPos] = kItemNone; + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, 0); + } + + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine_LoK::magicInMouseItem(int animIndex, int item, int itemPos) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + if (itemPos == -1) { + Common::Point mouse = getMousePos(); + x = mouse.x - 12; + y = mouse.y - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosX[itemPos] - 3; + } + if (item < 0) + return; + + int tableIndex = -1, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } + + _screen->hideMouse(); + backUpItemRect1(x, y); + if (animIndex == 2) + snd_playSoundEffect(0x5E); + else + snd_playSoundEffect(0x37); + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect1(x, y); + if (itemPos == -1) { + _screen->setMouseCursor(8, 15, _shapes[216 + item]); + _itemInHand = item; + } else { + _characterList[0].inventoryItems[itemPos] = item; + _screen->drawShape(0, _shapes[216 + item], _itemPosX[itemPos], _itemPosY[itemPos], 0, 0); + } + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine_LoK::specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops) { + static const uint8 table1[] = { + 0x23, 0x45, 0x55, 0x72, 0x84, 0xCF, 0x00, 0x00 + }; + static const uint8 table2[] = { + 0x73, 0xB5, 0x80, 0x21, 0x13, 0x39, 0x45, 0x55, 0x62, 0xB4, 0xCF, 0xD8 + }; + static const uint8 table3[] = { + 0x7C, 0xD0, 0x74, 0x84, 0x87, 0x00, 0x00, 0x00 + }; + int tableValue = 0; + if (animIndex == 0) + tableValue = table1[tableIndex]; + else if (animIndex == 1) + tableValue = table2[tableIndex]; + else if (animIndex == 2) + tableValue = table3[tableIndex]; + else + return; + processSpecialMouseItemFX(shape, x, y, tableValue, loopStart, maxLoops); +} + +void KyraEngine_LoK::processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops) { + uint8 shapeColorTable[16]; + uint8 *shapePtr = _shapes[shape] + 10; + if (_flags.useAltShapeHeader) + shapePtr += 2; + + for (int i = 0; i < 16; ++i) + shapeColorTable[i] = shapePtr[i]; + + for (int i = loopStart; i < loopStart + maxLoops; ++i) { + for (int i2 = 0; i2 < 16; ++i2) { + if (shapePtr[i2] == i) + shapeColorTable[i2] = (i + tableValue) - loopStart; + } + } + _screen->drawShape(0, _shapes[shape], x, y, 0, 0x8000, shapeColorTable); +} + +void KyraEngine_LoK::updatePlayerItemsForScene() { + if (_itemInHand >= 29 && _itemInHand < 33) { + ++_itemInHand; + if (_itemInHand > 33) + _itemInHand = 33; + _screen->setMouseCursor(8, 15, _shapes[216 + _itemInHand]); + } + + bool redraw = false; + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item >= 29 && item < 33) { + ++item; + _currentCharacter->inventoryItems[i] = item; + redraw = true; + } + } + + if (redraw) { + redrawInventory(0); + } + + if (_itemInHand == 33) + magicOutMouseItem(2, -1); + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item == 33) + magicOutMouseItem(2, i); + } + _screen->showMouse(); +} + +void KyraEngine_LoK::redrawInventory(int page) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = page; + for (int i = 0; i < 10; ++i) { + _screen->fillRect(_itemPosX[i], _itemPosY[i], _itemPosX[i] + 15, _itemPosY[i] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, page); + + if (_currentCharacter->inventoryItems[i] != kItemNone) { + uint8 item = _currentCharacter->inventoryItems[i]; + _screen->drawShape(page, _shapes[216 + item], _itemPosX[i], _itemPosY[i], 0, 0); + } + } + _screen->_curPage = videoPageBackUp; + _screen->updateScreen(); +} + +void KyraEngine_LoK::backUpItemRect0(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 3 << 3, 24); + _screen->copyRegionToBuffer(_screen->_curPage, xpos, ypos, 3 << 3, 24, _itemBkgBackUp[0]); +} + +void KyraEngine_LoK::restoreItemRect0(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 3 << 3, 24); + _screen->copyBlockToPage(_screen->_curPage, xpos, ypos, 3 << 3, 24, _itemBkgBackUp[0]); +} + +void KyraEngine_LoK::backUpItemRect1(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 4 << 3, 32); + _screen->copyRegionToBuffer(_screen->_curPage, xpos, ypos, 4 << 3, 32, _itemBkgBackUp[1]); +} + +void KyraEngine_LoK::restoreItemRect1(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 4 << 3, 32); + _screen->copyBlockToPage(_screen->_curPage, xpos, ypos, 4 << 3, 32, _itemBkgBackUp[1]); +} + +int KyraEngine_LoK::getItemListIndex(Item item) { + if (_flags.platform != Common::kPlatformAmiga) + return item; + + // "Unknown item" is at 81. + if (item == kItemNone) + return 81; + // The first item names are mapped directly + else if (item <= 28) + return item; + // There's only one string for "Fireberries" + else if (item >= 29 && item <= 33) + return 29; + // Correct offsets + else if (item >= 34 && item <= 59) + return item - 4; + // There's only one string for "Red Potion" + else if (item >= 60 && item <= 61) + return 56; + // There's only one string for "Blue Potion" + else if (item >= 62 && item <= 63) + return 57; + // There's only one string for "Yellow Potion" + else if (item >= 64 && item <= 65) + return 58; + // Correct offsets + else if (item >= 66 && item <= 69) + return item - 7; + // There's only one string for "Fresh Water" + else if (item >= 70 && item <= 71) + return 63; + // There's only one string for "Salt Water" + else if (item >= 72 && item <= 73) + return 64; + // There's only one string for "Mineral Water" + else if (item >= 74 && item <= 75) + return 65; + // There's only one string for "Magical Water" + else if (item >= 76 && item <= 77) + return 66; + // There's only one string for "Empty Flask" + else if (item >= 78 && item <= 79) + return 67; + // There's only one string for "Scroll" + else if (item >= 80 && item <= 89) + return 68; + // There's only one string for "Parchment scrap" + else if (item >= 90 && item <= 94) + return 69; + // Correct offsets + else if (item >= 95) + return item - 25; + + // This should never happen, but still GCC warns about it. + return 81; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_lol.cpp b/engines/kyra/engine/items_lol.cpp new file mode 100644 index 0000000000..446650d6e1 --- /dev/null +++ b/engines/kyra/engine/items_lol.cpp @@ -0,0 +1,594 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" + +namespace Kyra { + +LoLObject *LoLEngine::findObject(uint16 index) { + if (index & 0x8000) + return &_monsters[index & 0x7FFF]; + else + return &_itemsInPlay[index]; +} + +int LoLEngine::calcObjectPosition(LoLObject *i, uint16 direction) { + int x = i->x; + int y = i->y; + + calcSpriteRelPosition(_partyPosX, _partyPosY, x, y, direction); + + if (y < 0) + y = 0; + + int res = (i->flyingHeight << 12); + res |= (4095 - y); + + return res; +} + +void LoLEngine::removeAssignedObjectFromBlock(LevelBlockProperty *l, uint16 id) { + uint16 *blockItemIndex = &l->assignedObjects; + LoLObject *i = 0; + + while (*blockItemIndex) { + if (*blockItemIndex == id) { + i = findObject(id); + *blockItemIndex = i->nextAssignedObject; + i->nextAssignedObject = 0; + return; + } + + i = findObject(*blockItemIndex); + blockItemIndex = &i->nextAssignedObject; + } +} + +void LoLEngine::removeDrawObjectFromBlock(LevelBlockProperty *l, uint16 id) { + uint16 *blockItemIndex = &l->drawObjects; + LoLObject *i = 0; + + while (*blockItemIndex) { + if (*blockItemIndex == id) { + i = findObject(id); + *blockItemIndex = i->nextDrawObject; + i->nextDrawObject = 0; + return; + } + + i = findObject(*blockItemIndex); + blockItemIndex = &i->nextDrawObject; + } +} + +void LoLEngine::assignObjectToBlock(uint16 *assignedBlockObjects, uint16 id) { + LoLObject *t = findObject(id); + t->nextAssignedObject = *assignedBlockObjects; + *assignedBlockObjects = id; +} + +void LoLEngine::giveCredits(int credits, int redraw) { + if (redraw) + snd_playSoundEffect(101, -1); + + int t = credits / 30; + if (!t) + t = 1; + + int cnt = 0; + + while (credits) { + if (t > credits) + t = credits; + + if (_credits < 60 && t > 0) { + cnt = 0; + + do { + if (_credits < 60) { + int d = _stashSetupData[_credits % 12] - _credits / 12; + if (d < 0) + d += 5; + _moneyColumnHeight[d]++; + } + _credits++; + } while (++cnt < t); + } else if (_credits >= 60) { + _credits += t; + } + + if (redraw) { + gui_drawMoneyBox(6); + if (credits) + delay(_tickLength, 1); + } + credits -= t; + } +} + +void LoLEngine::takeCredits(int credits, int redraw) { + if (redraw) + snd_playSoundEffect(101, -1); + + if (credits > _credits) + credits = _credits; + + int t = credits / 30; + if (!t) + t = 1; + + int cnt = 0; + + while (credits && _credits > 0) { + if (t > credits) + t = credits; + + if (_credits - t < 60 && t > 0) { + cnt = 0; + + do { + if (--_credits < 60) { + int d = _stashSetupData[_credits % 12] - _credits / 12; + if (d < 0) + d += 5; + _moneyColumnHeight[d]--; + } + } while (++cnt < t); + } else if (_credits - t >= 60) { + _credits -= t; + } + + if (redraw) { + gui_drawMoneyBox(6); + if (credits) + delay(_tickLength, 1); + } + credits -= t; + } +} + +Item LoLEngine::makeItem(int itemType, int curFrame, int flags) { + int cnt = 0; + int r = 0; + Item i = 1; + + for (; i < 400; i++) { + if (_itemsInPlay[i].shpCurFrame_flg & 0x8000) { + cnt = 0; + break; + } + + if (_itemsInPlay[i].level < 1 || _itemsInPlay[i].level > 29 || _itemsInPlay[i].level == _currentLevel) + continue; + + int diff = ABS(_currentLevel - _itemsInPlay[i].level); + + if (diff <= cnt) + continue; + + bool t = false; + for (Item ii = i; ii && !t; ii = _itemsInPlay[ii].nextAssignedObject) + t = isItemMoveable(ii); + + if (t) { + cnt = diff; + r = i; + } + } + + Item slot = i; + if (cnt) { + slot = 0; + if (isItemMoveable(r)) { + if (_itemsInPlay[r].nextAssignedObject) + _itemsInPlay[_itemsInPlay[r].nextAssignedObject].level = _itemsInPlay[r].level; + deleteItem(r); + slot = r; + } else { + for (uint16 ii = _itemsInPlay[r].nextAssignedObject; ii; ii = _itemsInPlay[ii].nextAssignedObject) { + if (!isItemMoveable(ii)) + continue; + _itemsInPlay[r].nextAssignedObject = _itemsInPlay[ii].nextAssignedObject; + deleteItem(ii); + slot = ii; + break; + } + } + } + + memset(&_itemsInPlay[slot], 0, sizeof(LoLItem)); + + _itemsInPlay[slot].itemPropertyIndex = itemType; + _itemsInPlay[slot].shpCurFrame_flg = (curFrame & 0x1FFF) | flags; + _itemsInPlay[slot].level = -1; + + return slot; +} + +void LoLEngine::placeMoveLevelItem(Item itemIndex, int level, int block, int xOffs, int yOffs, int flyingHeight) { + calcCoordinates(_itemsInPlay[itemIndex].x, _itemsInPlay[itemIndex].y, block, xOffs, yOffs); + + if (_itemsInPlay[itemIndex].block) + removeLevelItem(itemIndex, _itemsInPlay[itemIndex].block); + + if (_currentLevel == level) { + setItemPosition(itemIndex, _itemsInPlay[itemIndex].x, _itemsInPlay[itemIndex].y, flyingHeight, 1); + } else { + _itemsInPlay[itemIndex].level = level; + _itemsInPlay[itemIndex].block = block; + _itemsInPlay[itemIndex].flyingHeight = flyingHeight; + _itemsInPlay[itemIndex].shpCurFrame_flg |= 0x4000; + } +} + +bool LoLEngine::addItemToInventory(Item itemIndex) { + int pos = 0; + int i = 0; + + for (; i < 48; i++) { + pos = _inventoryCurItem + i; + if (pos > 47) + pos -= 48; + + if (!_inventory[pos]) + break; + } + + if (i == 48) + return false; + + while ((_inventoryCurItem > pos) || ((_inventoryCurItem + 9) <= pos)) { + if (++_inventoryCurItem > 47) + _inventoryCurItem -= 48; + gui_drawInventory(); + } + + assert(pos >= 0 && pos < 48); + _inventory[pos] = itemIndex; + gui_drawInventory(); + + return true; +} + +bool LoLEngine::isItemMoveable(Item itemIndex) { + if (!(_itemsInPlay[itemIndex].shpCurFrame_flg & 0x4000)) + return false; + + if (_itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].flags & 4) + return false; + + return true; + +} + +void LoLEngine::deleteItem(Item itemIndex) { + memset(&_itemsInPlay[itemIndex], 0, sizeof(LoLItem)); + _itemsInPlay[itemIndex].shpCurFrame_flg |= 0x8000; +} + +void LoLEngine::runItemScript(int charNum, Item item, int flags, int next, int reg4) { + EMCState scriptState; + memset(&scriptState, 0, sizeof(EMCState)); + + uint8 func = item ? _itemProperties[_itemsInPlay[item].itemPropertyIndex].itemScriptFunc : 3; + if (func == 0xFF) + return; + + _emc->init(&scriptState, &_itemScript); + _emc->start(&scriptState, func); + + scriptState.regs[0] = flags; + scriptState.regs[1] = charNum; + scriptState.regs[2] = item; + scriptState.regs[3] = next; + scriptState.regs[4] = reg4; + + if (_emc->isValid(&scriptState)) { + if (*(scriptState.ip - 1) & flags) { + while (_emc->isValid(&scriptState)) + _emc->run(&scriptState); + } + } +} + +void LoLEngine::setHandItem(Item itemIndex) { + if (itemIndex && _itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].flags & 0x80) { + runItemScript(-1, itemIndex, 0x400, 0, 0); + if (_itemsInPlay[itemIndex].shpCurFrame_flg & 0x8000) + itemIndex = 0; + } + + int mouseOffs = 0; + + if (itemIndex && !(_flagsTable[31] & 0x02)) { + mouseOffs = 10; + if (!_currentControlMode || textEnabled()) + _txt->printMessage(0, getLangString(0x403E), getLangString(_itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].nameStringId)); + } + + _itemInHand = itemIndex; + _screen->setMouseCursor(mouseOffs, mouseOffs, getItemIconShapePtr(itemIndex)); +} + +bool LoLEngine::itemEquipped(int charNum, uint16 itemType) { + if (charNum < 0 || charNum > 3) + return false; + + if (!(_characters[charNum].flags & 1)) + return false; + + for (int i = 0; i < 11; i++) { + if (!_characters[charNum].items[i]) + continue; + + if (_itemsInPlay[_characters[charNum].items[i]].itemPropertyIndex == itemType) + return true; + } + + return false; +} + +void LoLEngine::setItemPosition(Item item, uint16 x, uint16 y, int flyingHeight, int moveable) { + if (!flyingHeight) { + x = (x & 0xFFC0) | 0x40; + y = (y & 0xFFC0) | 0x40; + } + + uint16 block = calcBlockIndex(x, y); + _itemsInPlay[item].x = x; + _itemsInPlay[item].y = y; + _itemsInPlay[item].block = block; + _itemsInPlay[item].flyingHeight = flyingHeight; + + if (moveable) + _itemsInPlay[item].shpCurFrame_flg |= 0x4000; + else + _itemsInPlay[item].shpCurFrame_flg &= 0xBFFF; + + + assignItemToBlock(&_levelBlockProperties[block].assignedObjects, item); + reassignDrawObjects(_currentDirection, item, &_levelBlockProperties[block], false); + + if (moveable) + runLevelScriptCustom(block, 0x80, -1, item, 0, 0); + + checkSceneUpdateNeed(block); +} + +void LoLEngine::removeLevelItem(Item item, int block) { + removeAssignedObjectFromBlock(&_levelBlockProperties[block], item); + removeDrawObjectFromBlock(&_levelBlockProperties[block], item); + runLevelScriptCustom(block, 0x100, -1, item, 0, 0); + _itemsInPlay[item].block = 0; + _itemsInPlay[item].level = 0; +} + +bool LoLEngine::launchObject(int objectType, Item item, int startX, int startY, int flyingHeight, int direction, int, int attackerId, int c) { + int sp = checkDrawObjectSpace(_partyPosX, _partyPosY, startX, startY); + FlyingObject *t = _flyingObjects; + int slot = -1; + int i = 0; + + for (; i < 8; i++) { + if (!t->enable) { + sp = -1; + break; + } + + int csp = checkDrawObjectSpace(_partyPosX, _partyPosY, t->x, t->y); + if (csp > sp) { + sp = csp; + slot = i; + } + t++; + } + + if (sp != -1 && slot != -1) { + i = slot; + + t = &_flyingObjects[i]; + endObjectFlight(t, startX, startY, 8); + } + + if (i == 8) + return false; + + t->enable = 1; + t->objectType = objectType; + t->item = item; + t->x = startX; + t->y = startY; + t->flyingHeight = flyingHeight; + t->direction = direction; + t->distance = 255; + t->attackerId = attackerId; + t->flags = 7; + t->wallFlags = 2; + t->c = c; + + if (attackerId != -1) { + if (attackerId & 0x8000) { + t->flags &= 0xFD; + } else { + t->flags &= 0xFB; + increaseExperience(attackerId, 1, 2); + } + } + + updateObjectFlightPosition(t); + + return true; +} + +void LoLEngine::endObjectFlight(FlyingObject *t, int x, int y, int collisionType) { + int cx = x; + int cy = y; + uint16 block = calcBlockIndex(t->x, t->y); + removeAssignedObjectFromBlock(&_levelBlockProperties[block], t->item); + removeDrawObjectFromBlock(&_levelBlockProperties[block], t->item); + + if (collisionType == 1) { + cx = t->x; + cy = t->y; + } + + if (t->objectType == 0 || t->objectType == 1) { + objectFlightProcessHits(t, cx, cy, collisionType); + t->x = (cx & 0xFFC0) | 0x40; + t->y = (cy & 0xFFC0) | 0x40; + t->flyingHeight = 0; + updateObjectFlightPosition(t); + } + + t->enable = 0; +} + +void LoLEngine::processObjectFlight(FlyingObject *t, int x, int y) { + int bl = calcBlockIndex(t->x, t->y); + LevelBlockProperty *l = &_levelBlockProperties[bl]; + removeAssignedObjectFromBlock(l, t->item); + removeDrawObjectFromBlock(l, t->item); + t->x = x; + t->y = y; + updateObjectFlightPosition(t); + checkSceneUpdateNeed(bl); +} + +void LoLEngine::updateObjectFlightPosition(FlyingObject *t) { + if (t->objectType == 0) { + setItemPosition(t->item, t->x, t->y, t->flyingHeight, (t->flyingHeight == 0) ? 1 : 0); + } else if (t->objectType == 1) { + if (t->flyingHeight == 0) { + deleteItem(t->item); + checkSceneUpdateNeed(calcBlockIndex(t->x, t->y)); + } else { + setItemPosition(t->item, t->x, t->y, t->flyingHeight, 0); + } + } +} + +void LoLEngine::objectFlightProcessHits(FlyingObject *t, int x, int y, int collisionType) { + if (collisionType == 1) { + runLevelScriptCustom(calcNewBlockPosition(_itemsInPlay[t->item].block, t->direction >> 1), 0x8000, -1, t->item, 0, 0); + + } else if (collisionType == 2) { + if (_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000) { + uint16 obj = _levelBlockProperties[_itemsInPlay[t->item].block].assignedObjects; + while (obj & 0x8000) { + runItemScript(t->attackerId, t->item, 0x8000, obj, 0); + obj = findObject(obj)->nextAssignedObject; + } + + } else { + runItemScript(t->attackerId, t->item, 0x8000, getNearestMonsterFromPos(x, y), 0); + } + + } else if (collisionType == 4) { + _partyAwake = true; + if (_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000) { + for (int i = 0; i < 4; i++) { + if (_characters[i].flags & 1) + runItemScript(t->attackerId, t->item, 0x8000, i, 0); + } + } else { + runItemScript(t->attackerId, t->item, 0x8000, getNearestPartyMemberFromPos(x, y), 0); + } + } +} + +void LoLEngine::updateFlyingObject(FlyingObject *t) { + int x = 0; + int y = 0; + getNextStepCoords(t->x, t->y, x, y, t->direction); + /* WORKAROUND: + Large fireballs cast by the "birds" in white tower level 2 and by the "wraith knights" in castle cimmeria + level 1 (or possible other objects with flag 0x4000) could not fly through corridors in ScummVM and would + be terminated prematurely. The original code (all versions) involuntarily circumvents this via a bug in the + next line of code. + The original checks for _itemProperties[t->item].flags instead of _itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags. + This leads to more or less unpredictable object widths. The large fireballs will usually get a width of 63 + instead of 256 making them work just fine in the original. + + I have fixed this by setting an object width of 63 of here. This produces results faithful to the original + at least. + + Other methods of working around this issue don't make too much sense. An object with a width of 256 + could never fly through corridors, since 256 is also the width of a block. Aligning the fireballs to the + middle of a block (or making the monsters align to the middle before casting them) wouldn't help here + (and wouldn't be faithful to the original either). + */ + int collisionType = checkBlockBeforeObjectPlacement(x, y, /*_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000 ? 256 :*/ 63, t->flags, t->wallFlags); + if (collisionType) { + endObjectFlight(t, x, y, collisionType); + } else { + if (--t->distance) { + processObjectFlight(t, x, y); + } else { + endObjectFlight(t, x, y, 8); + } + } +} + +void LoLEngine::assignItemToBlock(uint16 *assignedBlockObjects, int id) { + while (*assignedBlockObjects & 0x8000) { + LoLObject *tmp = findObject(*assignedBlockObjects); + assignedBlockObjects = &tmp->nextAssignedObject; + } + + LoLObject *newObject = findObject(id); + newObject->nextAssignedObject = *assignedBlockObjects; + ((LoLItem *)newObject)->level = -1; + *assignedBlockObjects = id; +} + +int LoLEngine::checkDrawObjectSpace(int x1, int y1, int x2, int y2) { + int dx = x1 - x2; + if (dx < 0) + dx = -dx; + + int dy = y1 - y2; + if (dy < 0) + dy = -dy; + + return dx + dy; +} + +int LoLEngine::checkSceneForItems(uint16 *blockDrawObjects, int color) { + while (*blockDrawObjects) { + if (!(*blockDrawObjects & 0x8000)) { + if (!--color) + return *blockDrawObjects; + } + + LoLObject *i = findObject(*blockDrawObjects); + blockDrawObjects = &i->nextDrawObject; + } + + return -1; +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/items_mr.cpp b/engines/kyra/engine/items_mr.cpp new file mode 100644 index 0000000000..3963934ffb --- /dev/null +++ b/engines/kyra/engine/items_mr.cpp @@ -0,0 +1,536 @@ +/* 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 "kyra/engine/kyra_mr.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::removeTrashItems() { + for (int i = 0; _trashItemList[i] != kItemNone; ++i) { + for (int item = findItem(_trashItemList[i]); item != -1; item = findItem(_trashItemList[i])) { + if (_itemList[item].sceneId != _mainCharacter.sceneId) + resetItem(item); + else + break; + } + } +} + +int KyraEngine_MR::findFreeInventorySlot() { + for (int i = 0; i < 10; ++i) { + if (_mainCharacter.inventory[i] == kItemNone) + return i; + } + return -1; +} + +int KyraEngine_MR::checkItemCollision(int x, int y) { + int itemIndex = -1; + int maxItemY = -1; + + for (int i = 0; i < 50; ++i) { + if (_itemList[i].id == kItemNone || _itemList[i].sceneId != _mainCharacter.sceneId) + continue; + + const int x1 = _itemList[i].x - 11; + const int x2 = _itemList[i].x + 10; + + if (x < x1 || x > x2) + continue; + + const int y1 = _itemList[i].y - _itemBuffer1[_itemList[i].id] - 3; + const int y2 = _itemList[i].y + 3; + + if (y < y1 || y > y2) + continue; + + if (_itemList[i].y >= maxItemY) { + itemIndex = i; + maxItemY = _itemList[i].y; + } + } + + return itemIndex; +} + +void KyraEngine_MR::setMouseCursor(Item item) { + int shape = 0; + int hotX = 1; + int hotY = 1; + + if (item != kItemNone) { + hotX = 12; + hotY = 19; + shape = item+248; + } + + _mouseState = item; + if ((int16)item >= 0) + _screen->setMouseCursor(hotX, hotY, getShapePtr(shape)); +} + +void KyraEngine_MR::setItemMouseCursor() { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(0, 0, _gameShapes[0]); + else + _screen->setMouseCursor(12, 19, _gameShapes[_itemInHand+248]); +} + +bool KyraEngine_MR::dropItem(int unk1, Item item, int x, int y, int unk2) { + if (_mouseState <= -1) + return false; + + if (processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2)) + return true; + + snd_playSoundEffect(13, 200); + + if (countAllItems() >= 50) { + removeTrashItems(); + if (processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2)) + return true; + + if (countAllItems() >= 50) + showMessageFromCCode(14, 0xB3, 0); + } + + if (!_chatText) + snd_playSoundEffect(13, 200); + return false; +} + +bool KyraEngine_MR::processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2) { + int itemPos = checkItemCollision(x, y); + + if (unk1) + itemPos = -1; + + if (itemPos >= 0) { + exchangeMouseItem(itemPos, 1); + return true; + } + + int freeItemSlot = -1; + + if (unk2 != 3) { + for (int i = 0; i < 50; ++i) { + if (_itemList[i].id == kItemNone) { + freeItemSlot = i; + break; + } + } + } + + if (freeItemSlot < 0) + return false; + + if (_mainCharacter.sceneId != sceneId) { + _itemList[freeItemSlot].x = x; + _itemList[freeItemSlot].y = y; + _itemList[freeItemSlot].id = item; + _itemList[freeItemSlot].sceneId = sceneId; + return true; + } + + int itemHeight = _itemBuffer1[item]; + + // no idea why it's '&&' here and not single checks for x and y + if (x == -1 && y == -1) { + x = _rnd.getRandomNumberRng(0x18, 0x128); + y = _rnd.getRandomNumberRng(0x14, 0x87); + } + + int posX = x, posY = y; + int itemX = -1, itemY = -1; + bool needRepositioning = true; + + while (needRepositioning) { + if ((_screen->getDrawLayer(posX, posY) <= 1 && _screen->getDrawLayer2(posX, posY, itemHeight) <= 1 && isDropable(posX, posY)) || posY == 187) { + int posX2 = posX, posX3 = posX; + bool repositioning = true; + + while (repositioning) { + if (isDropable(posX3, posY) && _screen->getDrawLayer2(posX3, posY, itemHeight) < 7 && checkItemCollision(posX3, posY) == -1) { + itemX = posX3; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (isDropable(posX2, posY) && _screen->getDrawLayer2(posX2, posY, itemHeight) < 7 && checkItemCollision(posX2, posY) == -1) { + itemX = posX2; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (repositioning) { + posX3 = MAX(posX3 - 2, 24); + posX2 = MIN(posX2 + 2, 296); + + if (posX3 <= 24 && posX2 >= 296) + repositioning = false; + } + } + } + + if (posY == 187) + needRepositioning = false; + else + posY = MIN(posY + 2, 187); + } + + if (itemX == -1 || itemY == -1) + return false; + + if (unk1 == 3) { + _itemList[freeItemSlot].x = itemX; + _itemList[freeItemSlot].y = itemY; + return true; + } else if (unk1 == 2) { + itemDropDown(x, y, itemX, itemY, freeItemSlot, item, 0); + } + + itemDropDown(x, y, itemX, itemY, freeItemSlot, item, (unk1 == 0) ? 1 : 0); + + if (!unk1 && unk2) { + int itemStr = 1; + if (_lang == 1) + itemStr = getItemCommandStringDrop(item); + updateItemCommand(item, itemStr, 0xFF); + } + + return true; +} + +void KyraEngine_MR::itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item, int remove) { + if (startX == dstX && startY == dstY) { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C, 0xC8); + addItemToAnimList(itemSlot); + } else { + uint8 *itemShape = getShapePtr(item + 248); + _screen->hideMouse(); + + if (startY <= dstY) { + int speed = 2; + int curY = startY; + int curX = startX - 12; + + backUpGfxRect32x32(curX, curY-16); + while (curY < dstY) { + restoreGfxRect32x32(curX, curY-16); + + curY = MIN(curY + speed, dstY); + ++speed; + + backUpGfxRect32x32(curX, curY-16); + uint32 endDelay = _system->getMillis() + _tickLength; + + _screen->drawShape(0, itemShape, curX, curY-16, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay); + } + restoreGfxRect32x32(curX, curY-16); + + if (dstX != dstY || (dstY - startY > 16)) { + snd_playSoundEffect(0x11, 0xC8); + speed = MAX(speed, 6); + int speedX = ((dstX - startX) << 4) / speed; + int origSpeed = speed; + speed >>= 1; + + if (dstY - startY <= 8) + speed >>= 1; + + speed = -speed; + + curX = startX << 4; + + int x = 0, y = 0; + while (--origSpeed) { + curY = MIN(curY + speed, dstY); + curX += speedX; + ++speed; + + x = (curX >> 4) - 8; + y = curY - 16; + backUpGfxRect32x32(x, y); + + uint16 endDelay = _system->getMillis() + _tickLength; + _screen->drawShape(0, itemShape, x, y, 0, 0); + _screen->updateScreen(); + + restoreGfxRect32x32(x, y); + + delayUntil(endDelay); + } + + restoreGfxRect32x32(x, y); + } + } + + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C, 0xC8); + addItemToAnimList(itemSlot); + _screen->showMouse(); + } + + if (remove) + removeHandItem(); +} + +void KyraEngine_MR::exchangeMouseItem(int itemPos, int runScript) { + if (itemListMagic(_itemInHand, itemPos)) + return; + + if (_itemInHand == 43) { + removeHandItem(); + return; + } + + deleteItemAnimEntry(itemPos); + + Item itemId = _itemList[itemPos].id; + _itemList[itemPos].id = _itemInHand; + _itemInHand = itemId; + + addItemToAnimList(itemPos); + snd_playSoundEffect(0x0B, 0xC8); + setMouseCursor(_itemInHand); + int str2 = 0; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateItemCommand(itemId, str2, 0xFF); + + if (runScript) + runSceneScript6(); +} + +bool KyraEngine_MR::pickUpItem(int x, int y, int runScript) { + int itemPos = checkItemCollision(x, y); + + if (itemPos <= -1) + return false; + + if (_itemInHand >= 0) { + exchangeMouseItem(itemPos, runScript); + } else { + deleteItemAnimEntry(itemPos); + Item itemId = _itemList[itemPos].id; + _itemList[itemPos].id = kItemNone; + snd_playSoundEffect(0x0B, 0xC8); + setMouseCursor(itemId); + int itemString = 0; + + if (_lang == 1) + itemString = getItemCommandStringPickUp(itemId); + + updateItemCommand(itemId, itemString, 0xFF); + _itemInHand = itemId; + + if (runScript) + runSceneScript6(); + } + + return true; +} + +bool KyraEngine_MR::isDropable(int x, int y) { + if (y < 14 || y > 187) + return false; + + x -= 12; + + for (int xpos = x; xpos < x + 24; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return false; + } + + return true; +} + +bool KyraEngine_MR::itemListMagic(Item handItem, int itemSlot) { + Item item = _itemList[itemSlot].id; + + if (_currentChapter == 1 && handItem == 3 && item == 3 && queryGameFlag(0x76)) { + eelScript(); + return true; + } else if ((handItem == 6 || handItem == 7) && item == 2) { + int animObjIndex = -1; + for (int i = 17; i <= 66; ++i) { + if (_animObjects[i].shapeIndex2 == 250) + animObjIndex = i; + } + + assert(animObjIndex != -1); + + snd_playSoundEffect(0x93, 0xC8); + for (int i = 109; i <= 141; ++i) { + _animObjects[animObjIndex].shapeIndex1 = i+248; + _animObjects[animObjIndex].needRefresh = true; + delay(1*_tickLength, true); + } + + deleteItemAnimEntry(itemSlot); + _itemList[itemSlot].id = kItemNone; + return true; + } + + if (_mainCharacter.sceneId == 51 && queryGameFlag(0x19B) && !queryGameFlag(0x19C) + && ((item == 63 && handItem == 56) || (item == 56 && handItem == 63))) { + + if (queryGameFlag(0x1AC)) { + setGameFlag(0x19C); + setGameFlag(0x1AD); + } else { + setGameFlag(0x1AE); + } + + _timer->setCountdown(12, 1); + _timer->enable(12); + } + + for (int i = 0; _itemMagicTable[i] != 0xFF; i += 4) { + if (_itemMagicTable[i+0] != handItem || (int8)_itemMagicTable[i+1] != item) + continue; + + uint8 resItem = _itemMagicTable[i+2]; + uint8 newItem = _itemMagicTable[i+3]; + + snd_playSoundEffect(0x0F, 0xC8); + + _itemList[itemSlot].id = (int8)resItem; + + deleteItemAnimEntry(itemSlot); + addItemToAnimList(itemSlot); + + if (newItem == 0xFE) + removeHandItem(); + else if (newItem != 0xFF) + setHandItem(newItem); + + if (_lang != 1) + updateItemCommand(resItem, 3, 0xFF); + + // Unlike the original we give points for when combining with scene items + if (resItem == 7) { + updateScore(35, 100); + delay(60*_tickLength, true); + } + + return true; + } + + return false; +} + +bool KyraEngine_MR::itemInventoryMagic(Item handItem, int invSlot) { + Item item = _mainCharacter.inventory[invSlot]; + + if (_currentChapter == 1 && handItem == 3 && item == 3 && queryGameFlag(0x76)) { + eelScript(); + return true; + } else if ((handItem == 6 || handItem == 7) && item == 2) { + _screen->hideMouse(); + snd_playSoundEffect(0x93, 0xC8); + for (int i = 109; i <= 141; ++i) { + _mainCharacter.inventory[invSlot] = i; + _screen->drawShape(2, getShapePtr(invSlot+422), 0, 144, 0, 0); + _screen->drawShape(2, getShapePtr(i+248), 0, 144, 0, 0); + _screen->copyRegion(0, 144, _inventoryX[invSlot], _inventoryY[invSlot], 24, 20, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delay(1*_tickLength, true); + } + + _mainCharacter.inventory[invSlot] = kItemNone; + clearInventorySlot(invSlot, 0); + _screen->showMouse(); + return true; + } + + for (int i = 0; _itemMagicTable[i] != 0xFF; i += 4) { + if (_itemMagicTable[i+0] != handItem || _itemMagicTable[i+1] != item) + continue; + + uint8 resItem = _itemMagicTable[i+2]; + uint8 newItem = _itemMagicTable[i+3]; + + snd_playSoundEffect(0x0F, 0xC8); + + _mainCharacter.inventory[invSlot] = (int8)resItem; + + clearInventorySlot(invSlot, 0); + drawInventorySlot(0, resItem, invSlot); + + if (newItem == 0xFE) + removeHandItem(); + else if (newItem != 0xFF) + setHandItem(newItem); + + if (_lang != 1) + updateItemCommand(resItem, 3, 0xFF); + + // Unlike the original we give points for every language + if (resItem == 7) { + updateScore(35, 100); + delay(60*_tickLength, true); + } + + return true; + } + + return false; +} + +int KyraEngine_MR::getItemCommandStringDrop(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringDrop[stringId]; +} + +int KyraEngine_MR::getItemCommandStringPickUp(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringPickUp[stringId]; +} + +int KyraEngine_MR::getItemCommandStringInv(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringInv[stringId]; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_v2.cpp b/engines/kyra/engine/items_v2.cpp new file mode 100644 index 0000000000..93afff62aa --- /dev/null +++ b/engines/kyra/engine/items_v2.cpp @@ -0,0 +1,100 @@ +/* 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 "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +void KyraEngine_v2::initItemList(int size) { + delete[] _itemList; + + _itemList = new ItemDefinition[size]; + assert(_itemList); + memset(_itemList, 0, sizeof(ItemDefinition)*size); + _itemListSize = size; + + resetItemList(); +} + +int KyraEngine_v2::findFreeItem() { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == kItemNone) + return i; + } + return -1; +} + +int KyraEngine_v2::countAllItems() { + int num = 0; + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id != kItemNone) + ++num; + } + return num; +} + +int KyraEngine_v2::findItem(uint16 sceneId, Item id) { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == id && _itemList[i].sceneId == sceneId) + return i; + } + return -1; +} + +int KyraEngine_v2::findItem(Item item) { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == item) + return i; + } + return -1; +} + +void KyraEngine_v2::resetItemList() { + for (int i = 0; i < _itemListSize; ++i) + resetItem(i); +} + +void KyraEngine_v2::resetItem(int index) { + _itemList[index].id = kItemNone; + _itemList[index].sceneId = 0xFFFF; + _itemList[index].x = 0; + _itemList[index].y = 0; +} + +void KyraEngine_v2::setHandItem(Item item) { + if (item == kItemNone) { + removeHandItem(); + } else { + setMouseCursor(item); + _itemInHand = item; + } +} + +void KyraEngine_v2::removeHandItem() { + Screen *scr = screen(); + scr->setMouseCursor(0, 0, getShapePtr(0)); + _itemInHand = kItemNone; + _mouseState = kItemNone; +} + +} // end of namesapce Kyra diff --git a/engines/kyra/engine/kyra_hof.cpp b/engines/kyra/engine/kyra_hof.cpp new file mode 100644 index 0000000000..94eca126ec --- /dev/null +++ b/engines/kyra/engine/kyra_hof.cpp @@ -0,0 +1,1933 @@ +/* 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 "kyra/engine/kyra_hof.h" +#include "kyra/resource/resource.h" +#include "kyra/text/text_hof.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" +#include "kyra/engine/util.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" +#include "common/config-manager.h" + +namespace Kyra { + +const KyraEngine_v2::EngineDesc KyraEngine_HoF::_hofEngineDesc = { + // Generic shape related + 64, + KyraEngine_HoF::_characterFrameTable, + + // Scene script + 8, + + // Animation script specific + 33, + + // Item specific + 175 +}; + +KyraEngine_HoF::KyraEngine_HoF(OSystem *system, const GameFlags &flags) : KyraEngine_v2(system, flags, _hofEngineDesc), _updateFunctor(this, &KyraEngine_HoF::update) { + _screen = 0; + _text = 0; + + _gamePlayBuffer = 0; + _cCodeBuffer = _optionsBuffer = _chapterBuffer = 0; + + _overwriteSceneFacing = false; + _mainCharX = _mainCharY = -1; + _drawNoShapeFlag = false; + _charPalEntry = 0; + _itemInHand = kItemNone; + _unkSceneScreenFlag1 = false; + _noScriptEnter = true; + _currentChapter = 0; + _newChapterFile = 1; + _oldTalkFile = -1; + _currentTalkFile = 0; + _lastSfxTrack = -1; + _mouseState = -1; + _unkHandleSceneChangeFlag = false; + _pathfinderFlag = 0; + _mouseX = _mouseY = 0; + + _nextIdleAnim = 0; + _lastIdleScript = -1; + _useSceneIdleAnim = false; + + _currentTalkSections.STATim = 0; + _currentTalkSections.TLKTim = 0; + _currentTalkSections.ENDTim = 0; + + memset(&_invWsa, 0, sizeof(_invWsa)); + _itemAnimDefinition = 0; + _nextAnimItem = 0; + + for (int i = 0; i < 15; i++) + memset(&_activeItemAnim[i], 0, sizeof(ActiveItemAnim)); + + _colorCodeFlag1 = 0; + _colorCodeFlag2 = -1; + _scriptCountDown = 0; + _dbgPass = 0; + + _gamePlayBuffer = 0; + _unkBuf500Bytes = 0; + _inventorySaved = false; + _unkBuf200kByte = 0; + memset(&_sceneShapeTable, 0, sizeof(_sceneShapeTable)); + + _talkObjectList = 0; + _shapeDescTable = 0; + _gfxBackUpRect = 0; + _sceneList = 0; + memset(&_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); + memset(&_wsaSlots, 0, sizeof(_wsaSlots)); + memset(&_buttonShapes, 0, sizeof(_buttonShapes)); + + _configTextspeed = 50; + + _inventoryButtons = _buttonList = 0; + + _dlgBuffer = 0; + _conversationState = new int8 *[19]; + for (int i = 0; i < 19; i++) + _conversationState[i] = new int8[14]; + _npcTalkChpIndex = _npcTalkDlgIndex = -1; + _mainCharacter.dlgIndex = 0; + setDlgIndex(-1); + + _bookMaxPage = 6; + _bookCurPage = 0; + _bookNewPage = 0; + _bookBkgd = 0; + + _cauldronState = 0; + _cauldronUseCount = 0; + memset(_cauldronStateTables, 0, sizeof(_cauldronStateTables)); + + _menuDirectlyToLoad = false; + _chatIsNote = false; + memset(&_npcScriptData, 0, sizeof(_npcScriptData)); + + _setCharPalFinal = false; + _useCharPal = false; + + memset(_characterFacingCountTable, 0, sizeof(_characterFacingCountTable)); +} + +KyraEngine_HoF::~KyraEngine_HoF() { + cleanup(); + + delete _screen; + delete _text; + delete _gui; + delete _tim; + _text = 0; + delete _invWsa.wsa; + + delete[] _dlgBuffer; + for (int i = 0; i < 19; i++) + delete[] _conversationState[i]; + delete[] _conversationState; + + for (Common::Array<const TIMOpcode *>::iterator i = _timOpcodes.begin(); i != _timOpcodes.end(); ++i) + delete *i; + _timOpcodes.clear(); +} + +void KyraEngine_HoF::pauseEngineIntern(bool pause) { + KyraEngine_v2::pauseEngineIntern(pause); + + seq_pausePlayer(pause); + + if (!pause) { + uint32 pausedTime = _system->getMillis() - _pauseStart; + _pauseStart = 0; + + _nextIdleAnim += pausedTime; + _tim->refreshTimersAfterPause(pausedTime); + } +} + +Common::Error KyraEngine_HoF::init() { + _screen = new Screen_HoF(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_HoF(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _text = new TextDisplayer_HoF(this, _screen); + assert(_text); + _gui = new GUI_HoF(this); + assert(_gui); + _gui->initStaticData(); + _tim = new TIMInterpreter(this, _screen, _system); + assert(_tim); + + if (_flags.isDemo && !_flags.isTalkie) { + _screen->loadFont(_screen->FID_8_FNT, "FONT9P.FNT"); + } else { + _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(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : _screen->FID_8_FNT); + + _screen->setAnimBlockPtr(3504); + _screen->setScreenDim(0); + + if (!_sound->init()) + error("Couldn't init sound"); + + // No mouse display in demo + if (_flags.isDemo && !_flags.isTalkie) + return Common::kNoError; + + _res->exists("PWGMOUSE.SHP", true); + uint8 *shapes = _res->fileData("PWGMOUSE.SHP", 0); + assert(shapes); + + for (int i = 0; i < 2; i++) + addShapeToPool(shapes, i, i); + + delete[] shapes; + + _screen->setMouseCursor(0, 0, getShapePtr(0)); + return Common::kNoError; +} + +Common::Error KyraEngine_HoF::go() { + int menuChoice = 0; + + if (_gameToLoad == -1) { + if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) + seq_showStarcraftLogo(); + + if (_flags.isDemo && !_flags.isTalkie) { + menuChoice = seq_playDemo(); + } else { + menuChoice = seq_playIntro(); + } + } else { + menuChoice = 1; + } + + _res->unloadAllPakFiles(); + + if (menuChoice != 4) { + // load just the pak files needed for ingame + _staticres->loadStaticResourceFile(); + + if (_flags.platform == Common::kPlatformDOS && _flags.isTalkie) { + if (!_res->loadFileList("FILEDATA.FDT")) + error("couldn't load 'FILEDATA.FDT'"); + } else { + _res->loadFileList(_ingamePakList, _ingamePakListSize); + } + + if (_flags.platform == Common::kPlatformPC98) { + _res->loadPakFile("AUDIO.PAK"); + _sound->loadSoundFile("SOUND.DAT"); + } + } + + _menuDirectlyToLoad = (menuChoice == 3) ? true : false; + _menuDirectlyToLoad &= saveFileLoadable(0); + + if (menuChoice & 1) { + startup(); + if (!shouldQuit()) + runLoop(); + cleanup(); + + if (_showOutro) + seq_playOutro(); + } + + return Common::kNoError; +} + +void KyraEngine_HoF::startup() { + _sound->selectAudioResourceSet(kMusicIngame); + // The track map is exactly the same + // for FM-TOWNS and DOS + _trackMap = _dosTrackMap; + _trackMapSize = _dosTrackMapSize; + + allocAnimObjects(1, 10, 30); + + _screen->_curPage = 0; + + memset(_sceneShapeTable, 0, sizeof(_sceneShapeTable)); + _gamePlayBuffer = new uint8[46080]; + _unkBuf500Bytes = new uint8[500]; + + loadMouseShapes(); + loadItemShapes(); + + _screen->setMouseCursor(0, 0, getShapePtr(0)); + + _screenBuffer = new uint8[64000]; + _unkBuf200kByte = new uint8[200000]; + + loadChapterBuffer(_newChapterFile); + + loadCCodeBuffer("C_CODE.XXX"); + + if (_flags.isTalkie) { + loadOptionsBuffer("OPTIONS.XXX"); + + showMessageFromCCode(265, 150, 0); + _screen->updateScreen(); + openTalkFile(0); + _currentTalkFile = 1; + openTalkFile(1); + } else { + _optionsBuffer = _cCodeBuffer; + } + + showMessage(0, 207); + + _screen->setShapePages(5, 3); + + _mainCharacter.height = 0x30; + _mainCharacter.facing = 4; + _mainCharacter.animFrame = 0x12; + + memset(_sceneAnims, 0, sizeof(_sceneAnims)); + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) + _sceneAnimMovie[i] = new WSAMovie_v2(this); + memset(_wsaSlots, 0, sizeof(_wsaSlots)); + for (int i = 0; i < ARRAYSIZE(_wsaSlots); ++i) + _wsaSlots[i] = new WSAMovie_v2(this); + + _screen->_curPage = 0; + + _talkObjectList = new TalkObject[72]; + memset(_talkObjectList, 0, sizeof(TalkObject)*72); + _shapeDescTable = new ShapeDesc[55]; + memset(_shapeDescTable, 0, sizeof(ShapeDesc)*55); + + for (int i = 9; i <= 32; ++i) { + _shapeDescTable[i-9].width = 30; + _shapeDescTable[i-9].height = 55; + _shapeDescTable[i-9].xAdd = -15; + _shapeDescTable[i-9].yAdd = -50; + } + + for (int i = 19; i <= 24; ++i) { + _shapeDescTable[i-9].width = 53; + _shapeDescTable[i-9].yAdd = -51; + } + + _gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)]; + initItemList(30); + loadButtonShapes(); + resetItemList(); + _characterShapeFile = 1; + loadCharacterShapes(_characterShapeFile); + initInventoryButtonList(); + setupLangButtonShapes(); + loadInventoryShapes(); + + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + _screen->loadBitmap("_PLAYFLD.CPS", 3, 3, 0); + _screen->copyPage(3, 0); + + clearAnimObjects(); + + for (int i = 0; i < 19; ++i) + memset(_conversationState[i], -1, sizeof(int8)*14); + clearCauldronTable(); + memset(_inputColorCode, -1, sizeof(_inputColorCode)); + memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); + for (int i = 0; i < 23; ++i) + resetCauldronStateTable(i); + + _sceneList = new SceneDesc[86]; + memset(_sceneList, 0, sizeof(SceneDesc)*86); + _sceneListSize = 86; + runStartScript(1, 0); + loadNPCScript(); + + if (_gameToLoad == -1) { + snd_playWanderScoreViaMap(52, 1); + enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1); + saveGameStateIntern(0, "New Game", 0); + } else { + loadGameStateCheck(_gameToLoad); + } + + _screen->showMouse(); + + if (_menuDirectlyToLoad) + (*_inventoryButtons[0].buttonCallback)(&_inventoryButtons[0]); + + setNextIdleAnimTimer(); + setWalkspeed(_configWalkspeed); +} + +void KyraEngine_HoF::runLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _screen->updateScreen(); + + _runFlag = true; + while (!shouldQuit() && _runFlag) { + if (_deathHandler >= 0) { + removeHandItem(); + delay(5); + _drawNoShapeFlag = 0; + _gui->optionsButton(0); + _deathHandler = -1; + + if (!_runFlag || shouldQuit()) + break; + } + + if (_system->getMillis() > _nextIdleAnim) + showIdleAnim(); + + if (queryGameFlag(0x159)) { + dinoRide(); + resetGameFlag(0x159); + } + + if (queryGameFlag(0x124) && !queryGameFlag(0x125)) { + _mainCharacter.animFrame = 32; + enterNewScene(39, -1, 0, 0, 0); + } + + if (queryGameFlag(0xD8)) { + resetGameFlag(0xD8); + if (_mainCharacter.sceneId == 34) { + if (queryGameFlag(0xD1)) { + initTalkObject(28); + npcChatSequence(getTableString(0xFA, _cCodeBuffer, 1), 28, 0x83, 0xFA); + deinitTalkObject(28); + enterNewScene(35, 4, 0, 0, 0); + } else if (queryGameFlag(0xD0)) { + initTalkObject(29); + npcChatSequence(getTableString(0xFB, _cCodeBuffer, 1), 29, 0x83, 0xFB); + deinitTalkObject(29); + enterNewScene(33, 6, 0, 0, 0); + } + } + } + + int inputFlag = checkInput(_buttonList, true); + removeInputTop(); + + update(); + + if (inputFlag == 198 || inputFlag == 199) { + _savedMouseState = _mouseState; + handleInput(_mouseX, _mouseY); + } + + //if (queryGameFlag(0x1EE) && inputFlag) + // sub_13B19(inputFlag); + + _system->delayMillis(10); + } +} + +void KyraEngine_HoF::handleInput(int x, int y) { + setNextIdleAnimTimer(); + if (_unk5) { + _unk5 = 0; + return; + } + + if (!_screen->isMouseVisible()) + return; + + if (_savedMouseState == -2) { + snd_playSoundEffect(13); + return; + } + + setNextIdleAnimTimer(); + + if (x <= 6 || x >= 312 || y <= 6 || y >= 135) { + bool exitOk = false; + assert(_savedMouseState + 6 >= 0); + switch (_savedMouseState + 6) { + case 0: + if (_sceneExit1 != 0xFFFF) + exitOk = true; + break; + + case 1: + if (_sceneExit2 != 0xFFFF) + exitOk = true; + break; + + case 2: + if (_sceneExit3 != 0xFFFF) + exitOk = true; + break; + + case 3: + if (_sceneExit4 != 0xFFFF) + exitOk = true; + break; + + default: + break; + } + + if (exitOk) { + inputSceneChange(x, y, 1, 1); + return; + } + } + + if (checkCharCollision(x, y) && _savedMouseState >= -1) { + runSceneScript2(); + return; + } else if (pickUpItem(x, y)) { + return; + } else { + int skipHandling = 0; + + if (checkItemCollision(x, y) == -1) { + resetGameFlag(0x1EF); + skipHandling = handleInputUnkSub(x, y) ? 1 : 0; + + if (queryGameFlag(0x1EF)) { + resetGameFlag(0x1EF); + return; + } + + if (_unk5) { + _unk5 = 0; + return; + } + } + + if (_deathHandler > -1) + skipHandling = 1; + + if (skipHandling) + return; + + if (checkCharCollision(x, y)) { + runSceneScript2(); + return; + } + + if (_itemInHand >= 0) { + if (y > 136) + return; + + dropItem(0, _itemInHand, x, y, 1); + } else { + if (_savedMouseState == -2 || y > 135) + return; + + if (!_unk5) { + inputSceneChange(x, y, 1, 1); + return; + } + + _unk5 = 0; + } + } +} + +bool KyraEngine_HoF::handleInputUnkSub(int x, int y) { + if (y > 143 || _deathHandler > -1 || queryGameFlag(0x164)) + return false; + + if (_mouseState <= -3 && findItem(_mainCharacter.sceneId, 13) >= 0) { + updateCharFacing(); + objectChat(getTableString(0xFC, _cCodeBuffer, 1), 0, 0x83, 0xFC); + return true; + } else { + _emc->init(&_sceneScriptState, &_sceneScriptData); + + _sceneScriptState.regs[1] = x; + _sceneScriptState.regs[2] = y; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 1); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + if (queryGameFlag(0x1ED)) { + _sound->beginFadeOut(); + _screen->fadeToBlack(); + _showOutro = true; + _runFlag = false; + } + + return _sceneScriptState.regs[3] != 0; + } +} + +void KyraEngine_HoF::update() { + updateInput(); + + refreshAnimObjectsIfNeed(); + updateMouse(); + updateSpecialSceneScripts(); + _timer->update(); + updateItemAnimations(); + updateInvWsa(); + fadeMessagePalette(); + _screen->updateScreen(); +} + +void KyraEngine_HoF::updateWithText() { + updateInput(); + + updateMouse(); + fadeMessagePalette(); + updateSpecialSceneScripts(); + _timer->update(); + updateItemAnimations(); + updateInvWsa(); + restorePage3(); + drawAnimObjects(); + + if (_chatTextEnabled && _chatText) { + int pageBackUp = _screen->_curPage; + _screen->_curPage = 2; + objectChatPrintText(_chatText, _chatObject); + _screen->_curPage = pageBackUp; + } + + refreshAnimObjects(0); + _screen->updateScreen(); +} + +void KyraEngine_HoF::updateMouse() { + int shapeIndex = 0; + int type = 0; + int xOffset = 0, yOffset = 0; + Common::Point mouse = getMousePos(); + + if (mouse.y <= 145) { + if (mouse.x <= 6) { + if (_sceneExit4 != 0xFFFF) { + type = -3; + shapeIndex = 4; + xOffset = 1; + yOffset = 5; + } else { + type = -2; + } + } else if (mouse.x >= 312) { + if (_sceneExit2 != 0xFFFF) { + type = -5; + shapeIndex = 2; + xOffset = 7; + yOffset = 5; + } else { + type = -2; + } + } else if (mouse.y >= 135) { + if (_sceneExit3 != 0xFFFF) { + type = -4; + shapeIndex = 3; + xOffset = 5; + yOffset = 10; + } else { + type = -2; + } + } else if (mouse.y <= 6) { + if (_sceneExit1 != 0xFFFF) { + type = -6; + shapeIndex = 1; + xOffset = 5; + yOffset = 1; + } else { + type = -2; + } + } + } + + for (int i = 0; i < _specialExitCount; ++i) { + if (checkSpecialSceneExit(i, mouse.x, mouse.y)) { + switch (_specialExitTable[20+i]) { + case 0: + type = -6; + shapeIndex = 1; + xOffset = 5; + yOffset = 1; + break; + + case 2: + type = -5; + shapeIndex = 2; + xOffset = 7; + yOffset = 5; + break; + + case 4: + type = -4; + shapeIndex = 3; + xOffset = 5; + yOffset = 7; + break; + + case 6: + type = -3; + shapeIndex = 4; + xOffset = 1; + yOffset = 5; + break; + + default: + break; + } + } + } + + if (type == -2) { + shapeIndex = 5; + xOffset = 5; + yOffset = 9; + } + + if (type != 0 && _mouseState != type && _screen->isMouseVisible()) { + _mouseState = type; + _screen->setMouseCursor(xOffset, yOffset, getShapePtr(shapeIndex)); + } + + if (type == 0 && _mouseState != _itemInHand && _screen->isMouseVisible()) { + if ((mouse.y > 145) || (mouse.x > 6 && mouse.x < 312 && mouse.y > 6 && mouse.y < 135)) { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(0, 0, getShapePtr(0)); + else + _screen->setMouseCursor(8, 15, getShapePtr(_itemInHand+64)); + } + } +} + +void KyraEngine_HoF::cleanup() { + delete[] _inventoryButtons; _inventoryButtons = 0; + + delete[] _gamePlayBuffer; _gamePlayBuffer = 0; + delete[] _unkBuf500Bytes; _unkBuf500Bytes = 0; + delete[] _unkBuf200kByte; _unkBuf200kByte = 0; + + freeSceneShapePtrs(); + + if (_optionsBuffer != _cCodeBuffer) + delete[] _optionsBuffer; + _optionsBuffer = 0; + delete[] _cCodeBuffer; _cCodeBuffer = 0; + delete[] _chapterBuffer; _chapterBuffer = 0; + + delete[] _talkObjectList; _talkObjectList = 0; + delete[] _shapeDescTable; _shapeDescTable = 0; + + delete[] _gfxBackUpRect; _gfxBackUpRect = 0; + + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) { + delete _sceneAnimMovie[i]; + _sceneAnimMovie[i] = 0; + } + for (int i = 0; i < ARRAYSIZE(_wsaSlots); ++i) { + delete _wsaSlots[i]; + _wsaSlots[i] = 0; + } + for (int i = 0; i < ARRAYSIZE(_buttonShapes); ++i) { + delete[] _buttonShapes[i]; + _buttonShapes[i] = 0; + } + + _emc->unload(&_npcScriptData); +} + +#pragma mark - Localization + +void KyraEngine_HoF::loadCCodeBuffer(const char *file) { + char tempString[13]; + strcpy(tempString, file); + changeFileExtension(tempString); + + delete[] _cCodeBuffer; + _cCodeBuffer = _res->fileData(tempString, 0); +} + +void KyraEngine_HoF::loadOptionsBuffer(const char *file) { + char tempString[13]; + strcpy(tempString, file); + changeFileExtension(tempString); + + delete[] _optionsBuffer; + _optionsBuffer = _res->fileData(tempString, 0); +} + +void KyraEngine_HoF::loadChapterBuffer(int chapter) { + char tempString[14]; + + static const char *const chapterFilenames[] = { + "CH1.XXX", "CH2.XXX", "CH3.XXX", "CH4.XXX", "CH5.XXX" + }; + + assert(chapter >= 1 && chapter <= ARRAYSIZE(chapterFilenames)); + strcpy(tempString, chapterFilenames[chapter-1]); + changeFileExtension(tempString); + + delete[] _chapterBuffer; + _chapterBuffer = _res->fileData(tempString, 0); + _currentChapter = chapter; +} + +void KyraEngine_HoF::changeFileExtension(char *buffer) { + while (*buffer != '.') + ++buffer; + + ++buffer; + strcpy(buffer, _languageExtension[_lang]); +} + +uint8 *KyraEngine_HoF::getTableEntry(uint8 *buffer, int id) { + return buffer + READ_LE_UINT16(buffer + (id<<1)); +} + +char *KyraEngine_HoF::getTableString(int id, uint8 *buffer, int decode) { + char *string = (char *)getTableEntry(buffer, id); + + if (decode && _flags.lang != Common::JA_JPN) { + Util::decodeString1(string, _internStringBuf); + Util::decodeString2(_internStringBuf, _internStringBuf); + string = _internStringBuf; + } + + return string; +} + +const char *KyraEngine_HoF::getChapterString(int id) { + if (_currentChapter != _newChapterFile) + loadChapterBuffer(_newChapterFile); + + return getTableString(id, _chapterBuffer, 1); +} + +#pragma mark - + +void KyraEngine_HoF::showMessageFromCCode(int id, int16 palIndex, int) { + const char *string = getTableString(id, _cCodeBuffer, 1); + showMessage(string, palIndex); +} + +void KyraEngine_HoF::showMessage(const char *string, int16 palIndex) { + _shownMessage = string; + _screen->fillRect(0, 190, 319, 199, 0xCF); + + if (string) { + if (palIndex != -1 || _fadeMessagePalette) { + palIndex *= 3; + memcpy(_messagePal, _screen->getPalette(0).getData() + palIndex, 3); + _screen->getPalette(0).copy(_screen->getPalette(0), palIndex / 3, 1, 255); + _screen->setScreenPalette(_screen->getPalette(0)); + } + + int x = _text->getCenterStringX(string, 0, 320); + _text->printText(string, x, 190, 255, 207, 0); + + setTimer1DelaySecs(7); + } + + _fadeMessagePalette = false; +} + +void KyraEngine_HoF::showChapterMessage(int id, int16 palIndex) { + showMessage(getChapterString(id), palIndex); +} + +void KyraEngine_HoF::updateCommandLineEx(int str1, int str2, int16 palIndex) { + char buffer[0x51]; + char *src = buffer; + + strcpy(src, getTableString(str1, _cCodeBuffer, 1)); + + if (_flags.lang != Common::JA_JPN) { + while (*src != 0x20) + ++src; + ++src; + *src = toupper(*src); + } + + strcpy((char *)_unkBuf500Bytes, src); + + if (str2 > 0) { + if (_flags.lang != Common::JA_JPN) + strcat((char *)_unkBuf500Bytes, " "); + strcat((char *)_unkBuf500Bytes, getTableString(str2, _cCodeBuffer, 1)); + } + + showMessage((char *)_unkBuf500Bytes, palIndex); +} + +void KyraEngine_HoF::fadeMessagePalette() { + if (!_fadeMessagePalette) + return; + + bool updatePalette = false; + for (int i = 0; i < 3; ++i) { + if (_messagePal[i] >= 4) { + _messagePal[i] -= 4; + updatePalette = true; + } else if (_messagePal[i] != 0) { + _messagePal[i] = 0; + updatePalette = true; + } + } + + if (updatePalette) { + _screen->getPalette(0).copy(_messagePal, 0, 1, 255); + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + _fadeMessagePalette = false; + } +} + +#pragma mark - + +void KyraEngine_HoF::loadMouseShapes() { + _screen->loadBitmap("_MOUSE.CSH", 3, 3, 0); + + for (int i = 0; i <= 8; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i); +} + +void KyraEngine_HoF::loadItemShapes() { + _screen->loadBitmap("_ITEMS.CSH", 3, 3, 0); + + for (int i = 64; i <= 239; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i-64); + + _res->loadFileToBuf("_ITEMHT.DAT", _itemHtDat, sizeof(_itemHtDat)); + assert(_res->getFileSize("_ITEMHT.DAT") == sizeof(_itemHtDat)); + + _screen->_curPage = 0; +} + +void KyraEngine_HoF::loadCharacterShapes(int shapes) { + char file[10]; + strcpy(file, "_ZX.SHP"); + + _characterShapeFile = shapes; + file[2] = '0' + shapes; + + uint8 *data = _res->fileData(file, 0); + for (int i = 9; i <= 32; ++i) + addShapeToPool(data, i, i-9); + delete[] data; + + _characterShapeFile = shapes; +} + +void KyraEngine_HoF::loadInventoryShapes() { + int curPageBackUp = _screen->_curPage; + _screen->_curPage = 2; + + _screen->loadBitmap("_PLAYALL.CPS", 3, 3, 0); + + for (int i = 0; i < 10; ++i) + addShapeToPool(_screen->encodeShape(_inventoryX[i], _inventoryY[i], 16, 16, 0), 240+i); + + _screen->_curPage = curPageBackUp; +} + +void KyraEngine_HoF::runStartScript(int script, int unk1) { + char filename[14]; + strcpy(filename, "_START0X.EMC"); + filename[7] = script + '0'; + + EMCData scriptData; + EMCState scriptState; + memset(&scriptData, 0, sizeof(EMCData)); + memset(&scriptState, 0, sizeof(EMCState)); + + _emc->load(filename, &scriptData, &_opcodes); + _emc->init(&scriptState, &scriptData); + scriptState.regs[6] = unk1; + _emc->start(&scriptState, 0); + while (_emc->isValid(&scriptState)) + _emc->run(&scriptState); + _emc->unload(&scriptData); +} + +void KyraEngine_HoF::loadNPCScript() { + _emc->unload(&_npcScriptData); + + char filename[] = "_NPC.EMC"; + + if (_flags.platform != Common::kPlatformDOS || _flags.isTalkie) { + switch (_lang) { + case 0: + filename[5] = 'E'; + break; + + case 1: + filename[5] = 'F'; + break; + + case 2: + filename[5] = 'G'; + break; + + case 3: + filename[5] = 'J'; + break; + + default: + break; + }; + } + + _emc->load(filename, &_npcScriptData, &_opcodes); +} + +#pragma mark - + +void KyraEngine_HoF::resetScaleTable() { + Common::fill(_scaleTable, ARRAYEND(_scaleTable), 0x100); +} + +void KyraEngine_HoF::setScaleTableItem(int item, int data) { + if (item >= 1 && item <= 15) + _scaleTable[item-1] = (data << 8) / 100; +} + +int KyraEngine_HoF::getScale(int x, int y) { + return _scaleTable[_screen->getLayer(x, y) - 1]; +} + +void KyraEngine_HoF::setDrawLayerTableEntry(int entry, int data) { + if (entry >= 1 && entry <= 15) + _drawLayerTable[entry-1] = data; +} + +int KyraEngine_HoF::getDrawLayer(int x, int y) { + int layer = _screen->getLayer(x, y); + layer = _drawLayerTable[layer-1]; + if (layer < 0) + layer = 0; + else if (layer >= 7) + layer = 6; + return layer; +} + +void KyraEngine_HoF::backUpPage0() { + if (_screenBuffer) { + memcpy(_screenBuffer, _screen->getCPagePtr(0), 64000); + } +} + +void KyraEngine_HoF::restorePage0() { + restorePage3(); + if (_screenBuffer) + _screen->copyBlockToPage(0, 0, 0, 320, 200, _screenBuffer); +} + +void KyraEngine_HoF::updateCharPal(int unk1) { + if (!_useCharPal) + return; + + int layer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1); + int palEntry = _charPalTable[layer]; + + if (palEntry != _charPalEntry && unk1) { + const uint8 *src = &_scenePal[(palEntry << 4) * 3]; + uint8 *ptr = _screen->getPalette(0).getData() + 336; + for (int i = 0; i < 48; ++i) { + *ptr -= (*ptr - *src) >> 1; + ++ptr; + ++src; + } + _screen->setScreenPalette(_screen->getPalette(0)); + _setCharPalFinal = true; + _charPalEntry = palEntry; + } else if (_setCharPalFinal || !unk1) { + _screen->getPalette(0).copy(_scenePal, palEntry << 4, 16, 112); + _screen->setScreenPalette(_screen->getPalette(0)); + _setCharPalFinal = false; + } +} + +void KyraEngine_HoF::setCharPalEntry(int entry, int value) { + if (entry > 15 || entry < 1) + entry = 1; + if (value > 8 || value < 0) + value = 0; + _charPalTable[entry] = value; + _useCharPal = 1; + _charPalEntry = 0; +} + +int KyraEngine_HoF::inputSceneChange(int x, int y, int unk1, int unk2) { + bool refreshNPC = false; + uint16 curScene = _mainCharacter.sceneId; + _pathfinderFlag = 15; + + if (!_unkHandleSceneChangeFlag) { + if (_savedMouseState == -3) { + if (_sceneList[curScene].exit4 != 0xFFFF) { + x = 4; + y = _sceneEnterY4; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -5) { + if (_sceneList[curScene].exit2 != 0xFFFF) { + x = 316; + y = _sceneEnterY2; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -6) { + if (_sceneList[curScene].exit1 != 0xFFFF) { + x = _sceneEnterX1; + y = _sceneEnterY1 - 2; + _pathfinderFlag = 14; + } + } else if (_savedMouseState == -4) { + if (_sceneList[curScene].exit3 != 0xFFFF) { + x = _sceneEnterX3; + y = 147; + _pathfinderFlag = 11; + } + } + } + + int strId = 0; + int vocH = _flags.isTalkie ? 131 : -1; + + if (_pathfinderFlag) { + if (findItem(curScene, 13) >= 0 && _savedMouseState <= -3) { + strId = 252; + } else if (_itemInHand == 72) { + strId = 257; + } else if (findItem(curScene, 72) >= 0 && _savedMouseState <= -3) { + strId = 256; + } else if (getInventoryItemSlot(72) != -1 && _savedMouseState <= -3) { + strId = 257; + } + } + + if (strId) { + updateCharFacing(); + objectChat(getTableString(strId, _cCodeBuffer, 1), 0, vocH, strId); + _pathfinderFlag = 0; + return 0; + } + + if (ABS(_mainCharacter.x1 - x) < 4 && ABS(_mainCharacter.y1 - y) < 2) { + _pathfinderFlag = 0; + return 0; + } + + int curX = _mainCharacter.x1 & ~3; + int curY = _mainCharacter.y1 & ~1; + int dstX = x & ~3; + int dstY = y & ~1; + + int wayLength = findWay(curX, curY, dstX, dstY, _movFacingTable, 600); + _pathfinderFlag = 0; + _timer->disable(5); + + if (wayLength != 0 && wayLength != 0x7D00) + refreshNPC = (trySceneChange(_movFacingTable, unk1, unk2) != 0); + + int charLayer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1); + if (_layerFlagTable[charLayer] != 0 && !queryGameFlag(0x163)) { + if (queryGameFlag(0x164)) { + _screen->hideMouse(); + _timer->disable(5); + runAnimationScript("_ZANBURN.EMC", 0, 1, 1, 0); + _deathHandler = 7; + snd_playWanderScoreViaMap(0x53, 1); + } else { + objectChat(getTableString(0xFD, _cCodeBuffer, 1), 0, 0x83, 0xFD); + setGameFlag(0x164); + _timer->enable(5); + _timer->setCountdown(5, 120); + } + } else if (queryGameFlag(0x164)) { + objectChat(getTableString(0xFE, _cCodeBuffer, 1), 0, 0x83, 0xFE); + resetGameFlag(0x164); + _timer->disable(5); + } + + if (refreshNPC) + enterNewSceneUnk2(0); + + _pathfinderFlag = 0; + return refreshNPC; +} + +int KyraEngine_HoF::getCharacterWalkspeed() const { + return _timer->getDelay(0); +} + +void KyraEngine_HoF::updateCharAnimFrame(int *table) { + static const int unkFrame1 = 17; + static const int unkFrame2 = 10; + static const int unkFrame3 = 24; + static const int unkFrame4 = 19; + static const int unkFrame5 = 21; + static const int unkFrame6 = 31; + static const int unkFrame7 = 26; + + Character *character = &_mainCharacter; + ++character->animFrame; + + int facing = character->facing; + + if (table) { + if (table[0] != table[-1] && table[-1] == table[1]) { + facing = getOppositeFacingDirection(table[-1]); + table[0] = table[-1]; + } + } + + if (!facing) { + ++_characterFacingCountTable[0]; + } else if (facing == 4) { + ++_characterFacingCountTable[1]; + } else if (facing == 7 || facing == 1 || facing == 5 || facing == 3) { + if (facing == 7 || facing == 1) { + if (_characterFacingCountTable[0] > 2) + facing = 0; + } else { + if (_characterFacingCountTable[1] > 2) + facing = 4; + } + + _characterFacingCountTable[0] = 0; + _characterFacingCountTable[1] = 0; + } + + if (facing == 0) { + if (character->animFrame < unkFrame7) + character->animFrame = unkFrame7; + + if (character->animFrame > unkFrame6) + character->animFrame = unkFrame7; + } else if (facing == 4) { + if (character->animFrame < unkFrame4) + character->animFrame = unkFrame4; + + if (character->animFrame > unkFrame3) + character->animFrame = unkFrame4; + } else { + if (character->animFrame > unkFrame4) + character->animFrame = unkFrame5; + + if (character->animFrame == unkFrame1) + character->animFrame = unkFrame2; + + if (character->animFrame > unkFrame1) + character->animFrame = unkFrame2 + 2; + } + + updateCharacterAnim(0); +} + +bool KyraEngine_HoF::checkCharCollision(int x, int y) { + int scale1 = 0, scale2 = 0, scale3 = 0; + int x1 = 0, x2 = 0, y1 = 0, y2 = 0; + scale1 = getScale(_mainCharacter.x1, _mainCharacter.y1); + scale2 = (scale1 * 24) >> 8; + scale3 = (scale1 * 48) >> 8; + + x1 = _mainCharacter.x1 - (scale2 >> 1); + x2 = _mainCharacter.x1 + (scale2 >> 1); + y1 = _mainCharacter.y1 - scale3; + y2 = _mainCharacter.y1; + + if (x >= x1 && x <= x2 && y >= y1 && y <= y2) + return true; + + return false; +} + +int KyraEngine_HoF::initAnimationShapes(uint8 *filedata) { + const int lastEntry = MIN(_animShapeLastEntry, 31); + for (int i = 0; i < lastEntry; ++i) { + addShapeToPool(filedata, i+33, i); + ShapeDesc *desc = &_shapeDescTable[24+i]; + desc->xAdd = _animShapeXAdd; + desc->yAdd = _animShapeYAdd; + desc->width = _animShapeWidth; + desc->height = _animShapeHeight; + } + return lastEntry; +} + +void KyraEngine_HoF::uninitAnimationShapes(int count, uint8 *filedata) { + for (int i = 0; i < count; ++i) + remShapeFromPool(i+33); + delete[] filedata; + setNextIdleAnimTimer(); +} + +void KyraEngine_HoF::setNextIdleAnimTimer() { + _nextIdleAnim = _system->getMillis() + _rnd.getRandomNumberRng(10, 15) * 60 * _tickLength; +} + +void KyraEngine_HoF::showIdleAnim() { + static const uint8 scriptMinTable[] = { + 0x00, 0x05, 0x07, 0x08, 0x00, 0x09, 0x0A, 0x0B, 0xFF, 0x00 + }; + + static const uint8 scriptMaxTable[] = { + 0x04, 0x06, 0x07, 0x08, 0x04, 0x09, 0x0A, 0x0B, 0xFF, 0x00 + }; + + if (queryGameFlag(0x159) && _flags.isTalkie) + return; + + if (!_useSceneIdleAnim && _flags.isTalkie) { + _useSceneIdleAnim = true; + randomSceneChat(); + } else { + _useSceneIdleAnim = false; + if (_characterShapeFile > 8) + return; + + int scriptMin = scriptMinTable[_characterShapeFile-1]; + int scriptMax = scriptMaxTable[_characterShapeFile-1]; + int script = 0; + + if (scriptMin < scriptMax) { + do { + script = _rnd.getRandomNumberRng(scriptMin, scriptMax); + } while (script == _lastIdleScript); + } else { + script = scriptMin; + } + + runIdleScript(script); + _lastIdleScript = script; + } +} + +void KyraEngine_HoF::runIdleScript(int script) { + if (script < 0 || script >= 12) + script = 0; + + if (_mainCharacter.animFrame != 18) { + setNextIdleAnimTimer(); + } else { + // FIXME: move this to staticres.cpp? + static const char *const idleScriptFiles[] = { + "_IDLHAIR.EMC", "_IDLDUST.EMC", "_IDLLEAN.EMC", "_IDLDIRT.EMC", "_IDLTOSS.EMC", "_IDLNOSE.EMC", + "_IDLBRSH.EMC", "_Z3IDLE.EMC", "_Z4IDLE.EMC", "_Z6IDLE.EMC", "_Z7IDLE.EMC", "_Z8IDLE.EMC" + }; + + runAnimationScript(idleScriptFiles[script], 1, 1, 1, 1); + } +} + +#pragma mark - + +void KyraEngine_HoF::backUpGfxRect24x24(int x, int y) { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, 24, 24, _gfxBackUpRect); +} + +void KyraEngine_HoF::restoreGfxRect24x24(int x, int y) { + _screen->copyBlockToPage(_screen->_curPage, x, y, 24, 24, _gfxBackUpRect); +} + +void KyraEngine_HoF::backUpGfxRect32x32(int x, int y) { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +void KyraEngine_HoF::restoreGfxRect32x32(int x, int y) { + _screen->copyBlockToPage(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +#pragma mark - + +void KyraEngine_HoF::openTalkFile(int newFile) { + char talkFilename[16]; + + if (_oldTalkFile > 0) { + sprintf(talkFilename, "CH%dVOC.TLK", _oldTalkFile); + _res->unloadPakFile(talkFilename); + _oldTalkFile = -1; + } + + if (newFile == 0) + strcpy(talkFilename, "ANYTALK.TLK"); + else + sprintf(talkFilename, "CH%dVOC.TLK", newFile); + + _oldTalkFile = newFile; + + 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(); + } + } +} + +void KyraEngine_HoF::snd_playVoiceFile(int id) { + char vocFile[9]; + assert(id >= 0 && id <= 9999999); + sprintf(vocFile, "%07d", id); + if (_sound->isVoicePresent(vocFile)) { + snd_stopVoice(); + + while (!_sound->voicePlay(vocFile, &_speechHandle)) { + updateWithText(); + _system->delayMillis(10); + } + } +} + +void KyraEngine_HoF::snd_loadSoundFile(int id) { + if (id < 0 || !_trackMap) + return; + + assert(id < _trackMapSize); + int file = _trackMap[id*2]; + _curSfxFile = _curMusicTheme = file; + _sound->loadSoundFile(file); +} + +void KyraEngine_HoF::playVoice(int high, int low) { + if (!_flags.isTalkie) + return; + int vocFile = high * 10000 + low * 10; + if (speechEnabled()) + snd_playVoiceFile(vocFile); +} + +void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) { + if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { + if (track == 10) + track = _lastSfxTrack; + + if (track == 10 || track == -1) + return; + + _lastSfxTrack = track; + } + + int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]); + if (vocIndex != -1) { + _sound->voicePlay(_ingameSoundList[vocIndex], 0, 255, 255, true); + } else if (_flags.platform == Common::kPlatformDOS) { + if (_sound->getSfxType() == Sound::kMidiMT32) + track = track < _mt32SfxMapSize ? _mt32SfxMap[track] - 1 : -1; + else if (_sound->getSfxType() == Sound::kMidiGM) + track = track < _gmSfxMapSize ? _gmSfxMap[track] - 1 : -1; + else if (_sound->getSfxType() == Sound::kPCSpkr) + track = track < _pcSpkSfxMapSize ? _pcSpkSfxMap[track] - 1 : -1; + + if (track != -1) + KyraEngine_v1::snd_playSoundEffect(track); + + // TODO ?? Maybe there is a way to let users select whether they want + // voc, midi or adl sfx (even though it makes no sense to choose anything but voc). + // The PC-98 version has support for non-pcm sound effects, but only for tracks + // which also have voc files. The syntax would be: + // KyraEngine_v1::snd_playSoundEffect(vocIndex); + } +} + +#pragma mark - + +void KyraEngine_HoF::loadInvWsa(const char *filename, int run_, int delayTime, int page, int sfx, int sFrame, int flags) { + int wsaFlags = 1; + if (flags) + wsaFlags |= 2; + + if (!_invWsa.wsa) + _invWsa.wsa = new WSAMovie_v2(this); + + if (!_invWsa.wsa->open(filename, wsaFlags, 0)) + error("Couldn't open inventory WSA file '%s'", filename); + + _invWsa.curFrame = 0; + _invWsa.lastFrame = _invWsa.wsa->frames(); + + _invWsa.x = _invWsa.wsa->xAdd(); + _invWsa.y = _invWsa.wsa->yAdd(); + _invWsa.w = _invWsa.wsa->width(); + _invWsa.h = _invWsa.wsa->height(); + _invWsa.x2 = _invWsa.x + _invWsa.w - 1; + _invWsa.y2 = _invWsa.y + _invWsa.h - 1; + + _invWsa.delay = delayTime; + _invWsa.page = page; + _invWsa.sfx = sfx; + + _invWsa.specialFrame = sFrame; + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, 0, _invWsa.page, Screen::CR_NO_P_CHECK); + + _invWsa.running = true; + _invWsa.timer = _system->getMillis(); + + if (run_) { + while (_invWsa.running && !skipFlag() && !shouldQuit()) { + update(); + _system->delayMillis(10); + } + + if (skipFlag()) { + resetSkipFlag(); + displayInvWsaLastFrame(); + } + } +} + +void KyraEngine_HoF::closeInvWsa() { + _invWsa.wsa->close(); + delete _invWsa.wsa; + _invWsa.wsa = 0; + _invWsa.running = false; +} + +void KyraEngine_HoF::updateInvWsa() { + if (!_invWsa.running || !_invWsa.wsa) + return; + + if (_invWsa.timer > _system->getMillis()) + return; + + _invWsa.wsa->displayFrame(_invWsa.curFrame, _invWsa.page, 0, 0, 0, 0, 0); + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, _invWsa.page, 0, Screen::CR_NO_P_CHECK); + + _invWsa.timer = _system->getMillis() + _invWsa.delay * _tickLength; + + ++_invWsa.curFrame; + if (_invWsa.curFrame >= _invWsa.lastFrame) + displayInvWsaLastFrame(); + + if (_invWsa.curFrame == _invWsa.specialFrame) + snd_playSoundEffect(_invWsa.sfx); + + if (_invWsa.sfx == -2) { + switch (_invWsa.curFrame) { + case 9: case 27: case 40: + snd_playSoundEffect(0x39); + break; + + case 18: case 34: case 44: + snd_playSoundEffect(0x33); + break; + + case 48: + snd_playSoundEffect(0x38); + break; + + default: + break; + } + } +} + +void KyraEngine_HoF::displayInvWsaLastFrame() { + if (!_invWsa.wsa) + return; + + _invWsa.wsa->displayFrame(_invWsa.lastFrame-1, _invWsa.page, 0, 0, 0, 0, 0); + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, _invWsa.page, 0, Screen::CR_NO_P_CHECK); + + closeInvWsa(); + + int32 countdown = _rnd.getRandomNumberRng(45, 80); + _timer->setCountdown(2, countdown * 60); +} + +#pragma mark - + +void KyraEngine_HoF::setCauldronState(uint8 state, bool paletteFade) { + _screen->copyPalette(2, 0); + Common::SeekableReadStream *file = _res->createReadStream("_POTIONS.PAL"); + if (!file) + error("Couldn't load cauldron palette"); + file->seek(state*18, SEEK_SET); + _screen->getPalette(2).loadVGAPalette(*file, 241, 6); + delete file; + file = 0; + + if (paletteFade) { + snd_playSoundEffect((state == 0) ? 0x6B : 0x66); + _screen->fadePalette(_screen->getPalette(2), 0x4B, &_updateFunctor); + } else { + _screen->setScreenPalette(_screen->getPalette(2)); + _screen->updateScreen(); + } + + _screen->getPalette(0).copy(_screen->getPalette(2), 241, 6); + _cauldronState = state; + _cauldronUseCount = 0; + if (state == 5) + setDlgIndex(5); +} + +void KyraEngine_HoF::clearCauldronTable() { + Common::fill(_cauldronTable, ARRAYEND(_cauldronTable), -1); +} + +void KyraEngine_HoF::addFrontCauldronTable(int item) { + for (int i = 23; i >= 0; --i) + _cauldronTable[i+1] = _cauldronTable[i]; + _cauldronTable[0] = item; +} + +void KyraEngine_HoF::cauldronItemAnim(int item) { + const int x = 282; + const int y = 135; + const int mouseDstX = (x + 7) & (~1); + const int mouseDstY = (y + 15) & (~1); + int mouseX = _mouseX & (~1); + int mouseY = _mouseY & (~1); + + while (mouseY != mouseDstY) { + if (mouseY < mouseDstY) + mouseY += 2; + else if (mouseY > mouseDstY) + mouseY -= 2; + uint32 waitEnd = _system->getMillis() + _tickLength; + setMousePos(mouseX, mouseY); + _system->updateScreen(); + delayUntil(waitEnd); + } + + while (mouseX != mouseDstX) { + if (mouseX < mouseDstX) + mouseX += 2; + else if (mouseX > mouseDstX) + mouseX -= 2; + uint32 waitEnd = _system->getMillis() + _tickLength; + setMousePos(mouseX, mouseY); + _system->updateScreen(); + delayUntil(waitEnd); + } + + if (itemIsFlask(item)) { + setHandItem(19); + delayUntil(_system->getMillis()+_tickLength*30); + setHandItem(18); + } else { + _screen->hideMouse(); + backUpGfxRect32x32(x, y); + uint8 *shape = getShapePtr(item+64); + + int curY = y; + for (int i = 0; i < 12; i += 2, curY += 2) { + restoreGfxRect32x32(x, y); + uint32 waitEnd = _system->getMillis() + _tickLength; + _screen->drawShape(0, shape, x, curY, 0, 0); + _screen->updateScreen(); + delayUntil(waitEnd); + } + + snd_playSoundEffect(0x17); + + for (int i = 16; i > 0; i -= 2, curY += 2) { + _screen->setNewShapeHeight(shape, i); + restoreGfxRect32x32(x, y); + uint32 waitEnd = _system->getMillis() + _tickLength; + _screen->drawShape(0, shape, x, curY, 0, 0); + _screen->updateScreen(); + delayUntil(waitEnd); + } + + restoreGfxRect32x32(x, y); + _screen->resetShapeHeight(shape); + removeHandItem(); + _screen->showMouse(); + } +} + +bool KyraEngine_HoF::updateCauldron() { + for (int i = 0; i < 23; ++i) { + const int16 *curStateTable = _cauldronStateTables[i]; + if (*curStateTable == -2) + continue; + + int cauldronState = i; + int16 cauldronTable[25]; + memcpy(cauldronTable, _cauldronTable, sizeof(cauldronTable)); + + while (*curStateTable != -2) { + int stateValue = *curStateTable++; + int j = 0; + for (; j < 25; ++j) { + int val = cauldronTable[j]; + + switch (val) { + case 68: + val = 70; + break; + + case 133: + case 167: + val = 119; + break; + + case 130: + case 143: + case 100: + val = 12; + break; + + case 132: + case 65: + case 69: + case 74: + val = 137; + break; + + case 157: + val = 134; + break; + + default: + break; + } + + if (val == stateValue) { + cauldronTable[j] = -1; + j = 26; + } + } + + if (j == 25) + cauldronState = -1; + } + + if (cauldronState >= 0) { + showMessage(0, 0xCF); + setCauldronState(cauldronState, true); + if (cauldronState == 7) + objectChat(getTableString(0xF2, _cCodeBuffer, 1), 0, 0x83, 0xF2); + clearCauldronTable(); + return true; + } + } + + return false; +} + +void KyraEngine_HoF::cauldronRndPaletteFade() { + showMessage(0, 0xCF); + int index = _rnd.getRandomNumberRng(0x0F, 0x16); + Common::SeekableReadStream *file = _res->createReadStream("_POTIONS.PAL"); + if (!file) + error("Couldn't load cauldron palette"); + file->seek(index*18, SEEK_SET); + _screen->getPalette(0).loadVGAPalette(*file, 241, 6); + snd_playSoundEffect(0x6A); + _screen->fadePalette(_screen->getPalette(0), 0x1E, &_updateFunctor); + file->seek(0, SEEK_SET); + _screen->getPalette(0).loadVGAPalette(*file, 241, 6); + delete file; + _screen->fadePalette(_screen->getPalette(0), 0x1E, &_updateFunctor); +} + +void KyraEngine_HoF::resetCauldronStateTable(int idx) { + for (int i = 0; i < 7; ++i) + _cauldronStateTables[idx][i] = -2; +} + +bool KyraEngine_HoF::addToCauldronStateTable(int data, int idx) { + for (int i = 0; i < 7; ++i) { + if (_cauldronStateTables[idx][i] == -2) { + _cauldronStateTables[idx][i] = data; + return true; + } + } + return false; +} + +void KyraEngine_HoF::listItemsInCauldron() { + int itemsInCauldron = 0; + for (int i = 0; i < 25; ++i) { + if (_cauldronTable[i] != -1) + ++itemsInCauldron; + else + break; + } + + if (!itemsInCauldron) { + if (!_cauldronState) + objectChat(getTableString(0xF4, _cCodeBuffer, 1), 0, 0x83, 0xF4); + else + objectChat(getTableString(0xF3, _cCodeBuffer, 1), 0, 0x83, 0xF3); + } else { + objectChat(getTableString(0xF7, _cCodeBuffer, 1), 0, 0x83, 0xF7); + + char buffer[80]; + for (int i = 0; i < itemsInCauldron-1; ++i) { + char *str = buffer; + strcpy(str, getTableString(_cauldronTable[i]+54, _cCodeBuffer, 1)); + if (_lang == 1) { + if (*str == 37) + str += 2; + } + strcpy((char *)_unkBuf500Bytes, "..."); + strcat((char *)_unkBuf500Bytes, str); + strcat((char *)_unkBuf500Bytes, "..."); + objectChat((const char *)_unkBuf500Bytes, 0, 0x83, _cauldronTable[i]+54); + } + + char *str = buffer; + strcpy(str, getTableString(_cauldronTable[itemsInCauldron-1]+54, _cCodeBuffer, 1)); + if (_lang == 1) { + if (*str == 37) + str += 2; + } + strcpy((char *)_unkBuf500Bytes, "..."); + strcat((char *)_unkBuf500Bytes, str); + strcat((char *)_unkBuf500Bytes, "."); + objectChat((const char *)_unkBuf500Bytes, 0, 0x83, _cauldronTable[itemsInCauldron-1]+54); + } +} + +#pragma mark - + +void KyraEngine_HoF::dinoRide() { + _mainCharX = _mainCharY = -1; + + setGameFlag(0x15A); + enterNewScene(41, -1, 0, 0, 0); + resetGameFlag(0x15A); + + setGameFlag(0x15B); + enterNewScene(39, -1, 0, 0, 0); + resetGameFlag(0x15B); + + setGameFlag(0x16F); + + setGameFlag(0x15C); + enterNewScene(42, -1, 0, 0, 0); + resetGameFlag(0x15C); + + setGameFlag(0x15D); + enterNewScene(39, -1, 0, 0, 0); + resetGameFlag(0x15D); + + setGameFlag(0x15E); + enterNewScene(40, -1, 0, 0, 0); + resetGameFlag(0x15E); + + _mainCharX = 262; + _mainCharY = 28; + _mainCharacter.facing = 5; + _mainCharacter.animFrame = _characterFrameTable[5]; + enterNewScene(39, 4, 0, 0, 0); + setHandItem(0x61); + _screen->showMouse(); + resetGameFlag(0x159); +} + +#pragma mark - + +void KyraEngine_HoF::playTim(const char *filename) { + TIM *tim = _tim->load(filename, &_timOpcodes); + if (!tim) + return; + + _tim->resetFinishedFlag(); + while (!shouldQuit() && !_tim->finished()) { + _tim->exec(tim, 0); + if (_chatText) + updateWithText(); + else + update(); + delay(10); + } + + _tim->unload(tim); +} + +#pragma mark - + +void KyraEngine_HoF::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the Kyra engine. + ConfMan.registerDefault("walkspeed", 5); +} + +void KyraEngine_HoF::writeSettings() { + ConfMan.setInt("talkspeed", ((_configTextspeed-2) * 255) / 95); + + switch (_lang) { + case 1: + _flags.lang = Common::FR_FRA; + break; + + case 2: + _flags.lang = Common::DE_DEU; + break; + + case 3: + _flags.lang = Common::JA_JPN; + 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)); + + KyraEngine_v1::writeSettings(); +} + +void KyraEngine_HoF::readSettings() { + KyraEngine_v2::readSettings(); + + int talkspeed = ConfMan.getInt("talkspeed"); + _configTextspeed = (talkspeed*95)/255 + 2; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_hof.h b/engines/kyra/engine/kyra_hof.h new file mode 100644 index 0000000000..588efbb5ab --- /dev/null +++ b/engines/kyra/engine/kyra_hof.h @@ -0,0 +1,697 @@ +/* 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 KYRA_KYRA_HOF_H +#define KYRA_KYRA_HOF_H + +#include "kyra/engine/kyra_v2.h" +#include "kyra/script/script.h" +#include "kyra/script/script_tim.h" +#include "kyra/graphics/screen_hof.h" +#include "kyra/text/text_hof.h" +#include "kyra/gui/gui_hof.h" + +#include "common/list.h" +#include "common/func.h" + +namespace Kyra { + +//class WSAMovie_v2; +//class KyraEngine_HoF; +class TextDisplayer_HoF; +class SeqPlayer_HOF; + +struct TIM; + +class KyraEngine_HoF : public KyraEngine_v2 { +friend class Debugger_HoF; +friend class TextDisplayer_HoF; +friend class GUI_HoF; +public: + KyraEngine_HoF(OSystem *system, const GameFlags &flags); + ~KyraEngine_HoF(); + + void pauseEngineIntern(bool pause); + + Screen *screen() { return _screen; } + Screen_v2 *screen_v2() const { return _screen; } + GUI *gui() const { return _gui; } + virtual TextDisplayer *text() { return _text; } + int language() const { return _lang; } + +protected: + static const EngineDesc _hofEngineDesc; + + // intro/outro + void seq_showStarcraftLogo(); + + int seq_playIntro(); + int seq_playOutro(); + int seq_playDemo(); + + void seq_pausePlayer(bool toggle); + + Common::Error init(); + Common::Error go(); + + Screen_HoF *_screen; + TextDisplayer_HoF *_text; + TIMInterpreter *_tim; + + static const int8 _dosTrackMap[]; + static const int _dosTrackMapSize; + static const int8 _mt32SfxMap[]; + static const int _mt32SfxMapSize; + static const int8 _gmSfxMap[]; + static const int _gmSfxMapSize; + static const int8 _pcSpkSfxMap[]; + static const int _pcSpkSfxMapSize; + +protected: + // game initialization + void startup(); + void runLoop(); + void cleanup(); + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + uint8 _configTextspeed; + + // TODO: get rid of all variables having pointers to the static resources if possible + // i.e. let them directly use the _staticres functions + void initStaticResource(); + + void setupTimers(); + void setupOpcodeTable(); + + void loadMouseShapes(); + void loadItemShapes(); + + // run + void update(); + void updateWithText(); + + Common::Functor0Mem<void, KyraEngine_HoF> _updateFunctor; + + void updateMouse(); + + void dinoRide(); + + void handleInput(int x, int y); + bool handleInputUnkSub(int x, int y); + + int inputSceneChange(int x, int y, int unk1, int unk2); + + // gfx/animation specific + bool _inventorySaved; + void backUpPage0(); + void restorePage0(); + + uint8 *_gfxBackUpRect; + + void backUpGfxRect24x24(int x, int y); + void restoreGfxRect24x24(int x, int y); + void backUpGfxRect32x32(int x, int y); + void restoreGfxRect32x32(int x, int y); + + uint8 *_sceneShapeTable[50]; + + WSAMovie_v2 *_wsaSlots[10]; + + void freeSceneShapePtrs(); + + struct ShapeDesc { + uint8 unk0, unk1, unk2, unk3, unk4; + uint16 width, height; + int16 xAdd, yAdd; + }; + + ShapeDesc *_shapeDescTable; + + void loadCharacterShapes(int shapes); + void loadInventoryShapes(); + + void resetScaleTable(); + void setScaleTableItem(int item, int data); + int getScale(int x, int y); + uint16 _scaleTable[15]; + + void setDrawLayerTableEntry(int entry, int data); + int getDrawLayer(int x, int y); + int _drawLayerTable[15]; + + int _layerFlagTable[16]; // seems to indicate layers where items get destroyed when dropped to (TODO: check this!) + + int initAnimationShapes(uint8 *filedata); + void uninitAnimationShapes(int count, uint8 *filedata); + + // animator + uint8 *_gamePlayBuffer; + void restorePage3(); + + void clearAnimObjects(); + + void refreshAnimObjects(int force); + + void drawAnimObjects(); + void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer); + void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer); + + void updateItemAnimations(); + + void updateCharFacing(); + void updateCharacterAnim(int); + void updateSceneAnim(int anim, int newFrame); + + int _animObj0Width, _animObj0Height; + void setCharacterAnimDim(int w, int h); + void resetCharacterAnimDim(); + + // scene + const char *_sceneCommentString; + uint8 _scenePal[688]; + + void enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3); + void enterNewSceneUnk1(int facing, int unk1, int unk2); + void enterNewSceneUnk2(int unk1); + void unloadScene(); + + void loadScenePal(); + void loadSceneMsc(); + + void fadeScenePal(int srcIndex, int delay); + + void startSceneScript(int unk1); + void runSceneScript2(); + void runSceneScript4(int unk1); + void runSceneScript7(); + + void initSceneAnims(int unk1); + void initSceneScreen(int unk1); + + int trySceneChange(int *moveTable, int unk1, int updateChar); + int checkSceneChange(); + + // pathfinder + bool lineIsPassable(int x, int y); + + // item + void setMouseCursor(Item item); + + uint8 _itemHtDat[176]; + + int checkItemCollision(int x, int y); + void updateWaterFlasks(); + + bool dropItem(int unk1, Item item, int x, int y, int unk2); + bool processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2); + void itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item); + void exchangeMouseItem(int itemPos); + bool pickUpItem(int x, int y); + + bool isDropable(int x, int y); + + static const byte _itemStringMap[]; + static const int _itemStringMapSize; + + static const Item _flaskTable[]; + bool itemIsFlask(Item item); + + // inventory + static const int _inventoryX[]; + static const int _inventoryY[]; + static const uint16 _itemMagicTable[]; + + int getInventoryItemSlot(Item item); + void removeSlotFromInventory(int slot); + bool checkInventoryItemExchange(Item item, int slot); + void drawInventoryShape(int page, Item item, int slot); + void clearInventorySlot(int slot, int page); + void redrawInventory(int page); + void scrollInventoryWheel(); + int findFreeVisibleInventorySlot(); + + ActiveItemAnim _activeItemAnim[15]; + int _nextAnimItem; + + // gui + bool _menuDirectlyToLoad; + GUI_HoF *_gui; + + void loadButtonShapes(); + void setupLangButtonShapes(); + uint8 *_buttonShapes[19]; + + void initInventoryButtonList(); + Button *_inventoryButtons; + Button *_buttonList; + + int scrollInventory(Button *button); + int buttonInventory(Button *button); + int bookButton(Button *button); + int cauldronButton(Button *button); + int cauldronClearButton(Button *button); + + // book + static const int _bookPageYOffset[]; + static const byte _bookTextColorMap[]; + + int _bookMaxPage; + int _bookNewPage; + int _bookCurPage; + int _bookBkgd; + bool _bookShown; + + void loadBookBkgd(); + void showBookPage(); + void bookLoop(); + + void bookDecodeText(uint8 *text); + void bookPrintText(int dstPage, const uint8 *text, int x, int y, uint8 color); + + int bookPrevPage(Button *button); + int bookNextPage(Button *button); + int bookClose(Button *button); + + // cauldron + uint8 _cauldronState; + int16 _cauldronUseCount; + int16 _cauldronTable[25]; + int16 _cauldronStateTables[23][7]; + + static const int16 _cauldronProtectedItems[]; + static const int16 _cauldronBowlTable[]; + static const int16 _cauldronMagicTable[]; + static const int16 _cauldronMagicTableScene77[]; + static const uint8 _cauldronStateTable[]; + + void resetCauldronStateTable(int idx); + bool addToCauldronStateTable(int data, int idx); + + void setCauldronState(uint8 state, bool paletteFade); + void clearCauldronTable(); + void addFrontCauldronTable(int item); + void cauldronItemAnim(int item); + void cauldronRndPaletteFade(); + bool updateCauldron(); + void listItemsInCauldron(); + + // localization + void loadCCodeBuffer(const char *file); + void loadOptionsBuffer(const char *file); + void loadChapterBuffer(int chapter); + uint8 *_optionsBuffer; + uint8 *_cCodeBuffer; + + uint8 *_chapterBuffer; + int _currentChapter; + int _newChapterFile; + + uint8 *getTableEntry(uint8 *buffer, int id); + char *getTableString(int id, uint8 *buffer, int decode); + const char *getChapterString(int id); + + void changeFileExtension(char *buffer); + + // - Just used in French version + int getItemCommandStringDrop(Item item); + int getItemCommandStringPickUp(Item item); + int getItemCommandStringInv(Item item); + // - + + char _internStringBuf[200]; + static const char *const _languageExtension[]; + static const char *const _scriptLangExt[]; + + // character + bool _useCharPal; + bool _setCharPalFinal; + int _charPalEntry; + uint8 _charPalTable[16]; + void updateCharPal(int unk1); + void setCharPalEntry(int entry, int value); + + int _characterFacingCountTable[2]; + + int getCharacterWalkspeed() const; + void updateCharAnimFrame(int *table); + + bool checkCharCollision(int x, int y); + + static const uint8 _characterFrameTable[]; + + // text + void showMessageFromCCode(int id, int16 palIndex, int); + void showMessage(const char *string, int16 palIndex); + void showChapterMessage(int id, int16 palIndex); + + void updateCommandLineEx(int str1, int str2, int16 palIndex); + + const char *_shownMessage; + + byte _messagePal[3]; + bool _fadeMessagePalette; + void fadeMessagePalette(); + + // chat + bool _chatIsNote; + + int chatGetType(const char *text); + int chatCalcDuration(const char *text); + + void objectChat(const char *text, int object, int vocHigh = -1, int vocLow = -1); + void objectChatInit(const char *text, int object, int vocHigh = -1, int vocLow = -1); + void objectChatPrintText(const char *text, int object); + void objectChatProcess(const char *script); + void objectChatWaitToFinish(); + + void startDialogue(int dlgIndex); + + void zanthSceneStartupChat(); + void randomSceneChat(); + void updateDlgBuffer(); + void loadDlgHeader(int &csEntry, int &vocH, int &scIndex1, int &scIndex2); + void processDialogue(int dlgOffset, int vocH = 0, int csEntry = 0); + void npcChatSequence(const char *str, int objectId, int vocHigh = -1, int vocLow = -1); + void setDlgIndex(int dlgIndex); + + int _npcTalkChpIndex; + int _npcTalkDlgIndex; + uint8 _newSceneDlgState[32]; + int8 **_conversationState; + uint8 *_dlgBuffer; + + // Talk object handling + void initTalkObject(int index); + void deinitTalkObject(int index); + + struct TalkObject { + char filename[13]; + int8 scriptId; + int16 x, y; + int8 color; + }; + TalkObject *_talkObjectList; + + struct TalkSections { + TIM *STATim; + TIM *TLKTim; + TIM *ENDTim; + }; + TalkSections _currentTalkSections; + + char _TLKFilename[13]; + + // tim + void playTim(const char *filename); + + int t2_initChat(const TIM *tim, const uint16 *param); + int t2_updateSceneAnim(const TIM *tim, const uint16 *param); + int t2_resetChat(const TIM *tim, const uint16 *param); + int t2_playSoundEffect(const TIM *tim, const uint16 *param); + + Common::Array<const TIMOpcode *> _timOpcodes; + + // sound + int _oldTalkFile; + int _currentTalkFile; + void openTalkFile(int newFile); + int _lastSfxTrack; + + virtual void snd_playVoiceFile(int id); + void snd_loadSoundFile(int id); + + void playVoice(int high, int low); + void snd_playSoundEffect(int track, int volume=0xFF); + + // timer + void timerFadeOutMessage(int); + void timerCauldronAnimation(int); + void timerFunc4(int); + void timerFunc5(int); + void timerBurnZanthia(int); + + void setTimer1DelaySecs(int secs); + + uint32 _nextIdleAnim; + int _lastIdleScript; + bool _useSceneIdleAnim; + + void setNextIdleAnimTimer(); + void showIdleAnim(); + void runIdleScript(int script); + + void setWalkspeed(uint8 speed); + + // ingame static sequence handling + void seq_makeBookOrCauldronAppear(int type); + void seq_makeBookAppear(); + + struct InventoryWsa { + int x, y, x2, y2, w, h; + int page; + int curFrame, lastFrame, specialFrame; + int sfx; + int delay; + bool running; + uint32 timer; + WSAMovie_v2 *wsa; + } _invWsa; + + // TODO: move inside KyraEngine_HoF::InventoryWsa? + void loadInvWsa(const char *filename, int run, int delay, int page, int sfx, int sFrame, int flags); + void closeInvWsa(); + + void updateInvWsa(); + void displayInvWsaLastFrame(); + + // opcodes + int o2_setCharacterFacingRefresh(EMCState *script); + int o2_setCharacterPos(EMCState *script); + int o2_defineObject(EMCState *script); + int o2_refreshCharacter(EMCState *script); + int o2_setSceneComment(EMCState *script); + int o2_setCharacterAnimFrame(EMCState *script); + int o2_setCharacterFacing(EMCState *script); + int o2_customCharacterChat(EMCState *script); + int o2_soundFadeOut(EMCState *script); + int o2_showChapterMessage(EMCState *script); + int o2_restoreTalkTextMessageBkgd(EMCState *script); + int o2_wsaClose(EMCState *script); + int o2_meanWhileScene(EMCState *script); + int o2_backUpScreen(EMCState *script); + int o2_restoreScreen(EMCState *script); + int o2_displayWsaFrame(EMCState *script); + int o2_displayWsaSequentialFramesLooping(EMCState *script); + int o2_wsaOpen(EMCState *script); + int o2_displayWsaSequentialFrames(EMCState *script); + int o2_displayWsaSequence(EMCState *script); + int o2_addItemToInventory(EMCState *script); + int o2_drawShape(EMCState *script); + int o2_addItemToCurScene(EMCState *script); + int o2_loadSoundFile(EMCState *script); + int o2_removeSlotFromInventory(EMCState *script); + int o2_removeItemFromInventory(EMCState *script); + int o2_countItemInInventory(EMCState *script); + int o2_countItemsInScene(EMCState *script); + int o2_wipeDownMouseItem(EMCState *script); + int o2_getElapsedSecs(EMCState *script); + int o2_getTimerDelay(EMCState *script); + int o2_delaySecs(EMCState *script); + int o2_setTimerDelay(EMCState *script); + int o2_setScaleTableItem(EMCState *script); + int o2_setDrawLayerTableItem(EMCState *script); + int o2_setCharPalEntry(EMCState *script); + int o2_loadZShapes(EMCState *script); + int o2_drawSceneShape(EMCState *script); + int o2_drawSceneShapeOnPage(EMCState *script); + int o2_disableAnimObject(EMCState *script); + int o2_enableAnimObject(EMCState *script); + int o2_loadPalette384(EMCState *script); + int o2_setPalette384(EMCState *script); + int o2_restoreBackBuffer(EMCState *script); + int o2_backUpInventoryGfx(EMCState *script); + int o2_disableSceneAnim(EMCState *script); + int o2_enableSceneAnim(EMCState *script); + int o2_restoreInventoryGfx(EMCState *script); + int o2_setSceneAnimPos2(EMCState *script); + int o2_fadeScenePal(EMCState *script); + int o2_enterNewScene(EMCState *script); + int o2_switchScene(EMCState *script); + int o2_setPathfinderFlag(EMCState *script); + int o2_getSceneExitToFacing(EMCState *script); + int o2_setLayerFlag(EMCState *script); + int o2_setZanthiaPos(EMCState *script); + int o2_loadMusicTrack(EMCState *script); + int o2_setSceneAnimPos(EMCState *script); + int o2_setCauldronState(EMCState *script); + int o2_showItemString(EMCState *script); + int o2_isAnySoundPlaying(EMCState *script); + int o2_setDrawNoShapeFlag(EMCState *script); + int o2_setRunFlag(EMCState *script); + int o2_showLetter(EMCState *script); + int o2_playFireflyScore(EMCState *script); + int o2_encodeShape(EMCState *script); + int o2_defineSceneAnim(EMCState *script); + int o2_updateSceneAnim(EMCState *script); + int o2_addToSceneAnimPosAndUpdate(EMCState *script); + int o2_useItemOnMainChar(EMCState *script); + int o2_startDialogue(EMCState *script); + int o2_addCauldronStateTableEntry(EMCState *script); + int o2_setCountDown(EMCState *script); + int o2_getCountDown(EMCState *script); + int o2_pressColorKey(EMCState *script); + int o2_objectChat(EMCState *script); + int o2_changeChapter(EMCState *script); + int o2_getColorCodeFlag1(EMCState *script); + int o2_setColorCodeFlag1(EMCState *script); + int o2_getColorCodeFlag2(EMCState *script); + int o2_setColorCodeFlag2(EMCState *script); + int o2_getColorCodeValue(EMCState *script); + int o2_setColorCodeValue(EMCState *script); + int o2_countItemInstances(EMCState *script); + int o2_removeItemFromScene(EMCState *script); + int o2_initObject(EMCState *script); + int o2_npcChat(EMCState *script); + int o2_deinitObject(EMCState *script); + int o2_playTimSequence(EMCState *script); + int o2_makeBookOrCauldronAppear(EMCState *script); + int o2_resetInputColorCode(EMCState *script); + int o2_mushroomEffect(EMCState *script); + int o2_customChat(EMCState *script); + int o2_customChatFinish(EMCState *script); + int o2_setupSceneAnimation(EMCState *script); + int o2_stopSceneAnimation(EMCState *script); + int o2_processPaletteIndex(EMCState *script); + int o2_updateTwoSceneAnims(EMCState *script); + int o2_getRainbowRoomData(EMCState *script); + int o2_drawSceneShapeEx(EMCState *script); + int o2_midiSoundFadeout(EMCState *script); + int o2_getSfxDriver(EMCState *script); + int o2_getVocSupport(EMCState *script); + int o2_getMusicDriver(EMCState *script); + int o2_zanthiaChat(EMCState *script); + int o2_isVoiceEnabled(EMCState *script); + int o2_isVoicePlaying(EMCState *script); + int o2_stopVoicePlaying(EMCState *script); + int o2_getGameLanguage(EMCState *script); + int o2_demoFinale(EMCState *script); + int o2_dummy(EMCState *script); + + // animation opcodes + int o2a_setCharacterFrame(EMCState *script); + + // script + void runStartScript(int script, int unk1); + void loadNPCScript(); + + bool _noScriptEnter; + + EMCData _npcScriptData; + + // pathfinder + uint8 *_unkBuf500Bytes; + uint8 *_unkBuf200kByte; + bool _chatAltFlag; + + // sequence player +/* ActiveWSA *_activeWSA; + ActiveText *_activeText; + */ + /*const char *const *_sequencePakList; + int _sequencePakListSize;*/ + const char *const *_ingamePakList; + int _ingamePakListSize; + + const char *const *_musicFileListIntro; + int _musicFileListIntroSize; + const char *const *_musicFileListFinale; + int _musicFileListFinaleSize; + const char *const *_musicFileListIngame; + int _musicFileListIngameSize; + const uint8 *_cdaTrackTableIntro; + int _cdaTrackTableIntroSize; + const uint8 *_cdaTrackTableIngame; + int _cdaTrackTableIngameSize; + const uint8 *_cdaTrackTableFinale; + int _cdaTrackTableFinaleSize; + const char *const *_ingameSoundList; + int _ingameSoundListSize; + const uint16 *_ingameSoundIndex; + int _ingameSoundIndexSize; + const uint16 *_ingameTalkObjIndex; + int _ingameTalkObjIndexSize; + const char *const *_ingameTimJpStr; + int _ingameTimJpStrSize; + + const ItemAnimDefinition *_itemAnimDefinition; + int _itemAnimDefinitionSize; + + /*const HofSeqData *_sequences; + + const ItemAnimData_v1 *_demoAnimData; + int _demoAnimSize; + + int _sequenceStringsDuration[33];*/ + +/* static const uint8 _seqTextColorPresets[]; + char *_seqProcessedString; + WSAMovie_v2 *_seqWsa; + + bool _abortIntroFlag; + int _menuChoice;*/ + + /*uint32 _seqFrameDelay; + uint32 _seqStartTime; + uint32 _seqSubFrameStartTime; + uint32 _seqEndTime; + uint32 _seqSubFrameEndTimeInternal; + uint32 _seqWsaChatTimeout; + uint32 _seqWsaChatFrameTimeout; + + int _seqFrameCounter; + int _seqScrollTextCounter; + int _seqWsaCurrentFrame; + bool _seqSpecialFlag; + bool _seqSubframePlaying; + uint8 _seqTextColor[2]; + uint8 _seqTextColorMap[16];*/ + + static const uint8 _rainbowRoomData[]; + + // color code related vars + int _colorCodeFlag1; + int _colorCodeFlag2; + uint8 _presetColorCode[7]; + uint8 _inputColorCode[7]; + uint32 _scriptCountDown; + int _dbgPass; + + // save/load specific + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_lok.cpp b/engines/kyra/engine/kyra_lok.cpp new file mode 100644 index 0000000000..30a83b2440 --- /dev/null +++ b/engines/kyra/engine/kyra_lok.cpp @@ -0,0 +1,990 @@ +/* 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 "kyra/engine/kyra_lok.h" +#include "kyra/resource/resource.h" +#include "kyra/sequence/seqplayer.h" +#include "kyra/engine/sprites.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/gui/debugger.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" + +namespace Kyra { + +KyraEngine_LoK::KyraEngine_LoK(OSystem *system, const GameFlags &flags) + : KyraEngine_v1(system, flags) { + + _seq_Forest = _seq_KallakWriting = _seq_KyrandiaLogo = _seq_KallakMalcolm = 0; + _seq_MalcolmTree = _seq_WestwoodLogo = _seq_Demo1 = _seq_Demo2 = _seq_Demo3 = 0; + _seq_Demo4 = 0; + + _seq_WSATable = _seq_CPSTable = _seq_COLTable = _seq_textsTable = 0; + _seq_WSATable_Size = _seq_CPSTable_Size = _seq_COLTable_Size = _seq_textsTable_Size = 0; + + _roomFilenameTable = _characterImageTable = 0; + _roomFilenameTableSize = _characterImageTableSize = 0; + _itemList = _takenList = _placedList = _droppedList = _noDropList = 0; + _itemList_Size = _takenList_Size = _placedList_Size = _droppedList_Size = _noDropList_Size = 0; + _putDownFirst = _waitForAmulet = _blackJewel = _poisonGone = _healingTip = 0; + _putDownFirst_Size = _waitForAmulet_Size = _blackJewel_Size = _poisonGone_Size = _healingTip_Size = 0; + _thePoison = _fluteString = _wispJewelStrings = _magicJewelString = _flaskFull = _fullFlask = 0; + _thePoison_Size = _fluteString_Size = _wispJewelStrings_Size = 0; + _magicJewelString_Size = _flaskFull_Size = _fullFlask_Size = 0; + + _defaultShapeTable = 0; + _healingShapeTable = _healingShape2Table = 0; + _defaultShapeTableSize = _healingShapeTableSize = _healingShape2TableSize = 0; + _posionDeathShapeTable = _fluteAnimShapeTable = 0; + _posionDeathShapeTableSize = _fluteAnimShapeTableSize = 0; + _winterScrollTable = _winterScroll1Table = _winterScroll2Table = 0; + _winterScrollTableSize = _winterScroll1TableSize = _winterScroll2TableSize = 0; + _drinkAnimationTable = _brandonToWispTable = _magicAnimationTable = _brandonStoneTable = 0; + _drinkAnimationTableSize = _brandonToWispTableSize = _magicAnimationTableSize = _brandonStoneTableSize = 0; + _specialPalettes = 0; + _sprites = 0; + _animator = 0; + _seq = 0; + _characterList = 0; + _roomTable = 0; + _movFacingTable = 0; + _buttonData = 0; + _buttonDataListPtr = 0; + memset(_shapes, 0, sizeof(_shapes)); + memset(_movieObjects, 0, sizeof(_movieObjects)); + _finalA = _finalB = _finalC = 0; + _endSequenceBackUpRect = 0; + memset(_panPagesTable, 0, sizeof(_panPagesTable)); + memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable)); + _currHeadShape = 0; + _currentHeadFrameTableIndex = 0; + _speechPlayTime = 0; + _seqPlayerFlag = false; + + memset(&_characterFacingZeroCount, 0, sizeof(_characterFacingZeroCount)); + memset(&_characterFacingFourCount, 0, sizeof(_characterFacingFourCount)); + + memset(&_itemBkgBackUp, 0, sizeof(_itemBkgBackUp)); + + _beadStateTimer1 = _beadStateTimer2 = 0; + memset(&_beadState1, 0, sizeof(_beadState1)); + _beadState1.x = -1; + memset(&_beadState2, 0, sizeof(_beadState2)); + + _malcolmFrame = 0; + _malcolmTimer1 = _malcolmTimer2 = 0; +} + +KyraEngine_LoK::~KyraEngine_LoK() { + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) { + if (_movieObjects[i]) + _movieObjects[i]->close(); + delete _movieObjects[i]; + _movieObjects[i] = 0; + } + + closeFinalWsa(); + if (_emc) { + _emc->unload(&_npcScriptData); + _emc->unload(&_scriptClickData); + } + + DebugMan.clearAllDebugChannels(); + + delete _screen; + delete _sprites; + delete _animator; + delete _seq; + + delete[] _characterList; + + delete[] _roomTable; + + delete[] _movFacingTable; + + delete[] _defaultShapeTable; + + delete[] _specialPalettes; + + delete[] _gui->_scrollUpButton.data0ShapePtr; + delete[] _gui->_scrollUpButton.data1ShapePtr; + delete[] _gui->_scrollUpButton.data2ShapePtr; + delete[] _gui->_scrollDownButton.data0ShapePtr; + delete[] _gui->_scrollDownButton.data1ShapePtr; + delete[] _gui->_scrollDownButton.data2ShapePtr; + + delete[] _buttonData; + delete[] _buttonDataListPtr; + + delete _gui; + + delete[] _itemBkgBackUp[0]; + delete[] _itemBkgBackUp[1]; + + for (int i = 0; i < ARRAYSIZE(_shapes); ++i) { + if (_shapes[i] != 0) { + delete[] _shapes[i]; + for (int i2 = 0; i2 < ARRAYSIZE(_shapes); i2++) { + if (_shapes[i2] == _shapes[i] && i2 != i) { + _shapes[i2] = 0; + } + } + _shapes[i] = 0; + } + } + + for (int i = 0; i < ARRAYSIZE(_sceneAnimTable); ++i) + delete[] _sceneAnimTable[i]; +} + +Common::Error KyraEngine_LoK::init() { + if (Common::parseRenderMode(ConfMan.get("render_mode")) == Common::kRenderPC9801) + _screen = new Screen_LoK_16(this, _system); + else + _screen = new Screen_LoK(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_LoK(this); + assert(_debugger); + + KyraEngine_v1::init(); + + _sprites = new Sprites(this, _system); + assert(_sprites); + _seq = new SeqPlayer(this, _system); + assert(_seq); + _animator = new Animator_LoK(this, _system); + assert(_animator); + _animator->init(5, 11, 12); + assert(*_animator); + _text = new TextDisplayer(this, screen()); + assert(_text); + _gui = new GUI_LoK(this, _screen); + assert(_gui); + + initStaticResource(); + + _sound->selectAudioResourceSet(kMusicIntro); + + if (_flags.platform == Common::kPlatformAmiga) { + _trackMap = _amigaTrackMap; + _trackMapSize = _amigaTrackMapSize; + } else { + _trackMap = _dosTrackMap; + _trackMapSize = _dosTrackMapSize; + } + + if (!_sound->init()) + error("Couldn't init sound"); + + _sound->loadSoundFile(0); + + setupButtonData(); + + _paletteChanged = 1; + _currentCharacter = 0; + _characterList = new Character[11]; + assert(_characterList); + memset(_characterList, 0, sizeof(Character) * 11); + + for (int i = 0; i < 11; ++i) + memset(_characterList[i].inventoryItems, 0xFF, sizeof(_characterList[i].inventoryItems)); + + _characterList[0].sceneId = 5; + _characterList[0].height = 48; + _characterList[0].facing = 3; + _characterList[0].currentAnimFrame = 7; + + memset(&_npcScriptData, 0, sizeof(EMCData)); + memset(&_scriptClickData, 0, sizeof(EMCData)); + + memset(&_npcScript, 0, sizeof(EMCState)); + memset(&_scriptMain, 0, sizeof(EMCState)); + memset(&_scriptClick, 0, sizeof(EMCState)); + + memset(_shapes, 0, sizeof(_shapes)); + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) + _movieObjects[i] = createWSAMovie(); + + memset(_flagsTable, 0, sizeof(_flagsTable)); + + _talkingCharNum = -1; + _charSayUnk3 = -1; + _disabledTalkAnimObject = _enabledTalkAnimObject = 0; + memset(_currSentenceColor, 0, 3); + _startSentencePalIndex = -1; + _fadeText = false; + + _cauldronState = 0; + _crystalState[0] = _crystalState[1] = -1; + + _brandonStatusBit = 0; + _brandonStatusBit0x02Flag = _brandonStatusBit0x20Flag = 10; + _brandonPosX = _brandonPosY = -1; + _poisonDeathCounter = 0; + + memset(_itemHtDat, 0, sizeof(_itemHtDat)); + memset(_exitList, 0xFF, sizeof(_exitList)); + _exitListPtr = 0; + _pathfinderFlag = _pathfinderFlag2 = 0; + _lastFindWayRet = 0; + _sceneChangeState = _loopFlag2 = 0; + + _movFacingTable = new int[150]; + assert(_movFacingTable); + _movFacingTable[0] = 8; + + _marbleVaseItem = -1; + memset(_foyerItemTable, -1, sizeof(_foyerItemTable)); + _itemInHand = kItemNone; + + _currentRoom = 0xFFFF; + _scenePhasingFlag = 0; + _lastProcessedItem = 0; + _lastProcessedItemHeight = 16; + + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; + _unkAmuletVar = 0; + + _endSequenceNeedLoading = 1; + _malcolmFlag = 0; + _beadStateVar = 0; + _endSequenceSkipFlag = 0; + _unkEndSeqVar2 = 0; + _endSequenceBackUpRect = 0; + _unkEndSeqVar4 = 0; + _unkEndSeqVar5 = 0; + _lastDisplayedPanPage = 0; + memset(_panPagesTable, 0, sizeof(_panPagesTable)); + _finalA = _finalB = _finalC = 0; + memset(&_kyragemFadingState, 0, sizeof(_kyragemFadingState)); + _kyragemFadingState.gOffset = 0x13; + _kyragemFadingState.bOffset = 0x13; + + _menuDirectlyToLoad = false; + + _lastMusicCommand = 0; + + return Common::kNoError; +} + +Common::Error KyraEngine_LoK::go() { + if (_res->getFileSize("6.FNT")) + _screen->loadFont(Screen::FID_6_FNT, "6.FNT"); + _screen->loadFont(Screen::FID_8_FNT, "8FAT.FNT"); + + _screen->setFont(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT); + + _screen->setScreenDim(0); + + _abortIntroFlag = false; + + if (_flags.isDemo && !_flags.isTalkie) { + _seqPlayerFlag = true; + seq_demo(); + _seqPlayerFlag = false; + } else { + setGameFlag(0xF3); + setGameFlag(0xFD); + if (_gameToLoad == -1) { + setGameFlag(0xEF); + _seqPlayerFlag = true; + seq_intro(); + _seqPlayerFlag = false; + + if (_flags.isDemo) { + _screen->fadeToBlack(); + return Common::kNoError; + } + + if (shouldQuit()) + return Common::kNoError; + + if (_skipIntroFlag && _abortIntroFlag && saveFileLoadable(0)) + resetGameFlag(0xEF); + } + _eventList.clear(); + startup(); + resetGameFlag(0xEF); + mainLoop(); + } + return Common::kNoError; +} + + +void KyraEngine_LoK::startup() { + static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 }; + _screen->setTextColorMap(colorMap); + + _sound->selectAudioResourceSet(kMusicIngame); + if (_flags.platform == Common::kPlatformPC98) + _sound->loadSoundFile("SE.DAT"); + else + _sound->loadSoundFile(0); + +// _screen->setFont(Screen::FID_6_FNT); + _screen->setAnimBlockPtr(3750); + memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable)); + loadMouseShapes(); + _currentCharacter = &_characterList[0]; + for (int i = 1; i < 5; ++i) + _animator->setCharacterDefaultFrame(i); + for (int i = 5; i <= 10; ++i) + setCharactersPositions(i); + _animator->setCharactersHeight(); + resetBrandonPoisonFlags(); + _screen->_curPage = 0; + // XXX + for (int i = 0; i < 12; ++i) { + int size = _screen->getRectSize(3, 24); + _shapes[361 + i] = new byte[size]; + } + + _itemBkgBackUp[0] = new uint8[_screen->getRectSize(3, 24)]; + memset(_itemBkgBackUp[0], 0, _screen->getRectSize(3, 24)); + _itemBkgBackUp[1] = new uint8[_screen->getRectSize(4, 32)]; + memset(_itemBkgBackUp[1], 0, _screen->getRectSize(4, 32)); + + for (int i = 0; i < _roomTableSize; ++i) { + for (int item = 0; item < 12; ++item) { + _roomTable[i].itemsTable[item] = kItemNone; + _roomTable[i].itemsXPos[item] = 0xFFFF; + _roomTable[i].itemsYPos[item] = 0xFF; + _roomTable[i].needInit[item] = 0; + } + } + + loadCharacterShapes(); + loadSpecialEffectShapes(); + loadItems(); + loadButtonShapes(); + initMainButtonList(); + loadMainScreen(); + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + + if (_flags.platform == Common::kPlatformAmiga) + _screen->loadPaletteTable("PALETTE.DAT", 6); + + // XXX + _animator->initAnimStateList(); + setCharactersInDefaultScene(); + + if (!_emc->load("_STARTUP.EMC", &_npcScriptData, &_opcodes)) + error("Could not load \"_STARTUP.EMC\" script"); + _emc->init(&_scriptMain, &_npcScriptData); + + if (!_emc->start(&_scriptMain, 0)) + error("Could not start script function 0 of script \"_STARTUP.EMC\""); + + while (_emc->isValid(&_scriptMain)) + _emc->run(&_scriptMain); + + _emc->unload(&_npcScriptData); + + if (!_emc->load("_NPC.EMC", &_npcScriptData, &_opcodes)) + error("Could not load \"_NPC.EMC\" script"); + + snd_playTheme(1, -1); + if (_gameToLoad == -1) { + enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1); + if (_abortIntroFlag && _skipIntroFlag && saveFileLoadable(0)) { + _menuDirectlyToLoad = true; + _screen->setMouseCursor(1, 1, _shapes[0]); + _screen->showMouse(); + _gui->buttonMenuCallback(0); + _menuDirectlyToLoad = false; + } else if (!shouldQuit()) { + saveGameStateIntern(0, "New game", 0); + } + } else { + _screen->setFont(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT); + loadGameStateCheck(_gameToLoad); + _gameToLoad = -1; + } +} + +void KyraEngine_LoK::mainLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _eventList.clear(); + + while (!shouldQuit()) { + int32 frameTime = (int32)_system->getMillis(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd() && _deathHandler != 8) + break; + } + + if (_deathHandler != -1) { + snd_playWanderScoreViaMap(0, 1); + snd_playSoundEffect(49); + _screen->setMouseCursor(1, 1, _shapes[0]); + removeHandItem(); + _gui->buttonMenuCallback(0); + _deathHandler = -1; + } + + if ((_brandonStatusBit & 2) && _brandonStatusBit0x02Flag) + _animator->animRefreshNPC(0); + + if ((_brandonStatusBit & 0x20) && _brandonStatusBit0x20Flag) { + _animator->animRefreshNPC(0); + _brandonStatusBit0x20Flag = 0; + } + + // FIXME: Why is this here? + _screen->showMouse(); + + int inputFlag = checkInput(_buttonList, _currentCharacter->sceneId != 210); + removeInputTop(); + + updateMousePointer(); + _timer->update(); + _sound->process(); + updateTextFade(); + + if (inputFlag == 198 || inputFlag == 199) + processInput(_mouseX, _mouseY); + + if (skipFlag()) + resetSkipFlag(); + + delay((frameTime + _gameSpeed) - _system->getMillis(), true, true); + } +} + +void KyraEngine_LoK::delayUntil(uint32 timestamp, bool updateTimers, bool update, bool isMainLoop) { + while (_system->getMillis() < timestamp && !shouldQuit() && !skipFlag()) { + if (updateTimers) + _timer->update(); + + if (timestamp - _system->getMillis() >= 10) + delay(10, update, isMainLoop); + } +} + +void KyraEngine_LoK::delay(uint32 amount, bool update, bool isMainLoop) { + uint32 start = _system->getMillis(); + do { + if (update) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + updateTextFade(); + updateMousePointer(); + } else { + // We need to do Screen::updateScreen here, since client code + // relies on this method to copy screen changes to the actual + // screen since at least 0af418e7ea3a41f93fcc551a45ee5bae822d812a. + _screen->updateScreen(); + } + + _isSaveAllowed = isMainLoop; + updateInput(); + _isSaveAllowed = false; + + if (_currentCharacter && _currentCharacter->sceneId == 210 && update) + updateKyragemFading(); + + if (amount > 0 && !skipFlag() && !shouldQuit()) + _system->delayMillis(10); + + // FIXME: Major hackery to allow skipping the intro + if (_seqPlayerFlag) { + for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) { + if (i->event.type == Common::EVENT_KEYDOWN && i->event.kbd.keycode == Common::KEYCODE_ESCAPE) + _abortIntroFlag = true; + else + i->causedSkip = false; + } + } + } + + if (skipFlag()) + snd_stopVoice(); + } while (!skipFlag() && _system->getMillis() < start + amount && !shouldQuit()); +} + +bool KyraEngine_LoK::skipFlag() const { + return KyraEngine_v1::skipFlag() || shouldQuit(); +} + +void KyraEngine_LoK::resetSkipFlag(bool removeEvent) { + if (removeEvent) { + _eventList.clear(); + } else { + KyraEngine_v1::resetSkipFlag(false); + } +} + +void KyraEngine_LoK::delayWithTicks(int ticks) { + uint32 nextTime = _system->getMillis() + ticks * _tickLength; + + while (_system->getMillis() < nextTime) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + seq_playEnd(); + } + + if (skipFlag()) + break; + + if (nextTime - _system->getMillis() >= 10) + delay(10); + } +} + +#pragma mark - +#pragma mark - Animation/shape specific code +#pragma mark - + +void KyraEngine_LoK::setupShapes123(const Shape *shapeTable, int endShape, int flags) { + for (int i = 123; i <= 172; ++i) + _shapes[i] = 0; + + uint8 curImage = 0xFF; + int curPageBackUp = _screen->_curPage; + _screen->_curPage = 8; // we are using page 8 here in the original page 2 was backuped and then used for this stuff + int shapeFlags = 2; + if (flags) + shapeFlags = 3; + for (int i = 123; i < 123 + endShape; ++i) { + uint8 newImage = shapeTable[i - 123].imageIndex; + if (newImage != curImage && newImage != 0xFF) { + assert(_characterImageTable); + _screen->loadBitmap(_characterImageTable[newImage], 8, 8, 0); + curImage = newImage; + } + _shapes[i] = _screen->encodeShape(shapeTable[i - 123].x << 3, shapeTable[i - 123].y, shapeTable[i - 123].w << 3, shapeTable[i - 123].h, shapeFlags); + assert(i - 7 < _defaultShapeTableSize); + _defaultShapeTable[i - 7].xOffset = shapeTable[i - 123].xOffset; + _defaultShapeTable[i - 7].yOffset = shapeTable[i - 123].yOffset; + _defaultShapeTable[i - 7].w = shapeTable[i - 123].w; + _defaultShapeTable[i - 7].h = shapeTable[i - 123].h; + } + _screen->_curPage = curPageBackUp; +} + +void KyraEngine_LoK::freeShapes123() { + for (int i = 123; i <= 172; ++i) { + delete[] _shapes[i]; + _shapes[i] = 0; + } +} + +#pragma mark - +#pragma mark - Misc stuff +#pragma mark - + +Movie *KyraEngine_LoK::createWSAMovie() { + if (_flags.platform == Common::kPlatformAmiga) + return new WSAMovieAmiga(this); + + return new WSAMovie_v1(this); +} + +void KyraEngine_LoK::setBrandonPoisonFlags(int reset) { + _brandonStatusBit |= 1; + + if (reset) + _poisonDeathCounter = 0; + + for (int i = 0; i < 0x100; ++i) + _brandonPoisonFlagsGFX[i] = i; + + _brandonPoisonFlagsGFX[0x99] = 0x34; + _brandonPoisonFlagsGFX[0x9A] = 0x35; + _brandonPoisonFlagsGFX[0x9B] = 0x37; + _brandonPoisonFlagsGFX[0x9C] = 0x38; + _brandonPoisonFlagsGFX[0x9D] = 0x2B; +} + +void KyraEngine_LoK::resetBrandonPoisonFlags() { + _brandonStatusBit = 0; + + for (int i = 0; i < 0x100; ++i) + _brandonPoisonFlagsGFX[i] = i; +} + +#pragma mark - +#pragma mark - Input +#pragma mark - + +void KyraEngine_LoK::processInput(int xpos, int ypos) { + if (processInputHelper(xpos, ypos)) + return; + + uint8 item = findItemAtPos(xpos, ypos); + if (item == 0xFF) { + _changedScene = false; + int handled = clickEventHandler(xpos, ypos); + if (_changedScene || handled) + return; + } + + // XXX _deathHandler specific + if (ypos <= 158) { + uint16 exit = 0xFFFF; + + if (xpos < 12) + exit = _walkBlockWest; + else if (xpos >= 308) + exit = _walkBlockEast; + else if (ypos >= 136) + exit = _walkBlockSouth; + else if (ypos < 12) + exit = _walkBlockNorth; + + if (exit != 0xFFFF) { + handleSceneChange(xpos, ypos, 1, 1); + return; + } else { + int script = checkForNPCScriptRun(xpos, ypos); + if (script >= 0) { + runNpcScript(script); + return; + } + if (_itemInHand != kItemNone) { + if (ypos < 155) { + if (hasClickedOnExit(xpos, ypos)) { + handleSceneChange(xpos, ypos, 1, 1); + return; + } + + dropItem(0, _itemInHand, xpos, ypos, 1); + } + } else { + if (ypos <= 155) + handleSceneChange(xpos, ypos, 1, 1); + } + } + } +} + +int KyraEngine_LoK::processInputHelper(int xpos, int ypos) { + uint8 item = findItemAtPos(xpos, ypos); + if (item != 0xFF) { + if (_itemInHand == kItemNone) { + _animator->animRemoveGameItem(item); + snd_playSoundEffect(53); + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + int item2 = currentRoom->itemsTable[item]; + currentRoom->itemsTable[item] = kItemNone; + setMouseItem(item2); + assert(_itemList && _takenList); + updateSentenceCommand(_itemList[getItemListIndex(item2)], _takenList[0], 179); + _itemInHand = item2; + clickEventHandler2(); + return 1; + } else { + exchangeItemWithMouseItem(_currentCharacter->sceneId, item); + return 1; + } + } + return 0; +} + +int KyraEngine_LoK::clickEventHandler(int xpos, int ypos) { + _emc->init(&_scriptClick, &_scriptClickData); + _scriptClick.regs[1] = xpos; + _scriptClick.regs[2] = ypos; + _scriptClick.regs[3] = 0; + _scriptClick.regs[4] = _itemInHand; + _emc->start(&_scriptClick, 1); + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + + return _scriptClick.regs[3]; +} + +void KyraEngine_LoK::updateMousePointer(bool forceUpdate) { + int shape = 0; + + int newMouseState = 0; + int newX = 0; + int newY = 0; + Common::Point mouse = getMousePos(); + if (mouse.y <= 158) { + if (mouse.x >= 12) { + if (mouse.x >= 308) { + if (_walkBlockEast == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + } + } else if (mouse.y >= 136) { + if (_walkBlockSouth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + } + } else if (mouse.y < 12) { + if (_walkBlockNorth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + } + } + } else { + if (_walkBlockWest == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -3; + newX = 1; + newY = shape = 5; + } + } + } + + if (mouse.x >= _entranceMouseCursorTracks[0] && mouse.y >= _entranceMouseCursorTracks[1] + && mouse.x <= _entranceMouseCursorTracks[2] && mouse.y <= _entranceMouseCursorTracks[3]) { + switch (_entranceMouseCursorTracks[4]) { + case 0: + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + break; + + case 2: + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + break; + + case 4: + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + break; + + case 6: + newMouseState = -3; + shape = 5; + newX = 1; + newY = 5; + break; + + default: + break; + } + } + + if (newMouseState == -2) { + shape = 6; + newX = 4; + newY = 4; + } + + if ((newMouseState && _mouseState != newMouseState) || (newMouseState && forceUpdate)) { + _mouseState = newMouseState; + _screen->setMouseCursor(newX, newY, _shapes[shape]); + } + + if (!newMouseState) { + if (_mouseState != _itemInHand || forceUpdate) { + if (mouse.y > 158 || (mouse.x >= 12 && mouse.x < 308 && mouse.y < 136 && mouse.y >= 12) || forceUpdate) { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(1, 1, _shapes[0]); + else + _screen->setMouseCursor(8, 15, _shapes[216 + _itemInHand]); + } + } + } +} + +bool KyraEngine_LoK::hasClickedOnExit(int xpos, int ypos) { + if (xpos < 16 || xpos >= 304) + return true; + + if (ypos < 8) + return true; + + if (ypos < 136 || ypos > 155) + return false; + + return true; +} + +void KyraEngine_LoK::clickEventHandler2() { + Common::Point mouse = getMousePos(); + + _emc->init(&_scriptClick, &_scriptClickData); + _scriptClick.regs[0] = _currentCharacter->sceneId; + _scriptClick.regs[1] = mouse.x; + _scriptClick.regs[2] = mouse.y; + _scriptClick.regs[4] = _itemInHand; + _emc->start(&_scriptClick, 6); + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +int KyraEngine_LoK::checkForNPCScriptRun(int xpos, int ypos) { + int returnValue = -1; + const Character *currentChar = _currentCharacter; + int charLeft = 0, charRight = 0, charTop = 0, charBottom = 0; + + int scaleFactor = _scaleTable[currentChar->y1]; + int addX = (((scaleFactor * 8) * 3) >> 8) >> 1; + int addY = ((scaleFactor * 3) << 4) >> 8; + + charLeft = currentChar->x1 - addX; + charRight = currentChar->x1 + addX; + charTop = currentChar->y1 - addY; + charBottom = currentChar->y1; + + if (xpos >= charLeft && charRight >= xpos && charTop <= ypos && charBottom >= ypos) + return 0; + + if (xpos > 304 || xpos < 16) + return -1; + + for (int i = 1; i < 5; ++i) { + currentChar = &_characterList[i]; + + if (currentChar->sceneId != _currentCharacter->sceneId) + continue; + + charLeft = currentChar->x1 - 12; + charRight = currentChar->x1 + 11; + charTop = currentChar->y1 - 48; + // if (!i) + // charBottom = currentChar->y2 - 16; + // else + charBottom = currentChar->y1; + + if (xpos < charLeft || xpos > charRight || ypos < charTop || charBottom < ypos) + continue; + + if (returnValue != -1) { + if (currentChar->y1 >= _characterList[returnValue].y1) + returnValue = i; + } else { + returnValue = i; + } + } + + return returnValue; +} + +void KyraEngine_LoK::runNpcScript(int func) { + _emc->init(&_npcScript, &_npcScriptData); + _emc->start(&_npcScript, func); + _npcScript.regs[0] = _currentCharacter->sceneId; + _npcScript.regs[4] = _itemInHand; + _npcScript.regs[5] = func; + + while (_emc->isValid(&_npcScript)) + _emc->run(&_npcScript); +} + +void KyraEngine_LoK::checkAmuletAnimFlags() { + if (_brandonStatusBit & 2) { + seq_makeBrandonNormal2(); + _timer->setCountdown(19, 300); + } + + if (_brandonStatusBit & 0x20) { + seq_makeBrandonNormal(); + _timer->setCountdown(19, 300); + } +} + +#pragma mark - + +void KyraEngine_LoK::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the Kyra engine. + ConfMan.registerDefault("walkspeed", 2); +} + +void KyraEngine_LoK::readSettings() { + int talkspeed = ConfMan.getInt("talkspeed"); + + // The default talk speed is 60. This should be mapped to "Normal". + + if (talkspeed == 0) + _configTextspeed = 3; // Clickable + if (talkspeed <= 50) + _configTextspeed = 0; // Slow + else if (talkspeed <= 150) + _configTextspeed = 1; // Normal + else + _configTextspeed = 2; // Fast + + KyraEngine_v1::readSettings(); +} + +void KyraEngine_LoK::writeSettings() { + int talkspeed; + + switch (_configTextspeed) { + case 0: // Slow + talkspeed = 1; + break; + case 1: // Normal + talkspeed = 60; + break; + case 2: // Fast + talkspeed = 255; + break; + default: // Clickable + talkspeed = 0; + } + + ConfMan.setInt("talkspeed", talkspeed); + + KyraEngine_v1::writeSettings(); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_lok.h b/engines/kyra/engine/kyra_lok.h new file mode 100644 index 0000000000..05053877c4 --- /dev/null +++ b/engines/kyra/engine/kyra_lok.h @@ -0,0 +1,816 @@ +/* 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 KYRA_KYRA_LOK_H +#define KYRA_KYRA_LOK_H + +#include "kyra/kyra_v1.h" +#include "kyra/script/script.h" +#include "kyra/graphics/screen_lok.h" +#include "kyra/gui/gui_lok.h" +#include "kyra/engine/item.h" + +namespace Kyra { + +class Movie; +class SoundDigital; +class SeqPlayer; +class Sprites; +class Animator_LoK; +class TextDisplayer; +class KyraEngine_LoK; + +struct Character { + uint16 sceneId; + uint8 height; + uint8 facing; + uint16 currentAnimFrame; + int8 inventoryItems[10]; + int16 x1, y1, x2, y2; +}; + +struct Shape { + uint8 imageIndex; + int8 xOffset, yOffset; + uint8 x, y, w, h; +}; + +struct Room { + uint8 nameIndex; + uint16 northExit; + uint16 eastExit; + uint16 southExit; + uint16 westExit; + int8 itemsTable[12]; + uint16 itemsXPos[12]; + uint8 itemsYPos[12]; + uint8 needInit[12]; +}; + +struct SeqLoop { + const uint8 *ptr; + uint16 count; +}; + +struct SceneExits { + uint16 northXPos; + uint8 northYPos; + uint16 eastXPos; + uint8 eastYPos; + uint16 southXPos; + uint8 southYPos; + uint16 westXPos; + uint8 westYPos; +}; + +struct BeadState { + int16 x; + int16 y; + int16 width; + int16 height; + int16 dstX; + int16 dstY; + int16 width2; + int16 unk8; + int16 unk9; + int16 tableIndex; +}; + +class KyraEngine_LoK : public KyraEngine_v1 { + friend class MusicPlayer; + friend class Debugger_LoK; + friend class Animator_LoK; + friend class GUI_LoK; +public: + KyraEngine_LoK(OSystem *system, const GameFlags &flags); + ~KyraEngine_LoK(); + + // _sprites and _seqplayer should be paused here too, to avoid some animation glitches, + // also parts of the hardcoded Malcolm fight might need some special handling. + + Screen *screen() { return _screen; } + Animator_LoK *animator() { return _animator; } + GUI *gui() const { return _gui; } + virtual Movie *createWSAMovie(); + + uint8 **shapes() { return _shapes; } + Character *currentCharacter() { return _currentCharacter; } + Character *characterList() { return _characterList; } + uint16 brandonStatus() { return _brandonStatusBit; } + + // TODO: remove me with workaround in animator.cpp l209 + uint16 getScene() { return _currentRoom; } + + int _paletteChanged; + int16 _northExitHeight; + + typedef bool (KyraEngine_LoK::*IntroProc)(); + + // static data access + const char *const *seqWSATable() { return _seq_WSATable; } + const char *const *seqCPSTable() { return _seq_CPSTable; } + const char *const *seqCOLTable() { return _seq_COLTable; } + const char *const *seqTextsTable() { return _seq_textsTable; } + + const uint8 *const *palTable1() { return &_specialPalettes[0]; } + const uint8 *const *palTable2() { return &_specialPalettes[29]; } + +protected: + virtual Common::Error go(); + virtual Common::Error init(); + +public: + // sequences + // -> misc + bool seq_skipSequence() const; +protected: + // -> demo + void seq_demo(); + + // -> intro + void seq_intro(); + bool seq_introPublisherLogos(); + bool seq_introLogos(); + bool seq_introStory(); + bool seq_introMalcolmTree(); + bool seq_introKallakWriting(); + bool seq_introKallakMalcolm(); + + // -> ingame animations + void seq_createAmuletJewel(int jewel, int page, int noSound, int drawOnly); + void seq_brandonHealing(); + void seq_brandonHealing2(); + void seq_poisonDeathNow(int now); + void seq_poisonDeathNowAnim(); + void seq_playFluteAnimation(); + void seq_winterScroll1(); + void seq_winterScroll2(); + void seq_makeBrandonInv(); + void seq_makeBrandonNormal(); + void seq_makeBrandonNormal2(); + void seq_makeBrandonWisp(); + void seq_dispelMagicAnimation(); + void seq_fillFlaskWithWater(int item, int type); + void seq_playDrinkPotionAnim(int item, int unk2, int flags); + void seq_brandonToStone(); + + // -> end fight + int seq_playEnd(); + void seq_playEnding(); + + int handleMalcolmFlag(); + int handleBeadState(); + void initBeadState(int x, int y, int x2, int y2, int unk1, BeadState *ptr); + int processBead(int x, int y, int &x2, int &y2, BeadState *ptr); + + // -> credits + void seq_playCredits(); + void seq_playCreditsAmiga(); + +public: + // delay + void delayUntil(uint32 timestamp, bool updateGameTimers = false, bool update = false, bool isMainLoop = false); + void delay(uint32 millis, bool update = false, bool isMainLoop = false); + void delayWithTicks(int ticks); + + bool skipFlag() const; + void resetSkipFlag(bool removeEvent = true); + + // TODO + void registerDefaultSettings(); + void readSettings(); + void writeSettings(); + + void snd_playSoundEffect(int track, int volume=0xFF); + void snd_playWanderScoreViaMap(int command, int restart); + virtual void snd_playVoiceFile(int id); + void snd_voiceWaitForFinish(bool ingame = true); + uint32 snd_getVoicePlayTime(); + +protected: + int32 _speechPlayTime; + + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); +protected: + // input + void processInput(int xpos, int ypos); + int processInputHelper(int xpos, int ypos); + int clickEventHandler(int xpos, int ypos); + void clickEventHandler2(); + void updateMousePointer(bool forceUpdate = false); + bool hasClickedOnExit(int xpos, int ypos); + + // scene + // -> init + void loadSceneMsc(); + void startSceneScript(int brandonAlive); + void setupSceneItems(); + void initSceneData(int facing, int unk1, int brandonAlive); + void initSceneObjectList(int brandonAlive); + void initSceneScreen(int brandonAlive); + void setupSceneResource(int sceneId); + + // -> process + void enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive); + int handleSceneChange(int xpos, int ypos, int unk1, int frameReset); + int processSceneChange(int *table, int unk1, int frameReset); + int changeScene(int facing); + + // -> modification + void transcendScenes(int roomIndex, int roomName); + void setSceneFile(int roomIndex, int roomName); + + // -> pathfinder + int findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize); + bool lineIsPassable(int x, int y); + + // -> item handling + // --> misc + void addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y); + + // --> drop handling + void itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item); + int processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2); + void dropItem(int unk1, int item, int x, int y, int unk2); + + // --> dropped item handling + int countItemsInScene(uint16 sceneId); + void exchangeItemWithMouseItem(uint16 sceneId, int itemIndex); + byte findFreeItemInScene(int scene); + byte findItemAtPos(int x, int y); + + // --> drop area handling + void addToNoDropRects(int x, int y, int w, int h); + void clearNoDropRects(); + int isDropable(int x, int y); + int checkNoDropRects(int x, int y); + + // --> player items handling + void updatePlayerItemsForScene(); + + // --> item GFX handling + void backUpItemRect0(int xpos, int ypos); + void restoreItemRect0(int xpos, int ypos); + void backUpItemRect1(int xpos, int ypos); + void restoreItemRect1(int xpos, int ypos); + + // items + // -> misc + void placeItemInGenericMapScene(int item, int index); + + // -> mouse item + void setHandItem(Item item); + void removeHandItem(); + void setMouseItem(Item item); + + int getItemListIndex(Item item); + + // -> graphics effects + void wipeDownMouseItem(int xpos, int ypos); + void itemSpecialFX(int x, int y, int item); + void itemSpecialFX1(int x, int y, int item); + void itemSpecialFX2(int x, int y, int item); + void magicOutMouseItem(int animIndex, int itemPos); + void magicInMouseItem(int animIndex, int item, int itemPos); + void specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops); + void processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops); + + // character + // -> movement + void moveCharacterToPos(int character, int facing, int xpos, int ypos); + void setCharacterPositionWithUpdate(int character); + int setCharacterPosition(int character, int *facingTable); + void setCharacterPositionHelper(int character, int *facingTable); + void setCharactersPositions(int character); + + // -> brandon + void setBrandonPoisonFlags(int reset); + void resetBrandonPoisonFlags(); + + // chat + // -> process + void characterSays(int vocFile, const char *chatStr, int8 charNum, int8 chatDuration); + void waitForChatToFinish(int vocFile, int16 chatDuration, const char *str, uint8 charNum, const bool printText); + + // -> initialization + int initCharacterChat(int8 charNum); + void backupChatPartnerAnimFrame(int8 charNum); + void restoreChatPartnerAnimFrame(int8 charNum); + int8 getChatPartnerNum(); + + // -> deinitialization + void endCharacterChat(int8 charNum, int16 arg_4); + + // graphics + // -> misc + int findDuplicateItemShape(int shape); + void updateKyragemFading(); + + // -> interface + void loadMainScreen(int page = 3); + void redrawInventory(int page); +public: + void drawSentenceCommand(const char *sentence, int unk1); + void updateSentenceCommand(const char *str1, const char *str2, int unk1); + void updateTextFade(); + +protected: + // -> amulet + void drawJewelPress(int jewel, int drawSpecial); + void drawJewelsFadeOutStart(); + void drawJewelsFadeOutEnd(int jewel); + + // -> shape handling + void setupShapes123(const Shape *shapeTable, int endShape, int flags); + void freeShapes123(); + + // misc (TODO) + void startup(); + void mainLoop(); + + int checkForNPCScriptRun(int xpos, int ypos); + void runNpcScript(int func); + + void loadMouseShapes(); + void loadCharacterShapes(); + void loadSpecialEffectShapes(); + void loadItems(); + void loadButtonShapes(); + void initMainButtonList(); + void setCharactersInDefaultScene(); + void setupPanPages(); + void freePanPages(); + void closeFinalWsa(); + + //void setTimer19(); + void setupTimers(); + void timerUpdateHeadAnims(int timerNum); + void timerTulipCreator(int timerNum); + void timerRubyCreator(int timerNum); + void timerAsInvisibleTimeout(int timerNum); + void timerAsWillowispTimeout(int timerNum); + void checkAmuletAnimFlags(); + void timerRedrawAmulet(int timerNum); + void timerLavenderRoseCreator(int timerNum); + void timerAcornCreator(int timerNum); + void timerBlueberryCreator(int timerNum); + void timerFadeText(int timerNum); + void timerWillowispFrameTimer(int timerNum); + void timerInvisibleFrameTimer(int timerNum); + void drawAmulet(); + void setTextFadeTimerCountdown(int16 countdown); + void setWalkspeed(uint8 newSpeed); + + void setItemCreationFlags(int offset, int count); + + int buttonInventoryCallback(Button *caller); + int buttonAmuletCallback(Button *caller); + + bool _seqPlayerFlag; + bool _skipIntroFlag; + bool _abortIntroFlag; + + bool _menuDirectlyToLoad; + uint8 *_itemBkgBackUp[2]; + uint8 *_shapes[373]; + Item _itemInHand; + bool _changedScene; + int _unkScreenVar1, _unkScreenVar2, _unkScreenVar3; + int _beadStateVar; + int _unkAmuletVar; + + int _malcolmFlag; + int _endSequenceSkipFlag; + int _endSequenceNeedLoading; + int _unkEndSeqVar2; + uint8 *_endSequenceBackUpRect; + int _unkEndSeqVar4; + int _unkEndSeqVar5; + int _lastDisplayedPanPage; + uint8 *_panPagesTable[20]; + Movie *_finalA, *_finalB, *_finalC; + + Movie *_movieObjects[10]; + + uint16 _entranceMouseCursorTracks[5]; + uint16 _walkBlockNorth; + uint16 _walkBlockEast; + uint16 _walkBlockSouth; + uint16 _walkBlockWest; + + int32 _scaleMode; + int16 _scaleTable[145]; + + Common::Rect _noDropRects[11]; + + int8 _birthstoneGemTable[4]; + int8 _idolGemsTable[3]; + + int8 _marbleVaseItem; + int8 _foyerItemTable[3]; + + int8 _cauldronState; + int8 _crystalState[2]; + + uint16 _brandonStatusBit; + uint8 _brandonStatusBit0x02Flag; + uint8 _brandonStatusBit0x20Flag; + uint8 _brandonPoisonFlagsGFX[256]; + int16 _brandonInvFlag; + uint8 _poisonDeathCounter; + int _brandonPosX; + int _brandonPosY; + + uint16 _currentChatPartnerBackupFrame; + uint16 _currentCharAnimFrame; + + int _characterFacingZeroCount[8]; + int _characterFacingFourCount[8]; + + int8 *_sceneAnimTable[50]; + + uint8 _itemHtDat[145]; + int _lastProcessedItem; + int _lastProcessedItemHeight; + + int16 *_exitListPtr; + int16 _exitList[11]; + SceneExits _sceneExits; + uint16 _currentRoom; + int _scenePhasingFlag; + + int _sceneChangeState; + int _loopFlag2; + + int _pathfinderFlag; + int _pathfinderFlag2; + int _lastFindWayRet; + int *_movFacingTable; + + int8 _talkingCharNum; + int8 _charSayUnk2; + int8 _charSayUnk3; + int8 _currHeadShape; + int _currentHeadFrameTableIndex; + int8 _disabledTalkAnimObject; + int8 _enabledTalkAnimObject; + uint8 _currSentenceColor[3]; + int8 _startSentencePalIndex; + bool _fadeText; + + uint8 _configTextspeed; + + Animator_LoK *_animator; + SeqPlayer *_seq; + Sprites *_sprites; + Screen_LoK *_screen; + + EMCState _scriptMain; + + EMCState _npcScript; + EMCData _npcScriptData; + + EMCState _scriptClick; + EMCData _scriptClickData; + + Character *_characterList; + Character *_currentCharacter; + + Button *_buttonList; + GUI_LoK *_gui; + + uint16 _malcolmFrame; + uint32 _malcolmTimer1; + uint32 _malcolmTimer2; + + uint32 _beadStateTimer1; + uint32 _beadStateTimer2; + BeadState _beadState1; + BeadState _beadState2; + + struct KyragemState { + uint16 nextOperation; + uint16 rOffset; + uint16 gOffset; + uint16 bOffset; + uint32 timerCount; + } _kyragemFadingState; + + static const int8 _dosTrackMap[]; + static const int _dosTrackMapSize; + + static const int8 _amigaTrackMap[]; + static const int _amigaTrackMapSize; + + // TODO: get rid of all variables having pointers to the static resources if possible + // i.e. let them directly use the _staticres functions + void initStaticResource(); + + const uint8 *_seq_Forest; + const uint8 *_seq_KallakWriting; + const uint8 *_seq_KyrandiaLogo; + const uint8 *_seq_KallakMalcolm; + const uint8 *_seq_MalcolmTree; + const uint8 *_seq_WestwoodLogo; + const uint8 *_seq_Demo1; + const uint8 *_seq_Demo2; + const uint8 *_seq_Demo3; + const uint8 *_seq_Demo4; + const uint8 *_seq_Reunion; + + const char *const *_seq_WSATable; + const char *const *_seq_CPSTable; + const char *const *_seq_COLTable; + const char *const *_seq_textsTable; + + const char *const *_storyStrings; + + int _seq_WSATable_Size; + int _seq_CPSTable_Size; + int _seq_COLTable_Size; + int _seq_textsTable_Size; + + int _storyStringsSize; + + const char *const *_itemList; + const char *const *_takenList; + const char *const *_placedList; + const char *const *_droppedList; + const char *const *_noDropList; + const char *const *_putDownFirst; + const char *const *_waitForAmulet; + const char *const *_blackJewel; + const char *const *_poisonGone; + const char *const *_healingTip; + const char *const *_thePoison; + const char *const *_fluteString; + const char *const *_wispJewelStrings; + const char *const *_magicJewelString; + const char *const *_flaskFull; + const char *const *_fullFlask; + const char *const *_veryClever; + const char *const *_homeString; + const char *const *_newGameString; + + int _itemList_Size; + int _takenList_Size; + int _placedList_Size; + int _droppedList_Size; + int _noDropList_Size; + int _putDownFirst_Size; + int _waitForAmulet_Size; + int _blackJewel_Size; + int _poisonGone_Size; + int _healingTip_Size; + int _thePoison_Size; + int _fluteString_Size; + int _wispJewelStrings_Size; + int _magicJewelString_Size; + int _flaskFull_Size; + int _fullFlask_Size; + int _veryClever_Size; + int _homeString_Size; + int _newGameString_Size; + + const char *const *_characterImageTable; + int _characterImageTableSize; + + const char *const *_guiStrings; + int _guiStringsSize; + + const char *const *_configStrings; + int _configStringsSize; + + Shape *_defaultShapeTable; + int _defaultShapeTableSize; + + const Shape *_healingShapeTable; + int _healingShapeTableSize; + const Shape *_healingShape2Table; + int _healingShape2TableSize; + + const Shape *_posionDeathShapeTable; + int _posionDeathShapeTableSize; + + const Shape *_fluteAnimShapeTable; + int _fluteAnimShapeTableSize; + + const Shape *_winterScrollTable; + int _winterScrollTableSize; + const Shape *_winterScroll1Table; + int _winterScroll1TableSize; + const Shape *_winterScroll2Table; + int _winterScroll2TableSize; + + const Shape *_drinkAnimationTable; + int _drinkAnimationTableSize; + + const Shape *_brandonToWispTable; + int _brandonToWispTableSize; + + const Shape *_magicAnimationTable; + int _magicAnimationTableSize; + + const Shape *_brandonStoneTable; + int _brandonStoneTableSize; + + Room *_roomTable; + int _roomTableSize; + const char *const *_roomFilenameTable; + int _roomFilenameTableSize; + + const uint8 *_amuleteAnim; + + const uint8 *const *_specialPalettes; + + // positions of the inventory + static const uint16 _itemPosX[]; + static const uint8 _itemPosY[]; + + void setupButtonData(); + Button *_buttonData; + Button **_buttonDataListPtr; + + static const uint8 _magicMouseItemStartFrame[]; + static const uint8 _magicMouseItemEndFrame[]; + static const uint8 _magicMouseItemStartFrame2[]; + static const uint8 _magicMouseItemEndFrame2[]; + + static const uint16 _amuletX[]; + static const uint16 _amuletY[]; + static const uint16 _amuletX2[]; + static const uint16 _amuletY2[]; + + // special palette handling for AMIGA + void setupZanthiaPalette(int pal); +protected: + void setupOpcodeTable(); + + // Opcodes + int o1_magicInMouseItem(EMCState *script); + int o1_characterSays(EMCState *script); + int o1_delay(EMCState *script); + int o1_drawSceneAnimShape(EMCState *script); + int o1_runNPCScript(EMCState *script); + int o1_setSpecialExitList(EMCState *script); + int o1_walkPlayerToPoint(EMCState *script); + int o1_dropItemInScene(EMCState *script); + int o1_drawAnimShapeIntoScene(EMCState *script); + int o1_savePageToDisk(EMCState *script); + int o1_sceneAnimOn(EMCState *script); + int o1_sceneAnimOff(EMCState *script); + int o1_getElapsedSeconds(EMCState *script); + int o1_mouseIsPointer(EMCState *script); + int o1_runSceneAnimUntilDone(EMCState *script); + int o1_fadeSpecialPalette(EMCState *script); + int o1_phaseInSameScene(EMCState *script); + int o1_setScenePhasingFlag(EMCState *script); + int o1_resetScenePhasingFlag(EMCState *script); + int o1_queryScenePhasingFlag(EMCState *script); + int o1_sceneToDirection(EMCState *script); + int o1_setBirthstoneGem(EMCState *script); + int o1_placeItemInGenericMapScene(EMCState *script); + int o1_setBrandonStatusBit(EMCState *script); + int o1_delaySecs(EMCState *script); + int o1_getCharacterScene(EMCState *script); + int o1_runNPCSubscript(EMCState *script); + int o1_magicOutMouseItem(EMCState *script); + int o1_internalAnimOn(EMCState *script); + int o1_forceBrandonToNormal(EMCState *script); + int o1_poisonDeathNow(EMCState *script); + int o1_setScaleMode(EMCState *script); + int o1_openWSAFile(EMCState *script); + int o1_closeWSAFile(EMCState *script); + int o1_runWSAFromBeginningToEnd(EMCState *script); + int o1_displayWSAFrame(EMCState *script); + int o1_enterNewScene(EMCState *script); + int o1_setSpecialEnterXAndY(EMCState *script); + int o1_runWSAFrames(EMCState *script); + int o1_popBrandonIntoScene(EMCState *script); + int o1_restoreAllObjectBackgrounds(EMCState *script); + int o1_setCustomPaletteRange(EMCState *script); + int o1_loadPageFromDisk(EMCState *script); + int o1_customPrintTalkString(EMCState *script); + int o1_restoreCustomPrintBackground(EMCState *script); + int o1_getCharacterX(EMCState *script); + int o1_getCharacterY(EMCState *script); + int o1_setCharacterFacing(EMCState *script); + int o1_copyWSARegion(EMCState *script); + int o1_printText(EMCState *script); + int o1_loadSoundFile(EMCState *script); + int o1_displayWSAFrameOnHidPage(EMCState *script); + int o1_displayWSASequentialFrames(EMCState *script); + int o1_refreshCharacter(EMCState *script); + int o1_internalAnimOff(EMCState *script); + int o1_changeCharactersXAndY(EMCState *script); + int o1_clearSceneAnimatorBeacon(EMCState *script); + int o1_querySceneAnimatorBeacon(EMCState *script); + int o1_refreshSceneAnimator(EMCState *script); + int o1_placeItemInOffScene(EMCState *script); + int o1_wipeDownMouseItem(EMCState *script); + int o1_placeCharacterInOtherScene(EMCState *script); + int o1_getKey(EMCState *script); + int o1_specificItemInInventory(EMCState *script); + int o1_popMobileNPCIntoScene(EMCState *script); + int o1_mobileCharacterInScene(EMCState *script); + int o1_hideMobileCharacter(EMCState *script); + int o1_unhideMobileCharacter(EMCState *script); + int o1_setCharacterLocation(EMCState *script); + int o1_walkCharacterToPoint(EMCState *script); + int o1_specialEventDisplayBrynnsNote(EMCState *script); + int o1_specialEventRemoveBrynnsNote(EMCState *script); + int o1_setLogicPage(EMCState *script); + int o1_fatPrint(EMCState *script); + int o1_preserveAllObjectBackgrounds(EMCState *script); + int o1_updateSceneAnimations(EMCState *script); + int o1_sceneAnimationActive(EMCState *script); + int o1_setCharacterMovementDelay(EMCState *script); + int o1_getCharacterFacing(EMCState *script); + int o1_bkgdScrollSceneAndMasksRight(EMCState *script); + int o1_dispelMagicAnimation(EMCState *script); + int o1_findBrightestFireberry(EMCState *script); + int o1_setFireberryGlowPalette(EMCState *script); + int o1_setDeathHandlerFlag(EMCState *script); + int o1_drinkPotionAnimation(EMCState *script); + int o1_makeAmuletAppear(EMCState *script); + int o1_drawItemShapeIntoScene(EMCState *script); + int o1_setCharacterCurrentFrame(EMCState *script); + int o1_waitForConfirmationMouseClick(EMCState *script); + int o1_pageFlip(EMCState *script); + int o1_setSceneFile(EMCState *script); + int o1_getItemInMarbleVase(EMCState *script); + int o1_setItemInMarbleVase(EMCState *script); + int o1_addItemToInventory(EMCState *script); + int o1_intPrint(EMCState *script); + int o1_shakeScreen(EMCState *script); + int o1_createAmuletJewel(EMCState *script); + int o1_setSceneAnimCurrXY(EMCState *script); + int o1_poisonBrandonAndRemaps(EMCState *script); + int o1_fillFlaskWithWater(EMCState *script); + int o1_getCharacterMovementDelay(EMCState *script); + int o1_getBirthstoneGem(EMCState *script); + int o1_queryBrandonStatusBit(EMCState *script); + int o1_playFluteAnimation(EMCState *script); + int o1_playWinterScrollSequence(EMCState *script); + int o1_getIdolGem(EMCState *script); + int o1_setIdolGem(EMCState *script); + int o1_totalItemsInScene(EMCState *script); + int o1_restoreBrandonsMovementDelay(EMCState *script); + int o1_setEntranceMouseCursorTrack(EMCState *script); + int o1_itemAppearsOnGround(EMCState *script); + int o1_setNoDrawShapesFlag(EMCState *script); + int o1_fadeEntirePalette(EMCState *script); + int o1_itemOnGroundHere(EMCState *script); + int o1_queryCauldronState(EMCState *script); + int o1_setCauldronState(EMCState *script); + int o1_queryCrystalState(EMCState *script); + int o1_setCrystalState(EMCState *script); + int o1_setPaletteRange(EMCState *script); + int o1_shrinkBrandonDown(EMCState *script); + int o1_growBrandonUp(EMCState *script); + int o1_setBrandonScaleXAndY(EMCState *script); + int o1_resetScaleMode(EMCState *script); + int o1_getScaleDepthTableValue(EMCState *script); + int o1_setScaleDepthTableValue(EMCState *script); + int o1_message(EMCState *script); + int o1_checkClickOnNPC(EMCState *script); + int o1_getFoyerItem(EMCState *script); + int o1_setFoyerItem(EMCState *script); + int o1_setNoItemDropRegion(EMCState *script); + int o1_walkMalcolmOn(EMCState *script); + int o1_passiveProtection(EMCState *script); + int o1_setPlayingLoop(EMCState *script); + int o1_brandonToStoneSequence(EMCState *script); + int o1_brandonHealingSequence(EMCState *script); + int o1_protectCommandLine(EMCState *script); + int o1_pauseMusicSeconds(EMCState *script); + int o1_resetMaskRegion(EMCState *script); + int o1_setPaletteChangeFlag(EMCState *script); + int o1_vocUnload(EMCState *script); + int o1_vocLoad(EMCState *script); + int o1_dummy(EMCState *script); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_mr.cpp b/engines/kyra/engine/kyra_mr.cpp new file mode 100644 index 0000000000..9cadf3c626 --- /dev/null +++ b/engines/kyra/engine/kyra_mr.cpp @@ -0,0 +1,1426 @@ +/* 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 "kyra/engine/kyra_mr.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/text/text_mr.h" +#include "kyra/graphics/vqa.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" +#include "kyra/gui/gui_mr.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_digital.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 = kItemNone; + _savedMouseState = -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; + _menu = 0; + _menuAnim = 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() { + uninitMainMenu(); + + 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<const Opcode *>::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(); + + _debugger = new Debugger_v2(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _soundDigital = new SoundDigital(this, _mixer); + assert(_soundDigital); + 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_8_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: + fadeOutMusic(60); + _screen->fadeToBlack(60); + uninitMainMenu(); + quitGame(); + running = false; + break; + + default: + break; + } + } + + if (_showOutro && !shouldQuit()) + 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); + + Common::String filename = Common::String::format("%s%d.VQA", name, _configVQAQuality); + + if (vqa.open(filename.c_str())) { + 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.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); + + _musicSoundChannel = _soundDigital->playSound(_soundList[track], 0xFF, Audio::Mixer::kMusicSoundType, 255, true); + } + + _lastMusicCommand = track; +} + +void KyraEngine_MR::stopMusicTrack() { + if (_musicSoundChannel != -1 && _soundDigital->isPlaying(_musicSoundChannel)) + _soundDigital->stopSound(_musicSoundChannel); + + _lastMusicCommand = -1; + _musicSoundChannel = -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) { + assert(_sfxFileMap[item*2+0] < _sfxFileListSize); + Common::String filename = Common::String::format("%s", _sfxFileList[_sfxFileMap[item*2+0]]); + uint8 priority = _sfxFileMap[item*2+1]; + + _soundDigital->playSound(filename.c_str(), 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) { + Common::String filename = Common::String::format("%.08u", (uint)file); + + if (speechEnabled()) + _voiceSoundChannel = _soundDigital->playSound(filename.c_str(), 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); + + _gamePlayBuffer = new uint8[64000]; + + _interface = new uint8[17920]; + _interfaceCommandLine = new uint8[3840]; + + _screen->setFont(Screen::FID_8_FNT); + + _stringBuffer = new char[500]; + allocAnimObjects(1, 16, 50); + + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + _screenBuffer = new uint8[64000]; + + 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"); + + openTalkFile(0); + _currentTalkFile = 0; + openTalkFile(1); + loadCostPal(); + + 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; + + _gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)]; + initItemList(50); + resetItemList(); + + loadShadowShape(); + loadExtrasShapes(); + _characterShapeFile = 0; + loadCharacterShapes(_characterShapeFile); + updateMalcolmShapes(); + initMainButtonList(true); + loadButtonShapes(); + loadInterfaceShapes(); + + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + _paletteOverlay = new uint8[256]; + _screen->generateOverlay(_screen->getPalette(0), _paletteOverlay, 0xF0, 0x19); + + loadInterface(); + + clearAnimObjects(); + + _scoreMax = 0; + for (int i = 0; i < _scoreTableSize; ++i) { + if (_scoreTable[i] > 0) + _scoreMax += _scoreTable[i]; + } + + memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); + memset(_conversationState, -1, sizeof(_conversationState)); + + _sceneList = new SceneDesc[98]; + assert(_sceneList); + memset(_sceneList, 0, sizeof(SceneDesc)*98); + _sceneListSize = 98; + + runStartupScript(1, 0); + _res->exists("MOODOMTR.WSA", true); + _invWsa = new WSAMovie_v2(this); + assert(_invWsa); + _invWsa->open("MOODOMTR.WSA", 1, 0); + _invWsaFrame = 6; + saveGameStateIntern(0, "New Game", 0); + if (_gameToLoad == -1) + enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1); + else + loadGameStateCheck(_gameToLoad); + + if (_menuDirectlyToLoad) + (*_mainButtonData[0].buttonCallback)(&_mainButtonData[0]); + + _screen->updateScreen(); + _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 *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() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _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) { + _savedMouseState = _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 (_savedMouseState == -3) { + snd_playSoundEffect(0x0D, 0x80); + return; + } + + setNextIdleAnimTimer(); + + int skip = 0; + + if (checkCharCollision(x, y) && _savedMouseState >= -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 && _savedMouseState >= 0) { + if (_itemInHand == 27) { + makeCharFacingMouse(); + } else if (y <= 187) { + if (_itemInHand == 43) + removeHandItem(); + else + dropItem(0, _itemInHand, x, y, 1); + } + return; + } else if (_savedMouseState == -3) { + return; + } else { + if (y > 187 && _savedMouseState > -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 (_savedMouseState == -4) { + if (_sceneList[curScene].exit4 != 0xFFFF) { + x = 4; + y = _sceneEnterY4; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -6) { + if (_sceneList[curScene].exit2 != 0xFFFF) { + x = 316; + y = _sceneEnterY2; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -7) { + if (_sceneList[curScene].exit1 != 0xFFFF) { + x = _sceneEnterX1; + y = _sceneEnterY1 - 2; + _pathfinderFlag = 14; + } + } else if (_savedMouseState == -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(); + + refreshAnimObjectsIfNeed(); + updateMouse(); + updateSpecialSceneScripts(); + updateCommandLine(); + updateItemAnimations(); + + _screen->updateScreen(); +} + +void KyraEngine_MR::updateWithText() { + updateInput(); + + updateMouse(); + updateItemAnimations(); + updateSpecialSceneScripts(); + updateCommandLine(); + + restorePage3(); + drawAnimObjects(); + if (_chatTextEnabled && _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 = kItemNone; + _itemInHand = kItemNone; + _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 - + +int KyraEngine_MR::loadLanguageFile(const char *file, uint8 *&buffer) { + delete[] buffer; + buffer = 0; + + uint32 size = 0; + Common::String nBuf = file; + nBuf += _languageExtension[_lang]; + buffer = _res->fileData(nBuf.c_str(), &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); + + strcpy(_stringBuffer, (const char *)getTableEntry(_scoreFile, strId)); + strcat(_stringBuffer, ": "); + + assert(scoreId < _scoreTableSize); + + int count = _scoreTable[scoreId]; + if (count > 0) + scoreIncrease(count, _stringBuffer); + + 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); + // 0 - best, 1 - mid, 2 - low + ConfMan.registerDefault("video_quality", 0); +} + +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_v2::readSettings(); + + _configStudio = ConfMan.getBool("studio_audience"); + _configSkip = ConfMan.getBool("skip_support"); + _configHelium = ConfMan.getBool("helium_mode"); + _configVQAQuality = CLIP(ConfMan.getInt("video_quality"), 0, 2); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_mr.h b/engines/kyra/engine/kyra_mr.h new file mode 100644 index 0000000000..83c97ebad9 --- /dev/null +++ b/engines/kyra/engine/kyra_mr.h @@ -0,0 +1,670 @@ +/* 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 KYRA_KYRA_MR_H +#define KYRA_KYRA_MR_H + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_mr.h" +#include "kyra/script/script.h" +#include "kyra/gui/gui_mr.h" + +#include "common/hashmap.h" +#include "common/list.h" + +namespace Kyra { + +class SoundDigital; +class Screen_MR; +class MainMenu; +class WSAMovie_v2; +class TextDisplayer_MR; +struct Button; + +class KyraEngine_MR : public KyraEngine_v2 { +friend class TextDisplayer_MR; +friend class GUI_MR; +public: + KyraEngine_MR(OSystem *system, const GameFlags &flags); + ~KyraEngine_MR(); + + // Regarding pausing of the engine: + // Idle animation time, item animations and album animations should be taken + // care of, but since those would just produce minor glitches it's not that + // important. + + Screen *screen() { return _screen; } + Screen_v2 *screen_v2() const { return _screen; } + GUI *gui() const { return _gui; } + SoundDigital *soundDigital() { return _soundDigital; } + int language() const { return _lang; } + bool heliumMode() const { return _configHelium; } + + Common::Error go(); + + void playVQA(const char *name); + +private: + static const EngineDesc _mrEngineDesc; + + // config + bool _configStudio; + bool _configSkip; + bool _configHelium; + int _configVQAQuality; + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + + void initStaticResource(); + + // -- + Screen_MR *_screen; + SoundDigital *_soundDigital; + + Common::Error init(); + + void preinit(); + void startup(); + void runStartupScript(int script, int unk1); + + void setupOpcodeTable(); + + // input + bool skipFlag() const; + void resetSkipFlag(bool removeEvent = true); + + // run + bool _menuDirectlyToLoad; + + void runLoop(); + void handleInput(int x, int y); + int inputSceneChange(int x, int y, int unk1, int unk2); + + void update(); + void updateWithText(); + void updateMouse(); + + // sound specific +private: + void playMenuAudioFile(); + + int _musicSoundChannel; + int _fadeOutMusicChannel; + const char *_menuAudioFile; + + const char *const *_soundList; + int _soundListSize; + + void snd_playWanderScoreViaMap(int track, int force); + void stopMusicTrack(); + + void fadeOutMusic(int ticks); + + void snd_playSoundEffect(int item, int volume); + + const uint8 *_sfxFileMap; + int _sfxFileMapSize; + const char *const *_sfxFileList; + int _sfxFileListSize; + + int _voiceSoundChannel; + + void playVoice(int high, int low); + void snd_playVoiceFile(int file); + bool snd_voiceIsPlaying(); + void snd_stopVoice(); + + int _curStudioSFX; + void playStudioSFX(const char *str); + + // gui + GUI_MR *_gui; + + Button *_mainButtonData; + Button *_mainButtonList; + bool _mainButtonListInitialized; + void initMainButtonList(bool disable); + + bool _enableInventory; + int buttonInventory(Button *button); + int buttonMoodChange(Button *button); + int buttonShowScore(Button *button); + int buttonJesterStaff(Button *button); + + void loadButtonShapes(); + int callbackButton1(Button *button); + int callbackButton2(Button *button); + int callbackButton3(Button *button); + + // -> main menu + void initMainMenu(); + void uninitMainMenu(); + + MainMenu *_menu; + WSAMovie_v2 *_menuAnim; + + // timer + void setupTimers(); + + void setWalkspeed(uint8); + void setCommandLineRestoreTimer(int secs); + + void timerRestoreCommandLine(int arg); + void timerRunSceneScript7(int arg); + void timerFleaDeath(int arg); + + uint32 _nextIdleAnim; + void setNextIdleAnimTimer(); + + // pathfinder + bool lineIsPassable(int x, int y); + +private: + // main menu + const char *const *_mainMenuStrings; + int _mainMenuStringsSize; + + static const char *const _mainMenuSpanishFan[]; + static const char *const _mainMenuItalianFan[]; + + // animator + uint8 *_gamePlayBuffer; + void restorePage3(); + + void clearAnimObjects(); + + void animSetupPaletteEntry(AnimObj *anim); + + void drawAnimObjects(); + void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer); + void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer); + + void refreshAnimObjects(int force); + + bool _loadingState; + void updateItemAnimations(); + void updateCharacterAnim(int charId); + + void updateSceneAnim(int anim, int newFrame); + void setupSceneAnimObject(int anim, uint16 flags, int x, int y, int x2, int y2, int w, int h, int unk10, int specialSize, int unk14, int shape, const char *filename); + void removeSceneAnimObject(int anim, int refresh); + + int _charBackUpWidth2, _charBackUpHeight2; + int _charBackUpWidth, _charBackUpHeight; + + void setCharacterAnimDim(int w, int h); + void resetCharacterAnimDim(); + + bool _nextIdleType; + void showIdleAnim(); + + const ItemAnimDefinition *_itemAnimDefinition; + ActiveItemAnim _activeItemAnim[10]; + int _nextAnimItem; + + // interface + uint8 *_interface; + uint8 *_interfaceCommandLine; + + void loadInterfaceShapes(); + void loadInterface(); + + void showMessage(const char *string, uint8 c0, uint8 c1); + void showMessageFromCCode(int string, uint8 c0, int); + void updateItemCommand(Item item, int str, uint8 c0); + + void updateCommandLine(); + void restoreCommandLine(); + void updateCLState(); + + int _commandLineY; + const char *_shownMessage; + bool _restoreCommandLine; + bool _inventoryState; + int _inventoryScrollSpeed; + + void showInventory(); + void hideInventory(); + + void drawMalcolmsMoodText(); + void drawMalcolmsMoodPointer(int frame, int page); + void drawJestersStaff(int type, int page); + + void drawScore(int page, int x, int y); + void drawScoreCounting(int oldScore, int newScore, int drawOld, const int x); + int getScoreX(const char *str); + + static const uint8 _inventoryX[]; + static const uint8 _inventoryY[]; + void redrawInventory(int page); + void clearInventorySlot(int slot, int page); + void drawInventorySlot(int page, Item item, int slot); + + WSAMovie_v2 *_invWsa; + int _invWsaFrame; + + // localization + uint8 *_scoreFile; + uint8 *_cCodeFile; + uint8 *_scenesFile; + uint8 *_itemFile; + uint8 *_optionsFile; + uint8 *_actorFile; + uint32 _actorFileSize; + uint8 *_sceneStrings; + + uint8 *getTableEntry(uint8 *buffer, int id); + void getTableEntry(Common::SeekableReadStream *stream, int id, char *dst); + + // items + int8 *_itemBuffer1; + int8 *_itemBuffer2; + + static const Item _trashItemList[]; + void removeTrashItems(); + + void initItems(); + + int checkItemCollision(int x, int y); + + bool dropItem(int unk1, Item item, int x, int y, int unk2); + bool processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2); + void itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item, int remove); + void exchangeMouseItem(int itemPos, int runScript); + bool pickUpItem(int x, int y, int runScript); + + bool isDropable(int x, int y); + + const uint8 *_itemMagicTable; + bool itemListMagic(Item handItem, int itemSlot); + bool itemInventoryMagic(Item handItem, int invSlot); + + const uint8 *_itemStringMap; + int _itemStringMapSize; + static const uint8 _itemStringPickUp[]; + static const uint8 _itemStringDrop[]; + static const uint8 _itemStringInv[]; + + int getItemCommandStringPickUp(uint16 item); + int getItemCommandStringDrop(uint16 item); + int getItemCommandStringInv(uint16 item); + + // -> hand item + void setItemMouseCursor(); + void setMouseCursor(Item item); + + // shapes + void initMouseShapes(); + + void loadCharacterShapes(int newShapes); + void updateMalcolmShapes(); + + int _malcolmShapeXOffset, _malcolmShapeYOffset; + + struct ShapeDesc { + uint8 width, height; + int8 xOffset, yOffset; + }; + static const ShapeDesc _shapeDescs[]; + static const int _shapeDescsSize; + + // scene animation + uint8 *_sceneShapes[20]; + + void freeSceneShapes(); + + // voice + int _currentTalkFile; + void openTalkFile(int file); + + // scene + bool _noScriptEnter; + void enterNewScene(uint16 scene, int facing, int unk1, int unk2, int unk3); + void enterNewSceneUnk1(int facing, int unk1, int unk2); + void enterNewSceneUnk2(int unk1); + int _enterNewSceneLock; + + void unloadScene(); + + void loadScenePal(); + void loadSceneMsc(); + void initSceneScript(int unk1); + void initSceneAnims(int unk1); + void initSceneScreen(int unk1); + + int runSceneScript1(int x, int y); + int runSceneScript2(); + bool _noStartupChat; + void runSceneScript4(int unk1); + void runSceneScript8(); + + int _sceneMinX, _sceneMaxX; + int _maskPageMinY, _maskPageMaxY; + + int trySceneChange(int *moveTable, int unk1, int unk2); + int checkSceneChange(); + + int8 _sceneDatPalette[45]; + int8 _sceneDatLayerTable[15]; + struct SceneShapeDesc { + // the original saves those variables, we don't, since + // they are just needed on scene load + /*int x, y; + int w, h;*/ + int drawX, drawY; + }; + SceneShapeDesc _sceneShapeDescs[20]; + + int getDrawLayer(int x, int y); + + int getScale(int x, int y); + int _scaleTable[15]; + + // character + int getCharacterWalkspeed() const; + void updateCharAnimFrame(int *table); + int8 _characterAnimTable[2]; + static const uint8 _characterFrameTable[]; + + void updateCharPal(int unk1); + int _lastCharPalLayer; + bool _charPalUpdate; + + bool checkCharCollision(int x, int y); + + int _malcolmsMood; + + void makeCharFacingMouse(); + + int findFreeInventorySlot(); + + // talk object + struct TalkObject { + char filename[13]; + int8 sceneAnim; + int8 sceneScript; + int16 x, y; + uint8 color; + uint8 sceneId; + }; + + TalkObject *_talkObjectList; + + bool talkObjectsInCurScene(); + + // chat + int chatGetType(const char *text); + int chatCalcDuration(const char *text); + + void objectChat(const char *text, int object, int vocHigh, int vocLow); + void objectChatInit(const char *text, int object, int vocHigh, int vocLow); + void objectChatPrintText(const char *text, int object); + void objectChatProcess(const char *script); + void objectChatWaitToFinish(); + + void badConscienceChat(const char *str, int vocHigh, int vocLow); + void badConscienceChatWaitToFinish(); + + void goodConscienceChat(const char *str, int vocHigh, int vocLow); + void goodConscienceChatWaitToFinish(); + + bool _albumChatActive; + void albumChat(const char *str, int vocHigh, int vocLow); + void albumChatInit(const char *str, int object, int vocHigh, int vocLow); + void albumChatWaitToFinish(); + + void malcolmSceneStartupChat(); + + byte _newSceneDlgState[40]; + int8 _conversationState[30][30]; + bool _chatAltFlag; + void setDlgIndex(int index); + void updateDlgIndex(); + + Common::SeekableReadStream *_cnvFile; + Common::SeekableReadStream *_dlgBuffer; + int _curDlgChapter, _curDlgIndex, _curDlgLang; + void updateDlgBuffer(); + void loadDlgHeader(int &vocHighBase, int &vocHighIndex, int &index1, int &index2); + + static const uint8 _vocHighTable[]; + bool _isStartupDialog; + void processDialog(int vocHighIndex, int vocHighBase, int funcNum); + + EMCData _dialogScriptData; + EMCState _dialogScriptState; + int _dialogSceneAnim; + int _dialogSceneScript; + int _dialogScriptFuncStart, _dialogScriptFuncProc, _dialogScriptFuncEnd; + + void dialogStartScript(int object, int funcNum); + void dialogEndScript(int object); + + void npcChatSequence(const char *str, int object, int vocHigh, int vocLow); + + Common::Array<const Opcode *> _opcodesDialog; + + int o3d_updateAnim(EMCState *script); + int o3d_delay(EMCState *script); + + void randomSceneChat(); + void doDialog(int dlgIndex, int funcNum); + + // conscience + bool _badConscienceShown; + int _badConscienceAnim; + bool _badConsciencePosition; + + static const uint8 _badConscienceFrameTable[]; + + void showBadConscience(); + void hideBadConscience(); + + bool _goodConscienceShown; + int _goodConscienceAnim; + bool _goodConsciencePosition; + + static const uint8 _goodConscienceFrameTable[]; + + void showGoodConscience(); + void hideGoodConscience(); + + // special script code + bool _useFrameTable; + + int o3a_setCharacterFrame(EMCState *script); + int o3a_playSoundEffect(EMCState *script); + + // special shape code + int initAnimationShapes(uint8 *filedata); + void uninitAnimationShapes(int count, uint8 *filedata); + + // unk + uint8 *_costPalBuffer; + uint8 *_paletteOverlay; + bool _useActorBuffer; + + int _currentChapter; + void changeChapter(int newChapter, int sceneId, int malcolmShapes, int facing); + + static const uint8 _chapterLowestScene[]; + + void loadCostPal(); + void loadShadowShape(); + void loadExtrasShapes(); + + uint8 *_gfxBackUpRect; + void backUpGfxRect32x32(int x, int y); + void restoreGfxRect32x32(int x, int y); + + char *_stringBuffer; + + int _score; + int _scoreMax; + + const uint8 *_scoreTable; + int _scoreTableSize; + + int8 _scoreFlagTable[26]; + bool updateScore(int scoreId, int strId); + void scoreIncrease(int count, const char *str); + + void eelScript(); + + // Album + struct Album { + uint8 *backUpPage; + uint8 *file; + WSAMovie_v2 *wsa; + uint8 *backUpRect; + + struct PageMovie { + WSAMovie_v2 *wsa; + int curFrame; + int maxFrame; + uint32 timer; + }; + + PageMovie leftPage, rightPage; + + int curPage, nextPage; + bool running; + bool isPage14; + } _album; + + static const int8 _albumWSAX[]; + static const int8 _albumWSAY[]; + + void showAlbum(); + + void loadAlbumPage(); + void loadAlbumPageWSA(); + + void printAlbumPageText(); + void printAlbumText(int page, const char *str, int x, int y, uint8 c0); + + void processAlbum(); + + void albumNewPage(); + void albumUpdateAnims(); + void albumAnim1(); + void albumAnim2(); + + void albumBackUpRect(); + void albumRestoreRect(); + void albumUpdateRect(); + + void albumSwitchPages(int oldPage, int newPage, int srcPage); + + int albumNextPage(Button *caller); + int albumPrevPage(Button *caller); + int albumClose(Button *caller); + + // save/load + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); + + // opcodes + int o3_getMalcolmShapes(EMCState *script); + int o3_setCharacterPos(EMCState *script); + int o3_defineObject(EMCState *script); + int o3_refreshCharacter(EMCState *script); + int o3_getMalcolmsMood(EMCState *script); + int o3_getCharacterFrameFromFacing(EMCState *script); + int o3_setCharacterFacing(EMCState *script); + int o3_showSceneFileMessage(EMCState *script); + int o3_setCharacterAnimFrameFromFacing(EMCState *script); + int o3_showBadConscience(EMCState *script); + int o3_hideBadConscience(EMCState *script); + int o3_showAlbum(EMCState *script); + int o3_setInventorySlot(EMCState *script); + int o3_getInventorySlot(EMCState *script); + int o3_addItemToInventory(EMCState *script); + int o3_addItemToCurScene(EMCState *script); + int o3_objectChat(EMCState *script); + int o3_resetInventory(EMCState *script); + int o3_removeInventoryItemInstances(EMCState *script); + int o3_countInventoryItemInstances(EMCState *script); + int o3_npcChatSequence(EMCState *script); + int o3_badConscienceChat(EMCState *script); + int o3_wipeDownMouseItem(EMCState *script); + int o3_setMalcolmsMood(EMCState *script); + int o3_updateScore(EMCState *script); + int o3_makeSecondChanceSave(EMCState *script); + int o3_setSceneFilename(EMCState *script); + int o3_removeItemsFromScene(EMCState *script); + int o3_disguiseMalcolm(EMCState *script); + int o3_drawSceneShape(EMCState *script); + int o3_drawSceneShapeOnPage(EMCState *script); + int o3_checkInRect(EMCState *script); + int o3_updateConversations(EMCState *script); + int o3_removeItemSlot(EMCState *script); + int o3_setSceneDim(EMCState *script); + int o3_setSceneAnimPosAndFrame(EMCState *script); + int o3_removeItemInstances(EMCState *script); + int o3_disableInventory(EMCState *script); + int o3_enableInventory(EMCState *script); + int o3_enterNewScene(EMCState *script); + int o3_switchScene(EMCState *script); + int o3_setMalcolmPos(EMCState *script); + int o3_stopMusic(EMCState *script); + int o3_playSoundEffect(EMCState *script); + int o3_getScore(EMCState *script); + int o3_daggerWarning(EMCState *script); + int o3_blockOutWalkableRegion(EMCState *script); + int o3_showSceneStringsMessage(EMCState *script); + int o3_showGoodConscience(EMCState *script); + int o3_goodConscienceChat(EMCState *script); + int o3_hideGoodConscience(EMCState *script); + int o3_defineSceneAnim(EMCState *script); + int o3_updateSceneAnim(EMCState *script); + int o3_runActorScript(EMCState *script); + int o3_doDialog(EMCState *script); + int o3_setConversationState(EMCState *script); + int o3_getConversationState(EMCState *script); + int o3_changeChapter(EMCState *script); + int o3_countItemInstances(EMCState *script); + int o3_dialogStartScript(EMCState *script); + int o3_dialogEndScript(EMCState *script); + int o3_customChat(EMCState *script); + int o3_customChatFinish(EMCState *script); + int o3_setupSceneAnimObject(EMCState *script); + int o3_removeSceneAnimObject(EMCState *script); + int o3_dummy(EMCState *script); + + // misc + TextDisplayer_MR *_text; + bool _wasPlayingVQA; + + // resource specific +private: + static const char *const _languageExtension[]; + static const int _languageExtensionSize; + + int loadLanguageFile(const char *file, uint8 *&buffer); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_rpg.cpp b/engines/kyra/engine/kyra_rpg.cpp new file mode 100644 index 0000000000..3d7a4df208 --- /dev/null +++ b/engines/kyra/engine/kyra_rpg.cpp @@ -0,0 +1,366 @@ +/* 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. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + +namespace Kyra { + +KyraRpgEngine::KyraRpgEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags), _numFlyingObjects(_flags.gameID == GI_LOL ? 8 : 10) { + _txt = 0; + _mouseClick = 0; + _preserveEvents = _buttonListChanged = false; + + _sceneXoffset = 0; + _sceneShpDim = 5; + + _activeButtons = 0; + + _currentLevel = 0; + + _vcnBlocks = 0; + _vcfBlocks = 0; + _vcnTransitionMask = 0; + _vcnShift = 0; + _vcnColTable = 0; + _vcnBpp = flags.useHiColorMode ? 2 : 1; + _vmpPtr = 0; + _blockBrightness = _wllVcnOffset = 0; + _blockDrawingBuffer = 0; + _sceneWindowBuffer = 0; + _monsterShapes = _monsterPalettes = 0; + + _doorShapes = 0; + + _levelDecorationProperties = 0; + _levelDecorationData = 0; + _levelDecorationShapes = 0; + _decorationCount = 0; + _mappedDecorationsCount = 0; + memset(_visibleBlockIndex, 0, sizeof(_visibleBlockIndex)); + + _lvlShapeTop = _lvlShapeBottom = _lvlShapeLeftRight = 0; + _levelBlockProperties = 0; + _hasTempDataFlags = 0; + + _wllVmpMap = _specialWallTypes = _wllWallFlags = 0; + _wllShapeMap = 0; + + _sceneDrawVarDown = _sceneDrawVarRight = _sceneDrawVarLeft = _wllProcessFlag = 0; + + _currentBlock = 0; + _currentDirection = 0; + _compassDirection = -1; + _updateFlags = _clickedSpecialFlag = 0; + _sceneDefaultUpdate = 0; + _sceneUpdateRequired = false; + + _flyingObjectsPtr = 0; + _flyingObjectStructSize = sizeof(EoBFlyingObject); + + _clickedShapeXOffs = _clickedShapeYOffs = 0; + + _dscShapeX = 0; + _dscTileIndex = 0; + _dscDoorScaleOffs = 0; + _dscDim1 = 0; + _dscDim2 = 0; + _dscBlockMap = 0; + _dscBlockIndex = 0; + _dscShapeIndex = 0; + _dscDimMap = 0; + _dscDoorShpIndex = 0; + _dscDoorY2 = 0; + _dscDoorFrameY1 = 0; + _dscDoorFrameY2 = 0; + _dscDoorFrameIndex1 = 0; + _dscDoorFrameIndex2 = 0; + + _shpDmX1 = _shpDmX2 = 0; + + memset(_openDoorState, 0, sizeof(_openDoorState)); + memset(_dialogueButtonString, 0, 3 * sizeof(const char *)); + _dialogueButtonPosX = 0; + _dialogueButtonPosY = 0; + _dialogueNumButtons = _dialogueButtonYoffs = _dialogueHighlightedButton = 0; + _currentControlMode = 0; + _specialSceneFlag = 0; + _updateCharNum = -1; + _activeVoiceFileTotalTime = 0; + _updatePortraitSpeechAnimDuration = _resetPortraitAfterSpeechAnim = _needSceneRestore = 0; + _fadeText = false; + + memset(_lvlTempData, 0, sizeof(_lvlTempData)); + + _dialogueField = false; + + _environmentSfx = _environmentSfxVol = _envSfxDistThreshold = 0; + _monsterStepCounter = _monsterStepMode = 0; +} + +KyraRpgEngine::~KyraRpgEngine() { + delete[] _wllVmpMap; + delete[] _wllShapeMap; + delete[] _specialWallTypes; + delete[] _wllWallFlags; + + delete[] _vmpPtr; + delete[] _vcnColTable; + delete[] _vcnBlocks; + delete[] _vcfBlocks; + delete[] _vcnTransitionMask; + delete[] _vcnShift; + delete[] _blockDrawingBuffer; + delete[] _sceneWindowBuffer; + + delete[] _lvlShapeTop; + delete[] _lvlShapeBottom; + delete[] _lvlShapeLeftRight; + + delete[] _doorShapes; + + delete[] _levelDecorationShapes; + delete[] _levelDecorationData; + delete[] _levelDecorationProperties; + delete[] _levelBlockProperties; +} + +Common::Error KyraRpgEngine::init() { + gui_resetButtonList(); + + _levelDecorationProperties = new LevelDecorationProperty[100]; + memset(_levelDecorationProperties, 0, 100 * sizeof(LevelDecorationProperty)); + _levelDecorationShapes = new uint8*[400]; + memset(_levelDecorationShapes, 0, 400 * sizeof(uint8 *)); + _levelBlockProperties = new LevelBlockProperty[1025]; + memset(_levelBlockProperties, 0, 1025 * sizeof(LevelBlockProperty)); + + _wllVmpMap = new uint8[256]; + memset(_wllVmpMap, 0, 256); + _wllShapeMap = new int8[256]; + memset(_wllShapeMap, 0, 256); + _specialWallTypes = new uint8[256]; + memset(_specialWallTypes, 0, 256); + _wllWallFlags = new uint8[256]; + memset(_wllWallFlags, 0, 256); + + _blockDrawingBuffer = new uint16[1320]; + memset(_blockDrawingBuffer, 0, 1320 * sizeof(uint16)); + int windowBufferSize = _flags.useHiColorMode ? 42240 : 21120; + _sceneWindowBuffer = new uint8[windowBufferSize]; + memset(_sceneWindowBuffer, 0, windowBufferSize); + + _lvlShapeTop = new int16[18]; + memset(_lvlShapeTop, 0, 18 * sizeof(int16)); + _lvlShapeBottom = new int16[18]; + memset(_lvlShapeBottom, 0, 18 * sizeof(int16)); + _lvlShapeLeftRight = new int16[36]; + memset(_lvlShapeLeftRight, 0, 36 * sizeof(int16)); + + _vcnColTable = new uint8[128]; + for (int i = 0; i < 128; i++) + _vcnColTable[i] = i & 0x0F; + + _doorShapes = new uint8*[6]; + memset(_doorShapes, 0, 6 * sizeof(uint8 *)); + + initStaticResource(); + + _envSfxDistThreshold = (_flags.gameID == GI_EOB2 || _sound->getSfxType() == Sound::kAdLib || _sound->getSfxType() == Sound::kPCSpkr) ? 15 : 3; + + _dialogueButtonLabelColor1 = guiSettings()->buttons.labelColor1; + _dialogueButtonLabelColor2 = guiSettings()->buttons.labelColor2; + _dialogueButtonWidth = guiSettings()->buttons.width; + + return Common::kNoError; +} + +bool KyraRpgEngine::posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2) { + if (posX < x1 || posX > x2 || posY < y1 || posY > y2) + return false; + return true; +} + +void KyraRpgEngine::drawDialogueButtons() { + int cp = screen()->setCurPage(0); + Screen::FontId of = screen()->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : ((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : Screen::FID_6_FNT)); + + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + if (_flags.lang == Common::JA_JPN && _flags.use16ColorMode) { + gui_drawBox(x, ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1, 74, 10, 0xEE, 0xCC, -1); + screen()->printText(_dialogueButtonString[i], (x + 37 - (screen()->getTextWidth(_dialogueButtonString[i])) / 2) & ~3, + ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2) & ~7, _dialogueHighlightedButton == i ? 0xC1 : 0xE1, 0); + } else { + int sjisYOffset = (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? 1 : ((_flags.lang == Common::JA_JPN && (_dialogueButtonString[i][0] & 0x80)) ? 2 : 0); + screen()->set16bitShadingLevel(4); + gui_drawBox(x, (_dialogueButtonYoffs + _dialogueButtonPosY[i]), _dialogueButtonWidth, guiSettings()->buttons.height, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + screen()->set16bitShadingLevel(0); + screen()->printText(_dialogueButtonString[i], x + (_dialogueButtonWidth >> 1) - (screen()->getTextWidth(_dialogueButtonString[i])) / 2, + (_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2 - sjisYOffset, _dialogueHighlightedButton == i ? _dialogueButtonLabelColor1 : _dialogueButtonLabelColor2, 0); + } + } + screen()->setFont(of); + screen()->setCurPage(cp); +} + +uint16 KyraRpgEngine::processDialogue() { + int df = _dialogueHighlightedButton; + int res = 0; + + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + int y = ((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i])); + Common::Point p = getMousePos(); + if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) { + _dialogueHighlightedButton = i; + break; + } + } + + if (_dialogueNumButtons == 0) { + int e = checkInput(0, false) & 0xFF; + removeInputTop(); + + if (e) { + gui_notifyButtonListChanged(); + + if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) { + snd_stopSpeech(true); + } + } + + if (snd_updateCharacterSpeech() != 2) { + res = 1; + if (!shouldQuit()) { + removeInputTop(); + gui_notifyButtonListChanged(); + } + } + } else { + int e = checkInput(0, false, 0) & 0xFF; + removeInputTop(); + if (e) + gui_notifyButtonListChanged(); + + if ((_flags.gameID == GI_LOL && (e == 200 || e == 202)) || (_flags.gameID != GI_LOL && (e == 199 || e == 201))) { + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + int y = (gameFlags().use16ColorMode ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i])); + Common::Point p = getMousePos(); + if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) { + _dialogueHighlightedButton = i; + res = _dialogueHighlightedButton + 1; + break; + } + } + } else if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) { + snd_stopSpeech(true); + res = _dialogueHighlightedButton + 1; + } else if (e == _keyMap[Common::KEYCODE_LEFT] || e == _keyMap[Common::KEYCODE_DOWN]) { + if (_dialogueNumButtons > 1 && _dialogueHighlightedButton > 0) + _dialogueHighlightedButton--; + } else if (e == _keyMap[Common::KEYCODE_RIGHT] || e == _keyMap[Common::KEYCODE_UP]) { + if (_dialogueNumButtons > 1 && _dialogueHighlightedButton < (_dialogueNumButtons - 1)) + _dialogueHighlightedButton++; + } + } + + if (df != _dialogueHighlightedButton) + drawDialogueButtons(); + + screen()->updateScreen(); + + if (res == 0) + return 0; + + stopPortraitSpeechAnim(); + + if (game() == GI_LOL) { + if (!textEnabled() && _currentControlMode) { + screen()->setScreenDim(5); + const ScreenDim *d = screen()->getScreenDim(5); + screen()->fillRect(d->sx, d->sy + d->h - 9, d->sx + d->w - 1, d->sy + d->h - 1, d->unkA); + } else { + const ScreenDim *d = screen()->_curDim; + if (gameFlags().use16ColorMode) + screen()->fillRect(d->sx, d->sy, d->sx + d->w - 3, d->sy + d->h - 2, d->unkA); + else + screen()->fillRect(d->sx, d->sy, d->sx + d->w - 2, d->sy + d->h - 1, d->unkA); + txt()->clearDim(4); + txt()->resetDimTextPositions(4); + } + } + + return res; +} + +void KyraRpgEngine::delayUntil(uint32 time, bool, bool doUpdate, bool isMainLoop) { + uint32 curTime = _system->getMillis(); + if (time > curTime) + delay(time - curTime, doUpdate, isMainLoop); +} + +int KyraRpgEngine::rollDice(int times, int pips, int inc) { + if (times <= 0 || pips <= 0) + return inc; + + int res = 0; + while (times--) + res += _rnd.getRandomNumberRng(1, pips); + + return res + inc; +} + +bool KyraRpgEngine::snd_processEnvironmentalSoundEffect(int soundId, int block) { + if (!_sound->sfxEnabled() || shouldQuit()) + return false; + + if (_environmentSfx) + snd_playSoundEffect(_environmentSfx, _environmentSfxVol); + + int dist = 0; + if (block) { + dist = getBlockDistance(_currentBlock, block); + if (dist > _envSfxDistThreshold) { + _environmentSfx = 0; + return false; + } + } + + _environmentSfx = soundId; + _environmentSfxVol = (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? (dist ? (16 - dist) * 8 - 1 : 127) : ((15 - ((block || (_flags.gameID == GI_LOL && dist < 2)) ? dist : 0)) << 4); + + return true; +} + +void KyraRpgEngine::updateEnvironmentalSfx(int soundId) { + snd_processEnvironmentalSoundEffect(soundId, _currentBlock); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL diff --git a/engines/kyra/engine/kyra_rpg.h b/engines/kyra/engine/kyra_rpg.h new file mode 100644 index 0000000000..a446c87a0e --- /dev/null +++ b/engines/kyra/engine/kyra_rpg.h @@ -0,0 +1,388 @@ +/* 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 KYRA_RPG_H +#define KYRA_RPG_H + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/kyra_v1.h" +#include "kyra/graphics/screen_eob.h" +#include "kyra/gui/gui_eob.h" +#include "kyra/text/text_lol.h" + +namespace Kyra { + +struct LevelDecorationProperty { + uint16 shapeIndex[10]; + uint8 scaleFlag[10]; + int16 shapeX[10]; + int16 shapeY[10]; + int8 next; + uint8 flags; +}; + +struct LevelBlockProperty { + uint8 walls[4]; + uint16 assignedObjects; + uint16 drawObjects; + uint8 direction; + uint16 flags; +}; + +struct OpenDoorState { + uint16 block; + int8 wall; + int8 state; +}; + +struct LevelTempData { + uint8 *wallsXorData; + uint16 *flags; + void *monsters; + void *flyingObjects; + void *wallsOfForce; + uint8 monsterDifficulty; +}; + +struct EoBFlyingObject { + uint8 enable; + uint8 objectType; + int16 attackerId; + Item item; + uint16 curBlock; + uint16 starting; + uint8 u1; + uint8 direction; + uint8 distance; + int8 callBackIndex; + uint8 curPos; + uint8 flags; + uint8 unused; +}; + +struct KyraRpgGUISettings { + struct DialogueButtons { + uint8 labelColor1; + uint8 labelColor2; + uint16 width; + uint16 height; + int waitReserve; + uint16 waitX[2]; + uint8 waitY[2]; + uint16 waitWidth[2]; + } buttons; + + struct Colors { + uint8 frame1; + uint8 frame2; + int fill; + + uint8 unused; + uint8 barGraph; + + uint8 warningFrame1; + uint8 warningFrame2; + int warningFill; + + uint8 extraFrame1; + uint8 extraFrame2; + int extraFill; + + uint8 inactiveTabFrame1; + uint8 inactiveTabFrame2; + int inactiveTabFill; + } colors; +}; + +class KyraRpgEngine : public KyraEngine_v1 { +friend class TextDisplayer_rpg; +public: + KyraRpgEngine(OSystem *system, const GameFlags &flags); + virtual ~KyraRpgEngine(); + + virtual Screen *screen() = 0; + virtual GUI *gui() const = 0; + +protected: + // Startup + virtual Common::Error init(); + virtual Common::Error go() = 0; + + // Init + void initStaticResource(); + + const uint8 **_itemIconShapes; + + // Main loop + virtual void update() = 0; + void updateEnvironmentalSfx(int soundId); + + // timers + virtual void setupTimers() = 0; + void enableSysTimer(int sysTimer); + void disableSysTimer(int sysTimer); + void enableTimer(int id); + virtual uint8 getClock2Timer(int index) = 0; + virtual uint8 getNumClock2Timers() = 0; + + void timerProcessDoors(int timerNum); + + // mouse + bool posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2); + virtual void setHandItem(Item itemIndex) = 0; + + // Characters + int _updateCharNum; + int _updatePortraitSpeechAnimDuration; + bool _fadeText; + int _resetPortraitAfterSpeechAnim; + int _needSceneRestore; + + // Items + int _itemInHand; + + // Monsters + int getBlockDistance(uint16 block1, uint16 block2); + + uint8 **_monsterPalettes; + uint8 **_monsterShapes; + + int16 _shpDmX1; + int16 _shpDmX2; + + int _monsterStepCounter; + int _monsterStepMode; + + // Level + virtual void addLevelItems() = 0; + virtual void loadBlockProperties(const char *file) = 0; + + virtual void drawScene(int pageNum) = 0; + virtual void drawSceneShapes(int start) = 0; + virtual void drawDecorations(int index) = 0; + + virtual const uint8 *getBlockFileData(int levelIndex) = 0; + void setLevelShapesDim(int index, int16 &x1, int16 &x2, int dim); + void setDoorShapeDim(int index, int16 &y1, int16 &y2, int dim); + void drawLevelModifyScreenDim(int dim, int16 x1, int16 y1, int16 x2, int16 y2); + void generateBlockDrawingBuffer(); + void generateVmpTileData(int16 startBlockX, uint8 startBlockY, uint8 wllVmpIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY); + void generateVmpTileDataFlipped(int16 startBlockX, uint8 startBlockY, uint8 wllVmpIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY); + bool hasWall(int index); + void assignVisibleBlocks(int block, int direction); + bool checkSceneUpdateNeed(int block); + void drawVcnBlocks(); + uint16 calcNewBlockPosition(uint16 curBlock, uint16 direction); + + virtual int clickedDoorSwitch(uint16 block, uint16 direction) = 0; + int clickedWallShape(uint16 block, uint16 direction); + int clickedLeverOn(uint16 block, uint16 direction); + int clickedLeverOff(uint16 block, uint16 direction); + int clickedWallOnlyScript(uint16 block); + virtual int clickedNiche(uint16 block, uint16 direction) = 0; + + void processDoorSwitch(uint16 block, int openClose); + void openCloseDoor(int block, int openClose); + void completeDoorOperations(); + + uint8 *_wllVmpMap; + int8 *_wllShapeMap; + uint8 *_specialWallTypes; + uint8 *_wllWallFlags; + + int _sceneXoffset; + int _sceneShpDim; + + LevelBlockProperty *_levelBlockProperties; + LevelBlockProperty *_visibleBlocks[18]; + LevelDecorationProperty *_levelDecorationData; + uint16 _levelDecorationDataSize; + LevelDecorationProperty *_levelDecorationProperties; + uint8 **_levelDecorationShapes; + uint16 _decorationCount; + int16 _mappedDecorationsCount; + uint16 *_vmpPtr; + uint8 *_vcnBlocks; + uint8 *_vcfBlocks; + uint8 *_vcnTransitionMask; + uint8 *_vcnShift; + uint8 *_vcnColTable; + uint8 _vcnBpp; + uint16 *_blockDrawingBuffer; + uint8 *_sceneWindowBuffer; + uint8 _blockBrightness; + uint8 _wllVcnOffset; + + uint8 **_doorShapes; + + uint8 _currentLevel; + uint16 _currentBlock; + uint16 _currentDirection; + int _sceneDefaultUpdate; + bool _sceneUpdateRequired; + + int16 _visibleBlockIndex[18]; + int16 *_lvlShapeLeftRight; + int16 *_lvlShapeTop; + int16 *_lvlShapeBottom; + + char _lastBlockDataFile[13]; + uint32 _hasTempDataFlags; + + int16 _sceneDrawVarDown; + int16 _sceneDrawVarRight; + int16 _sceneDrawVarLeft; + int _wllProcessFlag; + + OpenDoorState _openDoorState[3]; + + int _sceneDrawPage1; + int _sceneDrawPage2; + + const int8 *_dscShapeIndex; + const uint8 *_dscDimMap; + const int8 *_dscDim1; + const int8 *_dscDim2; + const int16 *_dscShapeX; + const uint8 *_dscDoorScaleOffs; + const uint8 *_dscBlockMap; + const int8 *_dscBlockIndex; + const uint8 *_dscTileIndex; + + const uint8 *_dscDoorShpIndex; + int _dscDoorShpIndexSize; + const uint8 *_dscDoorY2; + const uint8 *_dscDoorFrameY1; + const uint8 *_dscDoorFrameY2; + const uint8 *_dscDoorFrameIndex1; + const uint8 *_dscDoorFrameIndex2; + + // Script + virtual void runLevelScript(int block, int flags) = 0; + + // Gui + void removeInputTop(); + void gui_drawBox(int x, int y, int w, int h, int frameColor1, int frameColor2, int fillColor); + virtual void gui_drawHorizontalBarGraph(int x, int y, int w, int h, int32 curVal, int32 maxVal, int col1, int col2); + void gui_initButtonsFromList(const uint8 *list); + virtual void gui_initButton(int index, int x = -1, int y = -1, int val = -1) = 0; + void gui_resetButtonList(); + void gui_notifyButtonListChanged(); + + bool clickedShape(int shapeIndex); + + virtual const KyraRpgGUISettings *guiSettings() = 0; + + int _clickedShapeXOffs; + int _clickedShapeYOffs; + + Button *_activeButtons; + Button _activeButtonData[70]; + Common::Array<Button::Callback> _buttonCallbacks; + //bool _processingButtons; + + uint8 _mouseClick; + bool _preserveEvents; + bool _buttonListChanged; + + int _updateFlags; + int _clickedSpecialFlag; + + int _compassDirection; + + static const uint8 _dropItemDirIndex[]; + + // text + void drawDialogueButtons(); + uint16 processDialogue(); + + TextDisplayer_rpg *_txt; + virtual TextDisplayer_rpg *txt() { return _txt; } + + bool _dialogueField; + + const char *_dialogueButtonString[9]; + const uint16 *_dialogueButtonPosX; + const uint8 *_dialogueButtonPosY; + int16 _dialogueButtonYoffs; + uint16 _dialogueButtonWidth; + int _dialogueNumButtons; + int _dialogueHighlightedButton; + int _currentControlMode; + int _specialSceneFlag; + uint8 _dialogueButtonLabelColor1; + uint8 _dialogueButtonLabelColor2; + + const char *const *_moreStrings; + + // misc + virtual void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false) = 0; + void delayUntil(uint32 time, bool unused = false, bool doUpdate = false, bool isMainLoop = false); + int rollDice(int times, int pips, int inc = 0); + + virtual Common::Error loadGameState(int slot) = 0; + virtual Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail) = 0; + + void generateTempData(); + virtual void restoreBlockTempData(int levelIndex); + void releaseTempData(); + virtual void *generateMonsterTempData(LevelTempData *tmp) = 0; + virtual void restoreMonsterTempData(LevelTempData *tmp) = 0; + virtual void releaseMonsterTempData(LevelTempData *tmp) = 0; + void restoreFlyingObjectTempData(LevelTempData *tmp); + void *generateFlyingObjectTempData(LevelTempData *tmp); + void releaseFlyingObjectTempData(LevelTempData *tmp); + virtual void *generateWallOfForceTempData(LevelTempData *tmp) { return 0; } + virtual void restoreWallOfForceTempData(LevelTempData *tmp) {} + virtual void releaseWallOfForceTempData(LevelTempData *tmp) {} + + LevelTempData *_lvlTempData[29]; + const int _numFlyingObjects; + uint32 _flyingObjectStructSize; + void *_flyingObjectsPtr; + + // sound + virtual bool snd_processEnvironmentalSoundEffect(int soundId, int block); + virtual void snd_stopSpeech(bool) {} + virtual int snd_updateCharacterSpeech() { return 0; } + virtual void stopPortraitSpeechAnim() {} + virtual void setupOpcodeTable() {} + virtual void snd_playVoiceFile(int) {} + + int _environmentSfx; + int _environmentSfxVol; + int _envSfxDistThreshold; + + uint32 _activeVoiceFileTotalTime; + + // unused + void setWalkspeed(uint8) {} + void removeHandItem() {} + bool lineIsPassable(int, int) { return false; } +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL + +#endif diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp new file mode 100644 index 0000000000..e2896eb1a5 --- /dev/null +++ b/engines/kyra/engine/kyra_v1.cpp @@ -0,0 +1,698 @@ +/* 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 "kyra/kyra_v1.h" +#include "kyra/sound/sound_intern.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" + +#include "common/error.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" + +namespace Kyra { + +KyraEngine_v1::KyraEngine_v1(OSystem *system, const GameFlags &flags) + : Engine(system), _flags(flags), _rnd("kyra") { + _res = 0; + _sound = 0; + _text = 0; + _staticres = 0; + _timer = 0; + _emc = 0; + _debugger = 0; + + _configRenderMode = Common::kRenderDefault; + + if (_flags.platform == Common::kPlatformAmiga) + _gameSpeed = 50; + else + _gameSpeed = 60; + _tickLength = (uint8)(1000.0 / _gameSpeed); + + _trackMap = 0; + _trackMapSize = 0; + _lastMusicCommand = -1; + _curSfxFile = _curMusicTheme = -1; + + _gameToLoad = -1; + + _mouseState = -1; + _deathHandler = -1; + + memset(_flagsTable, 0, sizeof(_flagsTable)); + + _isSaveAllowed = false; + + _mouseX = _mouseY = 0; + + // sets up all engine specific debug levels + DebugMan.addDebugChannel(kDebugLevelScriptFuncs, "ScriptFuncs", "Script function debug level"); + DebugMan.addDebugChannel(kDebugLevelScript, "Script", "Script interpreter debug level"); + DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprite debug level"); + DebugMan.addDebugChannel(kDebugLevelScreen, "Screen", "Screen debug level"); + DebugMan.addDebugChannel(kDebugLevelSound, "Sound", "Sound debug level"); + DebugMan.addDebugChannel(kDebugLevelAnimator, "Animator", "Animator debug level"); + DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level"); + DebugMan.addDebugChannel(kDebugLevelGUI, "GUI", "GUI debug level"); + DebugMan.addDebugChannel(kDebugLevelSequence, "Sequence", "Sequence debug level"); + DebugMan.addDebugChannel(kDebugLevelMovie, "Movie", "Movie debug level"); + DebugMan.addDebugChannel(kDebugLevelTimer, "Timer", "Timer debug level"); +} + +::GUI::Debugger *KyraEngine_v1::getDebugger() { + return _debugger; +} + +void KyraEngine_v1::pauseEngineIntern(bool pause) { + Engine::pauseEngineIntern(pause); + if (_sound) + _sound->pause(pause); + if (_timer) + _timer->pause(pause); +} + +Common::Error KyraEngine_v1::init() { + // Setup mixer + syncSoundSettings(); + + if (!_flags.useDigSound) { + if (_flags.platform == Common::kPlatformFMTowns) { + if (_flags.gameID == GI_KYRA1) + _sound = new SoundTowns(this, _mixer); + else + _sound = new SoundTownsPC98_v2(this, _mixer); + } else if (_flags.platform == Common::kPlatformPC98) { + if (_flags.gameID == GI_KYRA1) + _sound = new SoundPC98(this, _mixer); + else + _sound = new SoundTownsPC98_v2(this, _mixer); + } else if (_flags.platform == Common::kPlatformAmiga) { + _sound = new SoundAmiga(this, _mixer); + } else { + // In Kyra 1 users who have specified a default MT-32 device in the launcher settings + // will get MT-32 music, otherwise AdLib. In Kyra 2 and LoL users who have specified a + // default GM device in the launcher will get GM music, otherwise AdLib. Users who want + // MT-32 music in Kyra2 or LoL have to select this individually (since we assume that + // most users rather have a GM device than a MT-32 device). + // Users who want PC speaker sound always have to select this individually for all + // Kyra games. + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_PCSPK | MDT_MIDI | MDT_ADLIB | ((_flags.gameID == GI_KYRA2 || _flags.gameID == GI_LOL) ? MDT_PREFER_GM : MDT_PREFER_MT32)); + if (MidiDriver::getMusicType(dev) == MT_ADLIB) { + _sound = new SoundAdLibPC(this, _mixer); + } else { + Sound::kType type; + const MusicType midiType = MidiDriver::getMusicType(dev); + + if (midiType == MT_PCSPK || midiType == MT_NULL) + type = Sound::kPCSpkr; + else if (midiType == MT_MT32 || ConfMan.getBool("native_mt32")) + type = Sound::kMidiMT32; + else + type = Sound::kMidiGM; + + MidiDriver *driver = 0; + + if (MidiDriver::getMusicType(dev) == MT_PCSPK) { + driver = new MidiDriver_PCSpeaker(_mixer); + } else { + driver = MidiDriver::createMidi(dev); + if (type == Sound::kMidiMT32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + } + + assert(driver); + + SoundMidiPC *soundMidiPc = new SoundMidiPC(this, _mixer, driver, type); + _sound = soundMidiPc; + assert(_sound); + + // Unlike some SCUMM games, it's not that the MIDI sounds are + // missing. It's just that at least at the time of writing they + // are decidedly inferior to the AdLib ones. + if (ConfMan.getBool("multi_midi")) { + SoundAdLibPC *adlib = new SoundAdLibPC(this, _mixer); + assert(adlib); + + _sound = new MixedSoundDriver(this, _mixer, soundMidiPc, adlib); + } + } + } + + assert(_sound); + } + + if (_sound) + _sound->updateVolumeSettings(); + + if (ConfMan.hasKey("render_mode")) + _configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + + _res = new Resource(this); + assert(_res); + _res->reset(); + + _staticres = new StaticResource(this); + assert(_staticres); + if (!_staticres->init()) + error("_staticres->init() failed"); + assert(screen()); + if (!screen()->init()) + error("screen()->init() failed"); + _timer = new TimerManager(this, _system); + assert(_timer); + setupTimers(); + _emc = new EMCInterpreter(this); + assert(_emc); + + setupOpcodeTable(); + readSettings(); + + if (ConfMan.hasKey("save_slot")) { + _gameToLoad = ConfMan.getInt("save_slot"); + if (!saveFileLoadable(_gameToLoad)) + _gameToLoad = -1; + } + + setupKeyMap(); + + // Prevent autosave on game startup + _lastAutosave = _system->getMillis(); + + return Common::kNoError; +} + +KyraEngine_v1::~KyraEngine_v1() { + for (Common::Array<const Opcode *>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete *i; + _opcodes.clear(); + _keyMap.clear(); + + delete _res; + delete _staticres; + delete _sound; + delete _text; + delete _timer; + delete _emc; + delete _debugger; +} + +Common::Point KyraEngine_v1::getMousePos() { + Common::Point mouse = _eventMan->getMousePos(); + + if (_flags.useHiRes) { + mouse.x >>= 1; + mouse.y >>= 1; + } + + return mouse; +} + +void KyraEngine_v1::setMousePos(int x, int y) { + if (_flags.useHiRes) { + x <<= 1; + y <<= 1; + } + _system->warpMouse(x, y); +} + +int KyraEngine_v1::checkInput(Button *buttonList, bool mainLoop, int eventFlag) { + _isSaveAllowed = mainLoop; + updateInput(); + _isSaveAllowed = false; + + if (mainLoop) + checkAutosave(); + + int keys = 0; + int8 mouseWheel = 0; + + while (!_eventList.empty()) { + Common::Event event = *_eventList.begin(); + bool breakLoop = false; + + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode >= Common::KEYCODE_1 && event.kbd.keycode <= Common::KEYCODE_9 && + (event.kbd.hasFlags(Common::KBD_CTRL) || event.kbd.hasFlags(Common::KBD_ALT)) && mainLoop) { + int saveLoadSlot = 9 - (event.kbd.keycode - Common::KEYCODE_0) + 990; + + if (event.kbd.hasFlags(Common::KBD_CTRL)) { + if (saveFileLoadable(saveLoadSlot)) + loadGameStateCheck(saveLoadSlot); + _eventList.clear(); + breakLoop = true; + } else { + char savegameName[14]; + sprintf(savegameName, "Quicksave %d", event.kbd.keycode - Common::KEYCODE_0); + saveGameStateIntern(saveLoadSlot, savegameName, 0); + } + } else if (event.kbd.hasFlags(Common::KBD_CTRL)) { + if (event.kbd.keycode == Common::KEYCODE_d) { + if (_debugger) + _debugger->attach(); + breakLoop = true; + } else if (event.kbd.keycode == Common::KEYCODE_q) { + quitGame(); + } + } else { + KeyMap::const_iterator keycode = _keyMap.find(event.kbd.keycode); + if (keycode != _keyMap.end()) { + keys = keycode->_value; + if (event.kbd.flags & Common::KBD_SHIFT) + keys |= 0x100; + } else { + keys = 0; + } + + // When we got an keypress, which we might need to handle, + // break the event loop and pass it to GUI code. + if (keys) + breakLoop = true; + } + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: { + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + if (_flags.useHiRes) { + _mouseX >>= 1; + _mouseY >>= 1; + } + keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800)); + breakLoop = true; + } break; + + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: { + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + if (_flags.useHiRes) { + _mouseX >>= 1; + _mouseY >>= 1; + } + keys = (event.type == Common::EVENT_RBUTTONDOWN ? 201 : (202 | 0x800)); + breakLoop = true; + } break; + + case Common::EVENT_WHEELUP: + mouseWheel = -1; + break; + + case Common::EVENT_WHEELDOWN: + mouseWheel = 1; + break; + + default: + break; + } + + if (_debugger) + _debugger->onFrame(); + + if (breakLoop) + break; + + _eventList.erase(_eventList.begin()); + } + + GUI *guiInstance = gui(); + if (guiInstance) { + if (keys) + return guiInstance->processButtonList(buttonList, keys | eventFlag, mouseWheel); + else + return guiInstance->processButtonList(buttonList, 0, mouseWheel); + } else { + return keys; + } +} + +void KyraEngine_v1::setupKeyMap() { + struct KeyCodeMapEntry { + Common::KeyCode kcScummVM; + int16 kcDOS; + int16 kcPC98; + int16 kcFMTowns; + }; + +#define UNKNOWN_KEYCODE -1 +#define KC(x) Common::KEYCODE_##x + static const KeyCodeMapEntry keys[] = { + { KC(SPACE), 61, 53, 32 }, + { KC(RETURN), 43, 29, 13 }, + { KC(UP), 96, 68, 30 }, + { KC(KP8), 96, 68, 30 }, + { KC(RIGHT), 102, 73, 28 }, + { KC(KP6), 102, 73, 28 }, + { KC(DOWN), 98, 76, 31 }, + { KC(KP2), 98, 76, 31 }, + { KC(KP5), 97, 72, UNKNOWN_KEYCODE }, + { KC(LEFT), 92, 71, 29 }, + { KC(KP4), 92, 71, 29 }, + { KC(HOME), 91, 67, 127 }, + { KC(KP7), 91, 67, 127 }, + { KC(PAGEUP), 101, 69, 18 }, + { KC(KP9), 101, 69, 18 }, + { KC(END), 93, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(KP1), 93, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(PAGEDOWN), 103, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(KP3), 103, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(F1), 112, 99, 93 }, + { KC(F2), 113, 100, 94 }, + { KC(F3), 114, 101, 95 }, + { KC(F4), 115, 102, 96 }, + { KC(F5), 116, 103, 97 }, + { KC(F6), 117, 104, 98 }, + { KC(a), 31, 31, UNKNOWN_KEYCODE }, + { KC(b), 50, 50, 66 }, + { KC(c), 48, 48, 67 }, + { KC(d), 33, 33, 68 }, + { KC(e), 19, 19, UNKNOWN_KEYCODE }, + { KC(f), 34, 34, 70 }, + { KC(i), 24, 24, 24 }, + { KC(k), 38, 38, 75 }, + { KC(m), 52, 52, 77 }, + { KC(n), 51, 51, UNKNOWN_KEYCODE }, + { KC(o), 25, 25, 79 }, + { KC(p), 26, 26, 80 }, + { KC(r), 20, 20, 82 }, + { KC(s), 32, 32, UNKNOWN_KEYCODE }, + { KC(w), 18, 18, UNKNOWN_KEYCODE }, + { KC(y), 22, 22, UNKNOWN_KEYCODE }, + { KC(z), 46, 46, UNKNOWN_KEYCODE }, + { KC(0), UNKNOWN_KEYCODE, UNKNOWN_KEYCODE, 48 }, + { KC(1), 2, UNKNOWN_KEYCODE, 49 }, + { KC(2), 3, UNKNOWN_KEYCODE, 50 }, + { KC(3), 4, UNKNOWN_KEYCODE, 51 }, + { KC(4), 5, UNKNOWN_KEYCODE, 52 }, + { KC(5), 6, UNKNOWN_KEYCODE, 53 }, + { KC(6), 7, UNKNOWN_KEYCODE, 54 }, + { KC(7), 8, UNKNOWN_KEYCODE, 55 }, + { KC(SLASH), 55, 55, 47 }, + { KC(ESCAPE), 110, 1, 27 }, + { KC(MINUS), 12, UNKNOWN_KEYCODE, 45 }, + { KC(KP_MINUS), 105, UNKNOWN_KEYCODE, 45 }, + { KC(PLUS), 13, UNKNOWN_KEYCODE, 43 }, + { KC(KP_PLUS), 106, UNKNOWN_KEYCODE, 43 }, + + // Multiple mappings for the keys to the right of the 'M' key, + // since these are different for QWERTZ, QWERTY and AZERTY keyboards. + // QWERTZ + { KC(COMMA), 53, UNKNOWN_KEYCODE, 60 }, + { KC(PERIOD), 54, UNKNOWN_KEYCODE, 62 }, + // AZERTY + { KC(SEMICOLON), 53, UNKNOWN_KEYCODE, 60 }, + { KC(COLON), 54, UNKNOWN_KEYCODE, 62 }, + // QWERTY + { KC(LESS), 53, UNKNOWN_KEYCODE, 60 }, + { KC(GREATER), 54, UNKNOWN_KEYCODE, 62 } + }; +#undef KC +#undef UNKNOWN_KEYCODE + + _keyMap.clear(); + + for (int i = 0; i < ARRAYSIZE(keys); i++) + _keyMap[keys[i].kcScummVM] = (_flags.platform == Common::kPlatformPC98) ? keys[i].kcPC98 : ((_flags.platform == Common::kPlatformFMTowns) ? keys[i].kcFMTowns : keys[i].kcDOS); +} + +void KyraEngine_v1::updateInput() { + Common::Event event; + + bool updateScreen = false; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_PERIOD || event.kbd.keycode == Common::KEYCODE_ESCAPE || + event.kbd.keycode == Common::KEYCODE_SPACE || event.kbd.keycode == Common::KEYCODE_RETURN || + event.kbd.keycode == Common::KEYCODE_UP || event.kbd.keycode == Common::KEYCODE_RIGHT || + event.kbd.keycode == Common::KEYCODE_DOWN || event.kbd.keycode == Common::KEYCODE_LEFT) + _eventList.push_back(Event(event, true)); + else if (event.kbd.keycode == Common::KEYCODE_q && event.kbd.hasFlags(Common::KBD_CTRL)) + quitGame(); + else + _eventList.push_back(event); + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + _eventList.push_back(Event(event, true)); + break; + + case Common::EVENT_MOUSEMOVE: + if (screen()->isMouseVisible()) + updateScreen = true; + break; + + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + _eventList.push_back(event); + break; + + default: + break; + } + } + + if (updateScreen) + _system->updateScreen(); +} + +void KyraEngine_v1::removeInputTop() { + if (!_eventList.empty()) + _eventList.erase(_eventList.begin()); +} + +bool KyraEngine_v1::skipFlag() const { + for (Common::List<Event>::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) + return true; + } + return false; +} + +void KyraEngine_v1::resetSkipFlag(bool removeEvent) { + for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) { + if (removeEvent) + _eventList.erase(i); + else + i->causedSkip = false; + return; + } + } +} + + +int KyraEngine_v1::setGameFlag(int flag) { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + _flagsTable[flag >> 3] |= (1 << (flag & 7)); + return 1; +} + +int KyraEngine_v1::queryGameFlag(int flag) const { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + return ((_flagsTable[flag >> 3] >> (flag & 7)) & 1); +} + +int KyraEngine_v1::resetGameFlag(int flag) { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + _flagsTable[flag >> 3] &= ~(1 << (flag & 7)); + return 0; +} + +void KyraEngine_v1::delayUntil(uint32 timestamp, bool updateTimers, bool update, bool isMainLoop) { + const uint32 curTime = _system->getMillis(); + if (curTime > timestamp) + return; + + uint32 del = timestamp - curTime; + while (del && !shouldQuit()) { + uint32 step = MIN<uint32>(del, _tickLength); + delay(step, update, isMainLoop); + del -= step; + } +} + +void KyraEngine_v1::delay(uint32 amount, bool update, bool isMainLoop) { + _system->delayMillis(amount); +} + +void KyraEngine_v1::delayWithTicks(int ticks) { + delay(ticks * _tickLength); +} + +void KyraEngine_v1::registerDefaultSettings() { + if (_flags.platform == Common::kPlatformFMTowns) + ConfMan.registerDefault("cdaudio", true); + if (_flags.fanLang != Common::UNK_LANG) { + // HACK/WORKAROUND: Since we can't use registerDefault here to overwrite + // the global subtitles settings, we're using this hack to enable subtitles + // for fan translations + const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain(); + if (!cur || (cur && cur->getVal("subtitles").empty())) + ConfMan.setBool("subtitles", true); + } +} + +void KyraEngine_v1::readSettings() { + _configWalkspeed = ConfMan.getInt("walkspeed"); + _configMusic = 0; + + if (!ConfMan.getBool("music_mute")) { + if (_flags.platform == Common::kPlatformFMTowns) + _configMusic = ConfMan.getBool("cdaudio") ? 2 : 1; + else + _configMusic = 1; + } + _configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1; + + if (_sound) { + _sound->enableMusic(_configMusic); + _sound->enableSFX(_configSounds); + } + + bool speechMute = ConfMan.getBool("speech_mute"); + bool subtitles = ConfMan.getBool("subtitles"); + + if (!speechMute && subtitles) + _configVoice = 2; // Voice & Text + else if (!speechMute && !subtitles) + _configVoice = 1; // Voice only + else + _configVoice = 0; // Text only + + setWalkspeed(_configWalkspeed); +} + +void KyraEngine_v1::writeSettings() { + bool speechMute, subtitles; + + ConfMan.setInt("walkspeed", _configWalkspeed); + ConfMan.setBool("music_mute", _configMusic == 0); + if (_flags.platform == Common::kPlatformFMTowns) + ConfMan.setBool("cdaudio", _configMusic == 2); + ConfMan.setBool("sfx_mute", _configSounds == 0); + + switch (_configVoice) { + case 0: // Text only + speechMute = true; + subtitles = true; + break; + case 1: // Voice only + speechMute = false; + subtitles = false; + break; + default: // Voice & Text + speechMute = false; + subtitles = true; + } + + if (_sound) { + if (!_configMusic) + _sound->beginFadeOut(); + _sound->enableMusic(_configMusic); + _sound->enableSFX(_configSounds); + } + + ConfMan.setBool("speech_mute", speechMute); + ConfMan.setBool("subtitles", subtitles); + + ConfMan.flushToDisk(); +} + +bool KyraEngine_v1::speechEnabled() { + return _flags.isTalkie && (_configVoice == 1 || _configVoice == 2); +} + +bool KyraEngine_v1::textEnabled() { + return !_flags.isTalkie || (_configVoice == 0 || _configVoice == 2); +} + +int KyraEngine_v1::convertVolumeToMixer(int value) { + value -= 2; + return (value * Audio::Mixer::kMaxMixerVolume) / 95; +} + +int KyraEngine_v1::convertVolumeFromMixer(int value) { + return (value * 95) / Audio::Mixer::kMaxMixerVolume + 2; +} + +void KyraEngine_v1::setVolume(kVolumeEntry vol, uint8 value) { + switch (vol) { + case kVolumeMusic: + ConfMan.setInt("music_volume", convertVolumeToMixer(value)); + break; + + case kVolumeSfx: + ConfMan.setInt("sfx_volume", convertVolumeToMixer(value)); + break; + + case kVolumeSpeech: + ConfMan.setInt("speech_volume", convertVolumeToMixer(value)); + break; + } + + // Resetup mixer + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); + if (_sound) + _sound->updateVolumeSettings(); +} + +uint8 KyraEngine_v1::getVolume(kVolumeEntry vol) { + switch (vol) { + case kVolumeMusic: + return convertVolumeFromMixer(ConfMan.getInt("music_volume")); + + case kVolumeSfx: + return convertVolumeFromMixer(ConfMan.getInt("sfx_volume")); + + case kVolumeSpeech: + if (speechEnabled()) + return convertVolumeFromMixer(ConfMan.getInt("speech_volume")); + else + return 2; + break; + } + + return 2; +} + +void KyraEngine_v1::syncSoundSettings() { + Engine::syncSoundSettings(); + + // We need to use this here to allow the subtitle options to be changed + // through the GMM's options dialog. + readSettings(); + + if (_sound) + _sound->updateVolumeSettings(); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_v2.cpp b/engines/kyra/engine/kyra_v2.cpp new file mode 100644 index 0000000000..e606a66c15 --- /dev/null +++ b/engines/kyra/engine/kyra_v2.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 "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +#include "common/config-manager.h" +#include "common/error.h" +#include "common/system.h" + +namespace Kyra { + +KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const EngineDesc &desc) : KyraEngine_v1(system, flags), _desc(desc) { + memset(&_sceneAnims, 0, sizeof(_sceneAnims)); + memset(&_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); + + _lastProcessedSceneScript = 0; + _specialSceneScriptRunFlag = false; + + _itemList = 0; + _itemListSize = 0; + + _characterShapeFile = -1; + + _updateCharPosNextUpdate = 0; + + memset(&_sceneScriptState, 0, sizeof(_sceneScriptState)); + memset(&_sceneScriptData, 0, sizeof(_sceneScriptData)); + + Common::fill(_sceneSpecialScriptsTimer, ARRAYEND(_sceneSpecialScriptsTimer), 0); + + _animObjects = 0; + + _runFlag = true; + _showOutro = false; + _deathHandler = -1; + _animNeedUpdate = false; + + _animShapeCount = 0; + _animShapeFiledata = 0; + + _vocHigh = -1; + _chatVocHigh = -1; + _chatVocLow = -1; + _chatText = 0; + _chatObject = -1; + _chatTextEnabled = false; + + memset(_hiddenItems, -1, sizeof(_hiddenItems)); + + _screenBuffer = 0; + + memset(&_mainCharacter, 0, sizeof(_mainCharacter)); + memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory)); + + _pauseStart = 0; + + _pathfinderFlag = 0; + _smoothingPath = false; + + _lang = 0; + Common::Language lang = Common::parseLanguage(ConfMan.get("language")); + if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) + lang = _flags.replacedLang; + + switch (lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + case Common::JA_JPN: + _lang = 3; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + } +} + +KyraEngine_v2::~KyraEngine_v2() { + if (!(_flags.isDemo && !_flags.isTalkie)) { + for (ShapeMap::iterator i = _gameShapes.begin(); i != _gameShapes.end(); ++i) { + delete[] i->_value; + i->_value = 0; + } + _gameShapes.clear(); + } + + delete[] _itemList; + delete[] _sceneList; + + _emc->unload(&_sceneScriptData); + + delete[] _animObjects; + + for (Common::Array<const Opcode *>::iterator i = _opcodesAnimation.begin(); i != _opcodesAnimation.end(); ++i) + delete *i; + _opcodesAnimation.clear(); + + delete[] _screenBuffer; +} + +void KyraEngine_v2::pauseEngineIntern(bool pause) { + KyraEngine_v1::pauseEngineIntern(pause); + + if (!pause) { + uint32 pausedTime = _system->getMillis() - _pauseStart; + + for (int i = 0; i < ARRAYSIZE(_sceneSpecialScriptsTimer); ++i) { + if (_sceneSpecialScriptsTimer[i]) + _sceneSpecialScriptsTimer[i] += pausedTime; + } + + } else { + _pauseStart = _system->getMillis(); + } +} + +void KyraEngine_v2::delay(uint32 amount, bool updateGame, bool isMainLoop) { + uint32 start = _system->getMillis(); + do { + if (updateGame) { + if (_chatText) + updateWithText(); + else + update(); + } else { + updateInput(); + } + + if (amount > 0) + _system->delayMillis(amount > 10 ? 10 : amount); + } while (!skipFlag() && _system->getMillis() < start + amount && !shouldQuit()); +} + +bool KyraEngine_v2::checkSpecialSceneExit(int num, int x, int y) { + if (_specialExitTable[0 + num] > x || _specialExitTable[5 + num] > y || + _specialExitTable[10 + num] < x || _specialExitTable[15 + num] < y) + return false; + return true; +} + +void KyraEngine_v2::addShapeToPool(const uint8 *data, int realIndex, int shape) { + remShapeFromPool(realIndex); + _gameShapes[realIndex] = screen_v2()->makeShapeCopy(data, shape); +} + +void KyraEngine_v2::addShapeToPool(uint8 *shpData, int index) { + remShapeFromPool(index); + _gameShapes[index] = shpData; +} + +void KyraEngine_v2::remShapeFromPool(int idx) { + ShapeMap::iterator iter = _gameShapes.find(idx); + if (iter != _gameShapes.end()) { + delete[] iter->_value; + iter->_value = 0; + } +} + +uint8 *KyraEngine_v2::getShapePtr(int shape) const { + ShapeMap::iterator iter = _gameShapes.find(shape); + if (iter == _gameShapes.end()) + return 0; + return iter->_value; +} + +void KyraEngine_v2::moveCharacter(int facing, int x, int y) { + x &= ~3; + y &= ~1; + _mainCharacter.facing = facing; + + switch (facing) { + case 0: + while (_mainCharacter.y1 > y) + updateCharPosWithUpdate(); + break; + + case 2: + while (_mainCharacter.x1 < x) + updateCharPosWithUpdate(); + break; + + case 4: + while (_mainCharacter.y1 < y) + updateCharPosWithUpdate(); + break; + + case 6: + while (_mainCharacter.x1 > x) + updateCharPosWithUpdate(); + break; + + default: + break; + } +} + +void KyraEngine_v2::updateCharPosWithUpdate() { + updateCharPos(0, 0); + update(); +} + +int KyraEngine_v2::updateCharPos(int *table, int force) { + if (_updateCharPosNextUpdate > _system->getMillis() && !force) + return 0; + _mainCharacter.x1 += _charAddXPosTable[_mainCharacter.facing]; + _mainCharacter.y1 += _charAddYPosTable[_mainCharacter.facing]; + updateCharAnimFrame(table); + _updateCharPosNextUpdate = _system->getMillis() + getCharacterWalkspeed() * _tickLength; + return 1; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_v2.h b/engines/kyra/engine/kyra_v2.h new file mode 100644 index 0000000000..87de4398e1 --- /dev/null +++ b/engines/kyra/engine/kyra_v2.h @@ -0,0 +1,400 @@ +/* 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 KYRA_KYRA_V2_H +#define KYRA_KYRA_V2_H + +#include "kyra/kyra_v1.h" +#include "kyra/gui/gui_v1.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/engine/item.h" + +#include "common/list.h" +#include "common/hashmap.h" + +namespace Kyra { + +struct FrameControl { + uint16 index; + uint16 delay; +}; + +struct ItemAnimDefinition { + Item itemIndex; + uint8 numFrames; + const FrameControl *frames; +}; + +struct ActiveItemAnim { + uint16 currentFrame; + uint32 nextFrameTime; +}; + +class Screen_v2; + +class KyraEngine_v2 : public KyraEngine_v1 { +friend class Debugger_v2; +friend class GUI_v2; +public: + struct EngineDesc { + // Generic shape related + int itemShapeStart; + const uint8 *characterFrameTable; + + // Scene script + int firstAnimSceneScript; + + // Animation script specific + int animScriptFrameAdd; + + // Item specific + Item maxItemId; + }; + + KyraEngine_v2(OSystem *system, const GameFlags &flags, const EngineDesc &desc); + ~KyraEngine_v2(); + + virtual void pauseEngineIntern(bool pause); + + virtual Screen_v2 *screen_v2() const = 0; + + void delay(uint32 time, bool update = false, bool isMainLoop = false); + + const EngineDesc &engineDesc() const { return _desc; } +protected: + EngineDesc _desc; + + // run + uint32 _pauseStart; + bool _runFlag; + bool _showOutro; + + virtual void update() = 0; + virtual void updateWithText() = 0; + + // detection + int _lang; + + // Input + virtual int inputSceneChange(int x, int y, int unk1, int unk2) = 0; + + // Animator + struct AnimObj { + uint16 index; + uint16 type; + bool enabled; + uint16 needRefresh; + uint16 specialRefresh; + uint16 animFlags; + uint16 flags; + int16 xPos1, yPos1; + uint8 *shapePtr; + uint16 shapeIndex1; + uint16 animNum; + uint16 shapeIndex3; + uint16 shapeIndex2; + int16 xPos2, yPos2; + int16 xPos3, yPos3; + int16 width, height; + int16 width2, height2; + uint16 palette; + AnimObj *nextObject; + }; + + void allocAnimObjects(int actors, int anims, int items); + AnimObj *_animObjects; + + AnimObj *_animActor; + AnimObj *_animAnims; + AnimObj *_animItems; + + bool _drawNoShapeFlag; + AnimObj *_animList; + + AnimObj *initAnimList(AnimObj *list, AnimObj *entry); + AnimObj *addToAnimListSorted(AnimObj *list, AnimObj *entry); + AnimObj *deleteAnimListEntry(AnimObj *list, AnimObj *entry); + + virtual void refreshAnimObjects(int force) = 0; + void refreshAnimObjectsIfNeed(); + + void flagAnimObjsSpecialRefresh(); + void flagAnimObjsForRefresh(); + + virtual void clearAnimObjects() = 0; + + virtual void drawAnimObjects() = 0; + virtual void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer) = 0; + virtual void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer) = 0; + + virtual void updateCharacterAnim(int) = 0; + virtual void updateSceneAnim(int anim, int newFrame) = 0; + + void addItemToAnimList(int item); + void deleteItemAnimEntry(int item); + + virtual void animSetupPaletteEntry(AnimObj *){} + + virtual void setCharacterAnimDim(int w, int h) = 0; + virtual void resetCharacterAnimDim() = 0; + + virtual int getScale(int x, int y) = 0; + + uint8 *_screenBuffer; + + // Scene + struct SceneDesc { + char filename1[10]; + char filename2[10]; + + uint16 exit1, exit2, exit3, exit4; + uint8 flags; + uint8 sound; + }; + + SceneDesc *_sceneList; + int _sceneListSize; + uint16 _currentScene; + + uint16 _sceneExit1, _sceneExit2, _sceneExit3, _sceneExit4; + int _sceneEnterX1, _sceneEnterY1, _sceneEnterX2, _sceneEnterY2, + _sceneEnterX3, _sceneEnterY3, _sceneEnterX4, _sceneEnterY4; + int _specialExitCount; + uint16 _specialExitTable[25]; + bool checkSpecialSceneExit(int num, int x, int y); + + bool _overwriteSceneFacing; + + virtual void enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3) = 0; + + void runSceneScript6(); + + EMCData _sceneScriptData; + EMCState _sceneScriptState; + + virtual int trySceneChange(int *moveTable, int unk1, int unk2) = 0; + + // Animation + virtual void restorePage3() = 0; + + struct SceneAnim { + uint16 flags; + int16 x, y; + int16 x2, y2; + int16 width, height; + uint16 specialSize; + int16 shapeIndex; + uint16 wsaFlag; + char filename[14]; + }; + + SceneAnim _sceneAnims[16]; + WSAMovie_v2 *_sceneAnimMovie[16]; + + void freeSceneAnims(); + + bool _specialSceneScriptState[10]; + bool _specialSceneScriptStateBackup[10]; + EMCState _sceneSpecialScripts[10]; + uint32 _sceneSpecialScriptsTimer[10]; + int _lastProcessedSceneScript; + bool _specialSceneScriptRunFlag; + + void updateSpecialSceneScripts(); + + // Sequences + EMCData _animationScriptData; + EMCState _animationScriptState; + Common::Array<const Opcode *> _opcodesAnimation; + + void runAnimationScript(const char *filename, int allowSkip, int resetChar, int newShapes, int shapeUnload); + + int o2a_setAnimationShapes(EMCState *script); + int o2a_setResetFrame(EMCState *script); + + char _animShapeFilename[14]; + + uint8 *_animShapeFiledata; + int _animShapeCount; + int _animShapeLastEntry; + + int _animNewFrame; + int _animDelayTime; + + int _animResetFrame; + + int _animShapeWidth, _animShapeHeight; + int _animShapeXAdd, _animShapeYAdd; + + bool _animNeedUpdate; + + virtual int initAnimationShapes(uint8 *filedata) = 0; + void processAnimationScript(int allowSkip, int resetChar); + virtual void uninitAnimationShapes(int count, uint8 *filedata) = 0; + + // Shapes + typedef Common::HashMap<int, uint8 *> ShapeMap; + ShapeMap _gameShapes; + + uint8 *getShapePtr(int index) const; + void addShapeToPool(const uint8 *data, int realIndex, int shape); + void addShapeToPool(uint8 *shpData, int index); + void remShapeFromPool(int idx); + + int _characterShapeFile; + virtual void loadCharacterShapes(int shapes) = 0; + + // pathfinder + int _movFacingTable[600]; + int _pathfinderFlag; + bool _smoothingPath; + + int findWay(int curX, int curY, int dstX, int dstY, int *moveTable, int moveTableSize); + + bool directLinePassable(int x, int y, int toX, int toY); + + int pathfinderInitPositionTable(int *moveTable); + int pathfinderAddToPositionTable(int index, int v1, int v2); + int pathfinderInitPositionIndexTable(int tableLen, int x, int y); + int pathfinderAddToPositionIndexTable(int index, int v); + void pathfinderFinializePath(int *moveTable, int unk1, int x, int y, int moveTableSize); + + int _pathfinderPositionTable[400]; + int _pathfinderPositionIndexTable[200]; + + // items + struct ItemDefinition { + Item id; + uint16 sceneId; + int16 x; + uint8 y; + }; + + void initItemList(int size); + + Item _hiddenItems[100]; + + ItemDefinition *_itemList; + int _itemListSize; + + int _itemInHand; + int _savedMouseState; + + int findFreeItem(); + int countAllItems(); + + int findItem(uint16 sceneId, Item id); + int findItem(Item item); + + void resetItemList(); + void resetItem(int index); + + virtual void setMouseCursor(Item item) = 0; + + void setHandItem(Item item); + void removeHandItem(); + + // character + struct Character { + uint16 sceneId; + int16 dlgIndex; + uint8 height; + uint8 facing; + uint16 animFrame; + byte walkspeed; + Item inventory[20]; + int16 x1, y1; + int16 x2, y2; + int16 x3, y3; + }; + + Character _mainCharacter; + int _mainCharX, _mainCharY; + int _charScale; + + void moveCharacter(int facing, int x, int y); + int updateCharPos(int *table, int force = 0); + void updateCharPosWithUpdate(); + + uint32 _updateCharPosNextUpdate; + + virtual int getCharacterWalkspeed() const = 0; + virtual void updateCharAnimFrame(int *table) = 0; + + // chat + int _vocHigh; + + const char *_chatText; + int _chatObject; + uint32 _chatEndTime; + int _chatVocHigh, _chatVocLow; + bool _chatTextEnabled; + + EMCData _chatScriptData; + EMCState _chatScriptState; + + virtual void setDlgIndex(int dlgIndex) = 0; + + virtual void randomSceneChat() = 0; + + // unknown + int _unk4, _unk5; + bool _unkSceneScreenFlag1; + bool _unkHandleSceneChangeFlag; + + // opcodes + int o2_getCharacterX(EMCState *script); + int o2_getCharacterY(EMCState *script); + int o2_getCharacterFacing(EMCState *script); + int o2_getCharacterScene(EMCState *script); + int o2_setCharacterFacingOverwrite(EMCState *script); + int o2_trySceneChange(EMCState *script); + int o2_moveCharacter(EMCState *script); + int o2_checkForItem(EMCState *script); + int o2_defineItem(EMCState *script); + int o2_addSpecialExit(EMCState *script); + int o2_delay(EMCState *script); + int o2_update(EMCState *script); + int o2_getShapeFlag1(EMCState *script); + int o2_waitForConfirmationClick(EMCState *script); + int o2_randomSceneChat(EMCState *script); + int o2_setDlgIndex(EMCState *script); + int o2_getDlgIndex(EMCState *script); + int o2_defineRoomEntrance(EMCState *script); + int o2_runAnimationScript(EMCState *script); + int o2_setSpecialSceneScriptRunTime(EMCState *script); + int o2_defineScene(EMCState *script); + int o2_setSpecialSceneScriptState(EMCState *script); + int o2_clearSpecialSceneScriptState(EMCState *script); + int o2_querySpecialSceneScriptState(EMCState *script); + int o2_setHiddenItemsEntry(EMCState *script); + int o2_getHiddenItemsEntry(EMCState *script); + int o2_disableTimer(EMCState *script); + int o2_enableTimer(EMCState *script); + int o2_setTimerCountdown(EMCState *script); + int o2_setVocHigh(EMCState *script); + int o2_getVocHigh(EMCState *script); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/lol.cpp b/engines/kyra/engine/lol.cpp new file mode 100644 index 0000000000..9cf045a876 --- /dev/null +++ b/engines/kyra/engine/lol.cpp @@ -0,0 +1,4513 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/engine/util.h" +#include "kyra/gui/debugger.h" +#include "kyra/sound/sound.h" + +#include "audio/audiostream.h" + +#include "common/config-manager.h" +#include "common/system.h" +#include "common/translation.h" + +#include "backends/keymapper/keymapper.h" + +namespace Kyra { + +const char *const LoLEngine::kKeymapName = "lol"; + +LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraRpgEngine(system, flags) { + _screen = 0; + _gui = 0; + _tim = 0; + + _lang = 0; + Common::Language lang = Common::parseLanguage(ConfMan.get("language")); + if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) + lang = _flags.replacedLang; + + switch (lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + case Common::JA_JPN: + _lang = 0; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + } + + _chargenFrameTable = _flags.isTalkie ? _chargenFrameTableTalkie : _chargenFrameTableFloppy; + _chargenWSA = 0; + _lastUsedStringBuffer = 0; + _landsFile = 0; + _levelLangFile = 0; + + _lastMusicTrack = -1; + _lastSfxTrack = -1; + _curTlkFile = -1; + _lastSpeaker = _lastSpeechId = _nextSpeechId = _nextSpeaker = -1; + + memset(_moneyColumnHeight, 0, sizeof(_moneyColumnHeight)); + _credits = 0; + + _itemsInPlay = 0; + _itemProperties = 0; + _itemInHand = 0; + memset(_inventory, 0, sizeof(_inventory)); + memset(_charStatusFlags, 0, sizeof(_charStatusFlags)); + _inventoryCurItem = 0; + _lastCharInventory = -1; + _emcLastItem = -1; + + _itemIconShapes = _itemShapes = _gameShapes = _thrownShapes = _effectShapes = _fireballShapes = _healShapes = _healiShapes = 0; + _levelShpList = _levelDatList = 0; + _gameShapeMap = 0; + memset(_monsterAnimType, 0, 3); + _healOverlay = 0; + _swarmSpellStatus = 0; + + _ingameMT32SoundIndex = _ingameGMSoundIndex = _ingamePCSpeakerSoundIndex = 0; + + _charSelection = -1; + _characters = 0; + _spellProperties = 0; + _selectedSpell = 0; + _updateCharNum = _portraitSpeechAnimMode = _textColorFlag = 0; + _palUpdateTimer = _updatePortraitNext = 0; + _lampStatusTimer = 0xFFFFFFFF; + + _weaponsDisabled = false; + _charInventoryUnk = 0; + _lastButtonShape = 0; + _buttonPressTimer = 0; + _selectedCharacter = 0; + _suspendScript = false; + _scriptDirection = 0; + _compassDirectionIndex = -1; + _compassStep = 0; + + _smoothScrollModeNormal = 1; + _wllAutomapData = 0; + _sceneXoffset = 112; + _sceneShpDim = 13; + _monsters = 0; + _monsterProperties = 0; + _lvlShapeIndex = 0; + _partyAwake = true; + _transparencyTable2 = 0; + _transparencyTable1 = 0; + _specialGuiShape = 0; + _specialGuiShapeX = _specialGuiShapeY = _specialGuiShapeMirrorFlag = 0; + memset(_characterFaceShapes, 0, sizeof(_characterFaceShapes)); + + _lampEffect = _brightness = _lampOilStatus = 0; + _lampStatusSuspended = false; + _tempBuffer5120 = 0; + _flyingObjects = 0; + _monsters = 0; + _lastMouseRegion = 0; + _objectLastDirection = 0; + _monsterCurBlock = 0; + _seqWindowX1 = _seqWindowY1 = _seqWindowX2 = _seqWindowY2 = _seqTrigger = 0; + _spsWindowX = _spsWindowY = _spsWindowW = _spsWindowH = 0; + + _dscWalls = 0; + _dscOvlMap = 0; + _dscShapeScaleW = 0; + _dscShapeScaleH = 0; + _dscShapeY = 0; + _dscShapeOvlIndex = 0; + _dscDoorMonsterX = _dscDoorMonsterY = 0; + _dscDoor4 = 0; + + _ingameSoundList = 0; + _ingameSoundIndex = 0; + _ingameSoundListSize = 0; + _musicTrackMap = 0; + _curMusicTheme = -1; + _curMusicFileExt = 0; + _curMusicFileIndex = -1; + _envSfxUseQueue = false; + _envSfxNumTracksInQueue = 0; + memset(_envSfxQueuedTracks, 0, sizeof(_envSfxQueuedTracks)); + memset(_envSfxQueuedBlocks, 0, sizeof(_envSfxQueuedBlocks)); + + _partyPosX = _partyPosY = 0; + _shpDmX = _shpDmY = _dmScaleW = _dmScaleH = 0; + + _floatingCursorControl = _currentFloatingCursor = 0; + + memset(_activeTim, 0, sizeof(_activeTim)); + memset(&_activeSpell, 0, sizeof(_activeSpell)); + + _pageBuffer1 = _pageBuffer2 = 0; + + memset(_charStatsTemp, 0, sizeof(_charStatsTemp)); + + _compassBroken = _drainMagic = 0; + + _buttonData = 0; + _preserveEvents = false; + _buttonList1 = _buttonList2 = _buttonList3 = _buttonList4 = _buttonList5 = _buttonList6 = _buttonList7 = _buttonList8 = 0; + + _mapOverlay = 0; + _automapShapes = 0; + _defaultLegendData = 0; + _mapCursorOverlay = 0; + + _lightningProps = 0; + _lightningCurSfx = -1; + _lightningDiv = 0; + _lightningFirstSfx = 0; + _lightningSfxFrame = 0; + + _compassTimer = 0; + _scriptCharacterCycle = 0; + _partyDamageFlags = -1; + + memset(&_itemScript, 0, sizeof(_itemScript)); +} + +LoLEngine::~LoLEngine() { + setupPrologueData(false); + releaseTempData(); + + delete[] _landsFile; + delete[] _levelLangFile; + + delete _screen; + _screen = 0; + delete _gui; + _gui = 0; + delete _tim; + _tim = 0; + delete _txt; + _txt = 0; + + delete[] _itemsInPlay; + delete[] _itemProperties; + delete[] _characters; + + delete[] _pageBuffer1; + delete[] _pageBuffer2; + + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) + delete[] _itemIconShapes[i]; + delete[] _itemIconShapes; + } + + if (_itemShapes) { + for (int i = 0; i < _numItemShapes; i++) + delete[] _itemShapes[i]; + delete[] _itemShapes; + } + + if (_gameShapes) { + for (int i = 0; i < _numGameShapes; i++) + delete[] _gameShapes[i]; + delete[] _gameShapes; + } + + if (_thrownShapes) { + for (int i = 0; i < _numThrownShapes; i++) + delete[] _thrownShapes[i]; + delete[] _thrownShapes; + } + + if (_effectShapes) { + for (int i = 0; i < _numEffectShapes; i++) + delete[] _effectShapes[i]; + delete[] _effectShapes; + } + + if (_fireballShapes) { + for (int i = 0; i < _numFireballShapes; i++) + delete[] _fireballShapes[i]; + delete[] _fireballShapes; + } + + if (_healShapes) { + for (int i = 0; i < _numHealShapes; i++) + delete[] _healShapes[i]; + delete[] _healShapes; + } + + if (_healiShapes) { + for (int i = 0; i < _numHealiShapes; i++) + delete[] _healiShapes[i]; + delete[] _healiShapes; + } + + if (_monsterDecorationShapes) { + for (int i = 0; i < 3; i++) + releaseMonsterShapes(i); + + delete[] _monsterShapes; + _monsterShapes = 0; + delete[] _monsterPalettes; + _monsterPalettes = 0; + delete[] _monsterDecorationShapes; + _monsterDecorationShapes = 0; + } + + for (int i = 0; i < 6; i++) { + delete[] _doorShapes[i]; + _doorShapes[i] = 0; + } + + releaseDecorations(); + + delete[] _automapShapes; + + for (Common::Array<const TIMOpcode *>::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i) + delete *i; + _timIntroOpcodes.clear(); + + for (Common::Array<const TIMOpcode *>::iterator i = _timOutroOpcodes.begin(); i != _timOutroOpcodes.end(); ++i) + delete *i; + _timOutroOpcodes.clear(); + + for (Common::Array<const TIMOpcode *>::iterator i = _timIngameOpcodes.begin(); i != _timIngameOpcodes.end(); ++i) + delete *i; + _timIngameOpcodes.clear(); + + delete[] _wllAutomapData; + delete[] _tempBuffer5120; + delete[] _flyingObjects; + delete[] _monsters; + delete[] _monsterProperties; + + delete[] _transparencyTable2; + delete[] _transparencyTable1; + delete[] _lightningProps; + + delete _lvlShpFileHandle; + + if (_ingameSoundList) { + for (int i = 0; i < _ingameSoundListSize; i++) + delete[] _ingameSoundList[i]; + delete[] _ingameSoundList; + } + + for (int i = 0; i < 3; i++) { + for (int ii = 0; ii < 40; ii++) + delete[] _characterFaceShapes[ii][i]; + } + + delete[] _healOverlay; + + delete[] _defaultLegendData; + delete[] _mapCursorOverlay; + delete[] _mapOverlay; + + for (Common::Array<const SpellProc *>::iterator i = _spellProcs.begin(); i != _spellProcs.end(); ++i) + delete *i; + _spellProcs.clear(); + + for (SpeechList::iterator i = _speechList.begin(); i != _speechList.end(); ++i) + delete *i; + _speechList.clear(); + + _emc->unload(&_itemScript); + _emc->unload(&_scriptData); +} + +Screen *LoLEngine::screen() { + return _screen; +} + +GUI *LoLEngine::gui() const { + return _gui; +} + +Common::Error LoLEngine::init() { + _screen = new Screen_LoL(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_LoL(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _gui = new GUI_LoL(this); + assert(_gui); + _gui->initStaticData(); + + _txt = new TextDisplayer_LoL(this, _screen); + + _screen->setAnimBlockPtr(10000); + _screen->setScreenDim(0); + + _pageBuffer1 = new uint8[0xFA00]; + memset(_pageBuffer1, 0, 0xFA00); + _pageBuffer2 = new uint8[0xFA00]; + memset(_pageBuffer2, 0, 0xFA00); + + _itemsInPlay = new LoLItem[400]; + memset(_itemsInPlay, 0, sizeof(LoLItem) * 400); + + _characters = new LoLCharacter[4]; + memset(_characters, 0, sizeof(LoLCharacter) * 4); + + if (!_sound->init()) + error("Couldn't init sound"); + + KyraRpgEngine::init(); + + _wllAutomapData = new uint8[80]; + memset(_wllAutomapData, 0, 80); + + _monsters = new LoLMonster[30]; + memset(_monsters, 0, 30 * sizeof(LoLMonster)); + _monsterProperties = new LoLMonsterProperty[5]; + memset(_monsterProperties, 0, 5 * sizeof(LoLMonsterProperty)); + + _tempBuffer5120 = new uint8[5120]; + memset(_tempBuffer5120, 0, 5120); + + _flyingObjects = new FlyingObject[_numFlyingObjects]; + _flyingObjectsPtr = _flyingObjects; + _flyingObjectStructSize = sizeof(FlyingObject); + memset(_flyingObjects, 0, _numFlyingObjects * sizeof(FlyingObject)); + + memset(_globalScriptVars, 0, sizeof(_globalScriptVars)); + + _lvlShpFileHandle = 0; + + _sceneDrawPage1 = 2; + _sceneDrawPage2 = 6; + + _clickedShapeXOffs = 136; + _clickedShapeYOffs = 8; + _clickedSpecialFlag = 0x40; + + _monsterShapes = new uint8*[48]; + memset(_monsterShapes, 0, 48 * sizeof(uint8 *)); + _monsterPalettes = new uint8*[48]; + memset(_monsterPalettes, 0, 48 * sizeof(uint8 *)); + _monsterDecorationShapes = new uint8*[576]; + memset(_monsterDecorationShapes, 0, 576 * sizeof(uint8 *)); + memset(&_scriptData, 0, sizeof(EMCData)); + + _activeMagicMenu = -1; + + _automapShapes = new const uint8*[109]; + _mapOverlay = new uint8[256]; + + memset(_availableSpells, -1, 8); + + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castSpark)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castHeal)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castIce)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castFireball)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castHandOfFate)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castMistOfDoom)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castLightning)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castFog)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castSwarm)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castVaelansCube)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castGuardian)); + +#ifdef ENABLE_KEYMAPPER + _eventMan->getKeymapper()->pushKeymap(kKeymapName, true); +#endif + + return Common::kNoError; +} + +void LoLEngine::initKeymap() { +#ifdef ENABLE_KEYMAPPER + Common::Keymapper *const mapper = _eventMan->getKeymapper(); + + // Do not try to recreate same keymap over again + if (mapper->getKeymap(kKeymapName) != 0) + return; + + Common::Keymap *const engineKeyMap = new Common::Keymap(kKeymapName); + + const Common::KeyActionEntry keyActionEntries[] = { + {Common::KeyState(Common::KEYCODE_F1, Common::ASCII_F1), "AT1", _("Attack 1")}, + {Common::KeyState(Common::KEYCODE_F2, Common::ASCII_F2), "AT2", _("Attack 2")}, + {Common::KeyState(Common::KEYCODE_F3, Common::ASCII_F3), "AT3", _("Attack 3")}, + {Common::KeyState(Common::KEYCODE_UP), "MVF", _("Move Forward")}, + {Common::KeyState(Common::KEYCODE_DOWN), "MVB", _("Move Back")}, + {Common::KeyState(Common::KEYCODE_LEFT), "SLL", _("Slide Left")}, + {Common::KeyState(Common::KEYCODE_RIGHT), "SLR", _("Slide Right")}, + {Common::KeyState(Common::KEYCODE_HOME), "TL", _("Turn Left")}, + {Common::KeyState(Common::KEYCODE_PAGEUP), "TR", _("Turn Right")}, + {Common::KeyState(Common::KEYCODE_r), "RST", _("Rest")}, + {Common::KeyState(Common::KEYCODE_o), "OPT", _("Options")}, + {Common::KeyState(Common::KEYCODE_SLASH), "SPL", _("Choose Spell")}, + {Common::KeyState(), 0, 0} + }; + + for (const Common::KeyActionEntry *entry = keyActionEntries; entry->id; ++entry) { + Common::Action *const act = new Common::Action(engineKeyMap, entry->id, entry->description); + act->addKeyEvent(entry->ks); + } + + mapper->addGameKeymap(engineKeyMap); +#endif +} + +void LoLEngine::pauseEngineIntern(bool pause) { + KyraEngine_v1::pauseEngineIntern(pause); + pauseDemoPlayer(pause); +} + +Common::Error LoLEngine::go() { + int action = -1; + + if (_gameToLoad == -1) { + action = processPrologue(); + if (action == -1) + return Common::kNoError; + } + + if (_flags.isTalkie && !_flags.isDemo) { + if (!_res->loadFileList("FILEDATA.FDT")) + error("Couldn't load file list: 'FILEDATA.FDT'"); + } else if (_pakFileList) { + _res->loadFileList(_pakFileList, _pakFileListSize); + } + + // Usually fonts etc. would be setup by the prologue code, if we skip + // the prologue code we need to setup them manually here. + if (_gameToLoad != -1 && action != 3) { + preInit(); + _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_9_FNT); + } + + // We have three sound.dat files, one for the intro, one for the + // end sequence and one for ingame, each contained in a different + // PAK file. Therefore a new call to loadSoundFile() is required + // whenever the PAK file configuration changes. + if (_flags.platform == Common::kPlatformPC98) + _sound->loadSoundFile("sound.dat"); + + _sound->selectAudioResourceSet(kMusicIngame); + if (_flags.platform != Common::kPlatformDOS) + _sound->loadSoundFile(0); + + _tim = new TIMInterpreter_LoL(this, _screen, _system); + assert(_tim); + + if (shouldQuit()) + return Common::kNoError; + + startup(); + + if (action == 0) { + startupNew(); + } else if (_gameToLoad != -1) { + // FIXME: Instead of throwing away the error returned by + // loadGameState, we should use it / augment it. + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d on startup", _gameToLoad); + _gameToLoad = -1; + } + + _screen->_fadeFlag = 3; + _sceneUpdateRequired = true; + enableSysTimer(1); + runLoop(); + + return Common::kNoError; +} + +#pragma mark - Initialization + +void LoLEngine::preInit() { + _res->loadPakFile("GENERAL.PAK"); + if (_flags.isTalkie) + _res->loadPakFile("STARTUP.PAK"); + + _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6P.FNT"); + + loadTalkFile(0); + + Common::String filename; + filename = Common::String::format("LANDS.%s", _languageExt[_lang]); + _res->exists(filename.c_str(), true); + delete[] _landsFile; + _landsFile = _res->fileData(filename.c_str(), 0); + loadItemIconShapes(); +} + +void LoLEngine::loadItemIconShapes() { + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) + delete[] _itemIconShapes[i]; + delete[] _itemIconShapes; + } + + _screen->loadBitmap("ITEMICN.SHP", 3, 3, 0); + const uint8 *shp = _screen->getCPagePtr(3); + _numItemIconShapes = READ_LE_UINT16(shp); + _itemIconShapes = new uint8*[_numItemIconShapes]; + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->setMouseCursor(0, 0, _itemIconShapes[0]); + + if (!_gameShapes) { + _screen->loadBitmap("GAMESHP.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numGameShapes = READ_LE_UINT16(shp); + _gameShapes = new uint8*[_numGameShapes]; + for (int i = 0; i < _numGameShapes; i++) + _gameShapes[i] = _screen->makeShapeCopy(shp, i); + } +} + +void LoLEngine::setMouseCursorToIcon(int icon) { + _flagsTable[31] |= 0x02; + int i = _itemProperties[_itemsInPlay[_itemInHand].itemPropertyIndex].shpIndex; + if (i == icon) + return; + _screen->setMouseCursor(0, 0, _itemIconShapes[icon]); +} + +void LoLEngine::setMouseCursorToItemInHand() { + _flagsTable[31] &= 0xFD; + int o = (_itemInHand == 0) ? 0 : 10; + _screen->setMouseCursor(o, o, getItemIconShapePtr(_itemInHand)); +} + +void LoLEngine::checkFloatingPointerRegions() { + if (!_floatingCursorsEnabled) + return; + + int t = -1; + + Common::Point p = getMousePos(); + + if (!(_updateFlags & 4) & !_floatingCursorControl) { + if (posWithinRect(p.x, p.y, 96, 0, 303, 136)) { + if (!posWithinRect(p.x, p.y, 128, 16, 271, 119)) { + if (posWithinRect(p.x, p.y, 112, 0, 287, 15)) + t = 0; + if (posWithinRect(p.x, p.y, 272, 88, 303, 319)) + t = 1; + if (posWithinRect(p.x, p.y, 112, 110, 287, 135)) + t = 2; + if (posWithinRect(p.x, p.y, 96, 88, 127, 119)) + t = 3; + if (posWithinRect(p.x, p.y, 96, 16, 127, 87)) + t = 4; + if (posWithinRect(p.x, p.y, 272, 16, 303, 87)) + t = 5; + + if (t < 4) { + int d = (_currentDirection + t) & 3; + if (!checkBlockPassability(calcNewBlockPosition(_currentBlock, d), d)) + t = 6; + } + } + } + } + + if (t == _currentFloatingCursor) + return; + + if (t == -1) { + setMouseCursorToItemInHand(); + } else { + static const uint8 floatingPtrX[] = { 7, 13, 7, 0, 0, 15, 7 }; + static const uint8 floatingPtrY[] = { 0, 7, 12, 7, 6, 6, 7 }; + _screen->setMouseCursor(floatingPtrX[t], floatingPtrY[t], _gameShapes[10 + t]); + } + + _currentFloatingCursor = t; +} + +uint8 *LoLEngine::getItemIconShapePtr(int index) { + int ix = _itemProperties[_itemsInPlay[index].itemPropertyIndex].shpIndex; + if (_itemProperties[_itemsInPlay[index].itemPropertyIndex].flags & 0x200) + ix += (_itemsInPlay[index].shpCurFrame_flg & 0x1FFF) - 1; + + return _itemIconShapes[ix]; +} + +int LoLEngine::mainMenu() { + bool hasSave = saveFileLoadable(0); + + MainMenu::StaticData data[] = { + // 256 color ASCII mode + { + { 0, 0, 0, 0, 0 }, + { 0x01, 0x04, 0x0C, 0x04, 0x00, 0x3D, 0x9F }, + { 0x2C, 0x19, 0x48, 0x2C }, + Screen::FID_9_FNT, 1 + }, + // 16 color SJIS mode + { + { 0, 0, 0, 0, 0 }, + { 0x01, 0x04, 0x0C, 0x04, 0x00, 0xC1, 0xE1 }, + { 0xCC, 0xDD, 0xDD, 0xDD }, + Screen::FID_SJIS_FNT, 1 + } + }; + + int dataIndex = _flags.use16ColorMode ? 1 : 0; + + if (!_flags.isTalkie) + --data[dataIndex].menuTable[3]; + + if (hasSave) + ++data[dataIndex].menuTable[3]; + + static const uint16 mainMenuStrings[4][5] = { + { 0x4248, 0x4249, 0x42DD, 0x424A, 0x0000 }, + { 0x4248, 0x4249, 0x42DD, 0x4001, 0x424A }, + { 0x4248, 0x4249, 0x424A, 0x0000, 0x0000 }, + { 0x4248, 0x4249, 0x4001, 0x424A, 0x0000 } + }; + + int tableOffs = _flags.isTalkie ? 0 : 2; + + for (int i = 0; i < 5; ++i) { + if (hasSave) + data[dataIndex].strings[i] = getLangString(mainMenuStrings[1 + tableOffs][i]); + else + data[dataIndex].strings[i] = getLangString(mainMenuStrings[tableOffs][i]); + } + + MainMenu *menu = new MainMenu(this); + assert(menu); + menu->init(data[dataIndex], MainMenu::Animation()); + + int selection = menu->handle(_flags.isTalkie ? (hasSave ? 19 : 6) : (hasSave ? 6 : 20)); + delete menu; + _screen->setScreenDim(0); + + if (!_flags.isTalkie && selection >= 2) + selection++; + + if (!hasSave && selection == 3) + selection = 4; + + return selection; +} + +void LoLEngine::startup() { + _screen->clearPage(0); + + Palette &pal = _screen->getPalette(0); + _screen->loadBitmap("PLAYFLD.CPS", 3, 3, &pal); + + if (_flags.use16ColorMode) { + memset(_screen->_paletteOverlay1, 0, 256); + memset(_screen->_paletteOverlay2, 0, 256); + + static const uint8 colTable1[] = { 0x00, 0xEE, 0xCC, 0xFF, 0x44, 0x66, 0x44, 0x88, 0xEE, 0xAA, 0x11, 0xCC, 0xDD, 0xEE, 0x44, 0xCC }; + static const uint8 colTable2[] = { 0x00, 0xCC, 0xFF, 0xBB, 0xEE, 0xBB, 0x55, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xFF, 0xCC, 0xDD, 0xBB }; + static const uint8 colTable3[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + + for (int i = 0; i < 16; i++) { + _screen->_paletteOverlay1[colTable3[i]] = colTable1[i]; + _screen->_paletteOverlay2[colTable3[i]] = colTable2[i]; + } + + } else { + _screen->copyPalette(1, 0); + pal.fill(0, 1, 0x3F); + pal.fill(2, 126, 0x3F); + pal.fill(192, 4, 0x3F); + _screen->generateOverlay(pal, _screen->_paletteOverlay1, 1, 96, 254); + _screen->generateOverlay(pal, _screen->_paletteOverlay2, 144, 65, 254); + _screen->copyPalette(0, 1); + } + + _screen->getPalette(1).clear(); + _screen->getPalette(2).clear(); + + loadItemIconShapes(); + _screen->setMouseCursor(0, 0, _itemIconShapes[0x85]); + + _screen->loadBitmap("ITEMSHP.SHP", 3, 3, 0); + const uint8 *shp = _screen->getCPagePtr(3); + _numItemShapes = READ_LE_UINT16(shp); + _itemShapes = new uint8*[_numItemShapes]; + for (int i = 0; i < _numItemShapes; i++) + _itemShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("THROWN.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numThrownShapes = READ_LE_UINT16(shp); + _thrownShapes = new uint8*[_numThrownShapes]; + for (int i = 0; i < _numThrownShapes; i++) + _thrownShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("ICE.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numEffectShapes = READ_LE_UINT16(shp); + _effectShapes = new uint8*[_numEffectShapes]; + for (int i = 0; i < _numEffectShapes; i++) + _effectShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("FIREBALL.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numFireballShapes = READ_LE_UINT16(shp); + _fireballShapes = new uint8*[_numFireballShapes]; + for (int i = 0; i < _numFireballShapes; i++) + _fireballShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("HEAL.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numHealShapes = READ_LE_UINT16(shp); + _healShapes = new uint8*[_numHealShapes]; + for (int i = 0; i < _numHealShapes; i++) + _healShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("HEALI.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numHealiShapes = READ_LE_UINT16(shp); + _healiShapes = new uint8*[_numHealiShapes]; + for (int i = 0; i < _numHealiShapes; i++) + _healiShapes[i] = _screen->makeShapeCopy(shp, i); + + memset(_itemsInPlay, 0, 400 * sizeof(LoLItem)); + for (int i = 0; i < 400; i++) + _itemsInPlay[i].shpCurFrame_flg |= 0x8000; + + runInitScript("ONETIME.INF", 0); + _emc->load("ITEM.INF", &_itemScript, &_opcodes); + + _transparencyTable1 = new uint8[256]; + _transparencyTable2 = new uint8[5120]; + + _loadSuppFilesFlag = 1; + + _sound->loadSfxFile("LORESFX"); + + setMouseCursorToItemInHand(); +} + +void LoLEngine::startupNew() { + _selectedSpell = 0; + _compassStep = 0; + _compassDirection = _compassDirectionIndex = -1; + + _lastMouseRegion = -1; + _currentLevel = 1; + + giveCredits(41, 0); + _inventory[0] = makeItem(216, 0, 0); + _inventory[1] = makeItem(217, 0, 0); + _inventory[2] = makeItem(218, 0, 0); + + _availableSpells[0] = 0; + setupScreenDims(); + + Common::fill(_globalScriptVars2, ARRAYEND(_globalScriptVars2), 0x100); + + static const int selectIds[] = { -9, -1, -8, -5 }; + assert(_charSelection >= 0); + assert(_charSelection < ARRAYSIZE(selectIds)); + addCharacter(selectIds[_charSelection]); + + gui_enableDefaultPlayfieldButtons(); + + loadLevel(_currentLevel); + + _screen->showMouse(); +} + +void LoLEngine::runLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + enableSysTimer(2); + + _flagsTable[73] |= 0x08; + + while (!shouldQuit()) { + if (_gameToLoad != -1) { + // FIXME: Instead of throwing away the error returned by + // loadGameState, we should use it / augment it. + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d", _gameToLoad); + _gameToLoad = -1; + } + + if (_nextScriptFunc) { + runLevelScript(_nextScriptFunc, 2); + _nextScriptFunc = 0; + } + + _timer->update(); + + checkFloatingPointerRegions(); + gui_updateInput(); + + update(); + + if (_sceneUpdateRequired) + gui_drawScene(0); + else + updateEnvironmentalSfx(0); + + if (_partyDamageFlags != -1) { + checkForPartyDeath(); + _partyDamageFlags = -1; + } + + delay(_tickLength); + } +} + +void LoLEngine::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the LoL engine. + ConfMan.registerDefault("floating_cursors", false); + ConfMan.registerDefault("smooth_scrolling", true); + ConfMan.registerDefault("monster_difficulty", 1); +} + +void LoLEngine::writeSettings() { + ConfMan.setInt("monster_difficulty", _monsterDifficulty); + ConfMan.setBool("floating_cursors", _floatingCursorsEnabled); + ConfMan.setBool("smooth_scrolling", _smoothScrollingEnabled); + + switch (_lang) { + case 1: + _flags.lang = Common::FR_FRA; + break; + + case 2: + _flags.lang = Common::DE_DEU; + break; + + case 0: + default: + if (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformFMTowns) + _flags.lang = Common::JA_JPN; + else + _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)); + + KyraEngine_v1::writeSettings(); +} + +void LoLEngine::readSettings() { + _monsterDifficulty = ConfMan.getInt("monster_difficulty"); + if (_monsterDifficulty < 0 || _monsterDifficulty > 2) { + _monsterDifficulty = CLIP(_monsterDifficulty, 0, 2); + warning("LoLEngine: Config file contains invalid difficulty setting."); + } + _smoothScrollingEnabled = ConfMan.getBool("smooth_scrolling"); + _floatingCursorsEnabled = ConfMan.getBool("floating_cursors"); + + KyraEngine_v1::readSettings(); +} + +void LoLEngine::update() { + updateSequenceBackgroundAnimations(); + + if (_updateCharNum != -1 && _system->getMillis() > _updatePortraitNext) + updatePortraitSpeechAnim(); + + if (_flagsTable[31] & 0x08 || !(_updateFlags & 4)) + updateLampStatus(); + + if (_flagsTable[31] & 0x40 && !(_updateFlags & 4) && (_compassDirection == -1 || (_currentDirection << 6) != _compassDirection || _compassStep)) + updateCompass(); + + snd_updateCharacterSpeech(); + fadeText(); + + updateInput(); + _screen->updateScreen(); +} + +#pragma mark - Localization + +char *LoLEngine::getLangString(uint16 id) { + if (id == 0xFFFF) + return 0; + + uint16 realId = id & 0x3FFF; + uint8 *buffer = 0; + + if (id & 0x4000) + buffer = _landsFile; + else + buffer = _levelLangFile; + + if (!buffer) + return 0; + + char *string = (char *)getTableEntry(buffer, realId); + + char *srcBuffer = _stringBuffer[_lastUsedStringBuffer]; + if (_flags.lang == Common::JA_JPN) { + decodeSjis(string, srcBuffer); + } else if (_flags.lang == Common::RU_RUS && !_flags.isTalkie) { + decodeCyrillic(string, srcBuffer); + Util::decodeString2(srcBuffer, srcBuffer); + } else { + Util::decodeString1(string, srcBuffer); + Util::decodeString2(srcBuffer, srcBuffer); + } + + ++_lastUsedStringBuffer; + _lastUsedStringBuffer %= ARRAYSIZE(_stringBuffer); + + return srcBuffer; +} + +uint8 *LoLEngine::getTableEntry(uint8 *buffer, uint16 id) { + if (!buffer) + return 0; + + return buffer + READ_LE_UINT16(buffer + (id << 1)); +} + +void LoLEngine::decodeSjis(const char *src, char *dst) { + char s[2]; + char d[3]; + s[1] = 0; + + uint8 cmd = 0; + while ((cmd = *src++) != 0) { + if (cmd == 27) { + cmd = *src++ & 0x7F; + memcpy(dst, src, cmd * 2); + dst += cmd * 2; + src += cmd * 2; + } else { + s[0] = cmd; + int size = Util::decodeString1(s, d); + memcpy(dst, d, size); + dst += size; + } + } + + *dst = 0; +} + +int LoLEngine::decodeCyrillic(const char *src, char *dst) { + static const uint8 decodeTable1[] = { + 0x20, 0xAE, 0xA5, 0xA0, 0xE2, 0xAD, 0xA8, 0xE0, 0xE1, 0xAB, 0xA2, + 0xA4, 0xAC, 0xAA, 0xE3, 0x2E + }; + + static const uint8 decodeTable2[] = { + 0xAD, 0xAF, 0xA2, 0xE1, 0xAC, 0xAA, 0x20, 0xA4, 0xAB, 0x20, + 0xE0, 0xE2, 0xA4, 0xA2, 0xA6, 0xAA, 0x20, 0xAD, 0xE2, 0xE0, + 0xAB, 0xAC, 0xE1, 0xA1, 0x20, 0xAC, 0xE1, 0xAA, 0xAB, 0xE0, + 0xE2, 0xAD, 0xAE, 0xEC, 0xA8, 0xA5, 0xA0, 0x20, 0xE0, 0xEB, + 0xAE, 0xA0, 0xA8, 0xA5, 0xEB, 0xEF, 0x20, 0xE3, 0xE2, 0x20, + 0xAD, 0xE7, 0xAB, 0xAC, 0xA5, 0xE0, 0xAE, 0xA0, 0xA5, 0xA8, + 0xE3, 0xEB, 0xEF, 0xAA, 0xE2, 0xEF, 0xA5, 0xEC, 0xAB, 0xAE, + 0xAA, 0xAF, 0xA8, 0xA0, 0xA5, 0xEF, 0xAE, 0xEE, 0xEC, 0xE3, + 0xA0, 0xAE, 0xA5, 0xA8, 0xEB, 0x20, 0xE0, 0xE3, 0xA0, 0xA5, + 0xAE, 0xA8, 0xE3, 0xE1, 0xAD, 0xAB, 0x20, 0xAE, 0xA5, 0xA0, + 0xA8, 0xAD, 0x2E, 0xE3, 0xAE, 0xA0, 0xA8, 0x20, 0xE0, 0xE3, + 0xAB, 0xE1, 0x20, 0xA4, 0xAD, 0xE2, 0xA1, 0xA6, 0xAC, 0xE1, + 0x0D, 0x20, 0x2E, 0x09, 0xA0, 0xA1, 0x9D, 0xA5 + }; + + int size = 0; + uint cChar = 0; + while ((cChar = *src++) != 0) { + if (cChar & 0x80) { + cChar &= 0x7F; + int index = (cChar & 0x78) >> 3; + *dst++ = decodeTable1[index]; + ++size; + assert(cChar < sizeof(decodeTable2)); + cChar = decodeTable2[cChar]; + } else if (cChar >= 0x70) { + cChar = *src++; + } else if (cChar >= 0x30) { + if (cChar < 0x60) + cChar -= 0x30; + cChar |= 0x80; + } + + *dst++ = cChar; + ++size; + } + + *dst++ = 0; + return size; +} + +bool LoLEngine::addCharacter(int id) { + const uint16 *cdf[] = { + _charDefsMan, _charDefsMan, _charDefsMan, _charDefsWoman, + _charDefsMan, _charDefsMan, _charDefsWoman, _charDefsKieran, _charDefsAkshel + }; + + int numChars = countActiveCharacters(); + if (numChars == 4) + return false; + + int i = 0; + for (; i < _charDefaultsSize; i++) { + if (_charDefaults[i].id == id) { + memcpy(&_characters[numChars], &_charDefaults[i], sizeof(LoLCharacter)); + _characters[numChars].defaultModifiers = cdf[i]; + break; + } + } + if (i == _charDefaultsSize) + return false; + + loadCharFaceShapes(numChars, id); + + _characters[numChars].nextAnimUpdateCountdown = rollDice(1, 12) + 6; + + for (i = 0; i < 11; i++) { + if (_characters[numChars].items[i]) { + _characters[numChars].items[i] = makeItem(_characters[numChars].items[i], 0, 0); + runItemScript(numChars, _characters[numChars].items[i], 0x80, 0, 0); + } + } + + calcCharPortraitXpos(); + if (numChars > 0) + setTemporaryFaceFrame(numChars, 2, 6, 0); + + return true; +} + +void LoLEngine::setTemporaryFaceFrame(int charNum, int frame, int updateDelay, int redraw) { + _characters[charNum].tempFaceFrame = frame; + if (frame || updateDelay) + setCharacterUpdateEvent(charNum, 6, updateDelay, 1); + if (redraw) + gui_drawCharPortraitWithStats(charNum); +} + +void LoLEngine::setTemporaryFaceFrameForAllCharacters(int frame, int updateDelay, int redraw) { + for (int i = 0; i < 4; i++) + setTemporaryFaceFrame(i, frame, updateDelay, 0); + if (redraw) + gui_drawAllCharPortraitsWithStats(); +} + +void LoLEngine::setCharacterUpdateEvent(int charNum, int updateType, int updateDelay, int overwrite) { + LoLCharacter *l = &_characters[charNum]; + for (int i = 0; i < 5; i++) { + if (l->characterUpdateEvents[i] && (!overwrite || l->characterUpdateEvents[i] != updateType)) + continue; + + l->characterUpdateEvents[i] = updateType; + l->characterUpdateDelay[i] = updateDelay; + _timer->setNextRun(3, _system->getMillis()); + _timer->resetNextRun(); + _timer->enable(3); + break; + } +} + +int LoLEngine::countActiveCharacters() { + int i = 0; + while (_characters[i].flags & 1) + i++; + return i; +} + +void LoLEngine::loadCharFaceShapes(int charNum, int id) { + if (id < 0) + id = -id; + + Common::String file = Common::String::format("FACE%02d.SHP", id); + _screen->loadBitmap(file.c_str(), 3, 3, 0); + + const uint8 *p = _screen->getCPagePtr(3); + for (int i = 0; i < 40; i++) { + delete[] _characterFaceShapes[i][charNum]; + _characterFaceShapes[i][charNum] = _screen->makeShapeCopy(p, i); + } +} + +void LoLEngine::updatePortraitSpeechAnim() { + int x = 0; + int y = 0; + bool redraw = false; + + if (_portraitSpeechAnimMode == 0) { + x = _activeCharsXpos[_updateCharNum]; + y = 144; + redraw = true; + } else if (_portraitSpeechAnimMode == 1) { + if (textEnabled()) { + x = 90; + y = 130; + } else { + x = _activeCharsXpos[_updateCharNum]; + y = 144; + } + } else if (_portraitSpeechAnimMode == 2) { + if (textEnabled()) { + x = 16; + y = 134; + } else { + x = _activeCharsXpos[_updateCharNum] + 10; + y = 145; + } + } + + int f = rollDice(1, 6) - 1; + if (f == _characters[_updateCharNum].curFaceFrame) + f++; + if (f > 5) + f -= 5; + f += 7; + + if (speechEnabled()) { + if (snd_updateCharacterSpeech() == 2) + // WORKAROUND for portrait speech animations which would "freeze" in some situations + if (_resetPortraitAfterSpeechAnim == 2) + _resetPortraitAfterSpeechAnim = 1; + else + _updatePortraitSpeechAnimDuration = 2; + else + _updatePortraitSpeechAnimDuration = 1; + } else if (_resetPortraitAfterSpeechAnim == 2) { + _resetPortraitAfterSpeechAnim = 1; + } + + _updatePortraitSpeechAnimDuration--; + + if (_updatePortraitSpeechAnimDuration) { + setCharFaceFrame(_updateCharNum, f); + if (redraw) + gui_drawCharPortraitWithStats(_updateCharNum); + else + gui_drawCharFaceShape(_updateCharNum, x, y, 0); + _updatePortraitNext = _system->getMillis() + 10 * _tickLength; + } else if (_resetPortraitAfterSpeechAnim != 0) { + faceFrameRefresh(_updateCharNum); + if (redraw) { + gui_drawCharPortraitWithStats(_updateCharNum); + initTextFading(0, 0); + } else { + gui_drawCharFaceShape(_updateCharNum, x, y, 0); + } + _updateCharNum = -1; + } +} + +void LoLEngine::stopPortraitSpeechAnim() { + if (_updateCharNum == -1) + return; + + _updatePortraitSpeechAnimDuration = 1; + // WORKAROUND for portrait speech animations which would "freeze" in some situations + _resetPortraitAfterSpeechAnim = 2; + updatePortraitSpeechAnim(); + _updatePortraitSpeechAnimDuration = 1; + _updateCharNum = -1; + + if (!_portraitSpeechAnimMode) + initTextFading(0, 0); +} + +void LoLEngine::initTextFading(int textType, int clearField) { + if (_textColorFlag == textType || !textType) { + _fadeText = true; + _palUpdateTimer = _system->getMillis(); + } + + if (!clearField) + return; + + stopPortraitSpeechAnim(); + if (_needSceneRestore) + _screen->setScreenDim(_txt->clearDim(3)); + + _fadeText = false; + _timer->disable(11); +} + +void LoLEngine::setCharFaceFrame(int charNum, int frameNum) { + _characters[charNum].curFaceFrame = frameNum; +} + +void LoLEngine::faceFrameRefresh(int charNum) { + if (_characters[charNum].curFaceFrame == 1) + setTemporaryFaceFrame(charNum, 0, 0, 0); + else if (_characters[charNum].curFaceFrame == 6) + if (_characters[charNum].tempFaceFrame != 5) + setTemporaryFaceFrame(charNum, 0, 0, 0); + else + _characters[charNum].curFaceFrame = 5; + else + _characters[charNum].curFaceFrame = 0; +} + +void LoLEngine::recalcCharacterStats(int charNum) { + for (int i = 0; i < 5; i++) + _charStatsTemp[i] = calculateCharacterStats(charNum, i); +} + +int LoLEngine::calculateCharacterStats(int charNum, int index) { + if (index == 0) { + // Might + int c = 0; + for (int i = 0; i < 8; i++) + c += _characters[charNum].itemsMight[i]; + if (c) + c += _characters[charNum].might; + else + c = _characters[charNum].defaultModifiers[8]; + + c = (c * _characters[charNum].defaultModifiers[1]) >> 8; + c = (c * _characters[charNum].totalMightModifier) >> 8; + + return c; + + } else if (index == 1) { + // Protection + return calculateProtection(charNum); + + } else if (index > 4) { + return -1; + + } else { + // Fighter + // Rogue + // Mage + index -= 2; + return _characters[charNum].skillLevels[index] + _characters[charNum].skillModifiers[index]; + } + + //return 1; +} + +int LoLEngine::calculateProtection(int index) { + int c = 0; + if (index & 0x8000) { + // Monster + index &= 0x7FFF; + c = (_monsters[index].properties->itemProtection * _monsters[index].properties->fightingStats[2]) >> 8; + } else { + // Character + c = _characters[index].itemProtection + _characters[index].protection; + c = (c * _characters[index].defaultModifiers[2]) >> 8; + c = (c * _characters[index].totalProtectionModifier) >> 8; + } + + return c; +} + +void LoLEngine::setCharacterMagicOrHitPoints(int charNum, int type, int points, int mode) { + static const uint16 barData[4][5] = { + // xPos, bar color, text color, flag, string id + { 0x27, 0x9A, 0x98, 0x01, 0x4254 }, + { 0x21, 0xA2, 0xA0, 0x00, 0x4253 }, + // 16 color mode + { 0x27, 0x66, 0x55, 0x01, 0x4254 }, + { 0x21, 0xAA, 0x99, 0x00, 0x4253 } + }; + + if (charNum > 2) + return; + + LoLCharacter *c = &_characters[charNum]; + if (!(c->flags & 1)) + return; + + int pointsMax = type ? c->magicPointsMax : c->hitPointsMax; + int pointsCur = type ? c->magicPointsCur : c->hitPointsCur; + + int newVal = (mode == 2) ? (pointsMax + points) : (mode ? (pointsCur + points) : points); + newVal = CLIP(newVal, 0, pointsMax); + + if (type) { + c->magicPointsCur = newVal; + } else { + c->hitPointsCur = newVal; + if (c->hitPointsCur < 1) + c->flags |= 8; + } + + if (_updateFlags & 2) + return; + + Screen::FontId cf = _screen->setFont(Screen::FID_6_FNT); + int cp = _screen->setCurPage(0); + + int s = 8192 / pointsMax; + pointsMax = (s * pointsMax) >> 8; + pointsCur = (s * pointsCur) >> 8; + newVal = (s * newVal) >> 8; + int newValScl = CLIP(newVal, 0, pointsMax); + + int step = (newVal > pointsCur) ? 2 : -2; + newVal = CLIP(newVal + step, 0, pointsMax); + + if (_flags.use16ColorMode) + type += 2; + + if (newVal != pointsCur) { + step = (newVal >= pointsCur) ? 2 : -2; + + for (int i = pointsCur; i != newVal || newVal != newValScl;) { + if (ABS(i - newVal) < ABS(step)) + step >>= 1; + + i += step; + + uint32 delayTimer = _system->getMillis() + _tickLength; + + gui_drawLiveMagicBar(barData[type][0] + _activeCharsXpos[charNum], 175, i, 0, pointsMax, 5, 32, barData[type][1], _flags.use16ColorMode ? 0x44 : 1, barData[type][3]); + _screen->printText(getLangString(barData[type][4]), barData[type][0] + _activeCharsXpos[charNum], 144, barData[type][2], 0); + _screen->updateScreen(); + + if (i == newVal) { + newVal = newValScl; + step = -step; + } + + delayUntil(delayTimer); + } + } + + _screen->setFont(cf); + _screen->setCurPage(cp); +} + +void LoLEngine::increaseExperience(int charNum, int skill, uint32 points) { + if (charNum & 0x8000) + return; + + if (_characters[charNum].flags & 8) + return; + + _characters[charNum].experiencePts[skill] += points; + + bool loop = true; + while (loop) { + if (_characters[charNum].experiencePts[skill] < _expRequirements[_characters[charNum].skillLevels[skill]]) + break; + + _characters[charNum].skillLevels[skill]++; + _characters[charNum].flags |= (0x200 << skill); + int inc = 0; + + switch (skill) { + case 0: + _txt->printMessage(0x8003, getLangString(0x4023), _characters[charNum].name); + inc = rollDice(4, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + case 1: + _txt->printMessage(0x8003, getLangString(0x4025), _characters[charNum].name); + inc = rollDice(2, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + case 2: + _txt->printMessage(0x8003, getLangString(0x4024), _characters[charNum].name); + inc = (_characters[charNum].defaultModifiers[6] * (rollDice(1, 8) + 17)) >> 8; + _characters[charNum].magicPointsCur += inc; + _characters[charNum].magicPointsMax += inc; + inc = rollDice(1, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + default: + break; + } + + snd_playSoundEffect(118, -1); + gui_drawCharPortraitWithStats(charNum); + } +} + +void LoLEngine::increaseCharacterHitpoints(int charNum, int points, bool ignoreDeath) { + if (_characters[charNum].hitPointsCur <= 0 && !ignoreDeath) + return; + + if (points <= 1) + points = 1; + + _characters[charNum].hitPointsCur = CLIP<int16>(_characters[charNum].hitPointsCur + points, 1, _characters[charNum].hitPointsMax); + _characters[charNum].flags &= 0xFFF7; +} + +void LoLEngine::setupScreenDims() { + if (textEnabled()) { + _screen->modifyScreenDim(4, 11, 124, 28, 45); + _screen->modifyScreenDim(5, 85, 123, 233, 54); + } else { + _screen->modifyScreenDim(4, 11, 124, 28, 9); + _screen->modifyScreenDim(5, 85, 123, 233, 18); + } +} + +void LoLEngine::initSceneWindowDialogue(int controlMode) { + resetPortraitsAndDisableSysTimer(); + gui_prepareForSequence(112, 0, 176, 120, controlMode); + + _updateFlags |= 3; + + _txt->setupField(true); + _txt->expandField(); + setupScreenDims(); + gui_disableControls(controlMode); +} + +void LoLEngine::toggleSelectedCharacterFrame(bool mode) { + if (countActiveCharacters() == 1) + return; + + int col = mode ? 212 : 1; + + int cp = _screen->setCurPage(0); + int x = _activeCharsXpos[_selectedCharacter]; + + _screen->drawBox(x, 143, x + 65, 176, col); + _screen->setCurPage(cp); +} + +void LoLEngine::gui_prepareForSequence(int x, int y, int w, int h, int buttonFlags) { + setSequenceButtons(x, y, w, h, buttonFlags); + + _seqWindowX1 = x; + _seqWindowY1 = y; + _seqWindowX2 = x + w; + _seqWindowY2 = y + h; + + int mouseOffs = _itemInHand ? 10 : 0; + _screen->setMouseCursor(mouseOffs, mouseOffs, getItemIconShapePtr(_itemInHand)); + + _lastMouseRegion = -1; + + if (w == 320) { + setLampMode(false); + _lampStatusSuspended = true; + } +} + +void LoLEngine::gui_specialSceneSuspendControls(int controlMode) { + if (controlMode) { + _updateFlags |= 4; + setLampMode(false); + } + _updateFlags |= 1; + _specialSceneFlag = 1; + _currentControlMode = controlMode; + calcCharPortraitXpos(); + checkFloatingPointerRegions(); +} + +void LoLEngine::gui_specialSceneRestoreControls(int restoreLamp) { + if (restoreLamp) { + _updateFlags &= 0xFFFA; + resetLampStatus(); + } + _updateFlags &= 0xFFFE; + _specialSceneFlag = 0; + checkFloatingPointerRegions(); +} + +void LoLEngine::restoreAfterSceneWindowDialogue(int redraw) { + gui_enableControls(); + _txt->setupField(false); + _updateFlags &= 0xFFDF; + + setDefaultButtonState(); + + for (int i = 0; i < 6; i++) + _tim->freeAnimStruct(i); + + _updateFlags = 0; + + if (redraw) { + if (_screen->_fadeFlag != 2) + _screen->fadeClearSceneWindow(10); + gui_drawPlayField(); + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + _screen->_fadeFlag = 0; + } + + _needSceneRestore = 0; + enableSysTimer(2); +} + +void LoLEngine::initDialogueSequence(int controlMode, int pageNum) { + if (controlMode) { + _timer->disable(11); + _fadeText = false; + int cp = _screen->setCurPage(pageNum); + + if (_flags.use16ColorMode) { + _screen->fillRect(0, 128, 319, 199, 0x44); + gui_drawBox(0, 129, 320, 71, 0xEE, 0xCC, -1); + gui_drawBox(1, 130, 318, 69, 0xEE, 0xCC, 0x11); + } else { + _screen->fillRect(0, 128, 319, 199, 1); + gui_drawBox(0, 129, 320, 71, 136, 251, -1); + gui_drawBox(1, 130, 318, 69, 136, 251, 252); + } + + _screen->modifyScreenDim(5, 8, 131, 306, 66); + _screen->modifyScreenDim(4, 1, 133, 38, 60); + _txt->clearDim(4); + + _updateFlags |= 2; + _currentControlMode = controlMode; + calcCharPortraitXpos(); + + if (!textEnabled() && (!(controlMode & 2))) { + int nc = countActiveCharacters(); + for (int i = 0; i < nc; i++) { + _portraitSpeechAnimMode = 2; + _updateCharNum = i; + _screen->drawShape(0, _gameShapes[88], _activeCharsXpos[_updateCharNum] + 8, 142, 0, 0); + stopPortraitSpeechAnim(); + } + } + + _screen->setCurPage(cp); + + } else { + _txt->setupField(true); + _txt->expandField(); + setupScreenDims(); + _txt->clearDim(4); + } + + _currentControlMode = controlMode; + _dialogueField = true; +} + +void LoLEngine::restoreAfterDialogueSequence(int controlMode) { + if (!_dialogueField) + return; + + stopPortraitSpeechAnim(); + _currentControlMode = controlMode; + calcCharPortraitXpos(); + + if (_currentControlMode) { + _screen->modifyScreenDim(4, 11, 124, 28, 45); + _screen->modifyScreenDim(5, 85, 123, 233, 54); + _updateFlags &= 0xFFFD; + } else { + const ScreenDim *d = _screen->getScreenDim(5); + _screen->fillRect(d->sx, d->sy, d->sx + d->w - (_flags.use16ColorMode ? 3 : 2), d->sy + d->h - 2, d->unkA); + _txt->clearDim(4); + _txt->setupField(false); + } + + _dialogueField = false; +} + +void LoLEngine::resetPortraitsAndDisableSysTimer() { + _needSceneRestore = 1; + if (!textEnabled() || (!(_currentControlMode & 2))) + timerUpdatePortraitAnimations(1); + + disableSysTimer(2); +} + +void LoLEngine::fadeText() { + if (!_fadeText) + return; + + if (_screen->fadeColor(192, 252, (_system->getMillis() - _palUpdateTimer) / _tickLength, 60)) + return; + + if (_needSceneRestore) + return; + + _screen->setScreenDim(_txt->clearDim(3)); + + _timer->disable(11); + + _fadeText = false; +} + +void LoLEngine::setPaletteBrightness(const Palette &srcPal, int brightness, int modifier) { + generateBrightnessPalette(srcPal, _screen->getPalette(1), brightness, modifier); + _screen->fadePalette(_screen->getPalette(1), 5, 0); + _screen->_fadeFlag = 0; +} + +void LoLEngine::generateBrightnessPalette(const Palette &src, Palette &dst, int brightness, int16 modifier) { + dst.copy(src); + if (_flags.use16ColorMode) { + if (!brightness) + modifier = 0; + else if (modifier < 0 || modifier > 7 || !(_flagsTable[31] & 0x08)) + modifier = 8; + + modifier >>= 1; + if (modifier) + modifier--; + if (modifier > 3) + modifier = 3; + _blockBrightness = modifier << 4; + _sceneUpdateRequired = true; + + } else { + _screen->loadSpecialColors(dst); + + brightness = (8 - brightness) << 5; + if (modifier >= 0 && modifier < 8 && (_flagsTable[31] & 0x08)) { + brightness = 256 - ((((modifier & 0xFFFE) << 5) * (256 - brightness)) >> 8); + if (brightness < 0) + brightness = 0; + } + + for (int i = 0; i < 384; i++) { + uint16 c = (dst[i] * brightness) >> 8; + dst[i] = c & 0xFF; + } + } +} + +void LoLEngine::generateFlashPalette(const Palette &src, Palette &dst, int colorFlags) { + dst.copy(src, 0, 2); + + for (int i = 2; i < 128; i++) { + for (int ii = 0; ii < 3; ii++) { + uint8 t = src[i * 3 + ii] & 0x3F; + if (colorFlags & (1 << ii)) + t += ((0x3F - t) >> 1); + else + t -= (t >> 1); + dst[i * 3 + ii] = t; + } + } + + dst.copy(src, 128); +} + +void LoLEngine::createTransparencyTables() { + if (_flags.isTalkie || _loadSuppFilesFlag) + return; + + uint8 *tpal = new uint8[768]; + + if (_flags.use16ColorMode) { + static const uint8 colTbl[] = { + 0x00, 0x00, 0x11, 0x00, 0x22, 0x00, 0x33, 0x00, 0x44, 0x00, 0x55, 0x00, 0x66, 0x00, 0x77, 0x00, + 0x88, 0x00, 0x99, 0x00, 0xAA, 0x00, 0xBB, 0x00, 0xCC, 0x00, 0xDD, 0x00, 0xEE, 0x00, 0xFF, 0x00 + }; + + memset(tpal, 0xFF, 768); + _res->loadFileToBuf("LOL.NOL", tpal, 48); + + for (int i = 15; i > -1; i--) { + int s = colTbl[i << 1] * 3; + tpal[s] = tpal[i * 3]; + tpal[s + 1] = tpal[i * 3 + 1]; + tpal[s + 2] = tpal[i * 3 + 2]; + tpal[i * 3 + 2] = tpal[i * 3 + 1] = tpal[i * 3] = 0xFF; + } + + _screen->createTransparencyTablesIntern(colTbl, 16, tpal, tpal, _transparencyTable1, _transparencyTable2, 80); + + } else { + _res->loadFileToBuf("fxpal.col", tpal, 768); + _screen->loadBitmap("fxpal.shp", 3, 3, 0); + const uint8 *shpPal = _screen->getPtrToShape(_screen->getCPagePtr(2), 0) + 11; + + _screen->createTransparencyTablesIntern(shpPal, 20, tpal, _screen->getPalette(1).getData(), _transparencyTable1, _transparencyTable2, 70); + } + + delete[] tpal; + _loadSuppFilesFlag = 1; +} + +void LoLEngine::updateSequenceBackgroundAnimations() { + if (_updateFlags & 8 || !_tim) + return; + if (!_tim->animator()) + return; + + for (int i = 0; i < 6; i++) + _tim->animator()->update(i); +} + +void LoLEngine::loadTalkFile(int index) { + if (index == _curTlkFile) + return; + + if (_curTlkFile > 0 && index > 0) + _res->unloadPakFile(Common::String::format("%02d.TLK", _curTlkFile)); + + if (index > 0) + _curTlkFile = index; + + _res->loadPakFile(Common::String::format("%02d.TLK", index)); +} + +int LoLEngine::characterSays(int track, int charId, bool redraw) { + if (charId == 1) { + charId = _selectedCharacter; + } if (charId <= 0) { + charId = 0; + } else { + int i = 0; + for (; i < 4; i++) { + if (charId != _characters[i].id || !(_characters[i].flags & 1)) + continue; + charId = i; + break; + } + + if (i == 4) + return 0; + } + + bool r = snd_playCharacterSpeech(track, charId, 0); + + if (r && redraw) { + stopPortraitSpeechAnim(); + _updateCharNum = charId; + _portraitSpeechAnimMode = 0; + _resetPortraitAfterSpeechAnim = 1; + _fadeText = false; + updatePortraitSpeechAnim(); + } + + return r ? (textEnabled() ? 1 : 0) : 1; +} + +int LoLEngine::playCharacterScriptChat(int charId, int mode, int restorePortrait, char *str, EMCState *script, const uint16 *paramList, int16 paramIndex) { + int ch = 0; + bool skipAnim = false; + + if ((charId == -1) || (!(charId & 0x70))) + charId = ch = (charId == 1) ? (_selectedCharacter ? _characters[_selectedCharacter].id : 0) : charId; + else + charId ^= 0x70; + + stopPortraitSpeechAnim(); + + if (charId < 0) { + charId = ch = _rnd.getRandomNumber(countActiveCharacters() - 1); + } else if (charId > 0) { + int i = 0; + + for (; i < 3; i++) { + if (_characters[i].id != charId || !(_characters[i].flags & 1)) + continue; + if (charId == ch) + ch = i; + charId = i; + break; + } + + if (i == 4) { + if (charId == 8) + skipAnim = true; + else + return 0; + } + } + + if (!skipAnim) { + _updateCharNum = charId; + _portraitSpeechAnimMode = mode; + _updatePortraitSpeechAnimDuration = strlen(str) >> 1; + _resetPortraitAfterSpeechAnim = restorePortrait; + } + + if (script) + snd_playCharacterSpeech(script->stack[script->sp + 2], ch, 0); + else if (paramList) + snd_playCharacterSpeech(paramList[2], ch, 0); + + if (textEnabled()) { + if (mode == 0) { + _txt->printDialogueText(3, str, script, paramList, paramIndex); + + } else if (mode == 1) { + _txt->clearDim(4); + _screen->modifyScreenDim(4, 16, 123, 23, 47); + _txt->printDialogueText(4, str, script, paramList, paramIndex); + _screen->modifyScreenDim(4, 11, 123, 28, 47); + + } else if (mode == 2) { + _txt->clearDim(4); + _screen->modifyScreenDim(4, 9, 133, 30, 60); + _txt->printDialogueText(4, str, script, paramList, 3); + _screen->modifyScreenDim(4, 1, 133, 37, 60); + } + } + + _fadeText = false; + if (!skipAnim) + updatePortraitSpeechAnim(); + + return 1; +} + +void LoLEngine::setupDialogueButtons(int numStr, const char *s1, const char *s2, const char *s3) { + screen()->setScreenDim(5); + + if (numStr == 1 && speechEnabled()) { + _dialogueNumButtons = 0; + _dialogueButtonString[0] = _dialogueButtonString[1] = _dialogueButtonString[2] = 0; + } else { + _dialogueNumButtons = numStr; + _dialogueButtonString[0] = s1; + _dialogueButtonString[1] = s2; + _dialogueButtonString[2] = s3; + _dialogueHighlightedButton = 0; + + const ScreenDim *d = screen()->getScreenDim(5); + + static uint16 posX[3]; + static uint8 posY[3]; + + memset(posY, d->sy + d->h - 9, 3); + + _dialogueButtonPosX = posX; + _dialogueButtonPosY = posY; + + if (numStr == 1) { + posX[0] = posX[1] = posX[2] = d->sx + d->w - (_dialogueButtonWidth + 3); + } else { + int xOffs = d->w / numStr; + posX[0] = d->sx + (xOffs >> 1) - 37; + posX[1] = posX[0] + xOffs; + posX[2] = posX[1] + xOffs; + } + + drawDialogueButtons(); + } + + if (!shouldQuit()) + removeInputTop(); +} + +void LoLEngine::giveItemToMonster(LoLMonster *monster, Item item) { + uint16 *c = &monster->assignedItems; + while (*c) + c = &_itemsInPlay[*c].nextAssignedObject; + *c = (uint16)item; + _itemsInPlay[item].nextAssignedObject = 0; +} + +const uint16 *LoLEngine::getCharacterOrMonsterStats(int id) { + return (id & 0x8000) ? (const uint16 *)_monsters[id & 0x7FFF].properties->fightingStats : _characters[id].defaultModifiers; +} + +uint16 *LoLEngine::getCharacterOrMonsterItemsMight(int id) { + return (id & 0x8000) ? _monsters[id & 0x7FFF].properties->itemsMight : _characters[id].itemsMight; +} + +uint16 *LoLEngine::getCharacterOrMonsterProtectionAgainstItems(int id) { + return (id & 0x8000) ? _monsters[id & 0x7FFF].properties->protectionAgainstItems : _characters[id].protectionAgainstItems; +} + +void LoLEngine::delay(uint32 millis, bool doUpdate, bool) { + while (millis && !shouldQuit()) { + if (doUpdate) + update(); + else + updateInput(); + + uint32 step = MIN<uint32>(millis, _tickLength); + _system->delayMillis(step); + millis -= step; + } +} + +const KyraRpgGUISettings *LoLEngine::guiSettings() { + return &_guiSettings; +} + +// spells + +int LoLEngine::castSpell(int charNum, int spellType, int spellLevel) { + _activeSpell.charNum = charNum; + _activeSpell.spell = spellType; + _activeSpell.p = &_spellProperties[spellType]; + + _activeSpell.level = ABS(spellLevel); + + if ((_spellProperties[spellType].flags & 0x100) && testWallFlag(calcNewBlockPosition(_currentBlock, _currentDirection), _currentDirection, 1)) { + _txt->printMessage(2, "%s", getLangString(0x4257)); + return 0; + } + + if (charNum < 0) { + _activeSpell.charNum = (charNum * -1) - 1; + if (_spellProcs[spellType]->isValid()) + return (*_spellProcs[spellType])(&_activeSpell); + } else { + if (_activeSpell.p->mpRequired[spellLevel] > _characters[charNum].magicPointsCur) + return 0; + + if (_activeSpell.p->hpRequired[spellLevel] >= _characters[charNum].hitPointsCur) + return 0; + + setCharacterMagicOrHitPoints(charNum, 1, -_activeSpell.p->mpRequired[spellLevel], 1); + setCharacterMagicOrHitPoints(charNum, 0, -_activeSpell.p->hpRequired[spellLevel], 1); + gui_drawCharPortraitWithStats(charNum); + + if (_spellProcs[spellType]->isValid()) + (*_spellProcs[spellType])(&_activeSpell); + } + + return 1; +} + +int LoLEngine::castSpark(ActiveSpell *a) { + processMagicSpark(a->charNum, a->level); + return 1; +} + +int LoLEngine::castHeal(ActiveSpell *a) { + if (a->level < 3) + processMagicHealSelectTarget(); + else + processMagicHeal(-1, a->level); + + return 1; +} + +int LoLEngine::castIce(ActiveSpell *a) { + processMagicIce(a->charNum, a->level); + return 1; +} + +int LoLEngine::castFireball(ActiveSpell *a) { + processMagicFireball(a->charNum, a->level); + return 1; +} + +int LoLEngine::castHandOfFate(ActiveSpell *a) { + processMagicHandOfFate(a->level); + return 1; +} + +int LoLEngine::castMistOfDoom(ActiveSpell *a) { + processMagicMistOfDoom(a->charNum, a->level); + return 1; +} + +int LoLEngine::castLightning(ActiveSpell *a) { + processMagicLightning(a->charNum, a->level); + return 1; +} + +int LoLEngine::castFog(ActiveSpell *a) { + processMagicFog(); + return 1; +} + +int LoLEngine::castSwarm(ActiveSpell *a) { + processMagicSwarm(a->charNum, 10); + return 1; +} + +int LoLEngine::castVaelansCube(ActiveSpell *a) { + return processMagicVaelansCube(); +} + +int LoLEngine::castGuardian(ActiveSpell *a) { + return processMagicGuardian(a->charNum); +} + +int LoLEngine::castHealOnSingleCharacter(ActiveSpell *a) { + processMagicHeal(a->target, a->level); + return 1; +} + +int LoLEngine::processMagicSpark(int charNum, int spellLevel) { + WSAMovie_v2 *mov = new WSAMovie_v2(this); + _screen->copyPage(0, 12); + + mov->open("spark1.wsa", 0, 0); + if (!mov->opened()) + error("SPARK: Unable to load SPARK1.WSA"); + snd_playSoundEffect(72, -1); + playSpellAnimation(mov, 0, 7, 4, _activeCharsXpos[charNum] - 2, 138, 0, 0, 0, 0, false); + mov->close(); + + _screen->copyPage(12, 0); + _screen->updateScreen(); + + uint16 targetBlock = 0; + int dist = getSpellTargetBlock(_currentBlock, _currentDirection, 4, targetBlock); + uint16 target = getNearestMonsterFromCharacterForBlock(targetBlock, charNum); + + static const uint8 dmg[] = { 7, 15, 25, 60 }; + if (target != 0xFFFF) { + inflictMagicalDamage(target, charNum, dmg[spellLevel], 5, 0); + updateDrawPage2(); + gui_drawScene(0); + _screen->copyPage(0, 12); + } + + int numFrames = mov->open("spark2.wsa", 0, 0); + if (!mov->opened()) + error("SPARK: Unable to load SPARK2.WSA"); + + uint16 wX[6]; + uint16 wY[6]; + uint16 wFrames[6]; + const uint16 width = mov->width(); + const uint16 height = mov->height(); + + for (int i = 0; i < 6; i++) { + wX[i] = (_rnd.getRandomNumber(0x7FFF) % 64) + ((176 - width) >> 1) + 80; + wY[i] = (_rnd.getRandomNumber(0x7FFF) % 32) + ((120 - height) >> 1) - 16; + wFrames[i] = i << 1; + } + + for (int i = 0, d = ((spellLevel << 1) + 12); i < d; i++) { + uint32 delayTimer = _system->getMillis() + 4 * _tickLength; + _screen->copyPage(12, 2); + + for (int ii = 0; ii <= spellLevel; ii++) { + if (wFrames[ii] >= i || wFrames[ii] + 13 <= i) + continue; + + if ((i - wFrames[ii]) == 1) + snd_playSoundEffect(162, -1); + + mov->displayFrame(((i - wFrames[ii]) + (dist << 4)) % numFrames, 2, wX[ii], wY[ii], 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(wX[ii], wY[ii], wX[ii], wY[ii], width, height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + if (i < d - 1) + delayUntil(delayTimer); + } + + mov->close(); + + _screen->copyPage(12, 2); + updateDrawPage2(); + + _sceneUpdateRequired = true; + + delete mov; + return 1; +} + +int LoLEngine::processMagicHealSelectTarget() { + _txt->printMessage(0, "%s", getLangString(0x4040)); + gui_resetButtonList(); + gui_setFaceFramesControlButtons(81, 0); + gui_initButtonsFromList(_buttonList8); + return 1; +} + +int LoLEngine::processMagicHeal(int charNum, int spellLevel) { + if (!_healOverlay) { + _healOverlay = new uint8[256]; + Palette tpal(256); + tpal.copy(_screen->getPalette(1)); + + if (_flags.use16ColorMode) { + tpal.fill(16, 240, 0xFF); + uint8 *dst = tpal.getData(); + for (int i = 1; i < 16; i++) { + int s = ((i << 4) | i) * 3; + SWAP(dst[s], dst[i]); + SWAP(dst[s + 1], dst[i + 1]); + SWAP(dst[s + 2], dst[i + 2]); + } + } + + _screen->generateGrayOverlay(tpal, _healOverlay, 52, 22, 20, 0, 256, true); + } + + const uint8 *healShpFrames = 0; + const uint8 *healiShpFrames = 0; + bool curePoison = false; + int points = 0; + + if (spellLevel == 0) { + points = 25; + healShpFrames = _healShapeFrames; + healiShpFrames = _healShapeFrames + 32; + + } else if (spellLevel == 1) { + points = 45; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 48; + + } else if (spellLevel > 3) { + curePoison = true; + points = spellLevel; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 64; + + } else { + curePoison = true; + points = 10000; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 64; + } + + int ch = 0; + int n = 4; + + if (charNum != -1) { + ch = charNum; + n = charNum + 1; + } + + charNum = ch; + + uint16 pX[4]; + uint16 pY = 138; + uint16 diff[4]; + uint16 pts[4]; + memset(pts, 0, sizeof(pts)); + + while (charNum < n) { + if (!(_characters[charNum].flags & 1)) { + charNum++; + continue; + } + + pX[charNum] = _activeCharsXpos[charNum] - 6; + _characters[charNum].damageSuffered = 0; + int dmg = _characters[charNum].hitPointsMax - _characters[charNum].hitPointsCur; + diff[charNum] = (dmg < points) ? dmg : points; + _screen->copyRegion(pX[charNum], pY, charNum * 77, 32, 77, 44, 0, 2, Screen::CR_NO_P_CHECK); + charNum++; + } + + int cp = _screen->setCurPage(2); + snd_playSoundEffect(68, -1); + + for (int i = 0; i < 16; i++) { + uint32 delayTimer = _system->getMillis() + 4 * _tickLength; + + for (charNum = ch; charNum < n; charNum++) { + if (!(_characters[charNum].flags & 1)) + continue; + + _screen->copyRegion(charNum * 77, 32, pX[charNum], pY, 77, 44, 2, 2, Screen::CR_NO_P_CHECK); + + pts[charNum] &= 0xFF; + pts[charNum] += ((diff[charNum] << 8) / 16); + increaseCharacterHitpoints(charNum, pts[charNum] / 256, true); + gui_drawCharPortraitWithStats(charNum); + + _screen->drawShape(2, _healShapes[healShpFrames[i]], pX[charNum], pY, 0, 0x1000, _transparencyTable1, _transparencyTable2); + _screen->fillRect(0, 0, 31, 31, 0); + + _screen->drawShape(_screen->_curPage, _healiShapes[healiShpFrames[i]], 0, 0, 0, 0); + _screen->applyOverlaySpecial(_screen->_curPage, 0, 0, 2, pX[charNum] + 7, pY + 6, 32, 32, 0, 0, _healOverlay); + + _screen->copyRegion(pX[charNum], pY, pX[charNum], pY, 77, 44, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + delayUntil(delayTimer); + } + + for (charNum = ch; charNum < n; charNum++) { + if (!(_characters[charNum].flags & 1)) + continue; + + _screen->copyRegion(charNum * 77, 32, pX[charNum], pY, 77, 44, 2, 2, Screen::CR_NO_P_CHECK); + + if (curePoison) + removeCharacterEffects(&_characters[charNum], 4, 4); + + gui_drawCharPortraitWithStats(charNum); + _screen->copyRegion(pX[charNum], pY, pX[charNum], pY, 77, 44, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + _screen->setCurPage(cp); + updateDrawPage2(); + return 1; +} + +int LoLEngine::processMagicIce(int charNum, int spellLevel) { + int cp = _screen->setCurPage(2); + + disableSysTimer(2); + + gui_drawScene(0); + _screen->copyPage(0, 12); + + Palette tpal(256), swampCol(256); + + if (_currentLevel == 11 && !(_flagsTable[52] & 0x04)) { + uint8 *sc = _screen->getPalette(0).getData(); + uint8 *dc = _screen->getPalette(2).getData(); + for (int i = 1; i < (_screen->getPalette(0).getNumColors() * 3); i++) + SWAP(sc[i], dc[i]); + + _flagsTable[52] |= 0x04; + static const uint8 freezeTimes[] = { 20, 28, 40, 60 }; + setCharacterUpdateEvent(charNum, 8, freezeTimes[spellLevel], 1); + } + + Palette s(256); + s.copy(_screen->getPalette(1)); + if (_flags.use16ColorMode) { + _screen->loadPalette("LOLICE.NOL", swampCol); + for (int i = 1; i < 16; i++) { + uint16 v = (s[i * 3] + s[i * 3 + 1] + s[i * 3 + 2]) / 3; + tpal[i * 3] = 0; + tpal[i * 3 + 1] = v; + tpal[i * 3 + 2] = v << 1; + + if (tpal[i * 3 + 2] > 29) + tpal[i * 3 + 2] = 29; + } + + } else { + _screen->loadPalette("SWAMPICE.COL", swampCol); + tpal.copy(s, 128); + swampCol.copy(s, 128); + + for (int i = 1; i < 128; i++) { + tpal[i * 3] = 0; + uint16 v = (s[i * 3] + s[i * 3 + 1] + s[i * 3 + 2]) / 3; + tpal[i * 3 + 1] = v; + tpal[i * 3 + 2] = v << 1; + + if (tpal[i * 3 + 2] > 0x3F) + tpal[i * 3 + 2] = 0x3F; + } + } + + generateBrightnessPalette(tpal, tpal, _brightness, _lampEffect); + generateBrightnessPalette(swampCol, swampCol, _brightness, _lampEffect); + swampCol[0] = swampCol[1] = swampCol[2] = tpal[0] = tpal[1] = tpal[2] = 0; + + generateBrightnessPalette(_screen->getPalette(0), s, _brightness, _lampEffect); + + int sX = 112; + int sY = 0; + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + if (spellLevel == 0) { + sX = 0; + } if (spellLevel == 1 || spellLevel == 2) { + mov->open("SNOW.WSA", 1, 0); + if (!mov->opened()) + error("Ice: Unable to load snow.wsa"); + } if (spellLevel == 3) { + mov->open("ICE.WSA", 1, 0); + if (!mov->opened()) + error("Ice: Unable to load ice.wsa"); + sX = 136; + sY = 12; + } + + snd_playSoundEffect(71, -1); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, s.getData(), tpal.getData(), 40, false); + + _screen->timedPaletteFadeStep(s.getData(), tpal.getData(), _system->getMillis(), _tickLength); + if (mov->opened()) { + int r = true; + if (spellLevel > 2) { + _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].flags |= 0x10; + snd_playSoundEffect(165, -1); + r = false; + }; + + playSpellAnimation(mov, 0, mov->frames(), 2, sX, sY, 0, 0, 0, 0, r); + mov->close(); + } + + delete mov; + static const uint8 snowDamage[] = { 10, 20, 30, 55 }; + static const uint8 iceDamageMax[] = {1, 2, 15, 20, 35}; + static const uint8 iceDamageMin[] = {10, 10, 3, 4, 4}; + static const uint8 iceDamageAdd[] = {5, 10, 30, 10, 10}; + + bool breakWall = false; + + if (spellLevel < 3) { + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, snowDamage[spellLevel], 3); + } else { + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + int might = rollDice(iceDamageMin[spellLevel], iceDamageMax[spellLevel]) + iceDamageAdd[spellLevel]; + int dmg = calcInflictableDamagePerItem(charNum, 0, might, 3, 2); + + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->hitPoints <= dmg) { + increaseExperience(charNum, 2, m->hitPoints); + o = m->nextAssignedObject; + + if (m->flags & 0x20) { + m->mode = 0; + monsterDropItems(m); + if (_currentLevel != 29) + setMonsterMode(m, 14); + runLevelScriptCustom(0x404, -1, o, o, 0, 0); + checkSceneUpdateNeed(m->block); + if (m->mode != 14) + placeMonster(m, 0, 0); + + } else { + killMonster(m); + } + + } else { + breakWall = true; + inflictDamage(o, dmg, charNum, 2, 3); + m->damageReceived = 0; + o = m->nextAssignedObject; + } + + if (m->flags & 0x20) + break; + } + } + + updateDrawPage2(); + gui_drawScene(0); + enableSysTimer(2); + + if (_currentLevel != 11) + generateBrightnessPalette(_screen->getPalette(0), swampCol, _brightness, _lampEffect); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, tpal.getData(), swampCol.getData(), 40, 0); + + _screen->timedPaletteFadeStep(tpal.getData(), swampCol.getData(), _system->getMillis(), _tickLength); + + if (breakWall) + breakIceWall(tpal.getData(), swampCol.getData()); + + _screen->setCurPage(cp); + return 1; +} + +int LoLEngine::processMagicFireball(int charNum, int spellLevel) { + int fbCnt = 0; + int d = 1; + + if (spellLevel == 0) { + fbCnt = 4; + } else if (spellLevel == 1) { + fbCnt = 5; + } else if (spellLevel == 2) { + fbCnt = 6; + } else if (spellLevel == 3) { + d = 0; + fbCnt = 5; + } + + int drawPage1 = 2; + int drawPage2 = 4; + + int bl = _currentBlock; + int fireballItem = makeItem(9, 0, 0); + + int i = 0; + for (; i < 3; i++) { + runLevelScriptCustom(bl, 0x200, -1, fireballItem, 0, 0); + uint16 o = _levelBlockProperties[bl].assignedObjects; + + if ((o & 0x8000) || (_wllWallFlags[_levelBlockProperties[bl].walls[_currentDirection ^ 2]] & 7)) { + while (o & 0x8000) { + static const uint8 fireballDamage[] = { 20, 40, 80, 100 }; + int dmg = calcInflictableDamagePerItem(charNum, o, fireballDamage[spellLevel], 4, 1); + LoLMonster *m = &_monsters[o & 0x7FFF]; + o = m->nextAssignedObject; + _envSfxUseQueue = true; + inflictDamage(m->id | 0x8000, dmg, charNum, 2, 4); + _envSfxUseQueue = false; + } + break; + } + + bl = calcNewBlockPosition(bl, _currentDirection); + } + + d += i; + if (d > 3) + d = 3; + + deleteItem(fireballItem); + + snd_playSoundEffect(69, -1); + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + int fireBallWH = (d << 4) * -1; + int numFireballs = 1; + if (fbCnt > 3) + numFireballs = fbCnt - 3; + + FireballState *fireballState[3]; + memset(&fireballState, 0, sizeof(fireballState)); + for (i = 0; i < numFireballs; i++) + fireballState[i] = new FireballState(i); + + _screen->copyPage(12, drawPage1); + + for (i = 0; i < numFireballs;) { + _screen->setCurPage(drawPage1); + uint32 ctime = _system->getMillis(); + + for (int ii = 0; ii < MIN(fbCnt, 3); ii++) { + FireballState *fb = fireballState[ii]; + if (!fb) + continue; + if (!fb->active) + continue; + + static const int8 finShpIndex1[] = { 5, 6, 7, 7, 6, 5 }; + static const int8 finShpIndex2[] = { -1, 1, 2, 3, 4, -1 }; + uint8 *shp = fb->finalize ? _fireballShapes[finShpIndex1[fb->finProgress]] : _fireballShapes[0]; + + int fX = (((fb->progress * _fireBallCoords[fb->tblIndex & 0xFF]) >> 16) + fb->destX) - ((fb->progress / 8 + shp[3] + fireBallWH) >> 1); + int fY = (((fb->progress * _fireBallCoords[(fb->tblIndex + 64) & 0xFF]) >> 16) + fb->destY) - ((fb->progress / 8 + shp[2] + fireBallWH) >> 1); + int sW = ((fb->progress / 8 + shp[3] + fireBallWH) << 8) / shp[3]; + int sH = ((fb->progress / 8 + shp[2] + fireBallWH) << 8) / shp[2]; + + if (fb->finalize) { + if (_flags.use16ColorMode) + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + else + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 0x1004, _transparencyTable1, _transparencyTable2, sW, sH); + + if (finShpIndex2[fb->finProgress] != -1) { + shp = _fireballShapes[finShpIndex2[fb->finProgress]]; + fX = (((fb->progress * _fireBallCoords[fb->tblIndex & 0xFF]) >> 16) + fb->destX) - ((fb->progress / 8 + shp[3] + fireBallWH) >> 1); + fY = (((fb->progress * _fireBallCoords[(fb->tblIndex + 64) & 0xFF]) >> 16) + fb->destY) - ((fb->progress / 8 + shp[2] + fireBallWH) >> 1); + sW = ((fb->progress / 8 + shp[3] + fireBallWH) << 8) / shp[3]; + sH = ((fb->progress / 8 + shp[2] + fireBallWH) << 8) / shp[2]; + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + } + + } else { + if (_flags.use16ColorMode) + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + else + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 0x1004, _transparencyTable1, _transparencyTable2, sW, sH); + } + + if (fb->finalize) { + if (++fb->finProgress >= 6) { + fb->active = false; + i++; + } + } else { + if (fb->step < 40) + fb->step += 2; + else + fb->step = 40; + + if (fb->progress < fb->step) { + if (ii < 1) { + fb->progress = fb->step = fb->finProgress = 0; + fb->finalize = true; + } else { + fb->active = false; + i++; + } + + static const uint8 fireballSfx[] = { 98, 167, 167, 168 }; + snd_playSoundEffect(fireballSfx[d], -1); + + } else { + fb->progress -= fb->step; + } + } + } + + int del = _tickLength - (_system->getMillis() - ctime); + if (del > 0) + delay(del); + + _screen->checkedPageUpdate(drawPage1, drawPage2); + _screen->updateScreen(); + SWAP(drawPage1, drawPage2); + _screen->copyPage(12, drawPage1); + } + + for (i = 0; i < numFireballs; i++) + delete fireballState[i]; + + _screen->setCurPage(cp); + _screen->copyPage(12, 0); + _screen->updateScreen(); + updateDrawPage2(); + snd_playQueuedEffects(); + runLevelScriptCustom(bl, 0x20, charNum, 3, 0, 0); + return 1; +} + +int LoLEngine::processMagicHandOfFate(int spellLevel) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("hand.wsa", 1, 0); + if (!mov->opened()) + error("Hand: Unable to load HAND.WSA"); + + static const uint8 frames[] = { 17, 26, 11, 16, 27, 35, 27, 35, 0, 75 }; + + snd_playSoundEffect(173, -1); + playSpellAnimation(mov, 0, 10, 3, 112, 0, 0, 0, 0, 0, false); + snd_playSoundEffect(151, -1); + playSpellAnimation(mov, frames[spellLevel * 2] , frames[spellLevel * 2 + 1], 3, 112, 0, 0, 0, 0, 0, false); + snd_playSoundEffect(18, -1); + playSpellAnimation(mov, 10, 0, 3, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + _screen->copyPage(12, 2); + gui_drawScene(2); + + if (spellLevel < 2) { + uint16 b1 = calcNewBlockPosition(_currentBlock, _currentDirection); + uint16 b2 = calcNewBlockPosition(b1, _currentDirection); + + if (!testWallFlag(b2, 0, 4)) { + if (!(_levelBlockProperties[b2].assignedObjects & 0x8000)) { + checkSceneUpdateNeed(b1); + + uint16 dir = (_currentDirection << 1); + uint16 o = _levelBlockProperties[b1].assignedObjects; + while (o & 0x8000) { + uint16 o2 = o; + LoLMonster *m = &_monsters[o & 0x7FFF]; + o = findObject(o)->nextAssignedObject; + int nX = 0; + int nY = 0; + + getNextStepCoords(m->x, m->y, nX, nY, dir); + for (int i = 0; i < 7; i++) + getNextStepCoords(nX, nY, nX, nY, dir); + + placeMonster(m, nX, nY); + runLevelScriptCustom(b2, 0x800, -1, o2, 0, 0); + } + } + } + + } else { + uint16 b1 = calcNewBlockPosition(_currentBlock, _currentDirection); + checkSceneUpdateNeed(b1); + + static const uint16 damage[] = { 75, 125, 175 }; + uint16 o = _levelBlockProperties[b1].assignedObjects; + + while (o & 0x8000) { + uint16 t = o; + o = findObject(o)->nextAssignedObject; + // This might be a bug in the original code, but using + // the hand of fate spell won't give any experience points + int dmg = calcInflictableDamagePerItem(-1, t, damage[spellLevel - 2], 0x80, 1); + inflictDamage(t, dmg, 0xFFFF, 3, 0x80); + } + } + + if (_currentLevel == 29) + _screen->copyPage(12, 2); + + _screen->copyPage(2, 0); + _screen->updateScreen(); + + gui_drawScene(2); + updateDrawPage2(); + return 1; +} + +int LoLEngine::processMagicMistOfDoom(int charNum, int spellLevel) { + static const uint8 mistDamage[] = { 30, 70, 110, 200 }; + + _envSfxUseQueue = true; + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, mistDamage[spellLevel], 0x80); + _envSfxUseQueue = false; + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 12); + + snd_playSoundEffect(155, -1); + + Common::String wsafile = Common::String::format("mists%0d.wsa", spellLevel + 1); + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open(wsafile.c_str(), 1, 0); + if (!mov->opened()) + error("Mist: Unable to load %s", wsafile.c_str()); + + snd_playSoundEffect(_mistAnimData[spellLevel].sound, -1); + playSpellAnimation(mov, _mistAnimData[spellLevel].part1First, _mistAnimData[spellLevel].part1Last, 7, 112, 0, 0, 0, 0, 0, false); + playSpellAnimation(mov, _mistAnimData[spellLevel].part2First, _mistAnimData[spellLevel].part2Last, 14, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + _screen->copyPage(12, 0); + + updateDrawPage2(); + snd_playQueuedEffects(); + return 1; +} + +int LoLEngine::processMagicLightning(int charNum, int spellLevel) { + _screen->hideMouse(); + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 12); + + _lightningCurSfx = _lightningProps[spellLevel].sfxId; + _lightningDiv = _lightningProps[spellLevel].frameDiv; + _lightningFirstSfx = 0; + + Common::String wsafile = Common::String::format("litning%d.wsa", spellLevel + 1); + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open(wsafile.c_str(), 1, 0); + if (!mov->opened()) + error("Litning: Unable to load %s", wsafile.c_str()); + + for (int i = 0; i < 4; i++) + playSpellAnimation(mov, 0, _lightningProps[spellLevel].lastFrame, 3, 93, 0, &LoLEngine::callbackProcessMagicLightning, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setScreenPalette(_screen->getPalette(1)); + _screen->copyPage(12, 2); + _screen->copyPage(12, 0); + updateDrawPage2(); + + static const uint8 lightningDamage[] = { 18, 35, 50, 72 }; + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, lightningDamage[spellLevel], 5); + + _sceneUpdateRequired = true; + gui_drawScene(0); + _screen->showMouse(); + return 1; +} + +int LoLEngine::processMagicFog() { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("fog.wsa", 0, 0); + if (!mov->opened()) + error("Fog: Unable to load fog.wsa"); + + snd_playSoundEffect(145, -1); + + for (int curFrame = 0; curFrame < numFrames; curFrame++) { + uint32 delayTimer = _system->getMillis() + 3 * _tickLength; + _screen->copyPage(12, 2); + mov->displayFrame(curFrame % numFrames, 2, 112, 0, 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delayUntil(delayTimer); + } + + mov->close(); + delete mov; + + _screen->copyPage(12, 2); + _screen->setCurPage(cp); + updateDrawPage2(); + + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + inflictMagicalDamage(o, -1, 15, 6, 0); + o = _monsters[o & 0x7FFF].nextAssignedObject; + } + + gui_drawScene(0); + return 1; +} + +int LoLEngine::processMagicSwarm(int charNum, int damage) { + createTransparencyTables(); + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + snd_playSoundEffect(74, -1); + + uint16 destIds[6]; + uint8 destModes[6]; + int8 destTicks[6]; + + memset(destIds, 0, sizeof(destIds)); + memset(destModes, 8, sizeof(destModes)); + memset(destTicks, 0, sizeof(destTicks)); + + int t = 0; + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + o &= 0x7FFF; + if (_monsters[o].mode != 13) { + destIds[t++] = o; + + if (!(_monsters[o].flags & 0x2000)) { + _envSfxUseQueue = true; + inflictMagicalDamage(o | 0x8000, charNum, damage, 0, 0); + _envSfxUseQueue = false; + _monsters[o].flags &= 0xFFEF; + } + } + o = _monsters[o].nextAssignedObject; + } + + for (int i = 0; i < t; i++) { + SWAP(destModes[i], _monsters[destIds[i]].mode); + SWAP(destTicks[i], _monsters[destIds[i]].fightCurTick); + } + + gui_drawScene(_screen->_curPage); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _screen->_curPage, 7); + + for (int i = 0; i < t; i++) { + _monsters[destIds[i]].mode = destModes[i]; + _monsters[destIds[i]].fightCurTick = destTicks[i]; + } + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + mov->open("swarm.wsa", 0, 0); + if (!mov->opened()) + error("Swarm: Unable to load SWARM.WSA"); + _screen->hideMouse(); + playSpellAnimation(mov, 0, 37, 2, 0, 0, 0, 0, 0, 0, false); + playSpellAnimation(mov, 38, 41, 8, 0, 0, &LoLEngine::callbackProcessMagicSwarm, 0, 0, 0, false); + _screen->showMouse(); + mov->close(); + + _screen->copyPage(12, 0); + _screen->updateScreen(); + updateDrawPage2(); + + snd_playQueuedEffects(); + + _screen->setCurPage(cp); + delete mov; + return 1; +} + +int LoLEngine::processMagicVaelansCube() { + uint8 *sp1 = _screen->getPalette(1).getData(); + int len = _screen->getPalette(1).getNumColors() * 3; + + uint8 *tmpPal1 = new uint8[len]; + uint8 *tmpPal2 = new uint8[len]; + + memcpy(tmpPal1, sp1, len); + memcpy(tmpPal2, sp1, len); + + if (_flags.use16ColorMode) { + for (int i = 0; i < 16; i++) { + uint16 a = sp1[i * 3 + 1] + 16; + tmpPal2[i * 3 + 1] = (a > 58) ? 58 : a; + tmpPal2[i * 3] = sp1[i * 3]; + a = sp1[i * 3 + 2] + 16; + tmpPal2[i * 3 + 2] = (a > 63) ? 63 : a; + } + } else { + for (int i = 0; i < 128; i++) { + uint16 a = sp1[i * 3] + 16; + tmpPal2[i * 3] = (a > 60) ? 60 : a; + tmpPal2[i * 3 + 1] = sp1[i * 3 + 1]; + a = sp1[i * 3 + 2] + 19; + tmpPal2[i * 3 + 2] = (a > 60) ? 60 : a; + } + } + + snd_playSoundEffect(146, -1); + + uint32 ctime = _system->getMillis(); + uint32 endTime = _system->getMillis() + 70 * _tickLength; + + while (_system->getMillis() < endTime) { + _screen->timedPaletteFadeStep(tmpPal1, tmpPal2, _system->getMillis() - ctime, 70 * _tickLength); + updateInput(); + } + + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + uint8 s = _levelBlockProperties[bl].walls[_currentDirection ^ 2]; + uint8 flg = _wllWallFlags[s]; + + int res = (s == 47 && (_currentLevel == 17 || _currentLevel == 24)) ? 1 : 0; + if ((_wllVmpMap[s] == 1 || _wllVmpMap[s] == 2) && (!(flg & 1)) && (_currentLevel != 22)) { + memset(_levelBlockProperties[bl].walls, 0, 4); + gui_drawScene(0); + res = 1; + } + + uint16 o = _levelBlockProperties[bl].assignedObjects; + while (o & 0x8000) { + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->properties->flags & 0x1000) { + inflictDamage(o, 100, 0xFFFF, 0, 0x80); + res = 1; + } + o = m->nextAssignedObject; + } + + ctime = _system->getMillis(); + endTime = _system->getMillis() + 70 * _tickLength; + + while (_system->getMillis() < endTime) { + _screen->timedPaletteFadeStep(tmpPal2, tmpPal1, _system->getMillis() - ctime, 70 * _tickLength); + updateInput(); + } + + delete[] tmpPal1; + delete[] tmpPal2; + + return res; +} + +int LoLEngine::processMagicGuardian(int charNum) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 2); + _screen->copyPage(2, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("guardian.wsa", 0, 0); + if (!mov->opened()) + error("Guardian: Unable to load guardian.wsa"); + snd_playSoundEffect(156, -1); + playSpellAnimation(mov, 0, 37, 2, 112, 0, 0, 0, 0, 0, false); + + _screen->copyPage(2, 12); + + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + int res = (_levelBlockProperties[bl].assignedObjects & 0x8000) ? 1 : 0; + inflictMagicalDamageForBlock(bl, charNum, 200, 0x80); + + _screen->copyPage(12, 2); + updateDrawPage2(); + gui_drawScene(2); + + _screen->copyPage(2, 12); + snd_playSoundEffect(176, -1); + playSpellAnimation(mov, 38, 48, 8, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + gui_drawPlayField(); + updateDrawPage2(); + return res; +} + +void LoLEngine::callbackProcessMagicSwarm(WSAMovie_v2 *mov, int x, int y) { + if (_swarmSpellStatus) + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, _screen->_curPage); + _swarmSpellStatus ^= 1; +} + +void LoLEngine::callbackProcessMagicLightning(WSAMovie_v2 *mov, int x, int y) { + if (_lightningDiv == 2) + shakeScene(1, 2, 3, 0); + + const Palette &p1 = _screen->getPalette(1); + + if (_lightningSfxFrame % _lightningDiv) { + _screen->setScreenPalette(p1); + } else { + Palette tpal(p1.getNumColors()); + tpal.copy(p1); + + int start = 6; + int end = 384; + + if (_flags.use16ColorMode) { + start = 3; + end = 48; + } + + for (int i = start; i < end; i++) { + uint16 v = (tpal[i] * 120) / 64; + tpal[i] = (v < 64) ? v : 63; + } + + _screen->setScreenPalette(tpal); + } + + if (_lightningDiv == 2) { + if (!_lightningFirstSfx) { + snd_playSoundEffect(_lightningCurSfx, -1); + _lightningFirstSfx = 1; + } + } else { + if (!(_lightningSfxFrame & 7)) + snd_playSoundEffect(_lightningCurSfx, -1); + } + + _lightningSfxFrame++; +} + +void LoLEngine::drinkBezelCup(int numUses, int charNum) { + createTransparencyTables(); + + int cp = _screen->setCurPage(2); + snd_playSoundEffect(73, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("bezel.wsa", 0, 0); + if (!mov->opened()) + error("Bezel: Unable to load bezel.wsa"); + + int x = _activeCharsXpos[charNum] - 11; + int y = 124; + int w = mov->width(); + int h = mov->height(); + + _screen->copyRegion(x, y, 0, 0, w, h, 0, 2, Screen::CR_NO_P_CHECK); + + static const uint8 bezelAnimData[] = { 0, 26, 20, 27, 61, 55, 62, 92, 86, 93, 131, 125 }; + int frm = bezelAnimData[numUses * 3]; + int hpDiff = _characters[charNum].hitPointsMax - _characters[charNum].hitPointsCur; + uint16 step = 0; + + do { + step = (step & 0xFF) + (hpDiff * 256) / (bezelAnimData[numUses * 3 + 1]); + increaseCharacterHitpoints(charNum, step / 256, true); + gui_drawCharPortraitWithStats(charNum); + + uint32 etime = _system->getMillis() + 4 * _tickLength; + + _screen->copyRegion(0, 0, x, y, w, h, 2, 2, Screen::CR_NO_P_CHECK); + mov->displayFrame(frm, 2, x, y, _flags.use16ColorMode ? 0x4000 : 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(x, y, x, y, w, h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(etime); + } while (++frm < bezelAnimData[numUses * 3 + 1]); + + _characters[charNum].hitPointsCur = _characters[charNum].hitPointsMax; + _screen->copyRegion(0, 0, x, y, w, h, 2, 2, Screen::CR_NO_P_CHECK); + removeCharacterEffects(&_characters[charNum], 4, 4); + gui_drawCharPortraitWithStats(charNum); + _screen->copyRegion(x, y, x, y, w, h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); +} + +void LoLEngine::addSpellToScroll(int spell, int charNum) { + bool assigned = false; + int slot = 0; + for (int i = 0; i < 7; i++) { + if (!assigned && _availableSpells[i] == -1) { + assigned = true; + slot = i; + } + + if (_availableSpells[i] == spell) { + _txt->printMessage(2, "%s", getLangString(0x42D0)); + return; + } + } + + if (spell > 1) + transferSpellToScollAnimation(charNum, spell, slot - 1); + + _availableSpells[slot] = spell; + gui_enableDefaultPlayfieldButtons(); +} + +void LoLEngine::transferSpellToScollAnimation(int charNum, int spell, int slot) { + int cX = 16 + _activeCharsXpos[charNum]; + + if (slot != 1) { + _screen->loadBitmap("playfld.cps", 3, 3, 0); + _screen->copyRegion(8, 0, 216, 0, 96, 120, 3, 3, Screen::CR_NO_P_CHECK); + _screen->copyPage(3, 10); + for (int i = 0; i < 9; i++) { + int h = (slot + 1) * 9 + i + 1; + uint32 delayTimer = _system->getMillis() + _tickLength; + _screen->copyPage(10, 3); + _screen->copyRegion(216, 0, 8, 0, 96, 120, 3, 3, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 12, 0, 87, 15, 2, 2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(201, 1, 17, 15, 6, h, 2, 2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(208, 1, 89, 15, 6, h, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + _screen->fillRect(21, 15, 89, h + 15, _flags.use16ColorMode ? 0xBB : 206); + _screen->copyRegion(112, 16, 12, h + 15, 87, 14, 2, 2, Screen::CR_NO_P_CHECK); + + int y = 15; + Screen::FontId of = _screen->setFont(Screen::FID_9_FNT); + for (int ii = 0; ii < 7; ii++) { + if (_availableSpells[ii] == -1) + continue; + uint8 col = (ii == _selectedSpell) ? 132 : 1; + if (_flags.use16ColorMode) + col = (ii == _selectedSpell) ? 0x88 : 0x44; + _screen->fprintString("%s", 24, y, col, 0, 0, getLangString(_spellProperties[_availableSpells[ii]].spellNameCode)); + y += 9; + } + _screen->setFont(of); + + _screen->setCurPage(cp); + _screen->copyRegion(8, 0, 8, 0, 96, 120, 3, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + } + + _screen->hideMouse(); + + _screen->copyPage(0, 12); + int vX = _updateSpellBookCoords[slot << 1] + 32; + int vY = _updateSpellBookCoords[(slot << 1) + 1] + 5; + + Common::String wsaFile = Common::String::format("write%0d", spell); + if (_flags.isTalkie) + wsaFile += (_lang == 1) ? 'f' : (_lang == 0 ? 'e' : 'g'); + wsaFile += ".wsa"; + snd_playSoundEffect(_updateSpellBookAnimData[(spell << 2) + 3], -1); + snd_playSoundEffect(95, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + mov->open("getspell.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load getspell anim"); + snd_playSoundEffect(128, -1); + playSpellAnimation(mov, 0, 25, 5, _activeCharsXpos[charNum], 148, 0, 0, 0, 0, true); + snd_playSoundEffect(128, -1); + playSpellAnimation(mov, 26, 52, 5, _activeCharsXpos[charNum], 148, 0, 0, 0, 0, true); + + for (int i = 16; i > 0; i--) { + uint32 delayTimer = _system->getMillis() + _tickLength; + _screen->copyPage(12, 2); + + int wsaX = vX + (((((cX - vX) << 8) / 16) * i) >> 8) - 16; + int wsaY = vY + (((((160 - vY) << 8) / 16) * i) >> 8) - 16; + + mov->displayFrame(51, 2, wsaX, wsaY, 0x5000, _transparencyTable1, _transparencyTable2); + + _screen->copyRegion(wsaX, wsaY, wsaX, wsaY, mov->width() + 48, mov->height() + 48, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + + mov->close(); + + mov->open("spellexp.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load spellexp anim"); + snd_playSoundEffect(168, -1); + playSpellAnimation(mov, 0, 8, 3, vX - 44, vY - 38, 0, 0, 0, 0, true); + mov->close(); + + mov->open("writing.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load writing anim"); + playSpellAnimation(mov, 0, 6, 5, _updateSpellBookCoords[slot << 1], _updateSpellBookCoords[(slot << 1) + 1], 0, 0, 0, 0, false); + mov->close(); + + mov->open(wsaFile.c_str(), 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load spellbook anim"); + snd_playSoundEffect(_updateSpellBookAnimData[(spell << 2) + 3], -1); + playSpellAnimation(mov, _updateSpellBookAnimData[(spell << 2) + 1], _updateSpellBookAnimData[(spell << 2) + 2], _updateSpellBookAnimData[spell << 2], _updateSpellBookCoords[slot << 1], _updateSpellBookCoords[(slot << 1) + 1], 0, 0, 0, 0, false); + mov->close(); + + gui_drawScene(2); + updateDrawPage2(); + + _screen->showMouse(); + + delete mov; +} + +void LoLEngine::playSpellAnimation(WSAMovie_v2 *mov, int firstFrame, int lastFrame, int frameDelay, int x, int y, SpellProcCallback callback, uint8 *pal1, uint8 *pal2, int fadeDelay, bool restoreScreen) { + int w = 0; + int h = 0; + + if (mov) { + w = mov->width(); + h = mov->height(); + } + + int w2 = w; + int h2 = h; + uint32 startTime = _system->getMillis(); + + if (x < 0) + w2 += x; + if (y < 0) + h2 += y; + + int dir = lastFrame >= firstFrame ? 1 : -1; + int curFrame = firstFrame; + + bool fin = false; + + while (!fin) { + uint32 delayTimer = _system->getMillis() + _tickLength * frameDelay; + + if (mov || callback) + _screen->copyPage(12, 2); + + if (callback) + (this->*callback)(mov, x, y); + + if (mov) + mov->displayFrame(curFrame % mov->frames(), 2, x, y, _flags.use16ColorMode ? 0x4000 : 0x5000, _transparencyTable1, _transparencyTable2); + + if (mov || callback) { + _screen->copyRegion(x, y, x, y, w2, h2, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + uint32 tm = _system->getMillis(); + uint32 del = (delayTimer > tm) ? (delayTimer - tm) : 0; + + do { + uint32 step = del > _tickLength ? _tickLength : del; + + if (!pal1 || !pal2) { + if (del) { + delay(step); + del -= step; + } else { + updateInput(); + } + continue; + } + + if (!_screen->timedPaletteFadeStep(pal1, pal2, _system->getMillis() - startTime, _tickLength * fadeDelay) && !mov) + return; + + if (del) { + delay(step); + del -= step; + } else { + updateInput(); + } + } while (del); + + if (!mov) + continue; + + curFrame += dir; + if ((dir > 0 && curFrame >= lastFrame) || (dir < 0 && curFrame < lastFrame)) + fin = true; + } + + if (restoreScreen && (mov || callback)) { + _screen->copyPage(12, 2); + _screen->copyRegion(x, y, x, y, w2, h2, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +int LoLEngine::checkMagic(int charNum, int spellNum, int spellLevel) { + if (_spellProperties[spellNum].mpRequired[spellLevel] > _characters[charNum].magicPointsCur) { + if (characterSays(0x4043, _characters[charNum].id, true)) + _txt->printMessage(6, getLangString(0x4043), _characters[charNum].name); + return 1; + } else if (_spellProperties[spellNum].hpRequired[spellLevel] >= _characters[charNum].hitPointsCur) { + _txt->printMessage(2, getLangString(0x4179), _characters[charNum].name); + return 1; + } + + return 0; +} + +int LoLEngine::getSpellTargetBlock(int currentBlock, int direction, int maxDistance, uint16 &targetBlock) { + targetBlock = 0xFFFF; + uint16 c = calcNewBlockPosition(currentBlock, direction); + + int i = 0; + for (; i < maxDistance; i++) { + if (_levelBlockProperties[currentBlock].assignedObjects & 0x8000) { + targetBlock = currentBlock; + return i; + } + + if (_wllWallFlags[_levelBlockProperties[c].walls[direction ^ 2]] & 7) { + targetBlock = c; + return i; + } + + currentBlock = c; + c = calcNewBlockPosition(currentBlock, direction); + } + + return i; +} + +void LoLEngine::inflictMagicalDamage(int target, int attacker, int damage, int index, int hitType) { + hitType = hitType ? 1 : 2; + damage = calcInflictableDamagePerItem(attacker, target, damage, index, hitType); + inflictDamage(target, damage, attacker, 2, index); +} + +void LoLEngine::inflictMagicalDamageForBlock(int block, int attacker, int damage, int index) { + uint16 o = _levelBlockProperties[block].assignedObjects; + while (o & 0x8000) { + inflictDamage(o, calcInflictableDamagePerItem(attacker, o, damage, index, 2), attacker, 2, index); + if ((_monsters[o & 0x7FFF].flags & 0x20) && (_currentLevel != 22)) + break; + o = _monsters[o & 0x7FFF].nextAssignedObject; + } +} + +// fight + +int LoLEngine::battleHitSkillTest(int16 attacker, int16 target, int skill) { + if (target == -1) + return 0; + if (attacker == -1) + return 1; + + if (target & 0x8000) { + if (_monsters[target & 0x7FFF].mode >= 13) + return 0; + } + + uint16 hitChanceModifier = 0; + uint16 evadeChanceModifier = 0; + int sk = 0; + + if (attacker & 0x8000) { + hitChanceModifier = _monsters[target & 0x7FFF].properties->fightingStats[0]; + sk = 100 - _monsters[target & 0x7FFF].properties->skillLevel; + } else { + hitChanceModifier = _characters[attacker].defaultModifiers[0]; + int8 m = _characters[attacker].skillModifiers[skill]; + if (skill == 1) + m *= 3; + sk = 100 - (_characters[attacker].skillLevels[skill] + m); + } + + if (target & 0x8000) { + evadeChanceModifier = _monsters[target & 0x7FFF].properties->fightingStats[3]; + if (_monsterModifiers4) + evadeChanceModifier = (evadeChanceModifier * _monsterModifiers4[_monsterDifficulty]) >> 8; + _monsters[target & 0x7FFF].flags |= 0x10; + } else { + evadeChanceModifier = _characters[target].defaultModifiers[3]; + } + + int r = rollDice(1, 100); + if (r >= sk) + return 2; + + uint16 v = (evadeChanceModifier << 8) / hitChanceModifier; + + if (r < v) + return 0; + + return 1; +} + +int LoLEngine::calcInflictableDamage(int16 attacker, int16 target, int hitType) { + const uint16 *s = getCharacterOrMonsterItemsMight(attacker); + + // The original code looks somewhat like the commented out part of the next line. + // In the end the value is always set to zero. I do not know whether this is done on purpose or not. + // It might be a bug in the original code. + int res = 0/*attacker & 0x8000 ? 0 : _characters[attacker].might*/; + for (int i = 0; i < 8; i++) + res += calcInflictableDamagePerItem(attacker, target, s[i], i, hitType); + + return res; +} + +int LoLEngine::inflictDamage(uint16 target, int damage, uint16 attacker, int skill, int flags) { + LoLMonster *m = 0; + LoLCharacter *c = 0; + + if (target & 0x8000) { + m = &_monsters[target & 0x7FFF]; + if (m->mode >= 13) + return 0; + + if (damage > 0) { + m->hitPoints -= damage; + m->damageReceived = 0x8000 | damage; + m->flags |= 0x10; + m->hitOffsX = rollDice(1, 24); + m->hitOffsX -= 12; + m->hitOffsY = rollDice(1, 24); + m->hitOffsY -= 12; + m->hitPoints = CLIP<int16>(m->hitPoints, 0, m->properties->hitPoints); + + if (!(attacker & 0x8000)) + applyMonsterDefenseSkill(m, attacker, flags, skill, damage); + + snd_queueEnvironmentalSoundEffect(m->properties->sounds[2], m->block); + checkSceneUpdateNeed(m->block); + + if (m->hitPoints <= 0) { + m->hitPoints = 0; + if (!(attacker & 0x8000)) + increaseExperience(attacker, skill, m->properties->hitPoints); + setMonsterMode(m, 13); + } + } else { + m->hitPoints -= damage; + m->hitPoints = CLIP<int16>(m->hitPoints, 1, m->properties->hitPoints); + } + + } else { + if (target > 3) { + // WORKAROUND for script bug + int i = 0; + for (; i < 4; i++) { + if (_characters[i].id == target) { + target = i; + break; + } + } + if (i == 4) + return 0; + } + + c = &_characters[target]; + if (!(c->flags & 1) || (c->flags & 8)) + return 0; + + if (!(c->flags & 0x1000)) + snd_playSoundEffect(c->screamSfx, -1); + + setTemporaryFaceFrame(target, 6, 4, 0); + + // check for equipped cloud ring + if (flags == 4 && itemEquipped(target, 229)) + damage >>= 2; + + setCharacterMagicOrHitPoints(target, 0, -damage, 1); + + if (c->hitPointsCur <= 0) { + characterHitpointsZero(target, flags); + } else { + _characters[target].damageSuffered = damage; + setCharacterUpdateEvent(target, 2, 4, 1); + } + gui_drawCharPortraitWithStats(target); + } + + if (!(attacker & 0x8000)) { + if (!skill) + _characters[attacker].weaponHit = damage; + increaseExperience(attacker, skill, damage); + } + + return damage; +} + +void LoLEngine::characterHitpointsZero(int16 charNum, int flags) { + LoLCharacter *c = &_characters[charNum]; + c->hitPointsCur = 0; + c->flags |= 8; + removeCharacterEffects(c, 1, 5); + _partyDamageFlags = flags; +} + +void LoLEngine::removeCharacterEffects(LoLCharacter *c, int first, int last) { + for (int i = first; i <= last; i++) { + switch (i - 1) { + case 0: + c->flags &= 0xFFFB; + c->weaponHit = 0; + break; + + case 1: + c->damageSuffered = 0; + break; + + case 2: + c->flags &= 0xFFBF; + break; + + case 3: + c->flags &= 0xFF7F; + break; + + case 4: + c->flags &= 0xFEFF; + break; + + case 6: + c->flags &= 0xEFFF; + break; + + default: + break; + } + + for (int ii = 0; ii < 5; ii++) { + if (i != c->characterUpdateEvents[ii]) + continue; + + c->characterUpdateEvents[ii] = 0; + c->characterUpdateDelay[ii] = 0; + } + } + + _timer->enable(3); +} + +int LoLEngine::calcInflictableDamagePerItem(int16 attacker, int16 target, uint16 itemMight, int index, int hitType) { + int dmg = (attacker == -1) ? 0x100 : getCharacterOrMonsterStats(attacker)[1]; + const uint16 *st_t = getCharacterOrMonsterProtectionAgainstItems(target); + + dmg = (dmg * itemMight) >> 8; + if (!dmg) + return 0; + + if (!(attacker & 0x8000)) { + dmg = (dmg * _characters[attacker].totalMightModifier) >> 8; + if (!dmg) + return 0; + } + + int d = (index & 0x80) ? st_t[7] : st_t[index]; + int r = (dmg * ABS(d)) >> 8; + dmg = d < 0 ? -r : r; + + if (hitType == 2 || !dmg) + return (dmg == 1) ? 2 : dmg; + + + int p = (calculateProtection(target) << 7) / dmg; + if (p > 217) + p = 217; + + d = 256 - p; + r = (dmg * ABS(d)) >> 8; + dmg = d < 0 ? -r : r; + + return (dmg < 2) ? 2 : dmg; +} + +void LoLEngine::checkForPartyDeath() { + Button b; + b.data0Val2 = b.data1Val2 = b.data2Val2 = 0xFE; + b.data0Val3 = b.data1Val3 = b.data2Val3 = 0x01; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || _characters[i].hitPointsCur <= 0) + continue; + return; + } + + if (_weaponsDisabled) + clickedExitCharInventory(&b); + + gui_drawAllCharPortraitsWithStats(); + + if (_partyDamageFlags & 0x40) { + _screen->fadeToBlack(40); + for (int i = 0; i < 4; i++) { + if (_characters[i].flags & 1) + increaseCharacterHitpoints(i, 1, true); + } + gui_drawAllCharPortraitsWithStats(); + _screen->fadeToPalette1(40); + + } else { + if (!_flags.use16ColorMode) + _screen->fadeClearSceneWindow(10); + restoreAfterSpecialScene(0, 1, 1, 0); + + snd_playTrack(325); + stopPortraitSpeechAnim(); + initTextFading(0, 1); + setMouseCursorToIcon(0); + _updateFlags |= 4; + setLampMode(true); + disableSysTimer(2); + + _gui->runMenu(_gui->_deathMenu); + + setMouseCursorToItemInHand(); + _updateFlags &= 0xFFFB; + resetLampStatus(); + + gui_enableDefaultPlayfieldButtons(); + enableSysTimer(2); + updateDrawPage2(); + } +} + +void LoLEngine::applyMonsterAttackSkill(LoLMonster *monster, int16 target, int16 damage) { + if (rollDice(1, 100) > monster->properties->attackSkillChance) + return; + + int t = 0; + + switch (monster->properties->attackSkillType - 1) { + case 0: + t = removeCharacterItem(target, 0x7FF); + if (t) { + giveItemToMonster(monster, t); + if (characterSays(0x4019, _characters[target].id, true)) + _txt->printMessage(6, "%s", getLangString(0x4019)); + } + break; + + case 1: + // poison character + paralyzePoisonCharacter(target, 0x80, 0x88, 100, 1); + break; + + case 2: + t = removeCharacterItem(target, 0x20); + if (t) { + deleteItem(t); + if (characterSays(0x401B, _characters[target].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401B)); + } + break; + + case 3: + t = removeCharacterItem(target, 0x0F); + if (t) { + if (characterSays(0x401E, _characters[target].id, true)) + _txt->printMessage(6, getLangString(0x401E), _characters[target].name); + setItemPosition(t, monster->x, monster->y, 0, 1); + } + break; + + case 5: + if (_characters[target].magicPointsCur <= 0) + return; + + monster->hitPoints += _characters[target].magicPointsCur; + _characters[target].magicPointsCur = 0; + gui_drawCharPortraitWithStats(target); + if (characterSays(0x4020, _characters[target].id, true)) + _txt->printMessage(6, getLangString(0x4020), _characters[target].name); + break; + + case 7: + stunCharacter(target); + break; + + case 8: + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 9: + // paralyze party (spider web) + paralyzePoisonAllCharacters(0x40, 0x48, 100); + break; + + default: + break; + } +} + +void LoLEngine::applyMonsterDefenseSkill(LoLMonster *monster, int16 attacker, int flags, int skill, int damage) { + if (rollDice(1, 100) > monster->properties->defenseSkillChance) + return; + + int itm = 0; + + switch (monster->properties->defenseSkillType - 1) { + case 0: + case 1: + if ((flags & 0x3F) == 2 || skill) + return; + + for (int i = 0; i < 3; i++) { + itm = _characters[attacker].items[i]; + if (!itm) + continue; + if ((_itemProperties[_itemsInPlay[itm].itemPropertyIndex].protection & 0x3F) != flags) + continue; + + removeCharacterItem(attacker, 0x7FFF); + + if (monster->properties->defenseSkillType == 1) { + giveItemToMonster(monster, itm); + if (characterSays(0x401C, _characters[attacker].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401C)); + + } else { + deleteItem(itm); + if (characterSays(0x401D, _characters[attacker].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401D)); + } + } + break; + + case 2: + if (!(flags & 0x80)) + return; + monster->flags |= 8; + monster->direction = calcMonsterDirection(monster->x, monster->y, _partyPosX, _partyPosY) ^ 4; + setMonsterMode(monster, 9); + monster->fightCurTick = 30; + break; + + case 3: + if (flags != 3) + return; + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 4: + if (!(flags & 0x80)) + return; + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 5: + if ((flags & 0x84) == 0x84) + monster->numDistAttacks++; + break; + + default: + break; + } +} + +int LoLEngine::removeCharacterItem(int charNum, int itemFlags) { + for (int i = 0; i < 11; i++) { + int s = _characters[charNum].items[i]; + if (!((1 << i) & itemFlags) || !s) + continue; + + _characters[charNum].items[i] = 0; + runItemScript(charNum, s, 0x100, 0, 0); + + return s; + } + + return 0; +} + +int LoLEngine::paralyzePoisonCharacter(int charNum, int typeFlag, int immunityFlags, int hitChance, int redraw) { + if (!(_characters[charNum].flags & 1) || (_characters[charNum].flags & immunityFlags)) + return 0; + + if (rollDice(1, 100) > hitChance) + return 0; + + int r = 0; + + if (typeFlag == 0x40) { + _characters[charNum].flags |= 0x40; + setCharacterUpdateEvent(charNum, 3, 3600, 1); + r = 1; + + // check for bezel ring + } else if (typeFlag == 0x80 && !itemEquipped(charNum, 225)) { + _characters[charNum].flags |= 0x80; + setCharacterUpdateEvent(charNum, 4, 10, 1); + if (characterSays(0x4021, _characters[charNum].id, true)) + _txt->printMessage(6, getLangString(0x4021), _characters[charNum].name); + r = 1; + + } else if (typeFlag == 0x1000) { + _characters[charNum].flags |= 0x1000; + setCharacterUpdateEvent(charNum, 7, 120, 1); + r = 1; + } + + if (r && redraw) + gui_drawCharPortraitWithStats(charNum); + + return r; +} + +void LoLEngine::paralyzePoisonAllCharacters(int typeFlag, int immunityFlags, int hitChance) { + bool r = false; + for (int i = 0; i < 4; i++) { + if (paralyzePoisonCharacter(i, typeFlag, immunityFlags, hitChance, 0)) + r = true; + } + if (r) + gui_drawAllCharPortraitsWithStats(); +} + +void LoLEngine::stunCharacter(int charNum) { + if (!(_characters[charNum].flags & 1) || (_characters[charNum].flags & 0x108)) + return; + + _characters[charNum].flags |= 0x100; + + setCharacterUpdateEvent(charNum, 5, 20, 1); + gui_drawCharPortraitWithStats(charNum); + + _txt->printMessage(6, getLangString(0x4026), _characters[charNum].name); +} + +void LoLEngine::restoreSwampPalette() { + _flagsTable[52] &= 0xFB; + if (_currentLevel != 11) + return; + + uint8 *s = _screen->getPalette(2).getData(); + uint8 *d = _screen->getPalette(0).getData(); + uint8 *d2 = _screen->getPalette(1).getData(); + + for (int i = 1; i < (_screen->getPalette(0).getNumColors() * 3); i++) + SWAP(s[i], d[i]); + + generateBrightnessPalette(_screen->getPalette(0), _screen->getPalette(1), _brightness, _lampEffect); + _screen->loadSpecialColors(_screen->getPalette(2)); + _screen->loadSpecialColors(_screen->getPalette(1)); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, s, d2, 40, 0); +} + +void LoLEngine::launchMagicViper() { + _partyAwake = true; + + int d = 0; + for (uint16 b = _currentBlock; d < 3; d++) { + uint16 o = _levelBlockProperties[b].assignedObjects; + if (o & 0x8000) + break; + b = calcNewBlockPosition(b, _currentDirection); + if (_wllWallFlags[_levelBlockProperties[b].walls[_currentDirection ^ 2]] & 7) + break; + } + + _screen->copyPage(0, 12); + snd_playSoundEffect(148, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("viper.wsa", 1, 0); + if (!mov->opened()) + error("Viper: Unable to load viper.wsa"); + + static const uint8 viperAnimData[] = { 15, 25, 20, 10, 25, 20, 5, 25, 20, 0, 25, 20 }; + const uint8 *v = &viperAnimData[d * 3]; + int frm = v[0]; + + for (bool running = true; running;) { + uint32 etime = _system->getMillis() + 5 * _tickLength; + _screen->copyPage(12, 2); + + if (frm == v[2]) + snd_playSoundEffect(172, -1); + + mov->displayFrame(frm++ % numFrames, 2, 112, 0, 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delayUntil(etime); + + if (frm > v[1]) + running = false; + } + + mov->close(); + delete mov; + + _screen->copyPage(12, 0); + _screen->copyPage(12, 2); + + int t = rollDice(1, 4); + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) { + t = t % 4; + continue; + } + inflictDamage(t, _currentLevel + 10, 0x8000, 2, 0x86); + } +} + +void LoLEngine::breakIceWall(uint8 *pal1, uint8 *pal2) { + _screen->hideMouse(); + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + _levelBlockProperties[bl].flags &= 0xEF; + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 10); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("shatter.wsa", 1, 0); + if (!mov->opened()) + error("Shatter: Unable to load shatter.wsa"); + snd_playSoundEffect(166, -1); + playSpellAnimation(mov, 0, numFrames, 1, 58, 0, 0, pal1, pal2, 20, true); + mov->close(); + delete mov; + + _screen->copyPage(10, 0); + updateDrawPage2(); + gui_drawScene(0); + _screen->showMouse(); +} + +uint16 LoLEngine::getNearestMonsterFromCharacter(int charNum) { + return getNearestMonsterFromCharacterForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum); +} + +uint16 LoLEngine::getNearestMonsterFromCharacterForBlock(uint16 block, int charNum) { + uint16 cX = 0; + uint16 cY = 0; + + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + if (block == 0xFFFF) + return id; + + calcCoordinatesForSingleCharacter(charNum, cX, cY); + + int o = _levelBlockProperties[block].assignedObjects; + + while (o & 0x8000) { + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->mode >= 13) { + o = m->nextAssignedObject; + continue; + } + + int d = ABS(cX - m->x) + ABS(cY - m->y); + if (d < minDist) { + minDist = d; + id = o; + } + + o = m->nextAssignedObject; + } + + return id; +} + +uint16 LoLEngine::getNearestMonsterFromPos(int x, int y) { + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode > 13) + continue; + + int d = ABS(x - _monsters[i].x) + ABS(y - _monsters[i].y); + if (d < minDist) { + minDist = d; + id = 0x8000 | i; + } + } + + return id; +} + +uint16 LoLEngine::getNearestPartyMemberFromPos(int x, int y) { + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || _characters[i].hitPointsCur <= 0) + continue; + + uint16 charX = 0; + uint16 charY = 0; + calcCoordinatesForSingleCharacter(i, charX, charY); + + int d = ABS(x - charX) + ABS(y - charY); + if (d < minDist) { + minDist = d; + id = i; + } + } + + return id; +} + +// magic atlas + +void LoLEngine::displayAutomap() { + snd_playSoundEffect(105, -1); + gui_toggleButtonDisplayMode(_flags.isTalkie ? 78 : 76, 1); + + _currentMapLevel = _currentLevel; + uint8 *tmpWll = new uint8[80]; + memcpy(tmpWll, _wllAutomapData, 80); + + _screen->loadBitmap("parch.cps", 2, 2, &_screen->getPalette(3)); + _screen->loadBitmap("autobut.shp", 3, 5, 0); + const uint8 *shp = _screen->getCPagePtr(5); + + for (int i = 0; i < 109; i++) + _automapShapes[i] = _screen->getPtrToShape(shp, i + 11); + + if (_flags.use16ColorMode) { + static const uint8 ovlSrc[] = { 0x00, 0xEE, 0xCC, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0x22, 0x11, 0xDD, 0xEE, 0xCC }; + memset(_mapOverlay, 0, 256); + for (int i = 0; i < 16; i++) + _mapOverlay[(i << 4) | i] = ovlSrc[i]; + } else + _screen->generateGrayOverlay(_screen->getPalette(3), _mapOverlay, 52, 0, 0, 0, 256, false); + + _screen->loadFont(Screen::FID_9_FNT, "FONT9PN.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6PN.FNT"); + + for (int i = 0; i < 11; i++) + _defaultLegendData[i].enable = false; + + disableSysTimer(2); + generateTempData(); + resetItems(1); + disableMonsters(); + + bool exitAutomap = false; + _mapUpdateNeeded = false; + + restoreBlockTempData(_currentMapLevel); + loadMapLegendData(_currentMapLevel); + _screen->fadeToBlack(10); + drawMapPage(2); + + _screen->copyPage(2, 0); + _screen->updateScreen(); + _screen->fadePalette(_screen->getPalette(3), 10); + uint32 delayTimer = _system->getMillis() + 8 * _tickLength; + + while (!exitAutomap && !shouldQuit()) { + if (_mapUpdateNeeded) { + drawMapPage(2); + _screen->copyPage(2, 0); + _screen->updateScreen(); + _mapUpdateNeeded = false; + } + + if (_system->getMillis() >= delayTimer) { + redrawMapCursor(); + delayTimer = _system->getMillis() + 8 * _tickLength; + } + + int f = checkInput(0) & 0xFF; + removeInputTop(); + + if (f) { + exitAutomap = automapProcessButtons(f); + gui_notifyButtonListChanged(); + } + + if (f == _keyMap[Common::KEYCODE_c]) { + for (int i = 0; i < 1024; i++) + _levelBlockProperties[i].flags |= 7; + _mapUpdateNeeded = true; + } else if (f == _keyMap[Common::KEYCODE_ESCAPE]) { + exitAutomap = true; + } + + delay(_tickLength); + } + + _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6P.FNT"); + + if (_flags.use16ColorMode) + _screen->clearPage(2); + + _screen->fadeToBlack(10); + loadLevelWallData(_currentLevel, false); + memcpy(_wllAutomapData, tmpWll, 80); + delete[] tmpWll; + restoreBlockTempData(_currentLevel); + addLevelItems(); + gui_notifyButtonListChanged(); + enableSysTimer(2); +} + +void LoLEngine::updateAutoMap(uint16 block) { + if (!(_flagsTable[31] & 0x10)) + return; + _levelBlockProperties[block].flags |= 7; + + uint16 x = block & 0x1F; + uint16 y = block >> 5; + + updateAutoMapIntern(block, x, y, -1, -1); + updateAutoMapIntern(block, x, y, 1, -1); + updateAutoMapIntern(block, x, y, -1, 1); + updateAutoMapIntern(block, x, y, 1, 1); + updateAutoMapIntern(block, x, y, 0, -1); + updateAutoMapIntern(block, x, y, 0, 1); + updateAutoMapIntern(block, x, y, -1, 0); + updateAutoMapIntern(block, x, y, 1, 0); +} + +bool LoLEngine::updateAutoMapIntern(uint16 block, uint16 x, uint16 y, int16 xOffs, int16 yOffs) { + static const int16 blockPosTable[] = { 1, -1, 3, 2, -1, 0, -1, 0, 1, -32, 0, 32 }; + x += xOffs; + y += yOffs; + + if ((x & 0xFFE0) || (y & 0xFFE0)) + return false; + + xOffs++; + yOffs++; + + int16 fx = blockPosTable[xOffs]; + uint16 b = block + blockPosTable[6 + xOffs]; + + if (fx != -1) { + if (_wllAutomapData[_levelBlockProperties[b].walls[fx]] & 0xC0) + return false; + } + + int16 fy = blockPosTable[3 + yOffs]; + b = block + blockPosTable[9 + yOffs]; + + if (fy != -1) { + if (_wllAutomapData[_levelBlockProperties[b].walls[fy]] & 0xC0) + return false; + } + + b = block + blockPosTable[6 + xOffs] + blockPosTable[9 + yOffs]; + + if ((fx != -1) && (fy != -1) && (_wllAutomapData[_levelBlockProperties[b].walls[fx]] & 0xC0) && (_wllAutomapData[_levelBlockProperties[b].walls[fy]] & 0xC0)) + return false; + + _levelBlockProperties[b].flags |= 7; + + return true; +} + +void LoLEngine::loadMapLegendData(int level) { + uint16 *legendData = (uint16 *)_tempBuffer5120; + for (int i = 0; i < 32; i++) { + legendData[i * 6] = 0xFFFF; + legendData[i * 6 + 5] = 0xFFFF; + } + + Common::String file = Common::String::format("level%d.xxx", level); + uint32 size = 0; + uint8 *data = _res->fileData(file.c_str(), &size); + uint8 *pos = data; + size = MIN<uint32>(size / 12, 32); + + for (uint32 i = 0; i < size; i++) { + uint16 *l = &legendData[i * 6]; + l[3] = READ_LE_UINT16(pos); + pos += 2; + l[4] = READ_LE_UINT16(pos); + pos += 2; + l[5] = READ_LE_UINT16(pos); + pos += 2; + l[0] = READ_LE_UINT16(pos); + pos += 2; + l[1] = READ_LE_UINT16(pos); + pos += 2; + l[2] = READ_LE_UINT16(pos); + pos += 2; + } + + delete[] data; +} + +void LoLEngine::drawMapPage(int pageNum) { + // WORKAROUND for French version. The text does not always properly fit the screen there. + const int8 xOffset = (_lang == 1) ? -2 : 0; + + if (_flags.use16ColorMode) + _screen->clearPage(pageNum); + + for (int i = 0; i < 2; i++) { + _screen->loadBitmap("parch.cps", pageNum, pageNum, &_screen->getPalette(3)); + if (_lang == 1) + _screen->copyRegion(236, 16, 236 + xOffset, 16, -xOffset, 1, pageNum, pageNum, Screen::CR_NO_P_CHECK); + + int cp = _screen->setCurPage(pageNum); + Screen::FontId of = _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_9_FNT); + _screen->printText(getLangString(_autoMapStrings[_currentMapLevel]), 236 + xOffset, 8, 1, 0); + uint16 blX = mapGetStartPosX(); + uint16 bl = (mapGetStartPosY() << 5) + blX; + + int sx = _automapTopLeftX; + int sy = _automapTopLeftY; + + for (; bl < 1024; bl++) { + uint8 *w = _levelBlockProperties[bl].walls; + if ((_levelBlockProperties[bl].flags & 7) == 7 && (!(_wllAutomapData[w[0]] & 0xC0)) && (!(_wllAutomapData[w[2]] & 0xC0)) && (!(_wllAutomapData[w[1]] & 0xC0)) && (!(_wllAutomapData[w[3]] & 0xC0))) { + uint16 b0 = calcNewBlockPosition(bl, 0); + uint16 b2 = calcNewBlockPosition(bl, 2); + uint16 b1 = calcNewBlockPosition(bl, 1); + uint16 b3 = calcNewBlockPosition(bl, 3); + + uint8 w02 = _levelBlockProperties[b0].walls[2]; + uint8 w20 = _levelBlockProperties[b2].walls[0]; + uint8 w13 = _levelBlockProperties[b1].walls[3]; + uint8 w31 = _levelBlockProperties[b3].walls[1]; + + // draw block + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 7, 6, 0, _mapOverlay); + + // draw north wall + drawMapBlockWall(b3, w31, sx, sy, 3); + drawMapShape(w31, sx, sy, 3); + if (_wllAutomapData[w31] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 1, 6, 0, _mapOverlay); + + // draw west wall + drawMapBlockWall(b1, w13, sx, sy, 1); + drawMapShape(w13, sx, sy, 1); + if (_wllAutomapData[w13] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx + 6, sy, _screen->_curPage, sx + 6, sy, 1, 6, 0, _mapOverlay); + + // draw east wall + drawMapBlockWall(b0, w02, sx, sy, 0); + drawMapShape(w02, sx, sy, 0); + if (_wllAutomapData[w02] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 7, 1, 0, _mapOverlay); + + //draw south wall + drawMapBlockWall(b2, w20, sx, sy, 2); + drawMapShape(w20, sx, sy, 2); + if (_wllAutomapData[w20] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy + 5, _screen->_curPage, sx, sy + 5, 7, 1, 0, _mapOverlay); + } + + sx += 7; + if (bl % 32 == 31) { + sx = _automapTopLeftX; + sy += 6; + bl += blX; + } + } + + _screen->setFont(of); + _screen->setCurPage(cp); + + of = _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_6_FNT); + + int tY = 0; + sx = mapGetStartPosX(); + sy = mapGetStartPosY(); + + uint16 *legendData = (uint16 *)_tempBuffer5120; + uint8 yOffset = _flags.use16ColorMode ? 4 : 0; + + for (int ii = 0; ii < 32; ii++) { + uint16 *l = &legendData[ii * 6]; + if (l[0] == 0xFFFF) + break; + + uint16 cbl = l[0] + (l[1] << 5); + if ((_levelBlockProperties[cbl].flags & 7) != 7) + continue; + + if (l[2] == 0xFFFF) + continue; + + printMapText(l[2], 244 + xOffset, (tY << 3) + 22 + yOffset); + + if (l[5] == 0xFFFF) { + tY++; + continue; + } + + uint16 cbl2 = l[3] + (l[4] << 5); + _levelBlockProperties[cbl2].flags |= 7; + _screen->drawShape(2, _automapShapes[l[5] << 2], (l[3] - sx) * 7 + _automapTopLeftX - 3, (l[4] - sy) * 6 + _automapTopLeftY - 3, 0, 0); + _screen->drawShape(2, _automapShapes[l[5] << 2], 231 + xOffset, (tY << 3) + 19 + yOffset, 0, 0); + tY++; + } + + cp = _screen->setCurPage(pageNum); + + for (int ii = 0; ii < 11; ii++) { + if (!_defaultLegendData[ii].enable) + continue; + _screen->copyBlockAndApplyOverlay(_screen->_curPage, 235, (tY << 3) + 21 + yOffset, _screen->_curPage, 235 + xOffset, (tY << 3) + 21 + yOffset, 7, 6, 0, _mapOverlay); + _screen->drawShape(_screen->_curPage, _automapShapes[_defaultLegendData[ii].shapeIndex << 2], 232 + xOffset, (tY << 3) + 18 + yOffset + _defaultLegendData[ii].y, 0, 0); + printMapText(_defaultLegendData[ii].stringId, 244 + xOffset, (tY << 3) + 22 + yOffset); + tY++; + } + + _screen->setFont(of); + _screen->setCurPage(cp); + } + + printMapExitButtonText(); +} + +bool LoLEngine::automapProcessButtons(int inputFlag) { + int r = -1; + if (inputFlag == _keyMap[Common::KEYCODE_RIGHT] || inputFlag == _keyMap[Common::KEYCODE_KP6]) { + r = 0; + } else if (inputFlag == _keyMap[Common::KEYCODE_LEFT] || inputFlag == _keyMap[Common::KEYCODE_KP4]) { + r = 1; + } else if (inputFlag == 199) { + if (posWithinRect(_mouseX, _mouseY, 252, 175, 273, 200)) + r = 0; + else if (posWithinRect(_mouseX, _mouseY, 231, 175, 252, 200)) + r = 1; + else if (posWithinRect(_mouseX, _mouseY, 275, 175, 315, 197)) + r = 2; + + printMapExitButtonText(); + + while (inputFlag == 199 || inputFlag == 200) { + inputFlag = checkInput(0, false); + removeInputTop(); + delay(_tickLength); + } + } else { + return false; + } + + if (r == 0) { + automapForwardButton(); + printMapExitButtonText(); + } else if (r == 1) { + automapBackButton(); + printMapExitButtonText(); + } if (r == 2) { + return true; + } + + return false; +} + +void LoLEngine::automapForwardButton() { + int i = _currentMapLevel + 1; + while (!(_hasTempDataFlags & (1 << (i - 1)))) + i = (i + 1) & 0x1F; + if (i == _currentMapLevel) + return; + + for (int l = 0; l < 11; l++) + _defaultLegendData[l].enable = false; + + _currentMapLevel = i; + loadLevelWallData(i, false); + restoreBlockTempData(i); + loadMapLegendData(i); + _mapUpdateNeeded = true; +} + +void LoLEngine::automapBackButton() { + int i = _currentMapLevel - 1; + while (!(_hasTempDataFlags & (1 << (i - 1)))) + i = (i - 1) & 0x1F; + if (i == _currentMapLevel) + return; + + for (int l = 0; l < 11; l++) + _defaultLegendData[l].enable = false; + + _currentMapLevel = i; + loadLevelWallData(i, false); + restoreBlockTempData(i); + loadMapLegendData(i); + _mapUpdateNeeded = true; +} + +void LoLEngine::redrawMapCursor() { + int sx = mapGetStartPosX(); + int sy = mapGetStartPosY(); + + if (_currentLevel != _currentMapLevel) + return; + + int cx = _automapTopLeftX + (((_currentBlock - sx) % 32) * 7); + int cy = _automapTopLeftY + (((_currentBlock - (sy << 5)) / 32) * 6); + + if (_flags.use16ColorMode) { + _screen->drawShape(0, _automapShapes[48 + _currentDirection], cx - 3, cy - 2, 0, 0); + } else { + _screen->fillRect(0, 0, 16, 16, 0, 2); + _screen->drawShape(2, _automapShapes[48 + _currentDirection], 0, 0, 0, 0); + _screen->copyRegion(cx, cy, cx, cy, 16, 16, 2, 0); + _screen->copyBlockAndApplyOverlay(2, 0, 0, 0, cx - 3, cy - 2, 16, 16, 0, _mapCursorOverlay); + + _mapCursorOverlay[24] = _mapCursorOverlay[1]; + for (int i = 1; i < 24; i++) + _mapCursorOverlay[i] = _mapCursorOverlay[i + 1]; + } + + _screen->updateScreen(); +} + +void LoLEngine::drawMapBlockWall(uint16 block, uint8 wall, int x, int y, int direction) { + if (((1 << direction) & _levelBlockProperties[block].flags) || ((_wllAutomapData[wall] & 0x1F) != 13)) + return; + + int cp = _screen->_curPage; + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[0][direction], y + _mapCoords[1][direction], cp, x + _mapCoords[0][direction], y + _mapCoords[1][direction], _mapCoords[2][direction], _mapCoords[3][direction], 0, _mapOverlay); + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[4][direction], y + _mapCoords[5][direction], cp, x + _mapCoords[4][direction], y + _mapCoords[5][direction], _mapCoords[8][direction], _mapCoords[9][direction], 0, _mapOverlay); + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[6][direction], y + _mapCoords[7][direction], cp, x + _mapCoords[6][direction], y + _mapCoords[7][direction], _mapCoords[8][direction], _mapCoords[9][direction], 0, _mapOverlay); +} + +void LoLEngine::drawMapShape(uint8 wall, int x, int y, int direction) { + int l = _wllAutomapData[wall] & 0x1F; + if (l == 0x1F) + return; + + _screen->drawShape(_screen->_curPage, _automapShapes[(l << 2) + direction], x + _mapCoords[10][direction] - 2, y + _mapCoords[11][direction] - 2, 0, 0); + mapIncludeLegendData(l); +} + +int LoLEngine::mapGetStartPosX() { + int c = 0; + int a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(a << 5) + c].flags) + break; + } + if (a == 32) + c++; + } while (c < 32 && a == 32); + + int d = 31; + a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(a << 5) + d].flags) + break; + } + if (a == 32) + d--; + } while (d > 0 && a == 32); + + _automapTopLeftX = (d > c) ? ((32 - (d - c)) >> 1) * 7 + 5 : 5; + return (d > c) ? c : 0; +} + +int LoLEngine::mapGetStartPosY() { + int c = 0; + int a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(c << 5) + a].flags) + break; + } + if (a == 32) + c++; + } while (c < 32 && a == 32); + + int d = 31; + a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(d << 5) + a].flags) + break; + } + if (a == 32) + d--; + } while (d > 0 && a == 32); + + _automapTopLeftY = (d > c) ? ((32 - (d - c)) >> 1) * 6 + 4 : 4; + return (d > c) ? c : 0; +} + +void LoLEngine::mapIncludeLegendData(int type) { + type &= 0x7F; + for (int i = 0; i < 11; i++) { + if (_defaultLegendData[i].shapeIndex != type) + continue; + _defaultLegendData[i].enable = true; + return; + } +} + +void LoLEngine::printMapText(uint16 stringId, int x, int y) { + int cp = _screen->setCurPage(2); + if (_flags.use16ColorMode) + _screen->printText(getLangString(stringId), x & ~3, y & ~7, 1, 0); + else + _screen->printText(getLangString(stringId), x, y, 239, 0); + _screen->setCurPage(cp); +} + +void LoLEngine::printMapExitButtonText() { + int cp = _screen->setCurPage(2); + Screen::FontId of = _screen->setFont(Screen::FID_9_FNT); + _screen->fprintString("%s", 295, 182, _flags.use16ColorMode ? 0xBB : 172, 0, 5, getLangString(0x4033)); + _screen->setFont(of); + _screen->setCurPage(cp); +} + + + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/lol.h b/engines/kyra/engine/lol.h new file mode 100644 index 0000000000..14811d21f1 --- /dev/null +++ b/engines/kyra/engine/lol.h @@ -0,0 +1,1345 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#ifndef KYRA_LOL_H +#define KYRA_LOL_H + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/script/script_tim.h" +#include "kyra/script/script.h" +#include "kyra/gui/gui_lol.h" +#include "kyra/text/text_lol.h" + +#include "common/list.h" + +namespace Audio { +class SeekableAudioStream; +} // End of namespace Audio + +namespace Kyra { + +class Screen_LoL; +class WSAMovie_v2; +struct Button; + +struct LoLCharacter { + uint16 flags; + char name[11]; + uint8 raceClassSex; + int16 id; + uint8 curFaceFrame; + uint8 tempFaceFrame; + uint8 screamSfx; + const uint16 *defaultModifiers; + uint16 itemsMight[8]; + uint16 protectionAgainstItems[8]; + uint16 itemProtection; + int16 hitPointsCur; + uint16 hitPointsMax; + int16 magicPointsCur; + uint16 magicPointsMax; + uint8 field_41; + uint16 damageSuffered; + uint16 weaponHit; + uint16 totalMightModifier; + uint16 totalProtectionModifier; + uint16 might; + uint16 protection; + int16 nextAnimUpdateCountdown; + uint16 items[11]; + uint8 skillLevels[3]; + int8 skillModifiers[3]; + int32 experiencePts[3]; + uint8 characterUpdateEvents[5]; + uint8 characterUpdateDelay[5]; +}; + +struct SpellProperty { + uint16 spellNameCode; + uint16 mpRequired[4]; + uint16 field_a; + uint16 field_c; + uint16 hpRequired[4]; + uint16 field_16; + uint16 field_18; + uint16 flags; +}; + +struct LoLMonsterProperty { + uint8 shapeIndex; + uint8 maxWidth; + uint16 fightingStats[9]; + uint16 itemsMight[8]; + uint16 protectionAgainstItems[8]; + uint16 itemProtection; + uint16 hitPoints; + uint8 speedTotalWaitTicks; + uint8 skillLevel; + uint16 flags; + uint16 unk5; + uint16 numDistAttacks; + uint16 numDistWeapons; + uint16 distWeapons[3]; + uint8 attackSkillChance; + uint8 attackSkillType; + uint8 defenseSkillChance; + uint8 defenseSkillType; + uint8 sounds[3]; +}; + +struct LoLObject { + uint16 nextAssignedObject; + uint16 nextDrawObject; + uint8 flyingHeight; + uint16 block; + uint16 x; + uint16 y; +}; + +struct LoLMonster : public LoLObject { + uint8 destDirection; + int8 shiftStep; + uint16 destX; + uint16 destY; + + int8 hitOffsX; + int8 hitOffsY; + uint8 currentSubFrame; + uint8 mode; + int8 fightCurTick; + uint8 id; + uint8 direction; + uint8 facing; + uint16 flags; + uint16 damageReceived; + int16 hitPoints; + uint8 speedTick; + uint8 type; + LoLMonsterProperty *properties; + uint8 numDistAttacks; + uint8 curDistWeapon; + int8 distAttackTick; + uint16 assignedItems; + uint8 equipmentShapes[4]; +}; + +struct LoLItem : public LoLObject { + int8 level; + uint16 itemPropertyIndex; + uint16 shpCurFrame_flg; +}; + +struct ItemProperty { + uint16 nameStringId; + uint8 shpIndex; + uint16 flags; + uint16 type; + uint8 itemScriptFunc; + int8 might; + uint8 skill; + uint8 protection; + uint16 unkB; + uint8 unkD; +}; + +struct CompassDef { + uint8 shapeIndex; + int8 x; + int8 y; + uint8 flags; +}; + +struct LoLButtonDef { + uint16 buttonflags; + uint16 keyCode; + uint16 keyCode2; + int16 x; + int16 y; + uint16 w; + uint16 h; + uint16 index; + uint16 screenDim; +}; + +struct ActiveSpell { + uint8 spell; + const SpellProperty *p; + uint8 charNum; + uint8 level; + uint8 target; +}; + +struct FlyingObject { + uint8 enable; + uint8 objectType; + uint16 attackerId; + Item item; + uint16 x; + uint16 y; + uint8 flyingHeight; + uint8 direction; + uint8 distance; + int8 field_D; + uint8 c; + uint8 flags; + uint8 wallFlags; +}; + +struct FlyingObjectShape { + uint8 shapeFront; + uint8 shapeBack; + uint8 shapeLeft; + uint8 drawFlags; + uint8 flipFlags; +}; + +struct MapLegendData { + uint8 shapeIndex; + bool enable; + int8 y; + uint16 stringId; +}; + +struct LightningProperty { + uint8 lastFrame; + uint8 frameDiv; + int16 sfxId; +}; + +struct FireballState { + FireballState(int i) { + active = true; + destX = 200; + destY = 60; + tblIndex = ((i * 50) % 255) + 200; + progress = 1000; + step = 10; + finalize = false; + finProgress = 0; + } + + bool active; + int16 destX; + int16 destY; + uint16 tblIndex; + int32 progress; + uint8 step; + bool finalize; + uint8 finProgress; +}; + +struct MistOfDoomAnimData { + uint8 part1First; + uint8 part1Last; + uint8 part2First; + uint8 part2Last; + uint8 sound; +}; + +class LoLEngine : public KyraRpgEngine { +friend class GUI_LoL; +friend class TextDisplayer_LoL; +friend class TIMInterpreter_LoL; +friend class TimAnimator; +friend class Debugger_LoL; +friend class HistoryPlayer; +public: + LoLEngine(OSystem *system, const GameFlags &flags); + virtual ~LoLEngine(); + + virtual void initKeymap(); + + void pauseEngineIntern(bool pause); + + Screen *screen(); + GUI *gui() const; + +private: + Screen_LoL *_screen; + GUI_LoL *_gui; + + TIMInterpreter *_tim; + + Common::Error init(); + Common::Error go(); + + // initialization + void initStaticResource(); + void preInit(); + + void loadItemIconShapes(); + int mainMenu(); + + void startup(); + void startupNew(); + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + + static const char *const kKeymapName; + + const char *const *_pakFileList; + int _pakFileListSize; + + // options + int _monsterDifficulty; + bool _smoothScrollingEnabled; + bool _floatingCursorsEnabled; + + // main loop + void runLoop(); + void update(); + + // mouse + void setMouseCursorToIcon(int icon); + void setMouseCursorToItemInHand(); + uint8 *getItemIconShapePtr(int index); + + void checkFloatingPointerRegions(); + int _floatingCursorControl; + int _currentFloatingCursor; + + // intro + character selection + int processPrologue(); + void setupPrologueData(bool load); + + void showIntro(); + + struct CharacterPrev { + int x, y; + int attrib[3]; + }; + + static const CharacterPrev _charPreviews[]; + static const char *const _charPreviewNamesDefault[]; + static const char *const _charPreviewNamesRussianFloppy[]; + + // PC98/FM-TOWNS specific data + static const uint16 _charPosXPC98[]; + static const char *const _charNamesJapanese[]; + + WSAMovie_v2 *_chargenWSA; + static const uint8 _chargenFrameTableTalkie[]; + static const uint8 _chargenFrameTableFloppy[]; + const uint8 *_chargenFrameTable; + int chooseCharacter(); + + void kingSelectionIntro(); + void kingSelectionReminder(); + void kingSelectionOutro(); + void processCharacterSelection(); + void updateSelectionAnims(); + int selectionCharInfo(int character); + void selectionCharInfoIntro(char *file); + + int getCharSelection(); + int selectionCharAccept(); + + void showStarcraftLogo(); + + int _charSelection; + int _charSelectionInfoResult; + + uint32 _selectionAnimTimers[4]; + uint8 _selectionAnimFrames[4]; + static const uint8 _selectionAnimIndexTable[]; + + static const uint16 _selectionPosTable[]; + + static const uint8 _selectionChar1IdxTable[]; + static const uint8 _selectionChar2IdxTable[]; + static const uint8 _selectionChar3IdxTable[]; + static const uint8 _selectionChar4IdxTable[]; + + static const uint8 _reminderChar1IdxTable[]; + static const uint8 _reminderChar2IdxTable[]; + static const uint8 _reminderChar3IdxTable[]; + static const uint8 _reminderChar4IdxTable[]; + + static const uint8 _charInfoFrameTable[]; + + // outro + void showOutro(int character, bool maxDifficulty); + void setupEpilogueData(bool load); + + void showCredits(); + void processCredits(char *text, int dimState, int page, int delay); + void loadOutroShapes(int file, uint8 **storage); + + uint8 _outroShapeTable[256]; + + // TODO: Consider moving these tables to kyra.dat + static const char *const _outroShapeFileTable[]; + static const uint8 _outroFrameTable[]; + + static const int16 _outroRightMonsterPos[]; + static const int16 _outroLeftMonsterPos[]; + static const int16 _outroRightDoorPos[]; + static const int16 _outroLeftDoorPos[]; + + static const int _outroMonsterScaleTableX[]; + static const int _outroMonsterScaleTableY[]; + + // Non-interactive demo + int playDemo(); + void pauseDemoPlayer(bool toggle); + + // timers + void setupTimers(); + + void timerProcessMonsters(int timerNum); + void timerSpecialCharacterUpdate(int timerNum); + void timerProcessFlyingObjects(int timerNum); + void timerRunSceneAnimScript(int timerNum); + void timerRegeneratePoints(int timerNum); + void timerUpdatePortraitAnimations(int skipUpdate); + void timerUpdateLampState(int timerNum); + void timerFadeMessageText(int timerNum); + + uint8 getClock2Timer(int index) { return index < _numClock2Timers ? _clock2Timers[index] : 0; } + uint8 getNumClock2Timers() { return _numClock2Timers; } + + static const uint8 _clock2Timers[]; + static const uint8 _numClock2Timers; + + // sound + int convertVolumeToMixer(int value); + int convertVolumeFromMixer(int value); + + void loadTalkFile(int index); + void snd_playVoiceFile(int track) {} + bool snd_playCharacterSpeech(int id, int8 speaker, int); + int snd_updateCharacterSpeech(); + void snd_stopSpeech(bool setFlag); + void snd_playSoundEffect(int track, int volume); + bool snd_processEnvironmentalSoundEffect(int soundId, int block); + void snd_queueEnvironmentalSoundEffect(int soundId, int block); + void snd_playQueuedEffects(); + void snd_loadSoundFile(int track); + int snd_playTrack(int track); + int snd_stopMusic(); + + int _lastSpeechId; + int _lastSpeaker; + int _lastSfxTrack; + int _lastMusicTrack; + int _curMusicFileIndex; + char _curMusicFileExt; + bool _envSfxUseQueue; + int _envSfxNumTracksInQueue; + uint16 _envSfxQueuedTracks[10]; + uint16 _envSfxQueuedBlocks[10]; + int _nextSpeechId; + int _nextSpeaker; + typedef Common::List<Audio::SeekableAudioStream *> SpeechList; + SpeechList _speechList; + + int _curTlkFile; + + char **_ingameSoundList; + int _ingameSoundListSize; + + const uint8 *_musicTrackMap; + const int16 *_ingameSoundIndex; + int _ingameSoundIndexSize; + const uint8 *_ingameGMSoundIndex; + int _ingameGMSoundIndexSize; + const uint8 *_ingameMT32SoundIndex; + int _ingameMT32SoundIndexSize; + const uint8 *_ingamePCSpeakerSoundIndex; + int _ingamePCSpeakerSoundIndexSize; + + // gui + void gui_drawPlayField(); + void gui_drawScene(int pageNum); + void gui_drawAllCharPortraitsWithStats(); + void gui_drawCharPortraitWithStats(int charNum); + void gui_drawCharFaceShape(int charNum, int x, int y, int pageNum); + void gui_highlightPortraitFrame(int charNum); + void gui_drawLiveMagicBar(int x, int y, int curPoints, int unk, int maxPoints, int w, int h, int col1, int col2, int flag); + void gui_drawMoneyBox(int pageNum); + void gui_drawInventory(); + void gui_drawInventoryItem(int index); + void gui_drawCompass(); + void gui_drawScroll(); + void gui_highlightSelectedSpell(bool mode); + void gui_displayCharInventory(int charNum); + void gui_printCharInventoryStats(int charNum); + void gui_printCharacterStats(int index, int redraw, int value); + void gui_changeCharacterStats(int charNum); + void gui_drawCharInventoryItem(int itemIndex); + + int gui_enableControls(); + int gui_disableControls(int controlMode); + void gui_toggleButtonDisplayMode(int shapeIndex, int mode); + void gui_toggleFightButtons(bool disable); + void gui_prepareForSequence(int x, int y, int w, int h, int buttonFlags); + void gui_specialSceneSuspendControls(int controlMode); + void gui_specialSceneRestoreControls(int restoreLamp); + + bool _weaponsDisabled; + int _lastButtonShape; + uint32 _buttonPressTimer; + int _selectedCharacter; + int _compassStep; + int _compassDirectionIndex; + uint32 _compassTimer; + int _charInventoryUnk; + + const CompassDef *_compassDefs; + + void gui_updateInput(); + void gui_triggerEvent(int eventType); + void gui_enableDefaultPlayfieldButtons(); + void gui_enableSequenceButtons(int x, int y, int w, int h, int enableFlags); + void gui_specialSceneRestoreButtons(); + void gui_enableCharInventoryButtons(int charNum); + + void gui_setFaceFramesControlButtons(int index, int xOffs); + void gui_initCharInventorySpecialButtons(int charNum); + void gui_initMagicScrollButtons(); + void gui_initMagicSubmenu(int charNum); + void gui_initButton(int index, int x = -1, int y = -1, int val = -1); + + LoLButtonDef _sceneWindowButton; + + int clickedUpArrow(Button *button); + int clickedDownArrow(Button *button); + int clickedLeftArrow(Button *button); + int clickedRightArrow(Button *button); + int clickedTurnLeftArrow(Button *button); + int clickedTurnRightArrow(Button *button); + int clickedAttackButton(Button *button); + int clickedMagicButton(Button *button); + int clickedMagicSubmenu(Button *button); + int clickedScreen(Button *button); + int clickedPortraitLeft(Button *button); + int clickedLiveMagicBarsLeft(Button *button); + int clickedPortraitEtcRight(Button *button); + int clickedCharInventorySlot(Button *button); + int clickedExitCharInventory(Button *button); + int clickedSceneDropItem(Button *button); + int clickedScenePickupItem(Button *button); + int clickedInventorySlot(Button *button); + int clickedInventoryScroll(Button *button); + int clickedWall(Button *button); + int clickedSequenceWindow(Button *button); + int clickedScroll(Button *button); + int clickedSpellTargetCharacter(Button *button); + int clickedSpellTargetScene(Button *button); + int clickedSceneThrowItem(Button *button); + int clickedOptions(Button *button); + int clickedRestParty(Button *button); + int clickedMoneyBox(Button *button); + int clickedCompass(Button *button); + int clickedAutomap(Button *button); + int clickedLamp(Button *button); + int clickedStatusIcon(Button *button); + + const LoLButtonDef *_buttonData; + const uint8 *_buttonList1; + const uint8 *_buttonList2; + const uint8 *_buttonList3; + const uint8 *_buttonList4; + const uint8 *_buttonList5; + const uint8 *_buttonList6; + const uint8 *_buttonList7; + const uint8 *_buttonList8; + + // text + int characterSays(int track, int charId, bool redraw); + int playCharacterScriptChat(int charId, int mode, int restorePortrait, char *str, EMCState *script, const uint16 *paramList, int16 paramIndex); + void setupDialogueButtons(int numStr, const char *s1, const char *s2, const char *s3); + + TextDisplayer_LoL *_txt; + TextDisplayer_rpg *txt() { return _txt; } + + // emc scripts + void runInitScript(const char *filename, int optionalFunc); + void runInfScript(const char *filename); + void runLevelScript(int block, int flags); + void runLevelScriptCustom(int block, int flags, int charNum, int item, int reg3, int reg4); + + EMCData _scriptData; + bool _suspendScript; + uint16 _scriptDirection; + int16 _globalScriptVars[24]; + + // emc opcode + int olol_setWallType(EMCState *script); + int olol_getWallType(EMCState *script); + int olol_drawScene(EMCState *script); + int olol_rollDice(EMCState *script); + int olol_moveParty(EMCState *script); + int olol_delay(EMCState *script); + int olol_setGameFlag(EMCState *script); + int olol_testGameFlag(EMCState *script); + int olol_loadLevelGraphics(EMCState *script); + int olol_loadBlockProperties(EMCState *script); + int olol_loadMonsterShapes(EMCState *script); + int olol_deleteHandItem(EMCState *script); + int olol_allocItemPropertiesBuffer(EMCState *script); + int olol_setItemProperty(EMCState *script); + int olol_makeItem(EMCState *script); + int olol_placeMoveLevelItem(EMCState *script); + int olol_createLevelItem(EMCState *script); + int olol_getItemPara(EMCState *script); + int olol_getCharacterStat(EMCState *script); + int olol_setCharacterStat(EMCState *script); + int olol_loadLevelShapes(EMCState *script); + int olol_closeLevelShapeFile(EMCState *script); + int olol_loadDoorShapes(EMCState *script); + int olol_initAnimStruct(EMCState *script); + int olol_playAnimationPart(EMCState *script); + int olol_freeAnimStruct(EMCState *script); + int olol_getDirection(EMCState *script); + int olol_characterSurpriseFeedback(EMCState *script); + int olol_setMusicTrack(EMCState *script); + int olol_setSequenceButtons(EMCState *script); + int olol_setDefaultButtonState(EMCState *script); + int olol_checkRectForMousePointer(EMCState *script); + int olol_clearDialogueField(EMCState *script); + int olol_setupBackgroundAnimationPart(EMCState *script); + int olol_startBackgroundAnimation(EMCState *script); + int olol_fadeToBlack(EMCState *script); + int olol_fadePalette(EMCState *script); + int olol_loadBitmap(EMCState *script); + int olol_stopBackgroundAnimation(EMCState *script); + int olol_getGlobalScriptVar(EMCState *script); + int olol_setGlobalScriptVar(EMCState *script); + int olol_getGlobalVar(EMCState *script); + int olol_setGlobalVar(EMCState *script); + int olol_triggerDoorSwitch(EMCState *script); + int olol_checkEquippedItemScriptFlags(EMCState *script); + int olol_setDoorState(EMCState *script); + int olol_updateBlockAnimations(EMCState *script); + int olol_assignLevelDecorationShape(EMCState *script); + int olol_resetBlockShapeAssignment(EMCState *script); + int olol_copyRegion(EMCState *script); + int olol_initMonster(EMCState *script); + int olol_fadeClearSceneWindow(EMCState *script); + int olol_fadeSequencePalette(EMCState *script); + int olol_redrawPlayfield(EMCState *script); + int olol_loadNewLevel(EMCState *script); + int olol_getNearestMonsterFromCharacter(EMCState *script); + int olol_dummy0(EMCState *script); + int olol_loadMonsterProperties(EMCState *script); + int olol_battleHitSkillTest(EMCState *script); + int olol_inflictDamage(EMCState *script); + int olol_moveMonster(EMCState *script); + int olol_setupDialogueButtons(EMCState *script); + int olol_giveTakeMoney(EMCState *script); + int olol_checkMoney(EMCState *script); + int olol_setScriptTimer(EMCState *script); + int olol_createHandItem(EMCState *script); + int olol_playAttackSound(EMCState *script); + int olol_addRemoveCharacter(EMCState *script); + int olol_giveItem(EMCState *script); + int olol_loadTimScript(EMCState *script); + int olol_runTimScript(EMCState *script); + int olol_releaseTimScript(EMCState *script); + int olol_initSceneWindowDialogue(EMCState *script); + int olol_restoreAfterSceneWindowDialogue(EMCState *script); + int olol_getItemInHand(EMCState *script); + int olol_checkMagic(EMCState *script); + int olol_giveItemToMonster(EMCState *script); + int olol_loadLangFile(EMCState *script); + int olol_playSoundEffect(EMCState *script); + int olol_processDialogue(EMCState *script); + int olol_stopTimScript(EMCState *script); + int olol_getWallFlags(EMCState *script); + int olol_changeMonsterStat(EMCState *script); + int olol_getMonsterStat(EMCState *script); + int olol_releaseMonsterShapes(EMCState *script); + int olol_playCharacterScriptChat(EMCState *script); + int olol_playEnvironmentalSfx(EMCState *script); + int olol_update(EMCState *script); + int olol_healCharacter(EMCState *script); + int olol_drawExitButton(EMCState *script); + int olol_loadSoundFile(EMCState *script); + int olol_playMusicTrack(EMCState *script); + int olol_deleteMonstersFromBlock(EMCState *script); + int olol_countBlockItems(EMCState *script); + int olol_characterSkillTest(EMCState *script); + int olol_countAllMonsters(EMCState *script); + int olol_playEndSequence(EMCState *script); + int olol_stopPortraitSpeechAnim(EMCState *script); + int olol_setPaletteBrightness(EMCState *script); + int olol_calcInflictableDamage(EMCState *script); + int olol_getInflictedDamage(EMCState *script); + int olol_checkForCertainPartyMember(EMCState *script); + int olol_printMessage(EMCState *script); + int olol_deleteLevelItem(EMCState *script); + int olol_calcInflictableDamagePerItem(EMCState *script); + int olol_distanceAttack(EMCState *script); + int olol_removeCharacterEffects(EMCState *script); + int olol_checkInventoryFull(EMCState *script); + int olol_moveBlockObjects(EMCState *script); + int olol_addSpellToScroll(EMCState *script); + int olol_playDialogueText(EMCState *script); + int olol_playDialogueTalkText(EMCState *script); + int olol_checkMonsterTypeHostility(EMCState *script); + int olol_setNextFunc(EMCState *script); + int olol_dummy1(EMCState *script); + int olol_suspendMonster(EMCState *script); + int olol_setScriptTextParameter(EMCState *script); + int olol_triggerEventOnMouseButtonClick(EMCState *script); + int olol_printWindowText(EMCState *script); + int olol_countSpecificMonsters(EMCState *script); + int olol_updateBlockAnimations2(EMCState *script); + int olol_checkPartyForItemType(EMCState *script); + int olol_blockDoor(EMCState *script); + int olol_resetTimDialogueState(EMCState *script); + int olol_getItemOnPos(EMCState *script); + int olol_removeLevelItem(EMCState *script); + int olol_savePage5(EMCState *script); + int olol_restorePage5(EMCState *script); + int olol_initDialogueSequence(EMCState *script); + int olol_restoreAfterDialogueSequence(EMCState *script); + int olol_setSpecialSceneButtons(EMCState *script); + int olol_restoreButtonsAfterSpecialScene(EMCState *script); + int olol_prepareSpecialScene(EMCState *script); + int olol_restoreAfterSpecialScene(EMCState *script); + int olol_assignCustomSfx(EMCState *script); + int olol_findAssignedMonster(EMCState *script); + int olol_checkBlockForMonster(EMCState *script); + int olol_crossFadeRegion(EMCState *script); + int olol_calcCoordinatesAddDirectionOffset(EMCState *script); + int olol_resetPortraitsAndDisableSysTimer(EMCState *script); + int olol_enableSysTimer(EMCState *script); + int olol_checkNeedSceneRestore(EMCState *script); + int olol_getNextActiveCharacter(EMCState *script); + int olol_paralyzePoisonCharacter(EMCState *script); + int olol_drawCharPortrait(EMCState *script); + int olol_removeInventoryItem(EMCState *script); + int olol_getAnimationLastPart(EMCState *script); + int olol_assignSpecialGuiShape(EMCState *script); + int olol_findInventoryItem(EMCState *script); + int olol_restoreFadePalette(EMCState *script); + int olol_getSelectedCharacter(EMCState *script); + int olol_setHandItem(EMCState *script); + int olol_drinkBezelCup(EMCState *script); + int olol_changeItemTypeOrFlag(EMCState *script); + int olol_placeInventoryItemInHand(EMCState *script); + int olol_castSpell(EMCState *script); + int olol_pitDrop(EMCState *script); + int olol_increaseSkill(EMCState *script); + int olol_paletteFlash(EMCState *script); + int olol_restoreMagicShroud(EMCState *script); + int olol_disableControls(EMCState *script); + int olol_enableControls(EMCState *script); + int olol_shakeScene(EMCState *script); + int olol_gasExplosion(EMCState *script); + int olol_calcNewBlockPosition(EMCState *script); + int olol_crossFadeScene(EMCState *script); + int olol_updateDrawPage2(EMCState *script); + int olol_setMouseCursor(EMCState *script); + int olol_characterSays(EMCState *script); + int olol_queueSpeech(EMCState *script); + int olol_getItemPrice(EMCState *script); + int olol_getLanguage(EMCState *script); + + // tim scripts + TIM *_activeTim[10]; + + // tim opcode + void setupOpcodeTable(); + + Common::Array<const TIMOpcode *> _timIntroOpcodes; + int tlol_setupPaletteFade(const TIM *tim, const uint16 *param); + int tlol_loadPalette(const TIM *tim, const uint16 *param); + int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param); + int tlol_processWsaFrame(const TIM *tim, const uint16 *param); + int tlol_displayText(const TIM *tim, const uint16 *param); + + Common::Array<const TIMOpcode *> _timOutroOpcodes; + int tlol_fadeInScene(const TIM *tim, const uint16 *param); + int tlol_unusedResourceFunc(const TIM *tim, const uint16 *param); + int tlol_fadeInPalette(const TIM *tim, const uint16 *param); + int tlol_fadeSoundOut(const TIM *tim, const uint16 *param); + int tlol_displayAnimFrame(const TIM *tim, const uint16 *param); + int tlol_delayForChat(const TIM *tim, const uint16 *param); + int tlol_fadeOutSound(const TIM *tim, const uint16 *param); + + Common::Array<const TIMOpcode *> _timIngameOpcodes; + int tlol_initSceneWindowDialogue(const TIM *tim, const uint16 *param); + int tlol_restoreAfterSceneWindowDialogue(const TIM *tim, const uint16 *param); + int tlol_giveItem(const TIM *tim, const uint16 *param); + int tlol_setPartyPosition(const TIM *tim, const uint16 *param); + int tlol_fadeClearWindow(const TIM *tim, const uint16 *param); + int tlol_copyRegion(const TIM *tim, const uint16 *param); + int tlol_characterChat(const TIM *tim, const uint16 *param); + int tlol_drawScene(const TIM *tim, const uint16 *param); + int tlol_update(const TIM *tim, const uint16 *param); + int tlol_clearTextField(const TIM *tim, const uint16 *param); + int tlol_loadSoundFile(const TIM *tim, const uint16 *param); + int tlol_playMusicTrack(const TIM *tim, const uint16 *param); + int tlol_playDialogueTalkText(const TIM *tim, const uint16 *param); + int tlol_playSoundEffect(const TIM *tim, const uint16 *param); + int tlol_startBackgroundAnimation(const TIM *tim, const uint16 *param); + int tlol_stopBackgroundAnimation(const TIM *tim, const uint16 *param); + + // translation + int _lang; + + uint8 *_landsFile; + uint8 *_levelLangFile; + + int _lastUsedStringBuffer; + char _stringBuffer[5][512]; // TODO: The original used a size of 512, it looks a bit large. + // Maybe we can someday reduce the size. + char *getLangString(uint16 id); + uint8 *getTableEntry(uint8 *buffer, uint16 id); + void decodeSjis(const char *src, char *dst); + int decodeCyrillic(const char *src, char *dst); + + static const char *const _languageExt[]; + + // graphics + void setupScreenDims(); + void initSceneWindowDialogue(int controlMode); + void restoreAfterSceneWindowDialogue(int redraw); + void initDialogueSequence(int controlMode, int pageNum); + void restoreAfterDialogueSequence(int controlMode); + void resetPortraitsAndDisableSysTimer(); + void toggleSelectedCharacterFrame(bool mode); + void fadeText(); + void transformRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage); + void setPaletteBrightness(const Palette &srcPal, int brightness, int modifier); + void generateBrightnessPalette(const Palette &src, Palette &dst, int brightness, int16 modifier); + void generateFlashPalette(const Palette &src, Palette &dst, int colorFlags); + void createTransparencyTables(); + void updateSequenceBackgroundAnimations(); + + uint8 **_itemIconShapes; + int _numItemIconShapes; + uint8 **_itemShapes; + int _numItemShapes; + uint8 **_gameShapes; + int _numGameShapes; + uint8 **_thrownShapes; + int _numThrownShapes; + uint8 **_effectShapes; + int _numEffectShapes; + + const int8 *_gameShapeMap; + + uint8 *_characterFaceShapes[40][3]; + + // characters + bool addCharacter(int id); + void setTemporaryFaceFrame(int charNum, int frame, int updateDelay, int redraw); + void setTemporaryFaceFrameForAllCharacters(int frame, int updateDelay, int redraw); + void setCharacterUpdateEvent(int charNum, int updateType, int updateDelay, int overwrite); + int countActiveCharacters(); + void loadCharFaceShapes(int charNum, int id); + void calcCharPortraitXpos(); + + void updatePortraitSpeechAnim(); + void stopPortraitSpeechAnim(); + void initTextFading(int textType, int clearField); + void setCharFaceFrame(int charNum, int frameNum); + void faceFrameRefresh(int charNum); + + void recalcCharacterStats(int charNum); + int calculateCharacterStats(int charNum, int index); + int calculateProtection(int index); + + void setCharacterMagicOrHitPoints(int charNum, int type, int points, int mode); + void increaseExperience(int charNum, int skill, uint32 points); + void increaseCharacterHitpoints(int charNum, int points, bool ignoreDeath); + + LoLCharacter *_characters; + uint16 _activeCharsXpos[3]; + + int _portraitSpeechAnimMode; + int _textColorFlag; + uint32 _palUpdateTimer; + uint32 _updatePortraitNext; + + int _loadLevelFlag; + int _activeMagicMenu; + uint16 _scriptCharacterCycle; + int _charStatsTemp[5]; + + const LoLCharacter *_charDefaults; + int _charDefaultsSize; + + const uint16 *_charDefsMan; + const uint16 *_charDefsWoman; + const uint16 *_charDefsKieran; + const uint16 *_charDefsAkshel; + const int32 *_expRequirements; + + // lamp + void resetLampStatus(); + void setLampMode(bool lampOn); + void updateLampStatus(); + + int8 _lampEffect; + int _brightness; + int _lampOilStatus; + uint32 _lampStatusTimer; + bool _lampStatusSuspended; + + // level + void loadLevel(int index); + void addLevelItems(); + void loadLevelWallData(int fileIndex, bool mapShapes); + void assignBlockItem(LevelBlockProperty *l, uint16 item); + int assignLevelDecorationShapes(int index); + uint8 *getLevelDecorationShapes(int index); + void releaseDecorations(int first = 0, int num = 400); + void restoreTempDataAdjustMonsterStrength(int index); + void loadBlockProperties(const char *cmzFile); + const uint8 *getBlockFileData(int levelIndex); + void loadLevelShpDat(const char *shpFile, const char *datFile, bool flag); + void loadLevelGraphics(const char *file, int specialColor, int weight, int vcnLen, int vmpLen, const char *palFile); + + void resetItems(int flag); + void disableMonsters(); + void resetBlockProperties(); + bool testWallFlag(int block, int direction, int flag); + bool testWallInvisibility(int block, int direction); + + void drawScene(int pageNum); + + void drawSceneShapes(int start = 0); + void drawDecorations(int index); + void drawBlockEffects(int index, int type); + void drawSpecialGuiShape(int pageNum); + void setWallType(int block, int wall, int val); + void updateDrawPage2(); + + void prepareSpecialScene(int fieldType, int hasDialogue, int suspendGui, int allowSceneUpdate, int controlMode, int fadeFlag); + int restoreAfterSpecialScene(int fadeFlag, int redrawPlayField, int releaseTimScripts, int sceneUpdateMode); + + void setSequenceButtons(int x, int y, int w, int h, int enableFlags); + void setSpecialSceneButtons(int x, int y, int w, int h, int enableFlags); + void setDefaultButtonState(); + + void updateCompass(); + + void moveParty(uint16 direction, int unk1, int unk2, int buttonShape); + void notifyBlockNotPassable(int scrollFlag); + virtual bool checkBlockPassability(uint16 block, uint16 direction); + + uint16 calcBlockIndex(uint16 x, uint16 y); + void calcCoordinates(uint16 &x, uint16 &y, int block, uint16 xOffs, uint16 yOffs); + void calcCoordinatesForSingleCharacter(int charNum, uint16 &x, uint16 &y); + void calcCoordinatesAddDirectionOffset(uint16 &x, uint16 &y, int direction); + + int clickedDoorSwitch(uint16 block, uint16 direction); + int clickedNiche(uint16 block, uint16 direction); + + void movePartySmoothScrollBlocked(int speed); + void movePartySmoothScrollUp(int speed); + void movePartySmoothScrollDown(int speed); + void movePartySmoothScrollLeft(int speed); + void movePartySmoothScrollRight(int speed); + void movePartySmoothScrollTurnLeft(int speed); + void movePartySmoothScrollTurnRight(int speed); + + void pitDropScroll(int numSteps); + + void shakeScene(int duration, int width, int height, int restore); + void processGasExplosion(int soundId); + + int smoothScrollDrawSpecialGuiShape(int pageNum); + + int _blockDoor; + + int _smoothScrollModeNormal; + + const uint8 *_scrollXTop; + const uint8 *_scrollYTop; + const uint8 *_scrollXBottom; + const uint8 *_scrollYBottom; + + int _nextScriptFunc; + int _lvlShapeIndex; + bool _partyAwake; + + uint8 *_specialGuiShape; + uint16 _specialGuiShapeX; + uint16 _specialGuiShapeY; + uint16 _specialGuiShapeMirrorFlag; + + Common::String _lastOverridePalFile; + int _lastSpecialColor; + int _lastSpecialColorWeight; + + uint8 *_transparencyTable2; + uint8 *_transparencyTable1; + + int _loadSuppFilesFlag; + uint8 *_wllAutomapData; + + uint16 _partyPosX; + uint16 _partyPosY; + + Common::SeekableReadStream *_lvlShpFileHandle; + + int _shpDmX; + int _shpDmY; + uint16 _dmScaleW; + uint16 _dmScaleH; + + int _lastMouseRegion; + int _seqWindowX1, _seqWindowY1, _seqWindowX2, _seqWindowY2, _seqTrigger; + int _spsWindowX, _spsWindowY, _spsWindowW, _spsWindowH; + + uint8 *_tempBuffer5120; + + const char *const *_levelDatList; + const char *const *_levelShpList; + + const int8 *_dscWalls; + + const uint8 *_dscOvlMap; + const uint8 *_dscShapeOvlIndex; + const uint16 *_dscShapeScaleW; + const uint16 *_dscShapeScaleH; + const int8 *_dscShapeY; + + const uint16 *_dscDoorMonsterScaleTable; + const uint16 *_dscDoor4; + const int16 *_dscDoorMonsterX; + const int16 *_dscDoorMonsterY; + + // objects (item/monster common) + LoLObject *findObject(uint16 index); + int calcObjectPosition(LoLObject *obj, uint16 direction); + void removeAssignedObjectFromBlock(LevelBlockProperty *l, uint16 id); + void removeDrawObjectFromBlock(LevelBlockProperty *l, uint16 id); + void assignObjectToBlock(uint16 *assignedBlockObjects, uint16 id); + + // items + void giveCredits(int credits, int redraw); + void takeCredits(int credits, int redraw); + Item makeItem(int itemType, int curFrame, int flags); + void placeMoveLevelItem(Item itemIndex, int level, int block, int xOffs, int yOffs, int flyingHeight); + bool addItemToInventory(Item itemIndex); + bool isItemMoveable(Item itemIndex); + void deleteItem(Item itemIndex); + void runItemScript(int charNum, Item item, int flags, int next, int reg4); + void setHandItem(Item itemIndex); + bool itemEquipped(int charNum, uint16 itemType); + + void setItemPosition(Item item, uint16 x, uint16 y, int flyingHeight, int moveable); + void removeLevelItem(Item item, int block); + bool launchObject(int objectType, Item item, int startX, int startY, int flyingHeight, int direction, int, int attackerId, int c); + void endObjectFlight(FlyingObject *t, int x, int y, int collisionType); + void processObjectFlight(FlyingObject *t, int x, int y); + void updateObjectFlightPosition(FlyingObject *t); + void objectFlightProcessHits(FlyingObject *t, int x, int y, int collisionType); + void updateFlyingObject(FlyingObject *t); + + void assignItemToBlock(uint16 *assignedBlockObjects, int id); + int checkDrawObjectSpace(int x1, int y1, int x2, int y2); + int checkSceneForItems(uint16 *blockDrawObjects, int color); + + uint8 _moneyColumnHeight[5]; + uint16 _credits; + + LoLItem *_itemsInPlay; + ItemProperty *_itemProperties; + + Item _itemInHand; + Item _inventory[48]; + Item _inventoryCurItem; + + int _lastCharInventory; + uint16 _charStatusFlags[3]; + int _emcLastItem; + + FlyingObject *_flyingObjects; + + EMCData _itemScript; + + const uint8 *_charInvIndex; + const uint8 *_charInvDefs; + const uint16 *_inventorySlotDesc; + const uint16 *_itemCost; + const uint8 *_stashSetupData; + const int8 *_sceneItemOffs; + const FlyingObjectShape *_flyingItemShapes; + + // monsters + void loadMonsterShapes(const char *file, int monsterIndex, int b); + void releaseMonsterShapes(int monsterIndex); + int deleteMonstersFromBlock(int block); + void setMonsterMode(LoLMonster *monster, int mode); + bool updateMonsterAdjustBlocks(LoLMonster *monster); + void placeMonster(LoLMonster *monster, uint16 x, uint16 y); + int calcMonsterDirection(uint16 x1, uint16 y1, uint16 x2, uint16 y2); + void setMonsterDirection(LoLMonster *monster, int dir); + void monsterDropItems(LoLMonster *monster); + void giveItemToMonster(LoLMonster *monster, Item item); + int checkBlockBeforeObjectPlacement(uint16 x, uint16 y, uint16 objectWidth, uint16 testFlag, uint16 wallFlag); + int testBlockPassability(int block, int x, int y, int objectWidth, int testFlag, int wallFlag); + int calcMonsterSkillLevel(int id, int a); + int checkBlockOccupiedByParty(int x, int y, int testFlag); + const uint16 *getCharacterOrMonsterStats(int id); + uint16 *getCharacterOrMonsterItemsMight(int id); + uint16 *getCharacterOrMonsterProtectionAgainstItems(int id); + + void drawBlockObjects(int blockArrayIndex); + void drawMonster(uint16 id); + int getMonsterCurFrame(LoLMonster *m, uint16 dirFlags); + void reassignDrawObjects(uint16 direction, uint16 itemIndex, LevelBlockProperty *l, bool flag); + void redrawSceneItem(); + void calcSpriteRelPosition(uint16 x1, uint16 y1, int &x2, int &y2, uint16 direction); + void drawDoor(uint8 *shape, uint8 *doorPalette, int index, int unk2, int w, int h, int flags); + void drawDoorOrMonsterEquipment(uint8 *shape, uint8 *objectPalette, int x, int y, int flags, const uint8 *brightnessOverlay); + uint8 *drawItemOrMonster(uint8 *shape, uint8 *monsterPalette, int x, int y, int fineX, int fineY, int flags, int tblValue, bool vflip); + int calcDrawingLayerParameters(int srcX, int srcY, int &x2, int &y2, uint16 &w, uint16 &h, uint8 *shape, int vflip); + + void updateMonster(LoLMonster *monster); + void moveMonster(LoLMonster *monster); + void walkMonster(LoLMonster *monster); + bool chasePartyWithDistanceAttacks(LoLMonster *monster); + void chasePartyWithCloseAttacks(LoLMonster *monster); + int walkMonsterCalcNextStep(LoLMonster *monster); + int checkForPossibleDistanceAttack(uint16 monsterBlock, int direction, int distance, uint16 curBlock); + int walkMonsterCheckDest(int x, int y, LoLMonster *monster, int unk); + void getNextStepCoords(int16 monsterX, int16 monsterY, int &newX, int &newY, uint16 direction); + void alignMonsterToParty(LoLMonster *monster); + void moveStrayingMonster(LoLMonster *monster); + void killMonster(LoLMonster *monster); + + LoLMonster *_monsters; + LoLMonsterProperty *_monsterProperties; + uint8 **_monsterDecorationShapes; + uint8 _monsterAnimType[3]; + uint16 _monsterCurBlock; + int _objectLastDirection; + + const uint16 *_monsterModifiers1; + const uint16 *_monsterModifiers2; + const uint16 *_monsterModifiers3; + const uint16 *_monsterModifiers4; + + const int8 *_monsterShiftOffs; + const uint8 *_monsterDirFlags; + const uint8 *_monsterScaleX; + const uint8 *_monsterScaleY; + const uint16 *_monsterScaleWH; + + // misc + void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false); + + const KyraRpgGUISettings *guiSettings(); + + uint8 _compassBroken; + uint8 _drainMagic; + uint16 _globalScriptVars2[8]; + + uint8 *_pageBuffer1; + uint8 *_pageBuffer2; + + static const KyraRpgGUISettings _guiSettings; + + // spells + typedef Common::Functor1Mem<ActiveSpell *, int, LoLEngine> SpellProc; + Common::Array<const SpellProc *> _spellProcs; + typedef void (LoLEngine::*SpellProcCallback)(WSAMovie_v2 *, int, int); + + int castSpell(int charNum, int spellType, int spellLevel); + + int castSpark(ActiveSpell *a); + int castHeal(ActiveSpell *a); + int castIce(ActiveSpell *a); + int castFireball(ActiveSpell *a); + int castHandOfFate(ActiveSpell *a); + int castMistOfDoom(ActiveSpell *a); + int castLightning(ActiveSpell *a); + int castFog(ActiveSpell *a); + int castSwarm(ActiveSpell *a); + int castVaelansCube(ActiveSpell *a); + int castGuardian(ActiveSpell *a); + int castHealOnSingleCharacter(ActiveSpell *a); + + int processMagicSpark(int charNum, int spellLevel); + int processMagicHealSelectTarget(); + int processMagicHeal(int charNum, int spellLevel); + int processMagicIce(int charNum, int spellLevel); + int processMagicFireball(int charNum, int spellLevel); + int processMagicHandOfFate(int spellLevel); + int processMagicMistOfDoom(int charNum, int spellLevel); + int processMagicLightning(int charNum, int spellLevel); + int processMagicFog(); + int processMagicSwarm(int charNum, int damage); + int processMagicVaelansCube(); + int processMagicGuardian(int charNum); + + void callbackProcessMagicSwarm(WSAMovie_v2 *mov, int x, int y); + void callbackProcessMagicLightning(WSAMovie_v2 *mov, int x, int y); + + void drinkBezelCup(int a, int charNum); + + void addSpellToScroll(int spell, int charNum); + void transferSpellToScollAnimation(int charNum, int spell, int slot); + + void playSpellAnimation(WSAMovie_v2 *mov, int firstFrame, int lastFrame, int frameDelay, int x, int y, SpellProcCallback callback, uint8 *pal1, uint8 *pal2, int fadeDelay, bool restoreScreen); + int checkMagic(int charNum, int spellNum, int spellLevel); + int getSpellTargetBlock(int currentBlock, int direction, int maxDistance, uint16 &targetBlock); + void inflictMagicalDamage(int target, int attacker, int damage, int index, int hitType); + void inflictMagicalDamageForBlock(int block, int attacker, int damage, int index); + + ActiveSpell _activeSpell; + int8 _availableSpells[8]; + int _selectedSpell; + const SpellProperty *_spellProperties; + //int _spellPropertiesSize; + int _subMenuIndex; + + LightningProperty *_lightningProps; + int16 _lightningCurSfx; + int16 _lightningDiv; + int16 _lightningFirstSfx; + int16 _lightningSfxFrame; + + uint8 *_healOverlay; + uint8 _swarmSpellStatus; + + uint8 **_fireballShapes; + int _numFireballShapes; + uint8 **_healShapes; + int _numHealShapes; + uint8 **_healiShapes; + int _numHealiShapes; + + static const MistOfDoomAnimData _mistAnimData[]; + + const uint8 *_updateSpellBookCoords; + const uint8 *_updateSpellBookAnimData; + const uint8 *_healShapeFrames; + const int16 *_fireBallCoords; + + // fight + int battleHitSkillTest(int16 attacker, int16 target, int skill); + int calcInflictableDamage(int16 attacker, int16 target, int hitType); + int inflictDamage(uint16 target, int damage, uint16 attacker, int skill, int flags); + void characterHitpointsZero(int16 charNum, int a); + void removeCharacterEffects(LoLCharacter *c, int first, int last); + int calcInflictableDamagePerItem(int16 attacker, int16 target, uint16 itemMight, int index, int hitType); + void checkForPartyDeath(); + + void applyMonsterAttackSkill(LoLMonster *monster, int16 target, int16 damage); + void applyMonsterDefenseSkill(LoLMonster *monster, int16 attacker, int flags, int skill, int damage); + int removeCharacterItem(int charNum, int itemFlags); + int paralyzePoisonCharacter(int charNum, int typeFlag, int immunityFlags, int hitChance, int redraw); + void paralyzePoisonAllCharacters(int typeFlag, int immunityFlags, int hitChance); + void stunCharacter(int charNum); + void restoreSwampPalette(); + + void launchMagicViper(); + + void breakIceWall(uint8 *pal1, uint8 *pal2); + + uint16 getNearestMonsterFromCharacter(int charNum); + uint16 getNearestMonsterFromCharacterForBlock(uint16 block, int charNum); + uint16 getNearestMonsterFromPos(int x, int y); + uint16 getNearestPartyMemberFromPos(int x, int y); + + int _partyDamageFlags; + + // magic atlas + void displayAutomap(); + void updateAutoMap(uint16 block); + bool updateAutoMapIntern(uint16 block, uint16 x, uint16 y, int16 xOffs, int16 yOffs); + void loadMapLegendData(int level); + void drawMapPage(int pageNum); + bool automapProcessButtons(int inputFlag); + void automapBackButton(); + void automapForwardButton(); + void redrawMapCursor(); + void drawMapBlockWall(uint16 block, uint8 wall, int x, int y, int direction); + void drawMapShape(uint8 wall, int x, int y, int direction); + int mapGetStartPosX(); + int mapGetStartPosY(); + void mapIncludeLegendData(int type); + void printMapText(uint16 stringId, int x, int y); + void printMapExitButtonText(); + + uint8 _currentMapLevel; + uint8 *_mapOverlay; + const uint8 **_automapShapes; + const uint16 *_autoMapStrings; + MapLegendData *_defaultLegendData; + uint8 *_mapCursorOverlay; + uint8 _automapTopLeftX; + uint8 _automapTopLeftY; + static const int8 _mapCoords[12][4]; + bool _mapUpdateNeeded; + + // unneeded + void setWalkspeed(uint8) {} + void removeHandItem() {} + bool lineIsPassable(int, int) { return false; } + + // save + Common::Error loadGameState(int slot); + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + + void *generateMonsterTempData(LevelTempData *tmp); + void restoreBlockTempData(int levelIndex); + void restoreMonsterTempData(LevelTempData *tmp); + void releaseMonsterTempData(LevelTempData *tmp); + + Graphics::Surface *generateSaveThumbnail() const; +}; + +class HistoryPlayer { +public: + HistoryPlayer(LoLEngine *vm); + ~HistoryPlayer(); + + void play(); +private: + OSystem *_system; + LoLEngine *_vm; + Screen *_screen; + + int _x, _y, _width, _height; + int _frame; + Movie *_wsa; + + void loadWsa(const char *filename); + void playWsa(bool direction); + void restoreWsaBkgd(); + + Movie *_fireWsa; + int _fireFrame; + uint32 _nextFireTime; + void updateFire(); +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/magic_eob.cpp b/engines/kyra/engine/magic_eob.cpp new file mode 100644 index 0000000000..d443b85c18 --- /dev/null +++ b/engines/kyra/engine/magic_eob.cpp @@ -0,0 +1,1381 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "common/system.h" + +namespace Kyra { + +void EoBCoreEngine::useMagicBookOrSymbol(int charIndex, int type) { + EoBCharacter *c = &_characters[charIndex]; + _openBookSpellLevel = c->slotStatus[3]; + _openBookSpellSelectedItem = c->slotStatus[2]; + _openBookSpellListOffset = c->slotStatus[4]; + _openBookChar = charIndex; + _openBookType = type; + _openBookSpellList = (type == 1) ? _clericSpellList : _mageSpellList; + _openBookAvailableSpells = (type == 1) ? c->clericSpells : c->mageSpells; + int8 *tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem; + + if (*tmp <= 0) { + for (bool loop = true; loop && _openBookSpellSelectedItem < 10;) { + tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem; + if (*tmp > 0) { + if (_openBookSpellSelectedItem > 5) { + _openBookSpellListOffset = 6; + _openBookSpellSelectedItem -= 6; + } + loop = false; + } else { + _openBookSpellSelectedItem++; + } + } + + if (_openBookSpellSelectedItem == 10) { + _openBookSpellListOffset = 0; + _openBookSpellSelectedItem = 6; + } + } + + if (!_updateFlags) + _screen->copyRegion(64, 121, 0, 0, 112, 56, 0, 10, Screen::CR_NO_P_CHECK); + _updateFlags = 1; + gui_setPlayFieldButtons(); + gui_drawSpellbook(); +} + +void EoBCoreEngine::useMagicScroll(int charIndex, int type, int weaponSlot) { + _openBookCharBackup = _openBookChar; + _openBookTypeBackup = _openBookType; + _castScrollSlot = weaponSlot + 1; + _openBookChar = charIndex; + _openBookType = type <= _clericSpellOffset ? 0 : 1; + castSpell(type, weaponSlot); +} + +void EoBCoreEngine::usePotion(int charIndex, int weaponSlot) { + EoBCharacter *c = &_characters[charIndex]; + + int val = deleteInventoryItem(charIndex, weaponSlot); + snd_playSoundEffect(10); + + if (_flags.gameID == GI_EOB1) + val--; + + switch (val) { + case 0: + sparkEffectDefensive(charIndex); + c->strengthCur = 22; + c->strengthExtCur = 0; + setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 7, 1); + break; + + case 1: + sparkEffectDefensive(charIndex); + modifyCharacterHitpoints(charIndex, rollDice(2, 4, 2)); + break; + + case 2: + sparkEffectDefensive(charIndex); + modifyCharacterHitpoints(charIndex, rollDice(3, 8, 3)); + break; + + case 3: + statusAttack(charIndex, 2, _potionStrings[0], 0, 1, 8, 1); + c->effectFlags &= ~0x2000; + if (c->flags & 2) + return; + break; + + case 4: + sparkEffectDefensive(charIndex); + c->food = 100; + if (_currentControlMode) + gui_drawCharPortraitWithStats(charIndex); + break; + + case 5: + sparkEffectDefensive(charIndex); + c->effectFlags |= 0x10000; + setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 12, 1); + snd_playSoundEffect(100); + gui_drawCharPortraitWithStats(charIndex); + break; + + case 6: + sparkEffectDefensive(charIndex); + c->effectFlags |= 0x40; + gui_drawCharPortraitWithStats(charIndex); + break; + + case 7: + sparkEffectDefensive(charIndex); + neutralizePoison(charIndex); + break; + + default: + break; + } + + _txt->printMessage(_potionStrings[1], -1, c->name, _potionEffectStrings[val]); +} + +void EoBCoreEngine::useWand(int charIndex, int weaponSlot) { + int v = _items[_characters[charIndex].inventory[weaponSlot]].value; + if (!v) { + _txt->printMessage(_wandStrings[0]); + return; + } + + if (v != 5) + useMagicScroll(charIndex, _wandTypes[v], weaponSlot); + else if (_flags.gameID == GI_EOB2) + useMagicScroll(charIndex, 64, weaponSlot); + else { + uint16 bl1 = calcNewBlockPosition(_currentBlock, _currentDirection); + uint16 bl2 = calcNewBlockPosition(bl1, _currentDirection); + snd_playSoundEffect(98); + sparkEffectOffensive(); + + if ((_wllWallFlags[_levelBlockProperties[bl2].walls[_currentDirection ^ 2]] & 4) && !(_levelBlockProperties[bl2].flags & 7) && (_levelBlockProperties[bl1].flags & 7)) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != bl1) + continue; + placeMonster(&_monsters[i], bl2, -1); + _sceneUpdateRequired = true; + } + } else { + _txt->printMessage(_wandStrings[1]); + } + } +} + +void EoBCoreEngine::castSpell(int spell, int weaponSlot) { + EoBSpell *s = &_spells[spell]; + EoBCharacter *c = &_characters[_openBookChar]; + _activeSpell = spell; + + if ((s->flags & 0x100) && (c->effectFlags & 0x40)) + // remove invisibility effect + removeCharacterEffect(_flags.gameID == GI_EOB1 ? 8 : 10, _openBookChar, 1); + + int ci = _openBookChar; + if (ci > 3) + ci -= 2; + + _activeSpellCharacterPos = _dropItemDirIndex[(_currentDirection << 2) + ci]; + + if (s->flags & 0x400) { + if (c->inventory[0] && c->inventory[1]) { + printWarning(_magicStrings1[2]); + return; + } + + if (isMagicEffectItem(c->inventory[0]) || isMagicEffectItem(c->inventory[1])) { + printWarning(_magicStrings1[3]); + return; + } + } + + if (!(_flags.gameID == GI_EOB2 && _activeSpell == 62)) { + if (!_castScrollSlot) { + int8 tmp = _openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem]; + if (_openBookSpellListOffset + _openBookSpellSelectedItem < 8) + memmove(&_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem], &_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem + 1], 8 - (_openBookSpellListOffset + _openBookSpellSelectedItem)); + _openBookAvailableSpells[_openBookSpellLevel * 10 + 8] = -tmp; + if (_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem] < 0) { + if (--_openBookSpellSelectedItem == -1) { + if (_openBookSpellListOffset) { + _openBookSpellListOffset = 0; + _openBookSpellSelectedItem = 5; + } else { + _openBookSpellSelectedItem = 6; + } + } + } + } else if (weaponSlot != -1) { + updateUsedCharacterHandItem(_openBookChar, weaponSlot); + } + } + + _txt->printMessage(_magicStrings1[4], -1, c->name, s->name); + + if (s->flags & 0x20) { + castOnWhomDialogue(); + return; + } + + _activeSpellCharId = _openBookChar; + startSpell(spell); +} + +void EoBCoreEngine::removeCharacterEffect(int spell, int charIndex, int showWarning) { + assert(spell >= 0); + EoBCharacter *c = &_characters[charIndex]; + EoBSpell *s = &_spells[spell]; + + if (showWarning) { + int od = _screen->curDimIndex(); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 3 : 2], c->name, s->name).c_str()); + _screen->setScreenDim(od); + _screen->setFont(of); + } + + if (s->endCallback) + (this->*s->endCallback)(c); + + if (s->flags & 1) + c->effectFlags &= ~s->effectFlags; + + if (s->flags & 4) + _partyEffectFlags &= ~s->effectFlags; + + if (s->flags & 0x200) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (!testCharacter(i, 2) && !(s->flags & 0x800)) + continue; + _characters[i].effectFlags &= ~s->effectFlags; + } + } + + if (s->flags & 0x2) + recalcArmorClass(_activeSpellCharId); + + if (showWarning) { + if (s->flags & 0x20A0) + gui_drawCharPortraitWithStats(charIndex); + else if (s->flags & 0x40) + gui_drawAllCharPortraitsWithStats(); + } +} + +void EoBCoreEngine::removeAllCharacterEffects(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + c->effectFlags = 0; + memset(c->effectsRemainder, 0, 4); + + for (int i = 0; i < 10; i++) { + if (c->events[i] < 0) + removeCharacterEffect(-c->events[i], charIndex, 0); + c->timers[i] = 0; + c->events[i] = 0; + } + + setupCharacterTimers(); + recalcArmorClass(charIndex); + c->disabledSlots = 0; + c->slotStatus[0] = c->slotStatus[1] = 0; + c->damageTaken = 0; + c->strengthCur = c->strengthMax; + c->strengthExtCur = c->strengthExtMax; + gui_drawAllCharPortraitsWithStats(); +} + +void EoBCoreEngine::castOnWhomDialogue() { + printWarning(_magicStrings3[0]); + gui_setCastOnWhomButtons(); +} + +void EoBCoreEngine::startSpell(int spell) { + EoBSpell *s = &_spells[spell]; + EoBCharacter *c = &_characters[_activeSpellCharId]; + snd_playSoundEffect(s->sound); + + if (s->flags & 0xA0) + sparkEffectDefensive(_activeSpellCharId); + else if (s->flags & 0x40) + sparkEffectDefensive(-1); + else if (s->flags & 0x1000) + sparkEffectOffensive(); + + if (s->flags & 0x20) { + _txt->printMessage(c->name); + _txt->printMessage(_flags.gameID == GI_EOB1 ? _magicStrings3[1] : _magicStrings1[5]); + } + + if ((s->flags & 0x30) && (s->effectFlags & c->effectFlags)) { + if (_flags.gameID == GI_EOB2) + printWarning(Common::String::format(_magicStrings7[0], c->name, s->name).c_str()); + } else if ((s->flags & 0x50) && (s->effectFlags & _partyEffectFlags)) { + if (_flags.gameID == GI_EOB1 && s->effectFlags == 0x400) + // EOB 1 only warns in case of a bless spell + printWarning(_magicStrings8[1]); + else + printWarning(Common::String::format(_magicStrings7[1], s->name).c_str()); + } else { + if (s->flags & 8) + setSpellEventTimer(spell, s->timingPara[0], s->timingPara[1], s->timingPara[2], s->timingPara[3]); + + _returnAfterSpellCallback = false; + if (s->startCallback) + (this->*s->startCallback)(); + if (_returnAfterSpellCallback) + return; + + if (s->flags & 1) + c->effectFlags |= s->effectFlags; + if (s->flags & 4) + _partyEffectFlags |= s->effectFlags; + + if (s->flags & 0x200) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (!testCharacter(i, 2) && !(s->flags & 0x800)) + continue; + _characters[i].effectFlags |= s->effectFlags; + } + } + + if (s->flags & 2) + recalcArmorClass(_activeSpellCharId); + + if (s->flags & 0x20A0) + gui_drawCharPortraitWithStats(_activeSpellCharId); + if (s->flags & 0x40) + gui_drawAllCharPortraitsWithStats(); + } + + if (_castScrollSlot) { + gui_updateSlotAfterScrollUse(); + } else { + _characters[_openBookChar].disabledSlots |= 4; + setCharEventTimer(_openBookChar, 72, 11, 1); + gui_toggleButtons(); + gui_drawSpellbook(); + } + + if (_flags.gameID == GI_EOB2) { + //_castSpellWd1 = spell; + runLevelScript(_currentBlock, 0x800); + //_castSpellWd1 = 0; + } +} + +void EoBCoreEngine::sparkEffectDefensive(int charIndex) { + int first = charIndex; + int last = charIndex; + if (charIndex == -1) { + first = 0; + last = 5; + } + + for (int i = 0; i < 8; i++) { + for (int ii = first; ii <= last; ii++) { + if (!testCharacter(ii, 1) || (_currentControlMode && ii != _updateCharNum)) + continue; + + gui_drawCharPortraitWithStats(ii); + + for (int iii = 0; iii < 4; iii++) { + int shpIndex = ((_sparkEffectDefSteps[i] & _sparkEffectDefSubSteps[iii]) >> _sparkEffectDefShift[iii]); + if (!shpIndex) + continue; + int x = _sparkEffectDefAdd[iii * 2] - 8; + int y = _sparkEffectDefAdd[iii * 2 + 1]; + if (_currentControlMode) { + x += 181; + y += 3; + } else { + x += (_sparkEffectDefX[ii] << 3); + y += _sparkEffectDefY[ii]; + } + _screen->drawShape(0, _sparkShapes[shpIndex - 1], x, y, 0); + _screen->updateScreen(); + } + } + delay(2 * _tickLength); + } + + for (int i = first; i < last; i++) + gui_drawCharPortraitWithStats(i); +} + +void EoBCoreEngine::sparkEffectOffensive() { + disableSysTimer(2); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 2, Screen::CR_NO_P_CHECK); + int sh = _flags.useHiColorMode ? 9 : 8; + + for (int i = 0; i < 16; i++) + _screen->copyRegionToBuffer(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << sh]); + + for (int i = 0; i < 11; i++) { + for (int ii = 0; ii < 16; ii++) + _screen->copyBlockToPage(2, _sparkEffectOfX[ii], _sparkEffectOfY[ii], 16, 16, &_spellAnimBuffer[ii << sh]); + + for (int ii = 0; ii < 16; ii++) { + int shpIndex = (_sparkEffectOfFlags1[i] & _sparkEffectOfFlags2[ii]) >> _sparkEffectOfShift[ii]; + if (shpIndex) + _screen->drawShape(2, _sparkShapes[shpIndex - 1], _sparkEffectOfX[ii], _sparkEffectOfY[ii], 0); + } + delay(2 * _tickLength); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + for (int i = 0; i < 16; i++) + _screen->copyBlockToPage(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << sh]); + + _screen->updateScreen(); + enableSysTimer(2); +} + +void EoBCoreEngine::setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer) { + assert(spell >= 0); + int l = _openBookType == 1 ? getClericPaladinLevel(_openBookChar) : getMageLevel(_openBookChar); + uint32 countdown = timerLength * timerBaseFactor + timerLength * l * timerLevelFactor; + setCharEventTimer(_activeSpellCharId, countdown, -spell, updateExistingTimer); +} + +void EoBCoreEngine::sortCharacterSpellList(int charIndex) { + int8 *list = _characters[charIndex].mageSpells; + + for (int i = 0; i < 16;) { + bool p = false; + for (int ii = 0; ii < 9; ii++) { + int8 *pos = &list[ii]; + + int s1 = pos[0]; + int s2 = pos[1]; + + if (s1 == 0) + s1 = 80; + else if (s1 < 0) + s1 = s1 * -1 + 40; + + if (s2 == 0) + s2 = 80; + else if (s2 < 0) + s2 = s2 * -1 + 40; + + if (s1 > s2) { + SWAP(pos[0], pos[1]); + p = true; + } + } + + if (p) + continue; + + list += 10; + if (++i == 8) + list = _characters[charIndex].clericSpells; + } +} + +bool EoBCoreEngine::magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level) { + int ignoreAttackerId = fo->flags & 0x10; + int singleTargetCheckAdjacent = fo->flags & 1; + int blockDamage = fo->flags & 2; + int hitTest = fo->flags & 4; + + int savingThrowType = 5; + int savingThrowEffect = 3; + if (fo->flags & 8) { + savingThrowType = 4; + savingThrowEffect = 0; + } + + int dmgFlag = _spells[fo->callBackIndex].damageFlags; + if (fo->attackerId >= 0) + dmgFlag |= 0x800; + + bool res = false; + if (!level) + level = 1; + + if ((_levelBlockProperties[fo->curBlock].flags & 7) && (fo->attackerId >= 0 || ignoreAttackerId)) { + _preventMonsterFlash = true; + + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, blockDamage, singleTargetCheckAdjacent); *m != -1; m++) { + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + + if (hitTest) { + if (!characterAttackHitTest(fo->attackerId, *m, 0, 0)) + continue; + } + + calcAndInflictMonsterDamage(&_monsters[*m], 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + res = true; + } + updateAllMonsterShapes(); + + } else if (fo->curBlock == _currentBlock && (fo->attackerId < 0 || ignoreAttackerId)) { + if (blockDamage) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (hitTest && !monsterAttackHitTest(&_monsters[0], i)) + continue; + + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + res = true; + + calcAndInflictCharacterDamage(i, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + } + } else { + int c = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)]; + if ((c > 2) && (testCharacter(4, 1) || testCharacter(5, 1)) && rollDice(1, 2, -1)) + c += 2; + + if (!fo->item && (_characters[c].effectFlags & 8)) { + res = true; + } else { + if ((_characters[c].flags & 1) && (!hitTest || monsterAttackHitTest(&_monsters[0], c))) { + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + res = true; + calcAndInflictCharacterDamage(c, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + } + } + } + } + + if (res && (fo->flags & 0x40)) + explodeObject(fo, fo->curBlock, fo->item); + else if ((_flags.gameID == GI_EOB1 && fo->item == 5) || (_flags.gameID == GI_EOB2 && fo->item == 4)) + res = false; + + return res; +} + +bool EoBCoreEngine::magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + if (tryEvade) { + if (tryMonsterAttackEvasion(m) || (p->immunityFlags & 0x10)) + return true; + } + + if (trySavingThrow(m, 0, p->level, mod, 6)) + return false; + + int para = 0; + + switch (type) { + case 0: + case 1: + case 2: + para = (type == 0) ? ((p->typeFlags & 1) ? 1 : 0) : ((type == 1) ? ((p->typeFlags & 2) ? 1 : 0) : 1); + if (para && !(p->immunityFlags & 2)) { + m->mode = 10; + m->spellStatusLeft = 15; + } + + break; + + case 3: + if (!(p->immunityFlags & 8)) + inflictMonsterDamage(m, 1000, true); + break; + + case 4: + inflictMonsterDamage(m, 1000, true); + break; + + case 5: + m->flags |= 0x20; + _sceneUpdateRequired = true; + break; + + case 6: + if (!(_flags.gameID == GI_EOB1 && !(p->typeFlags & 3)) && !(p->immunityFlags & 4) && m->mode != 7 && m->mode != 8 && m->mode != 10) { + m->mode = 0; + m->spellStatusLeft = 20; + m->flags |= 8; + walkMonsterNextStep(m, -1, (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1); + } + break; + + default: + break; + } + + return true; +} + +bool EoBCoreEngine::turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel) { + assert(_monsterProps[m->type].tuResist > 0); + uint8 e = _turnUndeadEffect[_monsterProps[m->type].tuResist * 14 + MIN(casterLevel, 14)]; + + if (e == 0xFF) { + calcAndInflictMonsterDamage(m, 0, 0, 500, 0x200, 5, 3); + } else if (hitChance < e) { + return false; + } else { + m->mode = 0; + m->flags |= 8; + m->spellStatusLeft = 40; + m->dir = (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1; + } + + return true; +} + +int EoBCoreEngine::getMagicWeaponSlot(int charIndex) { + return _characters[charIndex].inventory[1] ? 0 : 1; +} + +void EoBCoreEngine::causeWounds(int dcTimes, int dcPips, int dcOffs) { + if (_openBookChar == 0 || _openBookChar == 1) { + int d = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection)); + if (d != -1) { + if (!characterAttackHitTest(_openBookChar, d, 0, 1)) + return; + + if (dcTimes == -1) { + dcOffs = _monsters[d].hitPointsMax - rollDice(1, 4); + dcTimes = dcPips = 0; + } + calcAndInflictMonsterDamage(&_monsters[d], dcTimes, dcPips, dcOffs, 0x801, 4, 2); + } else { + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 4 : 3], _characters[_openBookChar].name).c_str()); + } + } else { + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 5 : 4], _characters[_openBookChar].name).c_str()); + } +} + +int EoBCoreEngine::createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNumDice, int dmgPips, int dmgInc, int extraProps) { + int i = 51; + for (; i < 57; i++) { + if (_itemTypes[i].armorClass == -30) + break; + } + + if (i == 57) + return -1; + + EoBItemType *tp = &_itemTypes[i]; + tp->invFlags = invFlags; + tp->requiredHands = 0; + tp->handFlags = handFlags; + tp->armorClass = armorClass; + tp->allowedClasses = allowedClasses; + tp->dmgNumDiceL = tp->dmgNumDiceS = dmgNumDice; + tp->dmgNumPipsL = tp->dmgNumPipsS = dmgPips; + tp->dmgIncL = tp->dmgIncS = dmgInc; + tp->extraProperties = extraProps; + + return i; +} + +Item EoBCoreEngine::createMagicWeaponItem(int flags, int icon, int value, int type) { + Item i = 11; + for (; i < 17; i++) { + if (_items[i].block == -2) + break; + } + + if (i == 17) + return -1; + + EoBItem *itm = &_items[i]; + itm->flags = 0x20 | flags; + itm->icon = icon; + itm->value = value; + itm->type = type; + itm->pos = 0; + itm->block = 0; + itm->nameId = itm->nameUnid = 0; + itm->prev = itm->next = 0; + + return i; +} + +void EoBCoreEngine::removeMagicWeaponItem(Item item) { + _itemTypes[_items[item].type].armorClass = -30; + _items[item].block = -2; + _items[item].level = 0xFF; +} + +void EoBCoreEngine::updateWallOfForceTimers() { + uint32 ct = _system->getMillis(); + for (int i = 0; i < 5; i++) { + if (!_wallsOfForce[i].block) + continue; + if (_wallsOfForce[i].duration < ct) + destroyWallOfForce(i); + } +} + +void EoBCoreEngine::destroyWallOfForce(int index) { + memset(_levelBlockProperties[_wallsOfForce[index].block].walls, 0, 4); + _wallsOfForce[index].block = 0; + _sceneUpdateRequired = true; +} + +int EoBCoreEngine::findSingleSpellTarget(int dist) { + uint16 bl = _currentBlock; + int res = -1; + + for (int i = 0; i < dist && res == -1; i++) { + bl = calcNewBlockPosition(bl, _currentDirection); + res = getClosestMonster(_openBookChar, bl); + if (!(_wllWallFlags[_levelBlockProperties[bl].walls[_sceneDrawVarDown]] & 1)) { + i = dist; + res = -1; + } + } + + return res; +} + +int EoBCoreEngine::findFirstCharacterSpellTarget() { + int curCharIndex = rollDice(1, 6, -1); + for (_characterSpellTarget = 0; _characterSpellTarget < 6; _characterSpellTarget++) { + if (testCharacter(curCharIndex, 3)) + return curCharIndex; + if (++curCharIndex == 6) + curCharIndex = 0; + } + return -1; +} + +int EoBCoreEngine::findNextCharacterSpellTarget(int curCharIndex) { + for (_characterSpellTarget++; _characterSpellTarget < 6;) { + if (++curCharIndex == 6) + curCharIndex = 0; + if (testCharacter(curCharIndex, 3)) + return curCharIndex; + } + return -1; +} + +int EoBCoreEngine::charDeathSavingThrow(int charIndex, int div) { + bool _beholderOrgBhv = true; + // Due to a bug in the original code the saving throw result is completely ignored + // here. The Beholders' disintegrate spell will alway succeed while their flesh to + // stone spell will always fail. + if (_beholderOrgBhv) + div >>= 1; + else + div = specialAttackSavingThrow(charIndex, 4) ? 1 : 0; + return div; +} + +void EoBCoreEngine::printWarning(const char *str) { + _txt->printMessage(str); + snd_playSoundEffect(79); +} + +void EoBCoreEngine::printNoEffectWarning() { + printWarning(_magicStrings4[0]); +} + +void EoBCoreEngine::spellCallback_start_armor() { + _characters[_activeSpellCharId].effectsRemainder[0] = getMageLevel(_openBookChar) + 8; + if ((getDexterityArmorClassModifier(_characters[_activeSpellCharId].dexterityCur) + 6) >= _characters[_activeSpellCharId].armorClass) + printWarning(Common::String::format(_magicStrings6[0], _characters[_activeSpellCharId].name).c_str()); +} + +void EoBCoreEngine::spellCallback_start_burningHands() { + static const int16 bX[] = { 0, 152, 24, 120, 56, 88 }; + static const int8 bY[] = { 64, 64, 56, 56, 56, 56 }; + + for (int i = 0; i < 6; i++) + drawBlockObject(i & 1, 0, _firebeamShapes[(5 - i) >> 1], bX[i], bY[i], 0); + _screen->updateScreen(); + delay(2 * _tickLength); + + int cl = getMageLevel(_openBookChar); + int bl = calcNewBlockPosition(_currentBlock, _currentDirection); + + const int8 *pos = getMonstersOnBlockPositions(bl); + _preventMonsterFlash = true; + + int numDest = (_flags.gameID == GI_EOB1) ? 2 : 6; + const uint8 *d = &_burningHandsDest[_currentDirection * (_flags.gameID == GI_EOB1 ? 2 : 8)]; + + for (int i = 0; i < numDest; i++, d++) { + if (pos[*d] == -1) + continue; + calcAndInflictMonsterDamage(&_monsters[pos[*d]], 1, 3, cl << 1, 0x21, 4, 0); + } + + updateAllMonsterShapes(); + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_detectMagic() { + setHandItem(_itemInHand); +} + +bool EoBCoreEngine::spellCallback_end_detectMagic(void *) { + setHandItem(_itemInHand); + return true; +} + +void EoBCoreEngine::spellCallback_start_magicMissile() { + launchMagicObject(_openBookChar, 0, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_magicMissile(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 4, 1, (getMageLevel(fo->attackerId) - 1) >> 1); +} + +void EoBCoreEngine::spellCallback_start_shockingGrasp() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 8, getMageLevel(_openBookChar), 1); + Item i = (t != -1) ? createMagicWeaponItem(0x10, 82, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[0]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +bool EoBCoreEngine::spellCallback_end_shockingGraspFlameBlade(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + for (int i = 0; i < 2; i++) { + if (isMagicEffectItem(c->inventory[i])) { + removeMagicWeaponItem(c->inventory[i]); + c->inventory[i] = 0; + } + } + return true; +} + +void EoBCoreEngine::spellCallback_start_improvedIdentify() { + for (int i = 0; i < 2; i++) { + Item itm = _characters[_activeSpellCharId].inventory[i]; + if (itm) + _items[itm].flags |= 0x40; + } +} + +void EoBCoreEngine::spellCallback_start_melfsAcidArrow() { + launchMagicObject(_openBookChar, 1, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_melfsAcidArrow(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + assert(fo); + return magicObjectDamageHit(fo, 2, 4, 0, getMageLevel(fo->attackerId) / 3); +} + +void EoBCoreEngine::spellCallback_start_dispelMagic() { + for (int i = 0; i < 6; i++) { + if (testCharacter(i, 1)) + removeAllCharacterEffects(i); + } +} + +void EoBCoreEngine::spellCallback_start_fireball() { + launchMagicObject(_openBookChar, 2, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_fireball(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_flameArrow() { + launchMagicObject(_openBookChar, 3, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_flameArrow(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 5, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_holdPerson() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 4 : 3, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_holdPerson(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + + if (_flags.gameID == GI_EOB2 && fo->curBlock == _currentBlock) { + // party hit + int numChar = rollDice(1, 4, 0); + int charIndex = rollDice(1, 6, -1); + for (int i = 0; i < 6 && numChar; i++) { + if (testCharacter(charIndex, 3)) { + statusAttack(charIndex, 4, _magicStrings8[1], 4, 5, 9, 1); + numChar--; + } + charIndex = (charIndex + 1) % 6; + } + res = true; + + } else { + // monster hit + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++) + res |= magicObjectStatusHit(&_monsters[*m], 0, true, 4); + } + + return res; +} + +void EoBCoreEngine::spellCallback_start_lightningBolt() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 5 : 4, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_lightningBolt(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_vampiricTouch() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, getMageLevel(_openBookChar) >> 1, 6, 0, 1); + Item i = (t != -1) ? createMagicWeaponItem(0x18, 83, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[2]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +bool EoBCoreEngine::spellCallback_end_vampiricTouch(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + if (c->hitPointsCur > c->hitPointsMax) + c->hitPointsCur = c->hitPointsMax; + spellCallback_end_shockingGraspFlameBlade(obj); + return true; +} + +void EoBCoreEngine::spellCallback_start_fear() { + sparkEffectOffensive(); + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + for (int i = 0; i < 30; i++) { + if (_monsters[i].block == bl) + magicObjectStatusHit(&_monsters[i], 6, true, 4); + } +} + +void EoBCoreEngine::spellCallback_start_iceStorm() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 6 : 5, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_iceStorm(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + static int8 blockAdv[] = { -32, 32, 1, -1 }; + bool res = magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); + if (res) { + for (int i = 0; i < 4; i++) { + uint16 bl = fo->curBlock; + fo->curBlock = (fo->curBlock + blockAdv[i]) & 0x3FF; + magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); + fo->curBlock = bl; + } + } + return res; +} + +void EoBCoreEngine::spellCallback_start_stoneSkin() { + _characters[_activeSpellCharId].effectsRemainder[1] = (getMageLevel(_openBookChar) >> 1) + rollDice(1, 4); +} + +void EoBCoreEngine::spellCallback_start_removeCurse() { + for (int i = 0; i < 27; i++) { + Item itm = _characters[_activeSpellCharId].inventory[i]; + if (itm && (_items[itm].flags & 0x20) && !isMagicEffectItem(itm)) + _items[itm].flags = (_items[itm].flags & ~0x20) | 0x40; + } +} + +void EoBCoreEngine::spellCallback_start_coneOfCold() { + const int8 *dirTables[] = { _coneOfColdDest1, _coneOfColdDest2, _coneOfColdDest3, _coneOfColdDest4 }; + + int cl = getMageLevel(_openBookChar); + + _screen->setCurPage(2); + _screen->fillRect(0, 0, 176, 120, 0); + _screen->setGfxParameters(0, 0, _screen->getPagePixel(2, 0, 0)); + drawSceneShapes(7); + _screen->setCurPage(0); + disableSysTimer(2); + _screen->drawVortex(150, 50, 10, 1, 100, _coneOfColdGfxTbl, _coneOfColdGfxTblSize); + enableSysTimer(2); + + const int8 *tbl = dirTables[_currentDirection]; + _preventMonsterFlash = true; + + for (int i = 0; i < 7; i++) { + for (const int16 *m = findBlockMonsters((_currentBlock + tbl[i]) & 0x3FF, 4, _currentDirection, 1, 1); *m != -1; m++) + calcAndInflictMonsterDamage(&_monsters[*m], cl, 4, cl, 0x41, 5, 0); + } + + updateAllMonsterShapes(); +} + +void EoBCoreEngine::spellCallback_start_holdMonster() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 7 : 6, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_holdMonster(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++) + res |= magicObjectStatusHit(&_monsters[*m], 1, true, 4); + return res; +} + +void EoBCoreEngine::spellCallback_start_wallOfForce() { + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + LevelBlockProperty *l = &_levelBlockProperties[bl]; + if (l->walls[0] || l->walls[1] || l->walls[2] || l->walls[3] || (l->flags & 7)) { + printWarning(_magicStrings8[3]); + return; + } + + uint32 dur = 0xFFFFFFFF; + int s = 0; + int i = 0; + + for (; i < 5; i++) { + if (!_wallsOfForce[i].block) + break; + if (_wallsOfForce[i].duration < dur) { + dur = _wallsOfForce[i].duration; + s = i; + } + } + + if (i == 5) + destroyWallOfForce(s); + + memset(_levelBlockProperties[bl].walls, 74, 4); + _wallsOfForce[s].block = bl; + _wallsOfForce[s].duration = _system->getMillis() + (((getMageLevel(_openBookChar) * 546) >> 1) + 546) * _tickLength; + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_disintegrate() { + int d = findSingleSpellTarget(1); + if (d != -1) + magicObjectStatusHit(&_monsters[d], 4, true, 4); + memset(_visibleBlocks[13]->walls, 0, 4); + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_fleshToStone() { + sparkEffectOffensive(); + int t = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection)); + if (t != -1) + magicObjectStatusHit(&_monsters[t], 5, true, 4); + else + printWarning(_magicStrings8[4]); +} + +void EoBCoreEngine::spellCallback_start_stoneToFlesh() { + if (_characters[_activeSpellCharId].flags & 8) + _characters[_activeSpellCharId].flags &= ~8; + else + printNoEffectWarning(); +} + +void EoBCoreEngine::spellCallback_start_trueSeeing() { + _wllVmpMap[46] = 0; +} + +bool EoBCoreEngine::spellCallback_end_trueSeeing(void *) { + _wllVmpMap[46] = 1; + return true; +} + +void EoBCoreEngine::spellCallback_start_slayLiving() { + int d = findSingleSpellTarget(2); + if (d != -1) { + if (!magicObjectStatusHit(&_monsters[d], 3, true, 4)) + inflictMonsterDamage(&_monsters[d], rollDice(2, 8, 1), true); + } +} + +void EoBCoreEngine::spellCallback_start_powerWordStun() { + int d = findSingleSpellTarget(2); + if (d != -1) { + if (_monsters[d].hitPointsCur < 90) + magicObjectStatusHit(&_monsters[d], 5, true, 4); + } +} + +void EoBCoreEngine::spellCallback_start_causeLightWounds() { + causeWounds(1, 8, 0); +} + +void EoBCoreEngine::spellCallback_start_cureLightWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(1, 8)); +} + +void EoBCoreEngine::spellCallback_start_aid() { + if (!testCharacter(_activeSpellCharId, 3)) { + printNoEffectWarning(); + } else if (_characters[_activeSpellCharId].effectsRemainder[3]) { + printWarning(Common::String::format(_magicStrings8[(_flags.gameID == GI_EOB1) ? 2 : 5], _characters[_activeSpellCharId].name).c_str()); + } else { + _characters[_activeSpellCharId].effectsRemainder[3] = rollDice(1, 8); + _characters[_activeSpellCharId].hitPointsCur += _characters[_activeSpellCharId].effectsRemainder[3]; + _characters[_activeSpellCharId].effectFlags |= 0x1000; + return; + } + + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); +} + +bool EoBCoreEngine::spellCallback_end_aid(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + c->hitPointsCur -= c->effectsRemainder[3]; + c->effectsRemainder[3] = 0; + c->effectFlags &= ~0x1000; + return true; +} + +void EoBCoreEngine::spellCallback_start_flameBlade() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 4, 4, 1); + Item i = (t != -1) ? createMagicWeaponItem(0, 84, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[0]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +void EoBCoreEngine::spellCallback_start_slowPoison() { + if (_characters[_activeSpellCharId].flags & 2) { + _characters[_activeSpellCharId].effectFlags |= 0x2000; + setSpellEventTimer(_activeSpell, 1, 32760, 1, 1); + } else { + printNoEffectWarning(); + } +} + +bool EoBCoreEngine::spellCallback_end_slowPoison(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + c->effectFlags &= ~0x2000; + return true; +} + +void EoBCoreEngine::spellCallback_start_createFood() { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + _characters[i].food = 100; + } +} + +void EoBCoreEngine::spellCallback_start_removeParalysis() { + int numChar = 4; + for (int i = 0; i < 6; i++) { + if (!(_characters[i].flags & 4) || !numChar) + continue; + _characters[i].flags &= ~4; + numChar--; + } +} + +void EoBCoreEngine::spellCallback_start_causeSeriousWounds() { + causeWounds(2, 8, 1); +} + +void EoBCoreEngine::spellCallback_start_cureSeriousWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(2, 8, 1)); +} + +void EoBCoreEngine::spellCallback_start_neutralizePoison() { + if (_characters[_activeSpellCharId].flags & 2) + neutralizePoison(_activeSpellCharId); + else + printNoEffectWarning(); +} + +void EoBCoreEngine::spellCallback_start_causeCriticalWounds() { + causeWounds(3, 8, 3); +} + +void EoBCoreEngine::spellCallback_start_cureCriticalWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(3, 8, 3)); +} + +void EoBCoreEngine::spellCallback_start_flameStrike() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 8 : 7, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_flameStrike(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 6, 8, 0, 0); +} + +void EoBCoreEngine::spellCallback_start_raiseDead() { + if (_characters[_activeSpellCharId].hitPointsCur == -10 && ((_characters[_activeSpellCharId].raceSex >> 1) != 1)) { + _characters[_activeSpellCharId].hitPointsCur = 1; + gui_drawCharPortraitWithStats(_activeSpellCharId); + } else { + printNoEffectWarning(); + } +} + +void EoBCoreEngine::spellCallback_start_harm() { + causeWounds(-1, -1, -1); +} + +void EoBCoreEngine::spellCallback_start_heal() { + EoBCharacter *c = &_characters[_activeSpellCharId]; + if (c->hitPointsMax <= c->hitPointsCur) + printWarning(_magicStrings4[0]); + else + modifyCharacterHitpoints(_activeSpellCharId, c->hitPointsMax - c->hitPointsCur); +} + +void EoBCoreEngine::spellCallback_start_layOnHands() { + modifyCharacterHitpoints(_activeSpellCharId, _characters[_openBookChar].level[0] << 1); +} + +void EoBCoreEngine::spellCallback_start_turnUndead() { + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + if (!(_levelBlockProperties[bl].flags & 7)) + return; + + int cl = _openBookCasterLevel ? _openBookCasterLevel : getClericPaladinLevel(_openBookChar); + int r = rollDice(1, 20); + bool hit = false; + + for (const int16 *m = findBlockMonsters(bl, 4, 4, 1, 1); *m != -1; m++) { + if ((_monsterProps[_monsters[*m].type].typeFlags & 4) && !(_monsters[*m].flags & 0x10)) { + _preventMonsterFlash = true; + _monsters[*m].flags |= 0x10; + hit |= turnUndeadHit(&_monsters[*m], r, cl); + } + } + + if (hit) { + turnUndeadAutoHit(); + snd_playSoundEffect(95); + updateAllMonsterShapes(); + } + + _preventMonsterFlash = false; +} + +bool EoBCoreEngine::spellCallback_end_monster_lightningBolt(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 0, 0, 12, 1); +} + +bool EoBCoreEngine::spellCallback_end_monster_fireball1(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + if (_partyEffectFlags & 0x20000) { + res = magicObjectDamageHit(fo, 4, 10, 6, 0); + if (res) { + gui_drawAllCharPortraitsWithStats(); + _partyEffectFlags &= ~0x20000; + } + } else { + res = magicObjectDamageHit(fo, 12, 10, 6, 0); + } + return res; +} + +bool EoBCoreEngine::spellCallback_end_monster_fireball2(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 0, 0, 18, 0); +} + +bool EoBCoreEngine::spellCallback_end_monster_deathSpell(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int numDest = rollDice(1, 4); + _txt->printMessage(_magicStrings2[2]); + for (int d = findFirstCharacterSpellTarget(); d != -1 && numDest; d = findNextCharacterSpellTarget(d)) { + if (_characters[d].level[0] < 8) { + inflictCharacterDamage(d, 300); + numDest--; + } + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_disintegrate(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + if (d != -1) { + if (!charDeathSavingThrow(d, 1)) { + inflictCharacterDamage(d, 300); + _txt->printMessage(_magicStrings2[1], -1, _characters[d].name); + } + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_causeCriticalWounds(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + if (d != -1) { + _txt->printMessage(_magicStrings2[3], -1, _characters[d].name); + inflictCharacterDamage(d, rollDice(3, 8, 3)); + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_fleshToStone(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + while (d != -1) { + if (!charDeathSavingThrow(d, 2)) { + statusAttack(d, 8, _magicStrings2[4], 5, 0, 0, 1); + d = -1; + } else { + d = findNextCharacterSpellTarget(d); + } + } + + return true; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp new file mode 100644 index 0000000000..3ff26cab8a --- /dev/null +++ b/engines/kyra/engine/scene_eob.cpp @@ -0,0 +1,862 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/script/script_eob.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + + +namespace Kyra { + +void EoBCoreEngine::loadLevel(int level, int sub) { + _currentLevel = level; + _currentSub = sub; + if (!_loading) + setHandItem(-1); + uint32 end = _system->getMillis() + 500; + + readLevelFileData(level); + + Common::String gfxFile; + // Work around for issue with corrupt (incomplete) monster property data + // when loading a savegame saved in a sub level + for (int i = 0; i <= sub; i++) + gfxFile = initLevelData(i); + + const uint8 *data = _screen->getCPagePtr(5); + const uint8 *pos = data + READ_LE_UINT16(data); + uint16 len = READ_LE_UINT16(pos); + uint16 len2 = len; + pos += 2; + + if (_flags.gameID == GI_EOB2) { + if (*pos++ == 0xEC) + pos = loadActiveMonsterData(pos, level); + else if (!(_hasTempDataFlags & (1 << (level - 1)))) + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + len2 = len - (pos - data); + _inf->loadData(pos, len2); + } else { + _inf->loadData(data, READ_LE_UINT16(data)); + } + + _screen->setCurPage(2); + addLevelItems(); + + if (_flags.gameID == GI_EOB2) { + pos = data + len; + len2 = READ_LE_UINT16(pos); + pos += 2; + } + + for (uint16 i = 0; i < len2; i++) { + LevelBlockProperty *p = &_levelBlockProperties[READ_LE_UINT16(pos)]; + pos += 2; + if (_flags.gameID == GI_EOB2) { + p->flags |= READ_LE_UINT16(pos); + pos += 2; + } else { + p->flags |= *pos++; + } + p->assignedObjects = READ_LE_UINT16(pos); + pos += 2; + } + + // WORKAROUND for bug #3596547 (EOB1: Door Buttons Don't Work) + if (_flags.gameID == GI_EOB1 && level == 7 && _levelBlockProperties[0x035C].assignedObjects == 0x0E89) + _levelBlockProperties[0x035C].assignedObjects = 0x0E8D; + + loadVcnData(gfxFile.c_str(), (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[level - 1]] : 0); + _screen->loadEoBBitmap("INVENT", _cgaMappingInv, 5, 3, 2); + delayUntil(end); + snd_stopSound(); + + enableSysTimer(2); + _sceneDrawPage1 = 2; + _sceneDrawPage2 = 1; + _screen->setCurPage(0); + setHandItem(_itemInHand); +} + +void EoBCoreEngine::readLevelFileData(int level) { + Common::String file; + Common::SeekableReadStream *s = 0; + static const char *const suffix[] = { "INF", "DRO", "ELO", "JOT", 0 }; + + for (const char *const *sf = suffix; *sf && !s; sf++) { + file = Common::String::format("LEVEL%d.%s", level, *sf); + s = _res->createReadStream(file); + } + + if (!s) + error("Failed to load level file LEVEL%d.INF/DRO/ELO/JOT", level); + + if (s->readUint16LE() + 2 == s->size()) { + if (s->readUint16LE() == 4) { + delete s; + s = 0; + _screen->loadBitmap(file.c_str(), 5, 5, 0); + } + } + + if (s) { + s->seek(0); + _screen->loadFileDataToPage(s, 5, 15000); + delete s; + } +} + +Common::String EoBCoreEngine::initLevelData(int sub) { + const uint8 *data = _screen->getCPagePtr(5) + 2; + const uint8 *pos = data; + + int slen = (_flags.gameID == GI_EOB1) ? 12 : 13; + + for (int i = 0; i < sub; i++) + pos = data + READ_LE_UINT16(pos); + + pos += 2; + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + if (_flags.gameID == GI_EOB1) + pos -= 3; + + loadBlockProperties((const char *)pos); + pos += slen; + + const char *vmpPattern = (_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.EMP" : "%s.VMP"; + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format(vmpPattern, (const char *)pos)); + uint16 size = (_flags.platform == Common::kPlatformFMTowns) ? 2916 : s->readUint16LE(); + delete[] _vmpPtr; + _vmpPtr = new uint16[size]; + for (int i = 0; i < size; i++) + _vmpPtr[i] = s->readUint16LE(); + delete s; + + const char *paletteFilePattern = (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA) ? "%s.EGA" : "%s.PAL"; + + Common::String tmpStr = Common::String::format(paletteFilePattern, (const char *)pos); + _curGfxFile = (const char *)pos; + pos += slen; + + if (*pos++ != 0xFF && _flags.gameID == GI_EOB2) { + tmpStr = Common::String::format(paletteFilePattern, (const char *)pos); + pos += 13; + } + + if (_flags.gameID == GI_EOB1) { + pos += 11; + _screen->setShapeFadingLevel(0); + _screen->enableShapeBackgroundFading(false); + } + + if (_flags.gameID == GI_EOB2 || _configRenderMode != Common::kRenderEGA) + _screen->loadPalette(tmpStr.c_str(), _screen->getPalette(0)); + + if (_flags.platform == Common::kPlatformFMTowns) { + uint16 *src = (uint16*)_screen->getPalette(0).getData(); + _screen->createFadeTable16bit(src, (uint16*)_greenFadingTable, 4, 75); + _screen->createFadeTable16bit(src, (uint16*)_blackFadingTable, 12, 200); + _screen->createFadeTable16bit(src, (uint16*)_blueFadingTable, 10, 85); + _screen->createFadeTable16bit(src, (uint16*)_lightBlueFadingTable, 11, 125); + _screen->createFadeTable16bit(src, (uint16*)_greyFadingTable, 0, 85); + _screen->setScreenPalette(_screen->getPalette(0)); + } else if (_configRenderMode != Common::kRenderCGA) { + Palette backupPal(256); + backupPal.copy(_screen->getPalette(0), 224, 32, 224); + _screen->getPalette(0).fill(224, 32, 0x3F); + uint8 *src = _screen->getPalette(0).getData(); + + _screen->createFadeTable(src, _greenFadingTable, 4, 75); + _screen->createFadeTable(src, _blackFadingTable, 12, 200); + _screen->createFadeTable(src, _blueFadingTable, 10, 85); + _screen->createFadeTable(src, _lightBlueFadingTable, 11, 125); + + _screen->getPalette(0).copy(backupPal, 224, 32, 224); + _screen->createFadeTable(src, _greyFadingTable, 12, 85); + _screen->setFadeTable(_greyFadingTable); + if (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA) + _screen->setScreenPalette(_screen->getPalette(0)); + } + } + + if (_flags.gameID == GI_EOB2) { + delay(3 * _tickLength); + _sound->loadSoundFile((const char *)pos); + pos += 13; + } + + releaseDoorShapes(); + releaseMonsterShapes(0, 36); + releaseDecorations(); + + if (_flags.gameID == GI_EOB1) { + loadDoorShapes(pos[0], pos[1], pos[2], pos[3]); + pos += 4; + _scriptTimersMode = *pos++; + _scriptTimers[0].ticks = READ_LE_UINT16(pos); + _scriptTimers[0].func = 0; + _scriptTimers[0].next = _system->getMillis() + _scriptTimers[0].ticks * _tickLength; + pos += 2; + } else { + for (int i = 0; i < 2; i++) { + int a = (*pos == 0xEC) ? 0 : ((*pos == 0xEA) ? 1 : -1); + pos++; + if (a == -1) + continue; + toggleWallState(pos[13], a); + _doorType[pos[13]] = pos[14]; + _noDoorSwitch[pos[13]] = pos[15]; + pos = loadDoorShapes((const char *)pos, pos[13], pos + 16); + } + } + + _stepsUntilScriptCall = READ_LE_UINT16(pos); + pos += 2; + _stepCounter = 0; + + for (int i = 0; i < 2; i++) { + if (_flags.gameID == GI_EOB1) { + if (*pos != 0xFF) + loadMonsterShapes((const char *)(pos + 1), i * 18, false, *pos * 18); + pos += 13; + } else { + if (*pos++ != 0xEC) + continue; + loadMonsterShapes((const char *)(pos + 2), pos[1] * 18, pos[15] ? true : false, *pos * 18); + pos += 16; + } + } + + if (_flags.gameID == GI_EOB1) + pos = loadActiveMonsterData(pos, _currentLevel) - 1; + else + pos = loadMonsterProperties(pos); + + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + int num = READ_LE_UINT16(pos); + pos += 2; + + for (int i = 0; i < num; i++) { + if (*pos++ == 0xEC) { + loadDecorations((const char *)pos, (const char *)(pos + slen)); + pos += (slen << 1); + } else { + assignWallsAndDecorations(pos[0], pos[1], (int8)pos[2], pos[3], pos[4]); + pos += 5; + } + } + } + + if (_flags.gameID == GI_EOB2) + initScriptTimers(pos); + + return _curGfxFile; +} + +void EoBCoreEngine::addLevelItems() { + for (int i = 0; i < 1024; i++) + _levelBlockProperties[i].drawObjects = 0; + + for (int i = 0; i < 600; i++) { + if (_items[i].level != _currentLevel || _items[i].block <= 0) + continue; + setItemPosition((Item *)&_levelBlockProperties[_items[i].block & 0x3FF].drawObjects, _items[i].block, i, _items[i].pos); + } +} + +void EoBCoreEngine::loadVcnData(const char *file, const uint8 *cgaMapping) { + if (file) + strcpy(_lastBlockDataFile, file); + + if (_flags.platform == Common::kPlatformFMTowns) { + uint32 size; + delete[] _vcnBlocks; + _vcnBlocks = _res->fileData(Common::String::format("%s.VCC", _lastBlockDataFile).c_str(), &size); + return; + } + + const char *filePattern = ((_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.ECN" : "%s.VCN"); + _screen->loadBitmap(Common::String::format(filePattern, _lastBlockDataFile).c_str(), 3, 3, 0); + const uint8 *pos = _screen->getCPagePtr(3); + + uint32 vcnSize = READ_LE_UINT16(pos) << 5; + pos += 2; + + const uint8 *colMap = pos; + pos += 32; + + delete[] _vcnBlocks; + _vcnBlocks = new uint8[vcnSize]; + + if (_configRenderMode == Common::kRenderCGA) { + uint8 *tmp = _screen->encodeShape(0, 0, 1, 8, false, cgaMapping); + delete[] tmp; + + delete[] _vcnTransitionMask; + _vcnTransitionMask = new uint8[vcnSize]; + uint8 tblSwitch = 1; + uint8 *dst = _vcnBlocks; + uint8 *dst2 = _vcnTransitionMask; + + while (dst < _vcnBlocks + vcnSize) { + const uint16 *table = _screen->getCGADitheringTable((tblSwitch++) & 1); + for (int ii = 0; ii < 2; ii++) { + *dst++ = (table[pos[0]] & 0x000F) | ((table[pos[0]] & 0x0F00) >> 4); + *dst++ = (table[pos[1]] & 0x000F) | ((table[pos[1]] & 0x0F00) >> 4); + *dst2++ = ((pos[0] & 0xF0 ? 0x30 : 0) | (pos[0] & 0x0F ? 0x03 : 0)) ^ 0x33; + *dst2++ = ((pos[1] & 0xF0 ? 0x30 : 0) | (pos[1] & 0x0F ? 0x03 : 0)) ^ 0x33; + pos += 2; + } + } + } else { + if (!(_flags.gameID == GI_EOB1 && _configRenderMode == Common::kRenderEGA)) + memcpy(_vcnColTable, colMap, 32); + memcpy(_vcnBlocks, pos, vcnSize); + } +} + +void EoBCoreEngine::loadBlockProperties(const char *mazFile) { + memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty)); + const uint8 *p = getBlockFileData(mazFile) + 6; + + if (_hasTempDataFlags & (1 << (_currentLevel - 1))) { + restoreBlockTempData(_currentLevel); + return; + } + + for (int i = 0; i < 1024; i++) { + for (int ii = 0; ii < 4; ii++) + _levelBlockProperties[i].walls[ii] = *p++; + } +} + +const uint8 *EoBCoreEngine::getBlockFileData(int) { + Common::SeekableReadStream *s = _res->createReadStream(_curBlockFile); + _screen->loadFileDataToPage(s, 15, s->size()); + delete s; + return _screen->getCPagePtr(15); +} + +Common::String EoBCoreEngine::getBlockFileName(int levelIndex, int sub) { + readLevelFileData(levelIndex); + const uint8 *data = _screen->getCPagePtr(5) + 2; + const uint8 *pos = data; + + for (int i = 0; i < sub; i++) + pos = data + READ_LE_UINT16(pos); + + pos += 2; + + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + if (_flags.gameID == GI_EOB1) + pos -= 3; + + return Common::String((const char *)pos); + } + + return Common::String(); +} + +const uint8 *EoBCoreEngine::getBlockFileData(const char *mazFile) { + _curBlockFile = mazFile; + return getBlockFileData(); +} + +void EoBCoreEngine::loadDecorations(const char *cpsFile, const char *decFile) { + _screen->loadShapeSetBitmap(cpsFile, 5, 3); + Common::SeekableReadStream *s = _res->createReadStream(decFile); + + _levelDecorationDataSize = s->readUint16LE(); + delete[] _levelDecorationData; + _levelDecorationData = new LevelDecorationProperty[_levelDecorationDataSize]; + memset(_levelDecorationData, 0, _levelDecorationDataSize * sizeof(LevelDecorationProperty)); + + for (int i = 0; i < _levelDecorationDataSize; i++) { + LevelDecorationProperty *l = &_levelDecorationData[i]; + for (int ii = 0; ii < 10; ii++) { + l->shapeIndex[ii] = s->readByte(); + if (l->shapeIndex[ii] == 0xFF) + l->shapeIndex[ii] = 0xFFFF; + } + l->next = s->readByte(); + l->flags = s->readByte(); + for (int ii = 0; ii < 10; ii++) + l->shapeX[ii] = s->readSint16LE(); + for (int ii = 0; ii < 10; ii++) + l->shapeY[ii] = s->readSint16LE(); + } + + int len = s->readUint16LE(); + delete[] _levelDecorationRects; + _levelDecorationRects = new EoBRect8[len]; + for (int i = 0; i < len; i++) { + EoBRect8 *l = &_levelDecorationRects[i]; + l->x = s->readUint16LE(); + l->y = s->readUint16LE(); + l->w = s->readUint16LE(); + l->h = s->readUint16LE(); + } + + delete s; +} + +void EoBCoreEngine::assignWallsAndDecorations(int wallIndex, int vmpIndex, int decIndex, int specialType, int flags) { + _wllVmpMap[wallIndex] = vmpIndex; + for (int i = 0; i < 6; i++) { + for (int ii = 0; ii < 10; ii++) { + if (_characters[i].events[ii] == -57) + spellCallback_start_trueSeeing(); + } + } + _wllShapeMap[wallIndex] = _mappedDecorationsCount + 1; + _specialWallTypes[wallIndex] = specialType; + _wllWallFlags[wallIndex] = flags ^ 4; + + if (decIndex == -1) { + _wllShapeMap[wallIndex] = 0; + return; + } + + do { + assert(decIndex < _levelDecorationDataSize); + memcpy(&_levelDecorationProperties[_mappedDecorationsCount], &_levelDecorationData[decIndex], sizeof(LevelDecorationProperty)); + + for (int i = 0; i < 10; i++) { + uint16 t = _levelDecorationProperties[_mappedDecorationsCount].shapeIndex[i]; + if (t == 0xFFFF) + continue; + + if (_levelDecorationShapes[t]) + continue; + + EoBRect8 *r = &_levelDecorationRects[t]; + if (r->w == 0 || r->h == 0) + error("Error trying to make decoration %d (x: %d, y: %d, w: %d, h: %d)", decIndex, r->x, r->y, r->w, r->h); + + _levelDecorationShapes[t] = _screen->encodeShape(r->x, r->y, r->w, r->h, false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + } + + decIndex = _levelDecorationProperties[_mappedDecorationsCount++].next; + + if (decIndex) + _levelDecorationProperties[_mappedDecorationsCount - 1].next = _mappedDecorationsCount + 1; + else + decIndex = -1; + + } while (decIndex != -1); +} + +void EoBCoreEngine::releaseDecorations() { + if (_levelDecorationShapes) { + for (int i = 0; i < 400; i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } + } + _mappedDecorationsCount = 0; +} + +void EoBCoreEngine::releaseDoorShapes() { + for (int i = 0; i < 6; i++) { + delete[] _doorShapes[i]; + _doorShapes[i] = 0; + delete[] _doorSwitches[i].shp; + _doorSwitches[i].shp = 0; + } +} + +void EoBCoreEngine::toggleWallState(int wall, int toggle) { + wall = wall * 10 + 3; + + for (int i = 0; i < 9 ; i++) { + if (i == 4) + continue; + + if (toggle) + _wllWallFlags[wall + i] |= 2; + else + _wllWallFlags[wall + i] &= 0xFD; + } +} + +void EoBCoreEngine::drawScene(int refresh) { + generateBlockDrawingBuffer(); + drawVcnBlocks(); + drawSceneShapes(); + + if (_sceneDrawPage2) { + if (refresh) + _screen->fillRect(0, 0, 176, 120, 12); + + if (!_loading) + _screen->setScreenPalette(_screen->getPalette(0)); + + _sceneDrawPage2 = 0; + } + + uint32 ct = _system->getMillis(); + if (_flashShapeTimer > ct) { + int diff = _flashShapeTimer - ct; + while ((diff > 0) && !shouldQuit()) { + updateInput(); + uint32 step = MIN<uint32>(diff, _tickLength / 5); + _system->delayMillis(step); + diff -= step; + } + } + + if (_sceneDefaultUpdate) + delayUntil(_drawSceneTimer); + + if (refresh && !_partyResting) + _screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + + updateEnvironmentalSfx(0); + + if (!_dialogueField && refresh && !_updateFlags) + gui_drawCompass(false); + + if (refresh && !_partyResting && !_loading) + _screen->updateScreen(); + + if (_sceneDefaultUpdate) { + _sceneDefaultUpdate = false; + _drawSceneTimer = _system->getMillis() + 4 * _tickLength; + } + + _sceneUpdateRequired = false; +} + +void EoBCoreEngine::drawSceneShapes(int start) { + for (int i = start; i < 18; i++) { + uint8 t = _dscTileIndex[i]; + uint8 s = _visibleBlocks[t]->walls[_sceneDrawVarDown]; + + _shpDmX1 = 0; + _shpDmX2 = 0; + + setLevelShapesDim(t, _shpDmX1, _shpDmX2, _sceneShpDim); + + if (_shpDmX2 <= _shpDmX1) + continue; + + drawDecorations(t); + + if (_visibleBlocks[t]->drawObjects) + drawBlockItems(t); + + if (t < 15) { + uint16 w = _wllWallFlags[s]; + + if (w & 8) + drawDoor(t); + + if (_visibleBlocks[t]->flags & 7) { + const ScreenDim *dm = _screen->getScreenDim(5); + _screen->modifyScreenDim(5, dm->sx, _lvlShapeTop[t], dm->w, _lvlShapeBottom[t] - _lvlShapeTop[t]); + drawMonsters(t); + drawLevelModifyScreenDim(5, _lvlShapeLeftRight[(t << 1)], 0, _lvlShapeLeftRight[(t << 1) + 1], 15); + } + + if (_flags.gameID == GI_EOB2 && s == 74) + drawWallOfForce(t); + } + + drawFlyingObjects(t); + + if (s == _teleporterWallId) + drawTeleporter(t); + } +} + +void EoBCoreEngine::drawDecorations(int index) { + for (int i = 1; i >= 0; i--) { + int s = index * 2 + i; + if (_dscWallMapping[s]) { + int16 d = *_dscWallMapping[s]; + int8 l = _wllShapeMap[_visibleBlocks[index]->walls[d]]; + + uint8 *shapeData = 0; + + int x = 0; + + while (l > 0) { + l--; + int8 ix = _dscShapeIndex[s]; + uint8 shpIx = ABS(ix) - 1; + uint8 flg = _levelDecorationProperties[l].flags; + + if ((i == 0) && (flg & 1 || ((flg & 2) && _wllProcessFlag))) + ix = -ix; + + if (_levelDecorationProperties[l].shapeIndex[shpIx] == 0xFFFF) { + l = _levelDecorationProperties[l].next; + continue; + } + + shapeData = _levelDecorationShapes[_levelDecorationProperties[l].shapeIndex[shpIx]]; + if (shapeData) { + x = 0; + if (i == 0) { + if (flg & 4) + x += _dscShapeCoords[(index * 5 + 4) << 1]; + else + x += _dscShapeX[index]; + } + + if (ix < 0) { + x += (176 - _levelDecorationProperties[l].shapeX[shpIx] - (shapeData[2] << 3)); + drawBlockObject(1, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim); + } else { + x += _levelDecorationProperties[l].shapeX[shpIx]; + drawBlockObject(0, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim); + + } + } + l = _levelDecorationProperties[l].next; + continue; + } + } + } +} + +int EoBCoreEngine::calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction) { + uint16 b = calcNewBlockPosition(curBlock, direction); + int w = _levelBlockProperties[b].walls[direction ^ 2]; + int f = _wllWallFlags[w]; + + assert((_flags.gameID == GI_EOB1 && w < 70) || (_flags.gameID == GI_EOB2 && w < 80)); + + if (_flags.gameID == GI_EOB2 && w == 74 && _currentBlock == curBlock) { + for (int i = 0; i < 5; i++) { + if (_wallsOfForce[i].block == b) { + destroyWallOfForce(i); + f = _wllWallFlags[0]; + } + } + } + + if (!(f & 1) || _levelBlockProperties[b].flags & 7) + return -1; + + return b; +} + +void EoBCoreEngine::notifyBlockNotPassable() { + _txt->printMessage(_warningStrings[0]); + snd_playSoundEffect(29); + removeInputTop(); +} + +void EoBCoreEngine::moveParty(uint16 block) { + updateAllMonsterDests(); + uint16 old = _currentBlock; + _currentBlock = block; + + runLevelScript(old, 2); + + if (++_moveCounter > 3) { + _txt->printMessage("\r"); + _moveCounter = 0; + } + + runLevelScript(block, 1); + + if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26) + memset(_levelBlockProperties[block].walls, 0, 4); + + updateAllMonsterDests(); + _stepCounter++; + //_keybControlUnk = -1; + _sceneUpdateRequired = true; + + checkFlyingObjects(); +} + +int EoBCoreEngine::clickedDoorSwitch(uint16 block, uint16 direction) { + uint8 v = _visibleBlocks[13]->walls[_sceneDrawVarDown]; + SpriteDecoration *d = &_doorSwitches[((v > 12 && v < 23) || v == 31) ? 3 : 0]; + int x1 = d->x + _dscShapeCoords[138] - 4; + int y1 = d->y - 4; + + if (_flags.gameID == GI_EOB1 && _currentLevel >= 4 && _currentLevel <= 6) { + if (v >= 30) + x1 += 4; + else + x1 += ((v - _dscDoorXE[v]) * 9); + } + + if (!posWithinRect(_mouseX, _mouseY, x1, y1, x1 + (d->shp[2] << 3) + 8, y1 + d->shp[1] + 8) && (_clickedSpecialFlag == 0x40)) + return clickedDoorNoPry(block, direction); + + processDoorSwitch(block, 0); + snd_playSoundEffect(6); + + return 1; +} + +int EoBCoreEngine::clickedNiche(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + if (_itemInHand) { + if (_dscItemShapeMap[_items[_itemInHand].icon] <= 14) { + _txt->printMessage(_pryDoorStrings[5]); + } else { + setItemPosition((Item *)&_levelBlockProperties[block & 0x3FF].drawObjects, block, _itemInHand, 8); + runLevelScript(block, 4); + setHandItem(0); + _sceneUpdateRequired = true; + } + } else { + int d = getQueuedItem((Item *)&_levelBlockProperties[block].drawObjects, 8, -1); + if (!d) + return 1; + runLevelScript(block, 8); + setHandItem(d); + _sceneUpdateRequired = true; + } + + return 1; +} + +int EoBCoreEngine::clickedDoorPry(uint16 block, uint16 direction) { + if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40)) + return 0; + + int d = -1; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x0D)) + continue; + if (d >= 0) { + int s1 = _characters[i].strengthCur + _characters[i].strengthExtCur; + int s2 = _characters[d].strengthCur + _characters[d].strengthExtCur; + if (s1 >= s2) + d = i; + } else { + d = i; + } + } + + if (d == -1) { + _txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 1 : 0]); + return 1; + } + + static const uint8 forceDoorChanceTable[] = { 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12, 13 }; + int s = _characters[d].strengthCur > 18 ? 18 : _characters[d].strengthCur; + + if (rollDice(1, 20) < forceDoorChanceTable[s]) { + _txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 2 : 1]); + _levelBlockProperties[block].walls[direction] = _levelBlockProperties[block].walls[direction ^ 2] = + (_levelBlockProperties[block].walls[direction] == (_flags.gameID == GI_EOB2 ? 51 : 30)) ? 8 : 18; + openDoor(block); + } else { + _txt->printMessage(_pryDoorStrings[3]); + } + + return 1; +} + +int EoBCoreEngine::clickedDoorNoPry(uint16 block, uint16 direction) { + if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40)) + return 0; + + if (!(_wllWallFlags[_levelBlockProperties[block].walls[direction]] & 0x20)) + return 0; + _txt->printMessage(_pryDoorStrings[6]); + return 1; +} + +int EoBCoreEngine::specialWallAction(int block, int direction) { + direction ^= 2; + uint8 type = _specialWallTypes[_levelBlockProperties[block].walls[direction]]; + if (!type || !(_clickedSpecialFlag & (((_levelBlockProperties[block].flags & 0xF8) >> 3) | 0xE0))) + return 0; + + int res = 0; + switch (type) { + case 1: + res = clickedDoorSwitch(block, direction); + break; + + case 2: + case 8: + res = clickedWallShape(block, direction); + break; + + case 3: + res = clickedLeverOn(block, direction); + break; + + case 4: + res = clickedLeverOff(block, direction); + break; + + case 5: + res = clickedDoorPry(block, direction); + break; + + case 6: + res = clickedDoorNoPry(block, direction); + break; + + case 7: + case 9: + res = clickedWallOnlyScript(block); + break; + + case 10: + res = clickedNiche(block, direction); + break; + + default: + break; + } + + _clickedSpecialFlag = 0; + _sceneUpdateRequired = true; + + return res; +} + +void EoBCoreEngine::openDoor(int block) { + openCloseDoor(block, 1); +} + +void EoBCoreEngine::closeDoor(int block) { + if (block == _currentBlock || _levelBlockProperties[block].flags & 7) + return; + openCloseDoor(block, -1); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/scene_hof.cpp b/engines/kyra/engine/scene_hof.cpp new file mode 100644 index 0000000000..e4747fd7d5 --- /dev/null +++ b/engines/kyra/engine/scene_hof.cpp @@ -0,0 +1,737 @@ +/* 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 "kyra/engine/kyra_hof.h" +#include "kyra/sound/sound.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_HoF::enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3) { + if (_newChapterFile != _currentTalkFile) { + _currentTalkFile = _newChapterFile; + if (_flags.isTalkie) { + showMessageFromCCode(265, 150, 0); + _screen->updateScreen(); + openTalkFile(_currentTalkFile); + } + showMessage(0, 207); + _screen->updateScreen(); + } + + _screen->hideMouse(); + + if (!unk3) { + updateWaterFlasks(); + displayInvWsaLastFrame(); + } + + if (unk1) { + int x = _mainCharacter.x1; + int y = _mainCharacter.y1; + + switch (facing) { + case 0: + y -= 6; + break; + + case 2: + x = 335; + break; + + case 4: + y = 147; + break; + + case 6: + x = -16; + break; + + default: + break; + } + + moveCharacter(facing, x, y); + } + + // TODO: Check how the original handled sfx still playing + _sound->stopAllSoundEffects(); + + bool newSoundFile = false; + uint32 waitTime = 0; + if (_sceneList[newScene].sound != _lastMusicCommand) { + newSoundFile = true; + waitTime = _system->getMillis() + 1000; + _sound->beginFadeOut(); + } + + _chatAltFlag = false; + + if (!unk3) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _emc->start(&_sceneScriptState, 5); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } + + Common::for_each(_wsaSlots, ARRAYEND(_wsaSlots), Common::mem_fun(&WSAMovie_v2::close)); + _specialExitCount = 0; + memset(_specialExitTable, -1, sizeof(_specialExitTable)); + + _mainCharacter.sceneId = newScene; + _sceneList[newScene].flags &= ~1; + loadScenePal(); + unloadScene(); + loadSceneMsc(); + + SceneDesc &scene = _sceneList[newScene]; + _sceneExit1 = scene.exit1; + _sceneExit2 = scene.exit2; + _sceneExit3 = scene.exit3; + _sceneExit4 = scene.exit4; + + if (newSoundFile) { + if (_sound->getMusicType() == Sound::kAdLib) { + while (_sound->isPlaying()) + _system->delayMillis(10); + } else { + while (waitTime > _system->getMillis()) + _system->delayMillis(10); + } + snd_loadSoundFile(_sceneList[newScene].sound); + } + + startSceneScript(unk3); + + if (_overwriteSceneFacing) { + facing = _mainCharacter.facing; + _overwriteSceneFacing = false; + } + + enterNewSceneUnk1(facing, unk2, unk3); + + setTimer1DelaySecs(-1); + _sceneScriptState.regs[3] = 1; + enterNewSceneUnk2(unk3); + _screen->showMouse(); + _unk5 = 0; + setNextIdleAnimTimer(); + + _currentScene = newScene; +} + +void KyraEngine_HoF::enterNewSceneUnk1(int facing, int unk1, int unk2) { + int x = 0, y = 0; + int x2 = 0, y2 = 0; + bool needProc = true; + + if (_mainCharX == -1 && _mainCharY == -1) { + switch (facing+1) { + case 1: case 2: case 8: + x2 = _sceneEnterX3; + y2 = _sceneEnterY3; + break; + + case 3: + x2 = _sceneEnterX4; + y2 = _sceneEnterY4; + break; + + case 4: case 5: case 6: + x2 = _sceneEnterX1; + y2 = _sceneEnterY1; + break; + + case 7: + x2 = _sceneEnterX2; + y2 = _sceneEnterY2; + break; + + default: + x2 = y2 = -1; + } + + if (x2 >= 316) + x2 = 312; + if (y2 >= 141) + y2 = 139; + if (x2 <= 4) + x2 = 8; + } + + if (_mainCharX >= 0) { + x = x2 = _mainCharX; + needProc = false; + } + + if (_mainCharY >= 0) { + y = y2 = _mainCharY; + needProc = false; + } + + _mainCharX = _mainCharY = -1; + + if (unk1 && needProc) { + x = x2; + y = y2; + + switch (facing) { + case 0: + y2 = 147; + break; + + case 2: + x2 = -16; + break; + + case 4: + y2 = y - 4; + break; + + case 6: + x2 = 335; + break; + + default: + break; + } + } + + x2 &= ~3; + x &= ~3; + y2 &= ~1; + y &= ~1; + + _mainCharacter.facing = facing; + _mainCharacter.x1 = _mainCharacter.x2 = x2; + _mainCharacter.y1 = _mainCharacter.y2 = y2; + initSceneAnims(unk2); + + if (!unk2) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + + if (unk1 && !unk2 && _mainCharacter.animFrame != 32) + moveCharacter(facing, x, y); +} + +void KyraEngine_HoF::enterNewSceneUnk2(int unk1) { + _savedMouseState = -1; + + if (_flags.isTalkie) { + if (_mainCharX == -1 && _mainCharY == -1 && _mainCharacter.sceneId != 61 && + !queryGameFlag(0x1F1) && !queryGameFlag(0x192) && !queryGameFlag(0x193) && + _mainCharacter.sceneId != 70 && !queryGameFlag(0x159) && _mainCharacter.sceneId != 37) { + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + } else if (_mainCharX != -1 && _mainCharY != -1) { + if (_characterFrameTable[_mainCharacter.facing] == 25) + _mainCharacter.facing = 5; + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + + if (!unk1) { + runSceneScript4(0); + zanthSceneStartupChat(); + } + + _unk4 = 0; + _savedMouseState = -1; +} + +int KyraEngine_HoF::trySceneChange(int *moveTable, int unk1, int updateChar) { + bool running = true; + bool unkFlag = false; + int8 updateType = -1; + int changedScene = 0; + const int *moveTableStart = moveTable; + _unk4 = 0; + while (running && !shouldQuit()) { + if (*moveTable >= 0 && *moveTable <= 7) { + _mainCharacter.facing = getOppositeFacingDirection(*moveTable); + unkFlag = true; + } else { + if (*moveTable == 8) { + running = false; + } else { + ++moveTable; + unkFlag = false; + } + } + + if (checkSceneChange()) { + running = false; + changedScene = 1; + } + + if (unk1) { + if (skipFlag()) { + resetSkipFlag(false); + running = false; + _unk4 = 1; + } + } + + if (!unkFlag || !running) + continue; + + int ret = 0; + if (moveTable == moveTableStart || moveTable[1] == 8) + ret = updateCharPos(0); + else + ret = updateCharPos(moveTable); + + if (ret) + ++moveTable; + + ++updateType; + if (!updateType) { + update(); + } else if (updateType == 1) { + refreshAnimObjectsIfNeed(); + updateType = -1; + } + + delay(10); + } + + if (updateChar) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + + return changedScene; +} + +int KyraEngine_HoF::checkSceneChange() { + SceneDesc &curScene = _sceneList[_mainCharacter.sceneId]; + int charX = _mainCharacter.x1, charY = _mainCharacter.y1; + int facing = 0; + int process = 0; + + if (_screen->getLayer(charX, charY) == 1 && _savedMouseState == -6) { + facing = 0; + process = 1; + } else if (charX >= 316 && _savedMouseState == -5) { + facing = 2; + process = 1; + } else if (charY >= 142 && _savedMouseState == -4) { + facing = 4; + process = 1; + } else if (charX <= 4 && _savedMouseState == -3) { + facing = 6; + process = 1; + } + + if (!process) + return 0; + + uint16 newScene = 0xFFFF; + switch (facing) { + case 0: + newScene = curScene.exit1; + break; + + case 2: + newScene = curScene.exit2; + break; + + case 4: + newScene = curScene.exit3; + break; + + case 6: + newScene = curScene.exit4; + break; + + default: + newScene = _mainCharacter.sceneId; + } + + if (newScene == 0xFFFF) + return 0; + + enterNewScene(newScene, facing, 1, 1, 0); + return 1; +} + +void KyraEngine_HoF::unloadScene() { + _emc->unload(&_sceneScriptData); + freeSceneShapePtrs(); + freeSceneAnims(); +} + +void KyraEngine_HoF::loadScenePal() { + uint16 sceneId = _mainCharacter.sceneId; + _screen->copyPalette(1, 0); + + char filename[14]; + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, ".COL"); + _screen->loadBitmap(filename, 3, 3, 0); + _screen->getPalette(1).copy(_screen->getCPagePtr(3), 0, 128); + _screen->getPalette(1).fill(0, 1, 0); + memcpy(_scenePal, _screen->getCPagePtr(3)+336, 432); +} + +void KyraEngine_HoF::loadSceneMsc() { + uint16 sceneId = _mainCharacter.sceneId; + char filename[14]; + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, ".MSC"); + _screen->loadBitmap(filename, 3, 5, 0); +} + +void KyraEngine_HoF::startSceneScript(int unk1) { + uint16 sceneId = _mainCharacter.sceneId; + char filename[14]; + + strcpy(filename, _sceneList[sceneId].filename1); + if (sceneId == 68 && (queryGameFlag(0x1BC) || queryGameFlag(0x1BD))) + strcpy(filename, "DOORX"); + strcat(filename, ".CPS"); + + _screen->loadBitmap(filename, 3, 3, 0); + resetScaleTable(); + _useCharPal = false; + memset(_charPalTable, 0, sizeof(_charPalTable)); + memset(_layerFlagTable, 0, sizeof(_layerFlagTable)); + memset(_specialSceneScriptState, 0, sizeof(_specialSceneScriptState)); + + _sceneEnterX1 = 160; + _sceneEnterY1 = 0; + _sceneEnterX2 = 296; + _sceneEnterY2 = 72; + _sceneEnterX3 = 160; + _sceneEnterY3 = 128; + _sceneEnterX4 = 24; + _sceneEnterY4 = 72; + + _sceneCommentString = "Undefined scene comment string!"; + _emc->init(&_sceneScriptState, &_sceneScriptData); + + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, "."); + strcat(filename, _scriptLangExt[(_flags.platform == Common::kPlatformDOS && !_flags.isTalkie) ? 0 : _lang]); + + _res->exists(filename, true); + _emc->load(filename, &_sceneScriptData, &_opcodes); + runSceneScript7(); + + _emc->start(&_sceneScriptState, 0); + _sceneScriptState.regs[0] = sceneId; + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + memcpy(_gamePlayBuffer, _screen->getCPagePtr(3), 46080); + + for (int i = 0; i < 10; ++i) { + _emc->init(&_sceneSpecialScripts[i], &_sceneScriptData); + _emc->start(&_sceneSpecialScripts[i], i+8); + _sceneSpecialScriptsTimer[i] = 0; + } + + _sceneEnterX1 &= ~3; + _sceneEnterX2 &= ~3; + _sceneEnterX3 &= ~3; + _sceneEnterX4 &= ~3; + _sceneEnterY1 &= ~1; + _sceneEnterY2 &= ~1; + _sceneEnterY3 &= ~1; + _sceneEnterY4 &= ~1; +} + +void KyraEngine_HoF::runSceneScript2() { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[4] = _itemInHand; + _emc->start(&_sceneScriptState, 2); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::runSceneScript4(int unk1) { + _sceneScriptState.regs[4] = _itemInHand; + _sceneScriptState.regs[5] = unk1; + + _emc->start(&_sceneScriptState, 4); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::runSceneScript7() { + int oldPage = _screen->_curPage; + _screen->_curPage = 2; + + _emc->start(&_sceneScriptState, 7); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + _screen->_curPage = oldPage; +} + +void KyraEngine_HoF::initSceneAnims(int unk1) { + for (int i = 0; i < 41; ++i) + _animObjects[i].enabled = 0; + + bool animInit = false; + + AnimObj *animState = &_animObjects[0]; + + if (_mainCharacter.animFrame != 32) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + animState->enabled = 1; + animState->xPos1 = _mainCharacter.x1; + animState->yPos1 = _mainCharacter.y1; + animState->shapePtr = getShapePtr(_mainCharacter.animFrame); + animState->shapeIndex1 = animState->shapeIndex2 = _mainCharacter.animFrame; + + int frame = _mainCharacter.animFrame - 9; + int shapeX = _shapeDescTable[frame].xAdd; + int shapeY = _shapeDescTable[frame].yAdd; + + animState->xPos2 = _mainCharacter.x1; + animState->yPos2 = _mainCharacter.y1; + + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + + int shapeXScaled = (shapeX * _charScale) >> 8; + int shapeYScaled = (shapeY * _charScale) >> 8; + + animState->xPos2 += shapeXScaled; + animState->yPos2 += shapeYScaled; + animState->xPos3 = animState->xPos2; + animState->yPos3 = animState->yPos2; + animState->needRefresh = 1; + animState->specialRefresh = 1; + + _animList = 0; + + AnimObj *charAnimState = animState; + + for (int i = 0; i < 10; ++i) { + animState = &_animObjects[i+1]; + animState->enabled = 0; + animState->needRefresh = 0; + animState->specialRefresh = 0; + + if (_sceneAnims[i].flags & 1) { + animState->enabled = 1; + animState->needRefresh = 1; + animState->specialRefresh = 1; + } + + animState->animFlags = _sceneAnims[i].flags & 8; + + if (_sceneAnims[i].flags & 2) + animState->flags = 0x800; + else + animState->flags = 0; + + if (_sceneAnims[i].flags & 4) + animState->flags |= 1; + + animState->xPos1 = _sceneAnims[i].x; + animState->yPos1 = _sceneAnims[i].y; + + if (_sceneAnims[i].flags & 0x20) + animState->shapePtr = _sceneShapeTable[_sceneAnims[i].shapeIndex]; + else + animState->shapePtr = 0; + + if (_sceneAnims[i].flags & 0x40) { + animState->shapeIndex3 = _sceneAnims[i].shapeIndex; + animState->animNum = i; + } else { + animState->shapeIndex3 = 0xFFFF; + animState->animNum = 0xFFFF; + } + + animState->shapeIndex2 = 0xFFFF; + + animState->xPos3 = animState->xPos2 = _sceneAnims[i].x2; + animState->yPos3 = animState->yPos2 = _sceneAnims[i].y2; + animState->width = _sceneAnims[i].width; + animState->height = _sceneAnims[i].height; + animState->width2 = animState->height2 = _sceneAnims[i].specialSize; + + if (_sceneAnims[i].flags & 1) { + if (animInit) { + _animList = addToAnimListSorted(_animList, animState); + } else { + _animList = initAnimList(_animList, animState); + animInit = true; + } + } + } + + if (animInit) { + _animList = addToAnimListSorted(_animList, charAnimState); + } else { + _animList = initAnimList(_animList, charAnimState); + animInit = true; + } + + for (int i = 0; i < 30; ++i) { + animState = &_animObjects[i+11]; + + uint16 shapeIndex = _itemList[i].id; + if (shapeIndex == 0xFFFF || _itemList[i].sceneId != _mainCharacter.sceneId) { + animState->enabled = 0; + animState->needRefresh = 0; + animState->specialRefresh = 0; + } else { + animState->xPos1 = _itemList[i].x; + animState->yPos1 = _itemList[i].y; + animState->shapePtr = getShapePtr(64+shapeIndex); + animState->shapeIndex1 = animState->shapeIndex2 = shapeIndex+64; + + animState->xPos2 = _itemList[i].x; + animState->yPos2 = _itemList[i].y; + int objectScale = getScale(animState->xPos2, animState->yPos2); + + const uint8 *shape = getShapePtr(animState->shapeIndex1); + animState->xPos2 -= (_screen->getShapeScaledWidth(shape, objectScale) >> 1); + animState->yPos2 -= (_screen->getShapeScaledHeight(shape, objectScale) >> 1); + animState->xPos3 = animState->xPos2; + animState->yPos3 = animState->yPos2; + + animState->enabled = 1; + animState->needRefresh = 1; + animState->specialRefresh = 1; + + if (animInit) { + _animList = addToAnimListSorted(_animList, animState); + } else { + _animList = initAnimList(_animList, animState); + animInit = true; + } + } + } + + _animObjects[0].specialRefresh = 1; + _animObjects[0].needRefresh = 1; + + for (int i = 1; i < 41; ++i) { + if (_animObjects[i].enabled) { + _animObjects[i].needRefresh = 1; + _animObjects[i].specialRefresh = 1; + } + } + + restorePage3(); + drawAnimObjects(); + _screen->hideMouse(); + initSceneScreen(unk1); + _screen->showMouse(); + refreshAnimObjects(0); +} + +void KyraEngine_HoF::initSceneScreen(int unk1) { + if (_unkSceneScreenFlag1) { + _screen->copyRegion(0, 0, 0, 0, 320, 144, 2, 0, Screen::CR_NO_P_CHECK); + return; + } + + if (_noScriptEnter) { + _screen->getPalette(0).fill(0, 128, 0); + _screen->setScreenPalette(_screen->getPalette(0)); + } + + _screen->copyRegion(0, 0, 0, 0, 320, 144, 2, 0, Screen::CR_NO_P_CHECK); + + if (_noScriptEnter) { + _screen->setScreenPalette(_screen->getPalette(1)); + _screen->getPalette(0).copy(_screen->getPalette(1), 0, 128); + } + + updateCharPal(0); + + _emc->start(&_sceneScriptState, 3); + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::freeSceneShapePtrs() { + for (int i = 0; i < ARRAYSIZE(_sceneShapeTable); ++i) + delete[] _sceneShapeTable[i]; + memset(_sceneShapeTable, 0, sizeof(_sceneShapeTable)); +} + +void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) { + _screen->getPalette(0).copy(_scenePal, srcIndex << 4, 16, 112); + _screen->fadePalette(_screen->getPalette(0), delayTime, &_updateFunctor); +} + +#pragma mark - +#pragma mark - Pathfinder +#pragma mark - + +bool KyraEngine_HoF::lineIsPassable(int x, int y) { + static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; + + if (_pathfinderFlag & 2) { + if (x >= 320) + return false; + } + + if (_pathfinderFlag & 4) { + if (y >= 144) + return false; + } + + if (_pathfinderFlag & 8) { + if (x < 0) + return false; + } + + if (y > 143) + return false; + + int unk1 = widthTable[getScale(x, y) >> 5]; + + if (y < 0) + y = 0; + x -= unk1 >> 1; + if (x < 0) + x = 0; + int x2 = x + unk1; + if (x2 > 320) + x2 = 320; + + for (;x < x2; ++x) + if (!_screen->getShapeFlag1(x, y)) + return false; + + return true; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_lok.cpp b/engines/kyra/engine/scene_lok.cpp new file mode 100644 index 0000000000..51348c5392 --- /dev/null +++ b/engines/kyra/engine/scene_lok.cpp @@ -0,0 +1,1301 @@ +/* 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 "kyra/engine/kyra_lok.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" +#include "kyra/engine/sprites.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_LoK::enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive) { + int unkVar1 = 1; + _screen->hideMouse(); + + // TODO: Check how the original handled sfx still playing + _sound->stopAllSoundEffects(); + + if (_flags.platform == Common::kPlatformFMTowns) { + int newSfxFile = -1; + if (_currentCharacter->sceneId == 7 && sceneId == 24) + newSfxFile = 2; + else if (_currentCharacter->sceneId == 25 && sceneId == 109) + newSfxFile = 3; + else if (_currentCharacter->sceneId == 120 && sceneId == 37) + newSfxFile = 4; + else if (_currentCharacter->sceneId == 52 && sceneId == 199) + newSfxFile = 5; + else if (_currentCharacter->sceneId == 37 && sceneId == 120) + newSfxFile = 3; + else if (_currentCharacter->sceneId == 109 && sceneId == 25) + newSfxFile = 2; + else if (_currentCharacter->sceneId == 24 && sceneId == 7) + newSfxFile = 1; + + if (newSfxFile != -1) { + _curSfxFile = newSfxFile; + _sound->loadSoundFile(_curSfxFile); + } + } + + switch (_currentCharacter->sceneId) { + case 1: + if (sceneId == 0) { + moveCharacterToPos(0, 0, _currentCharacter->x1, 84); + unkVar1 = 0; + } + break; + + case 3: + if (sceneId == 2) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 26: + if (sceneId == 27) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 44: + if (sceneId == 45) { + moveCharacterToPos(0, 2, 192, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + default: + break; + } + + if (unkVar1 && unk1) { + int xpos = _currentCharacter->x1; + int ypos = _currentCharacter->y1; + switch (facing) { + case 0: + ypos = _currentCharacter->y1 - 6; + break; + + case 2: + xpos = 336; + break; + + case 4: + ypos = 143; + break; + + case 6: + xpos = -16; + break; + + default: + break; + } + + moveCharacterToPos(0, facing, xpos, ypos); + } + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) + _movieObjects[i]->close(); + + if (!brandonAlive) { + _emc->init(&_scriptClick, &_scriptClickData); + _emc->start(&_scriptClick, 5); + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + } + + memset(_entranceMouseCursorTracks, 0xFF, sizeof(_entranceMouseCursorTracks)); + _currentCharacter->sceneId = sceneId; + + assert(sceneId < _roomTableSize); + assert(_roomTable[sceneId].nameIndex < _roomFilenameTableSize); + + Room *currentRoom = &_roomTable[sceneId]; + + setupSceneResource(sceneId); + + _currentRoom = sceneId; + + int tableId = _roomTable[sceneId].nameIndex; + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".DAT"); + _sprites->loadDat(fileNameBuffer, _sceneExits); + _sprites->setupSceneAnims(); + _emc->unload(&_scriptClickData); + loadSceneMsc(); + + _walkBlockNorth = currentRoom->northExit; + _walkBlockEast = currentRoom->eastExit; + _walkBlockSouth = currentRoom->southExit; + _walkBlockWest = currentRoom->westExit; + + if (_walkBlockNorth == 0xFFFF) + _screen->blockOutRegion(0, 0, 320, (_northExitHeight & 0xFF) + 3); + if (_walkBlockEast == 0xFFFF) + _screen->blockOutRegion(312, 0, 8, 139); + if (_walkBlockSouth == 0xFFFF) + _screen->blockOutRegion(0, 135, 320, 8); + if (_walkBlockWest == 0xFFFF) + _screen->blockOutRegion(0, 0, 8, 139); + + if (!brandonAlive) + updatePlayerItemsForScene(); + + startSceneScript(brandonAlive); + setupSceneItems(); + + initSceneData(facing, unk2, brandonAlive); + + _loopFlag2 = 0; + _screen->showMouse(); + if (!brandonAlive) + seq_poisonDeathNow(0); + updateMousePointer(true); + _changedScene = true; +} + +void KyraEngine_LoK::transcendScenes(int roomIndex, int roomName) { + assert(roomIndex < _roomTableSize); + + if (_flags.isTalkie) { + char file[32]; + assert(roomIndex < _roomTableSize); + int tableId = _roomTable[roomIndex].nameIndex; + assert(tableId < _roomFilenameTableSize); + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + } + + _roomTable[roomIndex].nameIndex = roomName; + _unkScreenVar2 = 1; + _unkScreenVar3 = 1; + _unkScreenVar1 = 0; + _brandonPosX = _currentCharacter->x1; + _brandonPosY = _currentCharacter->y1; + enterNewScene(roomIndex, _currentCharacter->facing, 0, 0, 0); + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; +} + +void KyraEngine_LoK::setSceneFile(int roomIndex, int roomName) { + assert(roomIndex < _roomTableSize); + _roomTable[roomIndex].nameIndex = roomName; +} + +void KyraEngine_LoK::moveCharacterToPos(int character, int facing, int xpos, int ypos) { + Character *ch = &_characterList[character]; + ch->facing = facing; + _screen->hideMouse(); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + _timer->disable(19); + _timer->disable(14); + _timer->disable(18); + uint32 nextFrame = 0; + + switch (facing) { + case 0: + while (ypos < ch->y1) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 2: + while (ch->x1 < xpos) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 4: + while (ypos > ch->y1) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 6: + while (ch->x1 > xpos) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + default: + break; + } + + _timer->enable(19); + _timer->enable(14); + _timer->enable(18); + _screen->showMouse(); +} + +void KyraEngine_LoK::setCharacterPositionWithUpdate(int character) { + setCharacterPosition(character, 0); + _sprites->updateSceneAnims(); + _timer->update(); + _animator->updateAllObjectShapes(); + updateTextFade(); + + if (_currentCharacter->sceneId == 210) + updateKyragemFading(); +} + +int KyraEngine_LoK::setCharacterPosition(int character, int *facingTable) { + if (character == 0) { + _currentCharacter->x1 += _charAddXPosTable[_currentCharacter->facing]; + _currentCharacter->y1 += _charAddYPosTable[_currentCharacter->facing]; + setCharacterPositionHelper(0, facingTable); + return 1; + } else { + _characterList[character].x1 += _charAddXPosTable[_characterList[character].facing]; + _characterList[character].y1 += _charAddYPosTable[_characterList[character].facing]; + if (_characterList[character].sceneId == _currentCharacter->sceneId) + setCharacterPositionHelper(character, 0); + } + return 0; +} + +void KyraEngine_LoK::setCharacterPositionHelper(int character, int *facingTable) { + Character *ch = &_characterList[character]; + ++ch->currentAnimFrame; + int facing = ch->facing; + if (facingTable) { + if (*facingTable != *(facingTable - 1)) { + if (*(facingTable - 1) == *(facingTable + 1)) { + facing = getOppositeFacingDirection(*(facingTable - 1)); + *facingTable = *(facingTable - 1); + } + } + } + + if (facing == 0) { + ++_characterFacingZeroCount[character]; + } else { + bool resetTables = false; + if (facing != 7) { + if (facing - 1 != 0) { + if (facing != 4) { + if (facing == 3 || facing == 5) { + if (_characterFacingFourCount[character] > 2) + facing = 4; + resetTables = true; + } + } else { + ++_characterFacingFourCount[character]; + } + } else { + if (_characterFacingZeroCount[character] > 2) + facing = 0; + resetTables = true; + } + } else { + if (_characterFacingZeroCount[character] > 2) + facing = 0; + resetTables = true; + } + + if (resetTables) { + _characterFacingZeroCount[character] = 0; + _characterFacingFourCount[character] = 0; + } + } + + static const uint16 maxAnimationFrame[] = { + 0x000F, 0x0031, 0x0055, 0x0000, 0x0000, 0x0000, + 0x0008, 0x002A, 0x004E, 0x0000, 0x0000, 0x0000, + 0x0022, 0x0046, 0x006A, 0x0000, 0x0000, 0x0000, + 0x001D, 0x0041, 0x0065, 0x0000, 0x0000, 0x0000, + 0x001F, 0x0043, 0x0067, 0x0000, 0x0000, 0x0000, + 0x0028, 0x004C, 0x0070, 0x0000, 0x0000, 0x0000, + 0x0023, 0x0047, 0x006B, 0x0000, 0x0000, 0x0000 + }; + + if (facing == 0) { + if (maxAnimationFrame[36 + character] > ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[36 + character]; + if (maxAnimationFrame[30 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[36 + character]; + } else if (facing == 4) { + if (maxAnimationFrame[18 + character] > ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[18 + character]; + if (maxAnimationFrame[12 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[18 + character]; + } else { + if (maxAnimationFrame[18 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[30 + character]; + if (maxAnimationFrame[character] == ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[6 + character]; + if (maxAnimationFrame[character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[6 + character] + 2; + } + + if (character == 0 && (_brandonStatusBit & 0x10)) + ch->currentAnimFrame = 88; + + _animator->animRefreshNPC(character); +} + +void KyraEngine_LoK::loadSceneMsc() { + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".MSC"); + _screen->fillRect(0, 0, 319, 199, 0, 5); + _res->exists(fileNameBuffer, true); + _screen->loadBitmap(fileNameBuffer, 3, 5, 0); +} + +void KyraEngine_LoK::startSceneScript(int brandonAlive) { + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".CPS"); + _screen->clearPage(3); + _res->exists(fileNameBuffer, true); + // FIXME: check this hack for amiga version + _screen->loadBitmap(fileNameBuffer, 3, 3, (_flags.platform == Common::kPlatformAmiga ? &_screen->getPalette(0) : 0)); + _sprites->loadSceneShapes(); + _exitListPtr = 0; + + _scaleMode = 1; + for (int i = 0; i < 145; ++i) + _scaleTable[i] = 256; + + clearNoDropRects(); + _emc->init(&_scriptClick, &_scriptClickData); + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".EMC"); + _res->exists(fileNameBuffer, true); + _emc->unload(&_scriptClickData); + _emc->load(fileNameBuffer, &_scriptClickData, &_opcodes); + _emc->start(&_scriptClick, 0); + _scriptClick.regs[0] = _currentCharacter->sceneId; + _scriptClick.regs[7] = brandonAlive; + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +void KyraEngine_LoK::initSceneData(int facing, int unk1, int brandonAlive) { + int16 xpos2 = 0; + int setFacing = 1; + + int16 xpos = 0, ypos = 0; + + if (_brandonPosX == -1 && _brandonPosY == -1) { + switch (facing + 1) { + case 0: + xpos = ypos = -1; + break; + + case 1: case 2: case 8: + xpos = _sceneExits.southXPos; + ypos = _sceneExits.southYPos; + break; + + case 3: + xpos = _sceneExits.westXPos; + ypos = _sceneExits.westYPos; + break; + + case 4: case 5: case 6: + xpos = _sceneExits.northXPos; + ypos = _sceneExits.northYPos; + break; + + case 7: + xpos = _sceneExits.eastXPos; + ypos = _sceneExits.eastYPos; + break; + + default: + break; + } + + if ((uint8)(_northExitHeight & 0xFF) + 2 >= ypos) + ypos = (_northExitHeight & 0xFF) + 4; + if (xpos >= 308) + xpos = 304; + if ((uint8)(_northExitHeight >> 8) - 2 <= ypos) + ypos = (_northExitHeight >> 8) - 4; + if (xpos <= 12) + xpos = 16; + } + + if (_brandonPosX > -1) + xpos = _brandonPosX; + if (_brandonPosY > -1) + ypos = _brandonPosY; + + int16 ypos2 = 0; + if (_brandonPosX > -1 && _brandonPosY > -1) { + switch (_currentCharacter->sceneId) { + case 1: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 4; + xpos2 = 192; + ypos2 = 104; + setFacing = 0; + unk1 = 1; + break; + + case 3: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 204; + ypos2 = 94; + setFacing = 0; + unk1 = 1; + break; + + case 26: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 192; + ypos2 = 128; + setFacing = 0; + unk1 = 1; + break; + + case 44: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 6; + xpos2 = 156; + ypos2 = 96; + setFacing = 0; + unk1 = 1; + break; + + case 37: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 148; + ypos2 = 114; + setFacing = 0; + unk1 = 1; + break; + + default: + break; + } + } + + _brandonPosX = _brandonPosY = -1; + + if (unk1 && setFacing) { + ypos2 = ypos; + xpos2 = xpos; + switch (facing) { + case 0: + ypos = 142; + break; + + case 2: + xpos = -16; + break; + + case 4: + ypos = (uint8)(_northExitHeight & 0xFF) - 4; + break; + + case 6: + xpos = 336; + break; + + default: + break; + } + } + + xpos2 = (int16)(xpos2 & 0xFFFC); + ypos2 = (int16)(ypos2 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + _currentCharacter->facing = facing; + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + + initSceneObjectList(brandonAlive); + + if (unk1 && brandonAlive == 0) + moveCharacterToPos(0, facing, xpos2, ypos2); + + _scriptClick.regs[4] = _itemInHand; + _scriptClick.regs[7] = brandonAlive; + _emc->start(&_scriptClick, 3); + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +void KyraEngine_LoK::initSceneObjectList(int brandonAlive) { + for (int i = 0; i < 28; ++i) + _animator->actors()[i].active = 0; + + int startAnimFrame = 0; + + Animator_LoK::AnimObject *curAnimState = _animator->actors(); + curAnimState->active = 1; + curAnimState->drawY = _currentCharacter->y1; + curAnimState->sceneAnimPtr = _shapes[_currentCharacter->currentAnimFrame]; + curAnimState->animFrameNumber = _currentCharacter->currentAnimFrame; + startAnimFrame = _currentCharacter->currentAnimFrame - 7; + int xOffset = _defaultShapeTable[startAnimFrame].xOffset; + int yOffset = _defaultShapeTable[startAnimFrame].yOffset; + + if (_scaleMode) { + curAnimState->x1 = _currentCharacter->x1; + curAnimState->y1 = _currentCharacter->y1; + + _animator->_brandonScaleX = _scaleTable[_currentCharacter->y1]; + _animator->_brandonScaleY = _scaleTable[_currentCharacter->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = _currentCharacter->x1 + xOffset; + curAnimState->y1 = _currentCharacter->y1 + yOffset; + } + + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + _animator->clearQueue(); + _animator->addObjectToQueue(curAnimState); + + int listAdded = 0; + int addedObjects = 1; + + for (int i = 1; i < 5; ++i) { + Character *ch = &_characterList[i]; + curAnimState = &_animator->actors()[addedObjects]; + if (ch->sceneId != _currentCharacter->sceneId) { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + ++addedObjects; + continue; + } + + curAnimState->drawY = ch->y1; + curAnimState->sceneAnimPtr = _shapes[ch->currentAnimFrame]; + curAnimState->animFrameNumber = ch->currentAnimFrame; + startAnimFrame = ch->currentAnimFrame - 7; + xOffset = _defaultShapeTable[startAnimFrame].xOffset; + yOffset = _defaultShapeTable[startAnimFrame].yOffset; + if (_scaleMode) { + curAnimState->x1 = ch->x1; + curAnimState->y1 = ch->y1; + + _animator->_brandonScaleX = _scaleTable[ch->y1]; + _animator->_brandonScaleY = _scaleTable[ch->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = ch->x1 + xOffset; + curAnimState->y1 = ch->y1 + yOffset; + } + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + if (ch->facing >= 1 && ch->facing <= 3) + curAnimState->flags |= 1; + else if (ch->facing >= 5 && ch->facing <= 7) + curAnimState->flags &= 0xFFFFFFFE; + + _animator->addObjectToQueue(curAnimState); + + ++addedObjects; + ++listAdded; + if (listAdded < 2) + i = 5; + } + + for (int i = 0; i < 11; ++i) { + curAnimState = &_animator->sprites()[i]; + + if (_sprites->_anims[i].play) { + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + curAnimState->height = _sprites->_anims[i].height; + curAnimState->height2 = _sprites->_anims[i].height2; + curAnimState->width = _sprites->_anims[i].width + 1; + curAnimState->width2 = _sprites->_anims[i].width2; + curAnimState->drawY = _sprites->_anims[i].drawY; + curAnimState->x1 = curAnimState->x2 = _sprites->_anims[i].x; + curAnimState->y1 = curAnimState->y2 = _sprites->_anims[i].y; + curAnimState->background = _sprites->_anims[i].background; + curAnimState->sceneAnimPtr = _sprites->_sceneShapes[_sprites->_anims[i].sprite]; + + curAnimState->disable = _sprites->_anims[i].disable; + + if (_sprites->_anims[i].unk2) + curAnimState->flags = 0x800; + else + curAnimState->flags = 0; + + if (_sprites->_anims[i].flipX) + curAnimState->flags |= 0x1; + + _animator->addObjectToQueue(curAnimState); + } + + for (int i = 0; i < 12; ++i) { + curAnimState = &_animator->items()[i]; + Room *curRoom = &_roomTable[_currentCharacter->sceneId]; + byte curItem = curRoom->itemsTable[i]; + if (curItem != 0xFF) { + curAnimState->drawY = curRoom->itemsYPos[i]; + curAnimState->sceneAnimPtr = _shapes[216 + curItem]; + curAnimState->animFrameNumber = (int16)0xFFFF; + curAnimState->y1 = curRoom->itemsYPos[i]; + curAnimState->x1 = curRoom->itemsXPos[i]; + + curAnimState->x1 -= (_animator->fetchAnimWidth(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY])) >> 1; + curAnimState->y1 -= _animator->fetchAnimHeight(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY]); + + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + _animator->addObjectToQueue(curAnimState); + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + } + + _animator->preserveAnyChangedBackgrounds(); + curAnimState = _animator->actors(); + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + for (int i = 1; i < 28; ++i) { + curAnimState = &_animator->objects()[i]; + if (curAnimState->active) { + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + } + } + _animator->restoreAllObjectBackgrounds(); + _animator->preserveAnyChangedBackgrounds(); + _animator->prepDrawAllObjects(); + initSceneScreen(brandonAlive); + _animator->copyChangedObjectsForward(0); +} + +void KyraEngine_LoK::initSceneScreen(int brandonAlive) { + if (_flags.platform == Common::kPlatformAmiga) { + if (_unkScreenVar1 && !queryGameFlag(0xF0)) { + _screen->getPalette(2).clear(); + if (_currentCharacter->sceneId != 117 || !queryGameFlag(0xB3)) + _screen->setScreenPalette(_screen->getPalette(2)); + } + + if (_unkScreenVar2 == 1) + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, _unkScreenVar3, false); + else + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0, Screen::CR_NO_P_CHECK); + + if (_unkScreenVar1 && !queryGameFlag(0xA0)) { + if (_currentCharacter->sceneId == 45 && _cauldronState) + _screen->getPalette(0).copy(_screen->getPalette(4), 12, 1); + + if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245 && (_brandonStatusBit & 1)) + _screen->copyPalette(0, 10); + + _screen->setScreenPalette(_screen->getPalette(0)); + } + } else { + if (_unkScreenVar1 && !queryGameFlag(0xA0)) { + for (int i = 0; i < 60; ++i) { + uint16 col = _screen->getPalette(0)[684 + i]; + col += _screen->getPalette(1)[684 + i] << 1; + col >>= 2; + _screen->getPalette(0)[684 + i] = col; + } + _screen->setScreenPalette(_screen->getPalette(0)); + } + + if (_unkScreenVar2 == 1) + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, _unkScreenVar3, false); + else + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + + if (_unkScreenVar1 && _paletteChanged) { + if (!queryGameFlag(0xA0)) { + _screen->getPalette(0).copy(_screen->getPalette(1), 228, 20); + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + _screen->getPalette(0).clear(); + } + } + } + + if (!_emc->start(&_scriptClick, 2)) + error("Could not start script function 2 of scene script"); + + _scriptClick.regs[7] = brandonAlive; + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + + setTextFadeTimerCountdown(-1); + + if (_currentCharacter->sceneId == 210) { + if (_itemInHand != kItemNone) + magicOutMouseItem(2, -1); + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + if (_currentCharacter->inventoryItems[i] != kItemNone) + magicOutMouseItem(2, i); + } + _screen->showMouse(); + } +} + +int KyraEngine_LoK::handleSceneChange(int xpos, int ypos, int unk1, int frameReset) { + if (queryGameFlag(0xEF)) + unk1 = 0; + + int sceneId = _currentCharacter->sceneId; + _pathfinderFlag = 0; + + if (xpos < 12) { + if (_roomTable[sceneId].westExit != 0xFFFF) { + xpos = 12; + ypos = _sceneExits.westYPos; + _pathfinderFlag = 7; + } + } else if (xpos >= 308) { + if (_roomTable[sceneId].eastExit != 0xFFFF) { + xpos = 307; + ypos = _sceneExits.eastYPos; + _pathfinderFlag = 13; + } + } + + if (ypos <= (_northExitHeight & 0xFF) + 2) { + if (_roomTable[sceneId].northExit != 0xFFFF) { + xpos = _sceneExits.northXPos; + ypos = _northExitHeight & 0xFF; + _pathfinderFlag = 14; + } + } else if (ypos >= 136) { + if (_roomTable[sceneId].southExit != 0xFFFF) { + xpos = _sceneExits.southXPos; + ypos = 136; + _pathfinderFlag = 11; + } + } + + int temp = xpos - _currentCharacter->x1; + if (ABS(temp) < 4) { + temp = ypos - _currentCharacter->y1; + if (ABS(temp) < 2) + return 0; + } + + int x = (int16)(_currentCharacter->x1 & 0xFFFC); + int y = (int16)(_currentCharacter->y1 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + + int ret = findWay(x, y, xpos, ypos, _movFacingTable, 150); + _pathfinderFlag = 0; + + if (ret >= _lastFindWayRet) + _lastFindWayRet = ret; + + if (ret == 0x7D00 || ret == 0) + return 0; + + return processSceneChange(_movFacingTable, unk1, frameReset); +} + +int KyraEngine_LoK::processSceneChange(int *table, int unk1, int frameReset) { + if (queryGameFlag(0xEF)) + unk1 = 0; + + int *tableStart = table; + _sceneChangeState = 0; + _loopFlag2 = 0; + bool running = true; + int returnValue = 0; + uint32 nextFrame = 0; + + while (running) { + bool forceContinue = false; + switch (*table) { + case 0: case 1: case 2: + case 3: case 4: case 5: + case 6: case 7: + _currentCharacter->facing = getOppositeFacingDirection(*table); + break; + + case 8: + forceContinue = true; + running = false; + break; + + default: + ++table; + forceContinue = true; + } + + returnValue = changeScene(_currentCharacter->facing); + if (returnValue) + running = false; + + if (unk1) { + if (skipFlag()) { + resetSkipFlag(false); + running = false; + _sceneChangeState = 1; + } + } + + if (forceContinue || !running) + continue; + + int temp = 0; + if (table == tableStart || table[1] == 8) + temp = setCharacterPosition(0, 0); + else + temp = setCharacterPosition(0, table); + + if (temp) + ++table; + + nextFrame = _timer->getDelay(5) * _tickLength + _system->getMillis(); + while (_system->getMillis() < nextFrame) { + _timer->update(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd() || _beadStateVar == 4 || _beadStateVar == 5) { + *table = 8; + running = false; + break; + } + } + + if ((nextFrame - _system->getMillis()) >= 10) + delay(10, true); + } + } + + if (frameReset && !(_brandonStatusBit & 2)) + _currentCharacter->currentAnimFrame = 7; + + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + return returnValue; +} + +int KyraEngine_LoK::changeScene(int facing) { + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) + return 0; + } + + int xpos = _charAddXPosTable[facing] + _currentCharacter->x1; + int ypos = _charAddYPosTable[facing] + _currentCharacter->y1; + + if (xpos >= 12 && xpos <= 308) { + if (!lineIsPassable(xpos, ypos)) + return false; + } + + if (_exitListPtr) { + int16 *ptr = _exitListPtr; + // this loop should be only entered one time, seems to be some hack in the original + while (true) { + if (*ptr == -1) + break; + + if (*ptr > _currentCharacter->x1 || _currentCharacter->y1 < ptr[1] || _currentCharacter->x1 > ptr[2] || _currentCharacter->y1 > ptr[3]) { + ptr += 10; + break; + } + + _brandonPosX = ptr[6]; + _brandonPosY = ptr[7]; + uint16 sceneId = ptr[5]; + facing = ptr[4]; + int unk1 = ptr[8]; + int unk2 = ptr[9]; + + if (sceneId == 0xFFFF) { + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + case 6: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + break; + + default: + break; + } + } + + _currentCharacter->facing = facing; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + enterNewScene(sceneId, facing, unk1, unk2, 0); + resetGameFlag(0xEE); + return 1; + } + } + + int returnValue = 0; + facing = 0; + + if ((_northExitHeight & 0xFF) + 2 >= ypos || (_northExitHeight & 0xFF) + 2 >= _currentCharacter->y1) { + facing = 0; + returnValue = 1; + } + + if (xpos >= 308 || (_currentCharacter->x1 + 4) >= 308) { + facing = 2; + returnValue = 1; + } + + if (((_northExitHeight >> 8) & 0xFF) - 2 < ypos || ((_northExitHeight >> 8) & 0xFF) - 2 < _currentCharacter->y1) { + facing = 4; + returnValue = 1; + } + + if (xpos <= 12 || _currentCharacter->y1 <= 12) { + facing = 6; + returnValue = 1; + } + + if (!returnValue) + return 0; + + uint16 sceneId = 0xFFFF; + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + default: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + } + + if (sceneId == 0xFFFF) + return 0; + + enterNewScene(sceneId, facing, 1, 1, 0); + return returnValue; +} + +void KyraEngine_LoK::setCharactersInDefaultScene() { + static const uint32 defaultSceneTable[][4] = { + { 0xFFFF, 0x0004, 0x0003, 0xFFFF }, + { 0xFFFF, 0x0022, 0xFFFF, 0x0000 }, + { 0xFFFF, 0x001D, 0x0021, 0xFFFF }, + { 0xFFFF, 0x0000, 0x0000, 0xFFFF } + }; + + for (int i = 1; i < 5; ++i) { + Character *cur = &_characterList[i]; + //cur->field_20 = 0; + + const uint32 *curTable = defaultSceneTable[i - 1]; + cur->sceneId = curTable[0]; + + if (cur->sceneId == _currentCharacter->sceneId) + //++cur->field_20; + cur->sceneId = curTable[1/*cur->field_20*/]; + + //cur->field_23 = curTable[cur->field_20+1]; + } +} + +void KyraEngine_LoK::setCharactersPositions(int character) { + static const uint16 initXPosTable[] = { + 0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B, + 0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042 + }; + static const uint8 initYPosTable[] = { + 0x00, 0xA2, 0x00, 0x42, 0x00, + 0x67, 0x67, 0x60, 0x5A, 0x71, + 0x76 + }; + + assert(character < ARRAYSIZE(initXPosTable)); + Character *edit = &_characterList[character]; + edit->x1 = edit->x2 = initXPosTable[character]; + edit->y1 = edit->y2 = initYPosTable[character]; +} + +#pragma mark - +#pragma mark - Pathfinder +#pragma mark - + +int KyraEngine_LoK::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + int ret = KyraEngine_v1::findWay(x, y, toX, toY, moveTable, moveTableSize); + if (ret == 0x7D00) + return 0; + return getMoveTableSize(moveTable); +} + +bool KyraEngine_LoK::lineIsPassable(int x, int y) { + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) + return true; + } + + if (_pathfinderFlag & 2) { + if (x >= 312) + return false; + } + + if (_pathfinderFlag & 4) { + if (y >= 136) + return false; + } + + if (_pathfinderFlag & 8) { + if (x < 8) + return false; + } + + if (_pathfinderFlag2) { + if (x <= 8 || x >= 312) + return true; + if (y < (_northExitHeight & 0xFF) || y > 135) + return true; + } + + if (y > 137) + return false; + + if (y < 0) + y = 0; + + int ypos = 8; + if (_scaleMode) { + ypos = (_scaleTable[y] >> 5) + 1; + if (8 < ypos) + ypos = 8; + } + + x -= (ypos >> 1); + + int xpos = x; + int xtemp = xpos + ypos - 1; + if (x < 0) + xpos = 0; + + if (xtemp > 319) + xtemp = 319; + + for (; xpos < xtemp; ++xpos) { + if (!_screen->getShapeFlag1(xpos, y)) + return false; + } + return true; +} + +#pragma mark - + +void KyraEngine_LoK::setupZanthiaPalette(int pal) { + uint8 r, g, b; + + switch (pal - 17) { + case 0: + // 0x88F + r = 33; + g = 33; + b = 63; + break; + + case 1: + // 0x00F + r = 0; + g = 0; + b = 63; + break; + + case 2: + // 0xF88 + r = 63; + g = 33; + b = 33; + break; + + case 3: + // 0xF00 + r = 63; + g = 0; + b = 0; + break; + + case 4: + // 0xFF9 + r = 63; + g = 63; + b = 37; + break; + + case 5: + // 0xFF1 + r = 63; + g = 63; + b = 4; + break; + + default: + // 0xFFF + r = 63; + g = 63; + b = 63; + } + + _screen->getPalette(4)[12 * 3 + 0] = r; + _screen->getPalette(4)[12 * 3 + 1] = g; + _screen->getPalette(4)[12 * 3 + 2] = b; +} + +#pragma mark - + +void KyraEngine_LoK::setupSceneResource(int sceneId) { + if (!_flags.isTalkie) + return; + + if (_currentRoom != 0xFFFF) { + assert(_currentRoom < _roomTableSize); + int tableId = _roomTable[_currentRoom].nameIndex; + assert(tableId < _roomFilenameTableSize); + + // unload our old room + char file[64]; + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".PAK"); + _res->unloadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".APK"); + _res->unloadPakFile(file); + } + + assert(sceneId < _roomTableSize); + int tableId = _roomTable[sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + + // load our new room + char file[64]; + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + if (_res->exists(file)) + _res->loadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".PAK"); + if (_res->exists(file)) + _res->loadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".APK"); + if (_res->exists(file)) + _res->loadPakFile(file); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_lol.cpp b/engines/kyra/engine/scene_lol.cpp new file mode 100644 index 0000000000..93ff588ece --- /dev/null +++ b/engines/kyra/engine/scene_lol.cpp @@ -0,0 +1,1577 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" + +#include "common/endian.h" +#include "common/system.h" + +namespace Kyra { + +void LoLEngine::loadLevel(int index) { + _flagsTable[73] |= 0x08; + setMouseCursorToIcon(0x85); + _nextScriptFunc = 0; + + snd_stopMusic(); + + stopPortraitSpeechAnim(); + + for (int i = 0; i < 400; i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } + _emc->unload(&_scriptData); + + resetItems(1); + disableMonsters(); + resetBlockProperties(); + + releaseMonsterShapes(0); + releaseMonsterShapes(1); + + for (int i = 0x50; i < 0x53; i++) + _timer->disable(i); + + _currentLevel = index; + _updateFlags = 0; + + setDefaultButtonState(); + + loadTalkFile(index); + + loadLevelWallData(index, true); + _loadLevelFlag = 1; + + Common::String filename = Common::String::format("LEVEL%d.INI", index); + + int f = _hasTempDataFlags & (1 << (index - 1)); + + runInitScript(filename.c_str(), f ? 0 : 1); + + if (f) + restoreBlockTempData(index); + + filename = Common::String::format("LEVEL%d.INF", index); + runInfScript(filename.c_str()); + + addLevelItems(); + deleteMonstersFromBlock(_currentBlock); + + if (!_flags.use16ColorMode) + _screen->generateGrayOverlay(_screen->getPalette(0), _screen->_grayOverlay, 32, 16, 0, 0, 128, true); + + _sceneDefaultUpdate = 0; + if (_screen->_fadeFlag == 3) + _screen->fadeToBlack(10); + + gui_drawPlayField(); + + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + setMouseCursorToItemInHand(); + + if (_flags.use16ColorMode) + _screen->fadeToPalette1(10); + + snd_playTrack(_curMusicTheme); +} + +void LoLEngine::addLevelItems() { + for (int i = 0; i < 400; i++) { + if (_itemsInPlay[i].level != _currentLevel) + continue; + + assignBlockItem(&_levelBlockProperties[_itemsInPlay[i].block], i); + + _levelBlockProperties[_itemsInPlay[i].block].direction = 5; + _itemsInPlay[i].nextDrawObject = 0; + } +} + +void LoLEngine::assignBlockItem(LevelBlockProperty *l, uint16 item) { + uint16 *index = &l->assignedObjects; + LoLObject *tmp = 0; + + while (*index & 0x8000) { + tmp = findObject(*index); + index = &tmp->nextAssignedObject; + } + + tmp = findObject(item); + ((LoLItem *)tmp)->level = -1; + + uint16 ix = *index; + + if (ix == item) + return; + + *index = item; + index = &tmp->nextAssignedObject; + + while (*index) { + tmp = findObject(*index); + index = &tmp->nextAssignedObject; + } + + *index = ix; +} + +void LoLEngine::loadLevelWallData(int index, bool mapShapes) { + Common::String filename = Common::String::format("LEVEL%d.WLL", index); + + uint32 size; + uint8 *file = _res->fileData(filename.c_str(), &size); + + uint16 c = READ_LE_UINT16(file); + loadLevelShpDat(_levelShpList[c], _levelDatList[c], false); + + uint8 *d = file + 2; + size = (size - 2) / 12; + for (uint32 i = 0; i < size; i++) { + c = READ_LE_UINT16(d); + d += 2; + _wllVmpMap[c] = *d; + d += 2; + + if (mapShapes) { + int16 sh = (int16) READ_LE_UINT16(d); + if (sh > 0) + _wllShapeMap[c] = assignLevelDecorationShapes(sh); + else + _wllShapeMap[c] = *d; + } + d += 2; + _specialWallTypes[c] = *d; + d += 2; + _wllWallFlags[c] = *d; + d += 2; + _wllAutomapData[c] = *d; + d += 2; + } + + delete[] file; + + delete _lvlShpFileHandle; + _lvlShpFileHandle = 0; +} + +int LoLEngine::assignLevelDecorationShapes(int index) { + uint16 *p1 = (uint16 *)_tempBuffer5120; + uint16 *p2 = (uint16 *)(_tempBuffer5120 + 4000); + + uint16 r = p2[index]; + if (r) + return r; + + uint16 o = _mappedDecorationsCount++; + + memcpy(&_levelDecorationProperties[o], &_levelDecorationData[index], sizeof(LevelDecorationProperty)); + + for (int i = 0; i < 10; i++) { + uint16 t = _levelDecorationProperties[o].shapeIndex[i]; + if (t == 0xFFFF) + continue; + + uint16 pv = p1[t]; + if (pv) { + _levelDecorationProperties[o].shapeIndex[i] = pv; + } else { + releaseDecorations(_lvlShapeIndex, 1); + _levelDecorationShapes[_lvlShapeIndex] = getLevelDecorationShapes(t); + p1[t] = _lvlShapeIndex; + _levelDecorationProperties[o].shapeIndex[i] = _lvlShapeIndex++; + } + } + + p2[index] = o; + if (_levelDecorationProperties[o].next) + _levelDecorationProperties[o].next = assignLevelDecorationShapes(_levelDecorationProperties[o].next); + + return o; +} + +uint8 *LoLEngine::getLevelDecorationShapes(int shapeIndex) { + if (_decorationCount <= shapeIndex) + return 0; + + _lvlShpFileHandle->seek(shapeIndex * 4 + 2, SEEK_SET); + uint32 offs = _lvlShpFileHandle->readUint32LE() + 2; + _lvlShpFileHandle->seek(offs, SEEK_SET); + + uint8 tmp[16]; + _lvlShpFileHandle->read(tmp, 16); + uint16 size = _screen->getShapeSize(tmp); + + _lvlShpFileHandle->seek(offs, SEEK_SET); + uint8 *res = new uint8[size]; + _lvlShpFileHandle->read(res, size); + + return res; +} + +void LoLEngine::releaseDecorations(int first, int num) { + for (int i = first; i < (first + num); i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } +} + +void LoLEngine::loadBlockProperties(const char *cmzFile) { + memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty)); + _screen->loadBitmap(cmzFile, 2, 2, 0); + const uint8 *h = _screen->getCPagePtr(2); + uint16 len = READ_LE_UINT16(&h[4]); + const uint8 *p = h + 6; + + for (int i = 0; i < 1024; i++) { + for (int ii = 0; ii < 4; ii++) + _levelBlockProperties[i].walls[ii] = p[i * len + ii]; + + _levelBlockProperties[i].direction = 5; + + if (_wllAutomapData[_levelBlockProperties[i].walls[0]] == 17) { + _levelBlockProperties[i].flags &= 0xEF; + _levelBlockProperties[i].flags |= 0x20; + } + } +} + +const uint8 *LoLEngine::getBlockFileData(int levelIndex) { + _screen->loadBitmap(Common::String::format("LEVEL%d.CMZ", levelIndex).c_str(), 15, 15, 0); + return _screen->getCPagePtr(14); +} + +void LoLEngine::loadLevelShpDat(const char *shpFile, const char *datFile, bool flag) { + memset(_tempBuffer5120, 0, 5120); + + _lvlShpFileHandle = _res->createReadStream(shpFile); + _decorationCount = _lvlShpFileHandle->readUint16LE(); + + Common::SeekableReadStream *s = _res->createReadStream(datFile); + + _levelDecorationDataSize = s->readUint16LE(); + delete[] _levelDecorationData; + _levelDecorationData = new LevelDecorationProperty[_levelDecorationDataSize]; + for (int i = 0; i < _levelDecorationDataSize; i++) { + LevelDecorationProperty *l = &_levelDecorationData[i]; + for (int ii = 0; ii < 10; ii++) + l->shapeIndex[ii] = s->readUint16LE(); + for (int ii = 0; ii < 10; ii++) + l->scaleFlag[ii] = s->readByte(); + for (int ii = 0; ii < 10; ii++) + l->shapeX[ii] = s->readSint16LE(); + for (int ii = 0; ii < 10; ii++) + l->shapeY[ii] = s->readSint16LE(); + l->next = s->readByte(); + l->flags = s->readByte(); + } + + delete s; + + if (!flag) { + _mappedDecorationsCount = 1; + _lvlShapeIndex = 1; + } +} + +void LoLEngine::loadLevelGraphics(const char *file, int specialColor, int weight, int vcnLen, int vmpLen, const char *palFile) { + if (file) { + _lastSpecialColor = specialColor; + _lastSpecialColorWeight = weight; + strcpy(_lastBlockDataFile, file); + if (palFile) + _lastOverridePalFile = palFile; + else + _lastOverridePalFile.clear(); + } + + if (_flags.use16ColorMode) { + if (_lastSpecialColor == 1) + _lastSpecialColor = 0x44; + else if (_lastSpecialColor == 0x66) + _lastSpecialColor = scumm_stricmp(_lastBlockDataFile, "YVEL2") ? 0xCC : 0x44; + else if (_lastSpecialColor == 0x6B) + _lastSpecialColor = 0xCC; + else + _lastSpecialColor = 0x44; + } + + Common::String fname; + const uint8 *v = 0; + int tlen = 0; + + if (_flags.use16ColorMode) { + fname = Common::String::format("%s.VCF", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + tlen = READ_LE_UINT16(v) << 5; + v += 2; + + delete[] _vcfBlocks; + _vcfBlocks = new uint8[tlen]; + + memcpy(_vcfBlocks, v, tlen); + } + + fname = Common::String::format("%s.VCN", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + tlen = READ_LE_UINT16(v); + v += 2; + + if (vcnLen == -1) + vcnLen = tlen << 5; + + delete[] _vcnBlocks; + _vcnBlocks = new uint8[vcnLen]; + + if (!_flags.use16ColorMode) { + delete[] _vcnShift; + _vcnShift = new uint8[tlen]; + + memcpy(_vcnShift, v, tlen); + v += tlen; + + memcpy(_vcnColTable, v, 128); + v += 128; + + if (!_lastOverridePalFile.empty()) { + _res->loadFileToBuf(_lastOverridePalFile.c_str(), _screen->getPalette(0).getData(), 384); + } else { + _screen->getPalette(0).copy(v, 0, 128); + } + + v += 384; + } + + if (_currentLevel == 11) { + if (_flags.use16ColorMode) { + _screen->loadPalette("LOLICE.NOL", _screen->getPalette(2)); + + } else { + _screen->loadPalette("SWAMPICE.COL", _screen->getPalette(2)); + _screen->getPalette(2).copy(_screen->getPalette(0), 128); + } + + if (_flagsTable[52] & 0x04) { + uint8 *pal0 = _screen->getPalette(0).getData(); + uint8 *pal2 = _screen->getPalette(2).getData(); + for (int i = 1; i < _screen->getPalette(0).getNumColors() * 3; i++) + SWAP(pal0[i], pal2[i]); + } + } + + memcpy(_vcnBlocks, v, vcnLen); + v += vcnLen; + + fname = Common::String::format("%s.VMP", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + + if (vmpLen == -1) + vmpLen = READ_LE_UINT16(v); + v += 2; + + delete[] _vmpPtr; + _vmpPtr = new uint16[vmpLen]; + + for (int i = 0; i < vmpLen; i++) + _vmpPtr[i] = READ_LE_UINT16(&v[i << 1]); + + Palette tpal(256); + if (_flags.use16ColorMode) { + uint8 *dst = tpal.getData(); + _res->loadFileToBuf("LOL.NOL", dst, 48); + for (int i = 1; i < 16; i++) { + int s = ((i << 4) | i) * 3; + SWAP(dst[s], dst[i * 3]); + SWAP(dst[s + 1], dst[i * 3 + 1]); + SWAP(dst[s + 2], dst[i * 3 + 2]); + } + } else { + tpal.copy(_screen->getPalette(0)); + } + + for (int i = 0; i < 7; i++) { + weight = 100 - (i * _lastSpecialColorWeight); + weight = (weight > 0) ? (weight * 255) / 100 : 0; + _screen->generateOverlay(tpal, _screen->getLevelOverlay(i), _lastSpecialColor, weight); + + int l = _flags.use16ColorMode ? 256 : 128; + uint8 *levelOverlay = _screen->getLevelOverlay(i); + for (int ii = 0; ii < l; ii++) { + if (levelOverlay[ii] == 255) + levelOverlay[ii] = 0; + } + + for (int ii = l; ii < 256; ii++) + levelOverlay[ii] = ii & 0xFF; + } + + uint8 *levelOverlay = _screen->getLevelOverlay(7); + for (int i = 0; i < 256; i++) + levelOverlay[i] = i & 0xFF; + + if (_flags.use16ColorMode) { + _screen->getLevelOverlay(6)[0xEE] = 0xEE; + if (_lastSpecialColor == 0x44) + _screen->getLevelOverlay(5)[0xEE] = 0xEE; + + for (int i = 0; i < 7; i++) + memcpy(_screen->getLevelOverlay(i), _screen->getLevelOverlay(i + 1), 256); + + _screen->loadPalette("LOL.NOL", _screen->getPalette(0)); + + for (int i = 0; i < 8; i++) { + uint8 *pl = _screen->getLevelOverlay(7 - i); + for (int ii = 0; ii < 16; ii++) + _vcnColTable[(i << 4) + ii] = pl[(ii << 4) | ii]; + } + } + + _loadSuppFilesFlag = 0; + generateBrightnessPalette(_screen->getPalette(0), _screen->getPalette(1), _brightness, _lampEffect); + + if (_flags.isTalkie) { + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format("LEVEL%.02d.TLC", _currentLevel)); + s->read(_transparencyTable1, 256); + s->read(_transparencyTable2, 5120); + delete s; + } else { + createTransparencyTables(); + } + + _loadSuppFilesFlag = 1; +} + +void LoLEngine::resetItems(int flag) { + for (int i = 0; i < 1024; i++) { + _levelBlockProperties[i].direction = 5; + uint16 id = _levelBlockProperties[i].assignedObjects; + LoLObject *r = 0; + + while (id & 0x8000) { + r = findObject(id); + id = r->nextAssignedObject; + } + + if (!id) + continue; + + LoLItem *it = &_itemsInPlay[id]; + it->level = _currentLevel; + it->block = i; + if (r) + r->nextAssignedObject = 0; + } + + if (flag) + memset(_flyingObjects, 0, 8 * sizeof(FlyingObject)); +} + +void LoLEngine::disableMonsters() { + memset(_monsters, 0, 30 * sizeof(LoLMonster)); + for (int i = 0; i < 30; i++) + _monsters[i].mode = 0x10; +} + +void LoLEngine::resetBlockProperties() { + for (int i = 0; i < 1024; i++) { + LevelBlockProperty *l = &_levelBlockProperties[i]; + if (l->flags & 0x10) { + l->flags &= 0xEF; + if (testWallInvisibility(i, 0) && testWallInvisibility(i, 1)) + l->flags |= 0x40; + } else { + if (l->flags & 0x40) + l->flags &= 0xBF; + else if (l->flags & 0x80) + l->flags &= 0x7F; + } + } +} + +bool LoLEngine::testWallFlag(int block, int direction, int flag) { + if (_levelBlockProperties[block].flags & 0x10) + return true; + + if (direction != -1) + return (_wllWallFlags[_levelBlockProperties[block].walls[direction ^ 2]] & flag) ? true : false; + + for (int i = 0; i < 4; i++) { + if (_wllWallFlags[_levelBlockProperties[block].walls[i]] & flag) + return true; + } + + return false; +} + +bool LoLEngine::testWallInvisibility(int block, int direction) { + uint8 w = _levelBlockProperties[block].walls[direction]; + if (_wllVmpMap[w] || _wllShapeMap[w] || _levelBlockProperties[block].flags & 0x80) + return false; + return true; +} + +void LoLEngine::resetLampStatus() { + _flagsTable[31] |= 0x04; + _lampEffect = -1; + updateLampStatus(); +} + +void LoLEngine::setLampMode(bool lampOn) { + _flagsTable[31] &= 0xFB; + if (!(_flagsTable[31] & 0x08) || !lampOn) + return; + + _screen->drawShape(0, _gameShapes[_flags.isTalkie ? 43 : 41], 291, 56, 0, 0); + _lampEffect = 8; +} + +void LoLEngine::updateLampStatus() { + int8 newLampEffect = 0; + uint8 tmpOilStatus = 0; + + if ((_updateFlags & 4) || !(_flagsTable[31] & 0x08)) + return; + + if (!_brightness || !_lampOilStatus) { + newLampEffect = 8; + if (newLampEffect != _lampEffect && _screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + } else { + tmpOilStatus = (_lampOilStatus < 100) ? _lampOilStatus : 100; + newLampEffect = (3 - ((tmpOilStatus - 1) / 25)) << 1; + + if (_lampEffect == -1) { + if (_screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + _lampStatusTimer = _system->getMillis() + (10 + rollDice(1, 30)) * _tickLength; + } else { + if ((_lampEffect & 0xFE) == (newLampEffect & 0xFE)) { + if (_system->getMillis() <= _lampStatusTimer) { + newLampEffect = _lampEffect; + } else { + newLampEffect = _lampEffect ^ 1; + _lampStatusTimer = _system->getMillis() + (10 + rollDice(1, 30)) * _tickLength; + } + } else { + if (_screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + } + } + } + + if (newLampEffect == _lampEffect) + return; + + _screen->hideMouse(); + + _screen->drawShape(_screen->_curPage, _gameShapes[(_flags.isTalkie ? 35 : 33) + newLampEffect], 291, 56, 0, 0); + _screen->showMouse(); + + _lampEffect = newLampEffect; +} + +void LoLEngine::updateCompass() { + if (!(_flagsTable[31] & 0x40) || (_updateFlags & 4)) + return; + + if (_compassDirection == -1) { + _compassStep = 0; + gui_drawCompass(); + return; + } + + if (_compassTimer >= _system->getMillis()) + return; + + if ((_currentDirection << 6) == _compassDirection && (!_compassStep)) + return; + + _compassTimer = _system->getMillis() + 3 * _tickLength; + int dir = _compassStep >= 0 ? 1 : -1; + if (_compassStep) + _compassStep -= (((ABS(_compassStep) >> 4) + 2) * dir); + + int16 diff = _compassBroken ? (int8(_rnd.getRandomNumber(255)) - _compassDirection) : (_currentDirection << 6) - _compassDirection; + if (diff <= -128) + diff += 256; + if (diff >= 128) + diff -= 256; + + diff >>= 2; + _compassStep += diff; + _compassStep = CLIP(_compassStep, -24, 24); + _compassDirection += _compassStep; + + if (_compassDirection < 0) + _compassDirection += 256; + if (_compassDirection > 255) + _compassDirection -= 256; + + if (((((_compassDirection + 3) & 0xFD) >> 6) == _currentDirection) && (_compassStep < 2) && (ABS(diff) < 4)) { + _compassDirection = _currentDirection << 6; + _compassStep = 0; + } + + gui_drawCompass(); +} + +void LoLEngine::moveParty(uint16 direction, int unk1, int unk2, int buttonShape) { + if (buttonShape) + gui_toggleButtonDisplayMode(buttonShape, 1); + + uint16 opos = _currentBlock; + uint16 npos = calcNewBlockPosition(_currentBlock, direction); + + if (!checkBlockPassability(npos, direction)) { + notifyBlockNotPassable(unk2 ? 0 : 1); + gui_toggleButtonDisplayMode(buttonShape, 0); + return; + } + + _scriptDirection = direction; + _currentBlock = npos; + _sceneDefaultUpdate = 1; + + calcCoordinates(_partyPosX, _partyPosY, _currentBlock, 0x80, 0x80); + _flagsTable[73] &= 0xFD; + + runLevelScript(opos, 4); + runLevelScript(npos, 1); + + if (!(_flagsTable[73] & 0x02)) { + initTextFading(2, 0); + + if (_sceneDefaultUpdate) { + switch (unk2) { + case 0: + movePartySmoothScrollUp(2); + break; + case 1: + movePartySmoothScrollDown(2); + break; + case 2: + movePartySmoothScrollLeft(1); + break; + case 3: + movePartySmoothScrollRight(1); + break; + default: + break; + } + } else { + gui_drawScene(0); + } + + gui_toggleButtonDisplayMode(buttonShape, 0); + + if (npos == _currentBlock) { + runLevelScript(opos, 8); + runLevelScript(npos, 2); + + if (_levelBlockProperties[npos].walls[0] == 0x1A) + memset(_levelBlockProperties[npos].walls, 0, 4); + } + } + + updateAutoMap(_currentBlock); +} + +uint16 LoLEngine::calcBlockIndex(uint16 x, uint16 y) { + return (((y & 0xFF00) >> 3) | (x >> 8)) & 0x3FF; +} + +void LoLEngine::calcCoordinates(uint16 &x, uint16 &y, int block, uint16 xOffs, uint16 yOffs) { + x = (block & 0x1F) << 8 | xOffs; + y = ((block & 0xFFE0) << 3) | yOffs; +} + +void LoLEngine::calcCoordinatesForSingleCharacter(int charNum, uint16 &x, uint16 &y) { + static const uint8 xOffsets[] = { 0x80, 0x00, 0x00, 0x40, 0xC0, 0x00, 0x40, 0x80, 0xC0 }; + int c = countActiveCharacters(); + if (!c) + return; + + c = (c - 1) * 3 + charNum; + + x = xOffsets[c]; + y = 0x80; + + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + + x |= (_partyPosX & 0xFF00); + y |= (_partyPosY & 0xFF00); +} + +void LoLEngine::calcCoordinatesAddDirectionOffset(uint16 &x, uint16 &y, int direction) { + if (!direction) + return; + + int tx = x; + int ty = y; + + if (direction & 1) + SWAP(tx, ty); + + if (direction != 1) + ty = (ty - 256) * -1; + + if (direction != 3) { + tx = (tx - 256) * -1; + } + + x = tx; + y = ty; +} + +bool LoLEngine::checkBlockPassability(uint16 block, uint16 direction) { + if (testWallFlag(block, direction, 1)) + return false; + + uint16 d = _levelBlockProperties[block].assignedObjects; + + while (d) { + if (d & 0x8000) + return false; + d = findObject(d)->nextAssignedObject; + } + + return true; +} + +void LoLEngine::notifyBlockNotPassable(int scrollFlag) { + if (scrollFlag) + movePartySmoothScrollBlocked(2); + + snd_stopSpeech(true); + _txt->printMessage(0x8002, "%s", getLangString(0x403F)); + snd_playSoundEffect(19, -1); +} + +int LoLEngine::clickedDoorSwitch(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + snd_playSoundEffect(78, -1); + _blockDoor = 0; + runLevelScript(block, 0x40); + + if (!_blockDoor) { + delay(15 * _tickLength); + processDoorSwitch(block, 0); + } + + return 1; +} + +int LoLEngine::clickedNiche(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v) || !_itemInHand) + return 0; + + uint16 x = 0x80; + uint16 y = 0xFF; + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + calcCoordinates(x, y, block, x, y); + setItemPosition(_itemInHand, x, y, 8, 1); + setHandItem(0); + return 1; +} + +void LoLEngine::movePartySmoothScrollBlocked(int speed) { + if (!_smoothScrollingEnabled || (_smoothScrollingEnabled && _needSceneRestore)) + return; + + _screen->backupSceneWindow(_sceneDrawPage2 == 2 ? 2 : 6, 6); + + for (int i = 0; i < 2; i++) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + for (int i = 2; i; i--) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(6, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollUp(int speed) { + if (!_smoothScrollingEnabled || (_smoothScrollingEnabled && _needSceneRestore)) + return; + + int d = 0; + + if (_sceneDrawPage2 == 2) { + d = smoothScrollDrawSpecialGuiShape(6); + gui_drawScene(6); + _screen->backupSceneWindow(6, 12); + _screen->backupSceneWindow(2, 6); + } else { + d = smoothScrollDrawSpecialGuiShape(2); + gui_drawScene(2); + _screen->backupSceneWindow(2, 12); + _screen->backupSceneWindow(6, 6); + } + + for (int i = 0; i < 5; i++) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + + if (d) + _screen->copyGuiShapeToSurface(14, 2); + + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (d) + _screen->copyGuiShapeToSurface(14, 12); + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(12, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollDown(int speed) { + if (!_smoothScrollingEnabled) + return; + + int d = smoothScrollDrawSpecialGuiShape(2); + gui_drawScene(2); + _screen->backupSceneWindow(2, 6); + + for (int i = 4; i >= 0; i--) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + + if (d) + _screen->copyGuiShapeToSurface(14, 2); + + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (d) + _screen->copyGuiShapeToSurface(14, 12); + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(6, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollLeft(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + gui_drawScene(_sceneDrawPage1); + + for (int i = 88, d = 88; i > 22; i -= 22, d += 22) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 66, d, i); + _screen->copyRegion(112 + i, 0, 112, 0, d, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + } + + if (_sceneDefaultUpdate != 2) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + SWAP(_sceneDrawPage1, _sceneDrawPage2); +} + +void LoLEngine::movePartySmoothScrollRight(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + gui_drawScene(_sceneDrawPage1); + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->copyRegion(112, 0, 222, 0, 66, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 22, 0, 66); + _screen->copyRegion(112, 0, 200, 0, 88, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 44, 0, 22); + _screen->copyRegion(112, 0, 178, 0, 110, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + SWAP(_sceneDrawPage1, _sceneDrawPage2); +} + +void LoLEngine::movePartySmoothScrollTurnLeft(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + int d = smoothScrollDrawSpecialGuiShape(_sceneDrawPage1); + gui_drawScene(_sceneDrawPage1); + int dp = _sceneDrawPage2 == 2 ? _sceneDrawPage2 : _sceneDrawPage1; + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep1(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep2(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep3(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +void LoLEngine::movePartySmoothScrollTurnRight(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + int d = smoothScrollDrawSpecialGuiShape(_sceneDrawPage1); + gui_drawScene(_sceneDrawPage1); + int dp = _sceneDrawPage2 == 2 ? _sceneDrawPage2 : _sceneDrawPage1; + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep3(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep2(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep1(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +void LoLEngine::pitDropScroll(int numSteps) { + _screen->copyRegionSpecial(0, 320, 200, 112, 0, 6, 176, 120, 0, 0, 176, 120, 0); + uint32 etime = 0; + + for (int i = 0; i < numSteps; i++) { + etime = _system->getMillis() + _tickLength; + int ys = ((30720 / numSteps) * i) >> 8; + _screen->copyRegionSpecial(6, 176, 120, 0, ys, 0, 320, 200, 112, 0, 176, 120 - ys, 0); + _screen->copyRegionSpecial(2, 320, 200, 112, 0, 0, 320, 200, 112, 120 - ys, 176, ys, 0); + _screen->updateScreen(); + + delayUntil(etime); + } + + etime = _system->getMillis() + _tickLength; + + _screen->copyRegionSpecial(2, 320, 200, 112, 0, 0, 320, 200, 112, 0, 176, 120, 0); + _screen->updateScreen(); + + delayUntil(etime); + + updateDrawPage2(); +} + +void LoLEngine::shakeScene(int duration, int width, int height, int restore) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK); + uint32 endTime = _system->getMillis() + duration * _tickLength; + + while (endTime > _system->getMillis()) { + uint32 delayTimer = _system->getMillis() + 2 * _tickLength; + + int s1 = width ? (_rnd.getRandomNumber(255) % (width << 1)) - width : 0; + int s2 = height ? (_rnd.getRandomNumber(255) % (height << 1)) - height : 0; + + int x1, y1, x2, y2, w, h; + if (s1 >= 0) { + x1 = 112; + x2 = 112 + s1; + w = 176 - s1; + } else { + x1 = 112 - s1; + x2 = 112; + w = 176 + s1; + } + + if (s2 >= 0) { + y1 = 0; + y2 = s2; + h = 120 - s2; + } else { + y1 = -s2; + y2 = 0; + h = 120 + s2; + } + + _screen->copyRegion(x1, y1, x2, y2, w, h, 6, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + + if (restore) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + updateDrawPage2(); + } +} + +void LoLEngine::processGasExplosion(int soundId) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + static const uint8 sounds[] = { 0x62, 0xA7, 0xA7, 0xA8 }; + snd_playSoundEffect(sounds[soundId], -1); + + uint16 targetBlock = 0; + int dist = getSpellTargetBlock(_currentBlock, _currentDirection, 3, targetBlock); + + uint8 *p1 = _screen->getPalette(1).getData(); + uint8 *p2 = _screen->getPalette(3).getData(); + + if (dist) { + WSAMovie_v2 *mov = new WSAMovie_v2(this); + Common::String file = Common::String::format("gasexp%0d.wsa", dist); + mov->open(file.c_str(), 1, 0); + if (!mov->opened()) + error("Gas: Unable to load gasexp.wsa"); + + playSpellAnimation(mov, 0, 6, 1, (176 - mov->width()) / 2 + 112, (120 - mov->height()) / 2, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + } else { + memcpy(p2, p1, 768); + + for (int i = 1; i < 128; i++) + p2[i * 3] = 0x3F; + + uint32 ctime = _system->getMillis(); + while (_screen->timedPaletteFadeStep(_screen->getPalette(0).getData(), p2, _system->getMillis() - ctime, 10)) + updateInput(); + + ctime = _system->getMillis(); + while (_screen->timedPaletteFadeStep(p2, _screen->getPalette(0).getData(), _system->getMillis() - ctime, 50)) + updateInput(); + } + + _screen->copyPage(12, 2); + _screen->setCurPage(cp); + + updateDrawPage2(); + _sceneUpdateRequired = true; + gui_drawScene(0); +} + +int LoLEngine::smoothScrollDrawSpecialGuiShape(int pageNum) { + if (!_specialGuiShape) + return 0; + + _screen->clearGuiShapeMemory(pageNum); + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX, _specialGuiShapeY, 2, 0); + _screen->copyGuiShapeFromSceneBackupBuffer(pageNum, 14); + return 1; +} + +void LoLEngine::drawScene(int pageNum) { + if (pageNum && pageNum != _sceneDrawPage1) { + SWAP(_sceneDrawPage1, _sceneDrawPage2); + updateDrawPage2(); + } + + if (pageNum && pageNum != _sceneDrawPage1) { + SWAP(_sceneDrawPage1, _sceneDrawPage2); + updateDrawPage2(); + } + + generateBlockDrawingBuffer(); + drawVcnBlocks(); + drawSceneShapes(); + + if (!pageNum) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + SWAP(_sceneDrawPage1, _sceneDrawPage2); + } + + updateEnvironmentalSfx(0); + gui_drawCompass(); + + _sceneUpdateRequired = false; +} + + +void LoLEngine::setWallType(int block, int wall, int val) { + if (wall == -1) { + for (int i = 0; i < 4; i++) + _levelBlockProperties[block].walls[i] = val; + if (_wllAutomapData[val] == 17) { + _levelBlockProperties[block].flags &= 0xEF; + _levelBlockProperties[block].flags |= 0x20; + } else { + _levelBlockProperties[block].flags &= 0xDF; + } + } else { + _levelBlockProperties[block].walls[wall] = val; + } + + checkSceneUpdateNeed(block); +} + +void LoLEngine::updateDrawPage2() { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 0, _sceneDrawPage2, Screen::CR_NO_P_CHECK); +} + +void LoLEngine::prepareSpecialScene(int fieldType, int hasDialogue, int suspendGui, int allowSceneUpdate, int controlMode, int fadeFlag) { + resetPortraitsAndDisableSysTimer(); + if (fieldType) { + if (suspendGui) + gui_specialSceneSuspendControls(1); + + if (!allowSceneUpdate) + _sceneDefaultUpdate = 0; + + if (hasDialogue) + initDialogueSequence(fieldType, 0); + + if (fadeFlag) { + if (_flags.use16ColorMode) + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + else + _screen->fadePalette(_screen->getPalette(3), 10); + _screen->_fadeFlag = 0; + } + + setSpecialSceneButtons(0, 0, 320, 130, controlMode); + + } else { + if (suspendGui) + gui_specialSceneSuspendControls(0); + + if (!allowSceneUpdate) + _sceneDefaultUpdate = 0; + + gui_disableControls(controlMode); + + if (fadeFlag) { + if (_flags.use16ColorMode) { + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + } else { + _screen->getPalette(3).copy(_screen->getPalette(0), 128); + _screen->loadSpecialColors(_screen->getPalette(3)); + _screen->fadePalette(_screen->getPalette(3), 10); + } + _screen->_fadeFlag = 0; + } + + if (hasDialogue) + initDialogueSequence(fieldType, 0); + + setSpecialSceneButtons(112, 0, 176, 120, controlMode); + } +} + +int LoLEngine::restoreAfterSpecialScene(int fadeFlag, int redrawPlayField, int releaseTimScripts, int sceneUpdateMode) { + if (!_needSceneRestore) + return 0; + + _needSceneRestore = 0; + enableSysTimer(2); + + if (_dialogueField) + restoreAfterDialogueSequence(_currentControlMode); + + if (_specialSceneFlag) + gui_specialSceneRestoreControls(_currentControlMode); + + int l = _currentControlMode; + _currentControlMode = 0; + + gui_specialSceneRestoreButtons(); + calcCharPortraitXpos(); + + _currentControlMode = l; + + if (releaseTimScripts) { + for (int i = 0; i < TIM::kWSASlots; i++) + _tim->freeAnimStruct(i); + + for (int i = 0; i < 10; i++) + _tim->unload(_activeTim[i]); + } + + gui_enableControls(); + + if (fadeFlag) { + if ((_screen->_fadeFlag != 1 && _screen->_fadeFlag != 2) || (_screen->_fadeFlag == 1 && _currentControlMode)) { + if (_currentControlMode) + _screen->fadeToBlack(10); + else + _screen->fadeClearSceneWindow(10); + } + + _currentControlMode = 0; + calcCharPortraitXpos(); + + if (redrawPlayField) + gui_drawPlayField(); + + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + + } else { + _currentControlMode = 0; + calcCharPortraitXpos(); + + if (redrawPlayField) + gui_drawPlayField(); + } + + _sceneDefaultUpdate = sceneUpdateMode; + return 1; +} + +void LoLEngine::setSequenceButtons(int x, int y, int w, int h, int enableFlags) { + gui_enableSequenceButtons(x, y, w, h, enableFlags); + _seqWindowX1 = x; + _seqWindowY1 = y; + _seqWindowX2 = x + w; + _seqWindowY2 = y + h; + int offs = _itemInHand ? 10 : 0; + _screen->setMouseCursor(offs, offs, getItemIconShapePtr(_itemInHand)); + _currentFloatingCursor = -1; + if (w == 320) { + setLampMode(0); + _lampStatusSuspended = true; + } +} + +void LoLEngine::setSpecialSceneButtons(int x, int y, int w, int h, int enableFlags) { + gui_enableSequenceButtons(x, y, w, h, enableFlags); + _spsWindowX = x; + _spsWindowY = y; + _spsWindowW = w; + _spsWindowH = h; +} + +void LoLEngine::setDefaultButtonState() { + gui_enableDefaultPlayfieldButtons(); + _seqWindowX1 = _seqWindowY1 = _seqWindowX2 = _seqWindowY2 = _seqTrigger = 0; + if (_lampStatusSuspended) + resetLampStatus(); + _lampStatusSuspended = false; +} + +void LoLEngine::drawSceneShapes(int) { + for (int i = 0; i < 18; i++) { + uint8 t = _dscTileIndex[i]; + uint8 s = _visibleBlocks[t]->walls[_sceneDrawVarDown]; + + _shpDmX1 = 0; + _shpDmX2 = 0; + + int16 dimY1 = 0; + int16 dimY2 = 0; + + setLevelShapesDim(t, _shpDmX1, _shpDmX2, _sceneShpDim); + + if (_shpDmX2 <= _shpDmX1) + continue; + + drawDecorations(t); + + uint16 w = _wllWallFlags[s]; + + if (t == 16) + w |= 0x80; + + drawBlockEffects(t, 0); + + if (_visibleBlocks[t]->assignedObjects && (w & 0x80)) + drawBlockObjects(t); + + drawBlockEffects(t, 1); + + if (!(w & 8)) + continue; + + uint16 v = 20 * (s - (s < 23 ? _dscDoorScaleOffs[s] : 0)); + if (v > 80) + v = 80; + + setDoorShapeDim(t, dimY1, dimY2, _sceneShpDim); + drawDoor(_doorShapes[(s < 23 ? _dscDoorShpIndex[s] : 0)], 0, t, 10, 0, -v, 2); + setLevelShapesDim(t, dimY1, dimY2, _sceneShpDim); + } +} + +void LoLEngine::drawDecorations(int index) { + for (int i = 1; i >= 0; i--) { + int s = index * 2 + i; + uint16 scaleW = _dscShapeScaleW[s]; + uint16 scaleH = _dscShapeScaleH[s]; + int8 ix = _dscShapeIndex[s]; + uint8 shpIx = ABS(ix); + uint8 ovlIndex = _dscShapeOvlIndex[4 + _dscDimMap[index] * 5] + 2; + if (ovlIndex > 7) + ovlIndex = 7; + + if (!scaleW || !scaleH) + continue; + + uint8 d = (_currentDirection + _dscWalls[s]) & 3; + int8 l = _wllShapeMap[_visibleBlocks[index]->walls[d]]; + + uint8 *shapeData = 0; + + int x = 0; + int y = 0; + int flags = 0; + + while (l > 0) { + if ((_levelDecorationProperties[l].flags & 8) && index != 3 && index != 9 && index != 13) { + l = _levelDecorationProperties[l].next; + continue; + } + + if (_dscOvlMap[shpIx] == 1 && ((_levelDecorationProperties[l].flags & 2) || ((_levelDecorationProperties[l].flags & 4) && _wllProcessFlag))) + ix = -ix; + + int xOffs = 0; + int yOffs = 0; + uint8 *ovl = 0; + + if (_levelDecorationProperties[l].scaleFlag[shpIx] & 1) { + xOffs = _levelDecorationProperties[l].shapeX[shpIx]; + yOffs = _levelDecorationProperties[l].shapeY[shpIx]; + shpIx = _dscOvlMap[shpIx]; + int ov = ovlIndex; + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (ov > bb) + ov -= bb; + else + ov = 0; + } + ovl = _screen->getLevelOverlay(ov); + } else if (_levelDecorationProperties[l].shapeIndex[shpIx] != 0xFFFF) { + scaleW = scaleH = 0x100; + int ov = 7; + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (ov > bb) + ov -= bb; + else + ov = 0; + } + ovl = _screen->getLevelOverlay(ov); + } + + if (_levelDecorationProperties[l].shapeIndex[shpIx] != 0xFFFF) { + shapeData = _levelDecorationShapes[_levelDecorationProperties[l].shapeIndex[shpIx]]; + if (shapeData) { + if (ix < 0) { + x = _dscShapeX[s] + xOffs + ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8); + if (ix == _dscShapeIndex[s]) { + x = _dscShapeX[s] - ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8) - + _screen->getShapeScaledWidth(shapeData, scaleW) - xOffs; + } + flags = 0x105; + } else { + x = _dscShapeX[s] + xOffs + ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8); + flags = 0x104; + } + + y = _dscShapeY[s] + yOffs + ((_levelDecorationProperties[l].shapeY[shpIx] * scaleH) >> 8); + _screen->drawShape(_sceneDrawPage1, shapeData, x + 112, y, _sceneShpDim, flags, ovl, 1, scaleW, scaleH); + + if ((_levelDecorationProperties[l].flags & 1) && shpIx < 4) { + //draw shadow + x += (_screen->getShapeScaledWidth(shapeData, scaleW)); + flags ^= 1; + _screen->drawShape(_sceneDrawPage1, shapeData, x + 112, y, _sceneShpDim, flags, ovl, 1, scaleW, scaleH); + } + } + } + + l = _levelDecorationProperties[l].next; + shpIx = (_dscShapeIndex[s] < 0) ? -_dscShapeIndex[s] : _dscShapeIndex[s]; + } + } +} + +void LoLEngine::drawBlockEffects(int index, int type) { + static const uint16 yOffs[] = { 0xFF, 0xFF, 0x80, 0x80 }; + uint8 flg = _visibleBlocks[index]->flags; + // flags: 0x10 = ice wall, 0x20 = teleporter, 0x40 = blue slime spot, 0x80 = blood spot + if (!(flg & 0xF0)) + return; + + type = (type == 0) ? 2 : 0; + + for (int i = 0; i < 2; i++, type++) { + if (!((0x10 << type) & flg)) + continue; + + uint16 x = 0x80; + uint16 y = yOffs[type]; + uint16 drawFlag = (type == 3) ? 0x80 : 0x20; + uint8 *ovl = (type == 3) ? _screen->_grayOverlay : 0; + + if (_flags.use16ColorMode) { + ovl = 0; + drawFlag = (type == 0 || type == 3) ? 0 : 0x20; + } + + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + + x |= ((_visibleBlockIndex[index] & 0x1F) << 8); + y |= ((_visibleBlockIndex[index] & 0xFFE0) << 3); + + drawItemOrMonster(_effectShapes[type], ovl, x, y, 0, (type == 1) ? -20 : 0, drawFlag, -1, false); + } +} + +void LoLEngine::drawSpecialGuiShape(int pageNum) { + if (!_specialGuiShape) + return; + + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX, _specialGuiShapeY, 2, 0); + + if (_specialGuiShapeMirrorFlag & 1) + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX + _specialGuiShape[3], _specialGuiShapeY, 2, 1); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/scene_mr.cpp b/engines/kyra/engine/scene_mr.cpp new file mode 100644 index 0000000000..8935863542 --- /dev/null +++ b/engines/kyra/engine/scene_mr.cpp @@ -0,0 +1,787 @@ +/* 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 "kyra/engine/kyra_mr.h" +#include "kyra/graphics/screen_mr.h" +#include "kyra/sound/sound_digital.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::enterNewScene(uint16 sceneId, int facing, int unk1, int unk2, int unk3) { + ++_enterNewSceneLock; + _screen->hideMouse(); + + showMessage(0, 0xF0, 0xF0); + if (_inventoryState) + hideInventory(); + + if (_currentChapter != _currentTalkFile) { + _currentTalkFile = _currentChapter; + openTalkFile(_currentTalkFile); + } + + if (unk1) { + int x = _mainCharacter.x1; + int y = _mainCharacter.y1; + + switch (facing) { + case 0: + y -= 6; + break; + + case 2: + x = 343; + break; + + case 4: + y = 191; + break; + + case 6: + x = -24; + break; + } + + moveCharacter(facing, x, y); + } + + uint32 waitUntilTimer = 0; + if (_lastMusicCommand != _sceneList[sceneId].sound) { + fadeOutMusic(60); + waitUntilTimer = _system->getMillis() + 60 * _tickLength; + } + + _chatAltFlag = false; + + if (!unk3) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _emc->start(&_sceneScriptState, 5); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } + + _specialExitCount = 0; + Common::fill(_specialExitTable, ARRAYEND(_specialExitTable), 0xFFFF); + + _mainCharacter.sceneId = sceneId; + _sceneList[sceneId].flags &= ~1; + unloadScene(); + + for (int i = 0; i < 4; ++i) { + if (i != _musicSoundChannel && i != _fadeOutMusicChannel) + _soundDigital->stopSound(i); + } + _fadeOutMusicChannel = -1; + loadScenePal(); + + if (queryGameFlag(0x1D9)) { + char filename[20]; + if (queryGameFlag(0x20D)) { + resetGameFlag(0x20D); + strcpy(filename, "COW1_"); + } else if (queryGameFlag(0x20E)) { + resetGameFlag(0x20E); + strcpy(filename, "COW2_"); + } else if (queryGameFlag(0x20F)) { + resetGameFlag(0x20F); + strcpy(filename, "COW3_"); + } else if (queryGameFlag(0x20C)) { + resetGameFlag(0x20C); + strcpy(filename, "BOAT"); + } else if (queryGameFlag(0x210)) { + resetGameFlag(0x210); + strcpy(filename, "JUNG"); + } + + playVQA(filename); + + resetGameFlag(0x1D9); + } + + loadSceneMsc(); + _sceneExit1 = _sceneList[sceneId].exit1; + _sceneExit2 = _sceneList[sceneId].exit2; + _sceneExit3 = _sceneList[sceneId].exit3; + _sceneExit4 = _sceneList[sceneId].exit4; + + while (_system->getMillis() < waitUntilTimer) + _system->delayMillis(10); + + initSceneScript(unk3); + + if (_overwriteSceneFacing) { + facing = _mainCharacter.facing; + _overwriteSceneFacing = false; + } + + enterNewSceneUnk1(facing, unk2, unk3); + setCommandLineRestoreTimer(-1); + _sceneScriptState.regs[3] = 1; + enterNewSceneUnk2(unk3); + if (queryGameFlag(0)) { + _showOutro = true; + _runFlag = false; + } else { + if (!--_enterNewSceneLock) + _unk5 = 0; + + setNextIdleAnimTimer(); + + if (_itemInHand < 0) { + _itemInHand = kItemNone; + _mouseState = kItemNone; + _screen->setMouseCursor(0, 0, _gameShapes[0]); + } + + Common::Point pos = getMousePos(); + if (pos.y > 187) + setMousePos(pos.x, 179); + } + _screen->showMouse(); + + _currentScene = sceneId; +} + +void KyraEngine_MR::enterNewSceneUnk1(int facing, int unk1, int unk2) { + int x = 0, y = 0; + int x2 = 0, y2 = 0; + bool needProc = true; + + if (_mainCharX == -1 && _mainCharY == -1) { + switch (facing+1) { + case 1: case 2: case 8: + x2 = _sceneEnterX3; + y2 = _sceneEnterY3; + break; + + case 3: + x2 = _sceneEnterX4; + y2 = _sceneEnterY4; + break; + + case 4: case 5: case 6: + x2 = _sceneEnterX1; + y2 = _sceneEnterY1; + break; + + case 7: + x2 = _sceneEnterX2; + y2 = _sceneEnterY2; + break; + + default: + x2 = y2 = -1; + } + + if (x2 >= 316) + x2 = 312; + if (y2 >= 185) + y2 = 183; + if (x2 <= 4) + x2 = 8; + } + + if (_mainCharX >= 0) { + x = x2 = _mainCharX; + needProc = false; + } + + if (_mainCharY >= 0) { + y = y2 = _mainCharY; + needProc = false; + } + + _mainCharX = _mainCharY = -1; + + if (unk1 && needProc) { + x = x2; + y = y2; + + switch (facing) { + case 0: + y2 = 191; + break; + + case 2: + x2 = -24; + break; + + case 4: + y2 = y - 4; + break; + + case 6: + x2 = 343; + break; + + default: + break; + } + } + + x2 &= ~3; + x &= ~3; + y2 &= ~1; + y &= ~1; + + _mainCharacter.facing = facing; + _mainCharacter.x1 = _mainCharacter.x2 = x2; + _mainCharacter.y1 = _mainCharacter.y2 = y2; + initSceneAnims(unk2); + + if (_mainCharacter.sceneId == 9 && !_soundDigital->isPlaying(_musicSoundChannel)) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + if (!unk2) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + + if (unk1 && !unk2 && _mainCharacter.animFrame != 87) + moveCharacter(facing, x, y); +} + +void KyraEngine_MR::enterNewSceneUnk2(int unk1) { + _savedMouseState = -1; + if (_mainCharX == -1 && _mainCharY == -1 && !unk1) { + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + + if (!unk1) { + runSceneScript4(0); + malcolmSceneStartupChat(); + } + + _unk4 = 0; + _savedMouseState = -1; +} + +void KyraEngine_MR::unloadScene() { + delete[] _sceneStrings; + _sceneStrings = 0; + _emc->unload(&_sceneScriptData); + freeSceneShapes(); + freeSceneAnims(); +} + +void KyraEngine_MR::freeSceneShapes() { + for (uint i = 0; i < ARRAYSIZE(_sceneShapes); ++i) { + delete[] _sceneShapes[i]; + _sceneShapes[i] = 0; + } +} + +void KyraEngine_MR::loadScenePal() { + char filename[16]; + _screen->copyPalette(2, 0); + strcpy(filename, _sceneList[_mainCharacter.sceneId].filename1); + strcat(filename, ".COL"); + + _screen->loadBitmap(filename, 3, 3, 0); + _screen->getPalette(2).copy(_screen->getCPagePtr(3), 0, 144); + _screen->getPalette(2).fill(0, 1, 0); + + for (int i = 144; i <= 167; ++i) { + uint8 *palette = _screen->getPalette(2).getData() + i * 3; + palette[0] = palette[2] = 63; + palette[1] = 0; + } + + _screen->generateOverlay(_screen->getPalette(2), _paletteOverlay, 0xF0, 0x19); + + _screen->getPalette(2).copy(_costPalBuffer, _characterShapeFile * 24, 24, 144); +} + +void KyraEngine_MR::loadSceneMsc() { + char filename[16]; + strcpy(filename, _sceneList[_mainCharacter.sceneId].filename1); + strcat(filename, ".MSC"); + + _res->exists(filename, true); + Common::SeekableReadStream *stream = _res->createReadStream(filename); + assert(stream); + int16 minY = 0, height = 0; + minY = stream->readSint16LE(); + height = stream->readSint16LE(); + delete stream; + stream = 0; + _maskPageMinY = minY; + _maskPageMaxY = minY + height - 1; + + _screen->setShapePages(5, 3, _maskPageMinY, _maskPageMaxY); + + _screen->loadBitmap(filename, 5, 5, 0, true); + + // HACK + uint8 *data = new uint8[320*200]; + _screen->copyRegionToBuffer(5, 0, 0, 320, 200, data); + _screen->clearPage(5); + _screen->copyBlockToPage(5, 0, _maskPageMinY, 320, height, data); + delete[] data; + +} + +void KyraEngine_MR::initSceneScript(int unk1) { + const SceneDesc &scene = _sceneList[_mainCharacter.sceneId]; + + char filename[16]; + strcpy(filename, scene.filename1); + strcat(filename, ".DAT"); + + _res->exists(filename, true); + Common::SeekableReadStream *stream = _res->createReadStream(filename); + assert(stream); + stream->seek(2, SEEK_CUR); + + byte scaleTable[15]; + stream->read(scaleTable, 15); + stream->read(_sceneDatPalette, 45); + stream->read(_sceneDatLayerTable, 15); + int16 shapesCount = stream->readSint16LE(); + + for (int i = 0; i < 15; ++i) + _scaleTable[i] = (uint16(scaleTable[i]) << 8) / 100; + + if (shapesCount > 0) { + strcpy(filename, scene.filename1); + strcat(filename, "9.CPS"); + _screen->loadBitmap(filename, 3, 3, 0); + int pageBackUp = _screen->_curPage; + _screen->_curPage = 2; + for (int i = 0; i < shapesCount; ++i) { + int16 x = stream->readSint16LE(); + int16 y = stream->readSint16LE(); + int16 w = stream->readSint16LE(); + int16 h = stream->readSint16LE(); + _sceneShapeDescs[i].drawX = stream->readSint16LE(); + _sceneShapeDescs[i].drawY = stream->readSint16LE(); + _sceneShapes[i] = _screen->encodeShape(x, y, w, h, 0); + assert(_sceneShapes[i]); + } + _screen->_curPage = pageBackUp; + } + delete stream; + stream = 0; + + strcpy(filename, scene.filename1); + strcat(filename, ".CPS"); + _screen->loadBitmap(filename, 3, 3, 0); + + Common::fill(_specialSceneScriptState, ARRAYEND(_specialSceneScriptState), false); + _sceneEnterX1 = 160; + _sceneEnterY1 = 0; + _sceneEnterX2 = 296; + _sceneEnterY2 = 93; + _sceneEnterX3 = 160; + _sceneEnterY3 = 171; + _sceneEnterX4 = 24; + _sceneEnterY4 = 93; + _sceneMinX = 0; + _sceneMaxX = 319; + + _emc->init(&_sceneScriptState, &_sceneScriptData); + strcpy(filename, scene.filename2); + strcat(filename, ".EMC"); + _res->exists(filename, true); + _emc->load(filename, &_sceneScriptData, &_opcodes); + + strcpy(filename, scene.filename2); + strcat(filename, "."); + loadLanguageFile(filename, _sceneStrings); + + runSceneScript8(); + _emc->start(&_sceneScriptState, 0); + _sceneScriptState.regs[0] = _mainCharacter.sceneId; + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + _screen->copyRegionToBuffer(3, 0, 0, 320, 200, _gamePlayBuffer); + + for (int i = 0; i < 10; ++i) { + _emc->init(&_sceneSpecialScripts[i], &_sceneScriptData); + _emc->start(&_sceneSpecialScripts[i], i+9); + _sceneSpecialScriptsTimer[i] = 0; + } + + _sceneEnterX1 &= ~3; + _sceneEnterY1 &= ~1; + _sceneEnterX2 &= ~3; + _sceneEnterY2 &= ~1; + _sceneEnterX3 &= ~3; + _sceneEnterY3 &= ~1; + _sceneEnterX4 &= ~3; + _sceneEnterY4 &= ~1; +} + +void KyraEngine_MR::initSceneAnims(int unk1) { + for (int i = 0; i < 67; ++i) + _animObjects[i].enabled = false; + + AnimObj *obj = &_animObjects[0]; + + if (_mainCharacter.animFrame != 87 && !unk1) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + obj->enabled = true; + obj->xPos1 = _mainCharacter.x1; + obj->yPos1 = _mainCharacter.y1; + obj->shapePtr = getShapePtr(_mainCharacter.animFrame); + obj->shapeIndex2 = obj->shapeIndex1 = _mainCharacter.animFrame; + obj->xPos2 = _mainCharacter.x1; + obj->yPos2 = _mainCharacter.y1; + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + obj->xPos3 = obj->xPos2 += (_malcolmShapeXOffset * _charScale) >> 8; + obj->yPos3 = obj->yPos2 += (_malcolmShapeYOffset * _charScale) >> 8; + _mainCharacter.x3 = _mainCharacter.x1 - (_charScale >> 4) - 1; + _mainCharacter.y3 = _mainCharacter.y1 - (_charScale >> 6) - 1; + obj->needRefresh = true; + _animList = 0; + + for (int i = 0; i < 16; ++i) { + const SceneAnim &anim = _sceneAnims[i]; + obj = &_animObjects[i+1]; + obj->enabled = false; + obj->needRefresh = false; + + if (anim.flags & 1) { + obj->enabled = true; + obj->needRefresh = true; + } + + obj->specialRefresh = (anim.flags & 0x20) ? 1 : 0; + obj->flags = (anim.flags & 0x10) ? 0x800 : 0; + if (anim.flags & 2) + obj->flags |= 1; + + obj->xPos1 = anim.x; + obj->yPos1 = anim.y; + + if ((anim.flags & 4) && anim.shapeIndex != -1) + obj->shapePtr = _sceneShapes[anim.shapeIndex]; + else + obj->shapePtr = 0; + + if (anim.flags & 8) { + obj->shapeIndex3 = anim.shapeIndex; + obj->animNum = i; + } else { + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + } + + obj->xPos3 = obj->xPos2 = anim.x2; + obj->yPos3 = obj->yPos2 = anim.y2; + obj->width = anim.width; + obj->height = anim.height; + obj->width2 = obj->height2 = anim.specialSize; + + if (anim.flags & 1) { + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + } + } + + if (_animList) + _animList = addToAnimListSorted(_animList, &_animObjects[0]); + else + _animList = initAnimList(_animList, &_animObjects[0]); + + for (int i = 0; i < 50; ++i) { + obj = &_animObjects[i+17]; + const ItemDefinition &item = _itemList[i]; + if (item.id != kItemNone && item.sceneId == _mainCharacter.sceneId) { + obj->xPos1 = item.x; + obj->yPos1 = item.y; + animSetupPaletteEntry(obj); + obj->shapePtr = 0; + obj->shapeIndex1 = obj->shapeIndex2 = item.id + 248; + obj->xPos2 = item.x; + obj->yPos2 = item.y; + + int scale = getScale(obj->xPos1, obj->yPos1); + const uint8 *shape = getShapePtr(obj->shapeIndex1); + obj->xPos3 = obj->xPos2 -= (_screen->getShapeScaledWidth(shape, scale) >> 1); + obj->yPos3 = obj->yPos2 -= _screen->getShapeScaledHeight(shape, scale) - 1; + obj->enabled = true; + obj->needRefresh = true; + + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + } else { + obj->enabled = false; + obj->needRefresh = false; + } + } + + for (int i = 0; i < 67; ++i) + _animObjects[i].needRefresh = _animObjects[i].enabled; + + restorePage3(); + drawAnimObjects(); + _screen->hideMouse(); + initSceneScreen(unk1); + _screen->showMouse(); + refreshAnimObjects(0); +} + +void KyraEngine_MR::initSceneScreen(int unk1) { + _screen->copyBlockToPage(2, 0, 188, 320, 12, _interfaceCommandLine); + + if (_unkSceneScreenFlag1) { + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + return; + } + + if (_noScriptEnter) { + _screen->getPalette(0).fill(0, 144, 0); + if (!_wasPlayingVQA) + _screen->setScreenPalette(_screen->getPalette(0)); + } + + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + + if (_noScriptEnter) { + if (!_wasPlayingVQA) + _screen->setScreenPalette(_screen->getPalette(2)); + _screen->getPalette(0).copy(_screen->getPalette(2), 0, 144); + if (_wasPlayingVQA) { + _screen->fadeFromBlack(0x3C); + _wasPlayingVQA = false; + } + } + + updateCharPal(0); + _screen->updateScreen(); + + if (!_menuDirectlyToLoad) { + _emc->start(&_sceneScriptState, 3); + _sceneScriptState.regs[5] = unk1; + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } +} + +int KyraEngine_MR::trySceneChange(int *moveTable, int unk1, int updateChar) { + bool running = true; + bool unkFlag = false; + int changedScene = 0; + const int *moveTableStart = moveTable; + _unk4 = 0; + + while (running && !shouldQuit()) { + if (*moveTable >= 0 && *moveTable <= 7) { + _mainCharacter.facing = getOppositeFacingDirection(*moveTable); + unkFlag = true; + } else { + if (*moveTable == 8) { + running = false; + } else { + ++moveTable; + unkFlag = false; + } + } + + if (checkSceneChange()) { + running = false; + changedScene = 1; + } + + if (unk1) { + // Notice that we can't use KyraEngine_MR's skipFlag handling + // here, since Kyra3 allows disabling of skipFlag support + if (KyraEngine_v2::skipFlag()) { + resetSkipFlag(false); + running = false; + _unk4 = 1; + } + } + + if (!unkFlag || !running) + continue; + + int ret = 0; + if (moveTable == moveTableStart || moveTable[1] == 8) + ret = updateCharPos(0, 0); + else + ret = updateCharPos(moveTable, 0); + + if (ret) + ++moveTable; + + delay(10, true); + } + + if (updateChar) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + + return changedScene; +} + +int KyraEngine_MR::checkSceneChange() { + const SceneDesc &curScene = _sceneList[_mainCharacter.sceneId]; + int charX = _mainCharacter.x1, charY = _mainCharacter.y1; + int facing = 0; + int process = 0; + + if (_screen->getLayer(charX, charY) == 1 && _savedMouseState == -7) { + facing = 0; + process = 1; + } else if (charX >= 316 && _savedMouseState == -6) { + facing = 2; + process = 1; + } else if (charY >= 186 && _savedMouseState == -5) { + facing = 4; + process = 1; + } else if (charX <= 4 && _savedMouseState == -4) { + facing = 6; + process = 1; + } + + if (!process) + return 0; + + uint16 newScene = 0xFFFF; + switch (facing) { + case 0: + newScene = curScene.exit1; + break; + + case 2: + newScene = curScene.exit2; + break; + + case 4: + newScene = curScene.exit3; + break; + + case 6: + newScene = curScene.exit4; + break; + + default: + newScene = _mainCharacter.sceneId; + } + + if (newScene == 0xFFFF) + return 0; + + enterNewScene(newScene, facing, 1, 1, 0); + return 1; +} +int KyraEngine_MR::runSceneScript1(int x, int y) { + if (y > 187 && _savedMouseState > -4) + return 0; + if (_deathHandler >= 0) + return 0; + + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[1] = x; + _sceneScriptState.regs[2] = y; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 1); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + return _sceneScriptState.regs[3]; +} + +int KyraEngine_MR::runSceneScript2() { + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 2); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + return _sceneScriptState.regs[3]; +} + +void KyraEngine_MR::runSceneScript4(int unk1) { + _sceneScriptState.regs[4] = _itemInHand; + _sceneScriptState.regs[5] = unk1; + _sceneScriptState.regs[3] = 0; + _noStartupChat = false; + + _emc->start(&_sceneScriptState, 4); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + if (_sceneScriptState.regs[3]) + _noStartupChat = true; +} + +void KyraEngine_MR::runSceneScript8() { + _emc->start(&_sceneScriptState, 8); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +bool KyraEngine_MR::lineIsPassable(int x, int y) { + static const uint8 widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; + + if ((_pathfinderFlag & 2) && x >= 320) + return false; + if ((_pathfinderFlag & 4) && y >= 188) + return false; + if ((_pathfinderFlag & 8) && x < 0) + return false; + if (y > 187) + return false; + + uint width = widthTable[getScale(x, y) >> 5]; + + if (y < 0) + y = 0; + x -= width >> 1; + if (x < 0) + x = 0; + int x2 = x + width; + if (x2 > 320) + x2 = 320; + + for (; x < x2; ++x) { + if (y < _maskPageMinY || y > _maskPageMaxY) + return false; + + if (!_screen->getShapeFlag1(x, y)) + return false; + } + + return true; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_rpg.cpp b/engines/kyra/engine/scene_rpg.cpp new file mode 100644 index 0000000000..72922d4b53 --- /dev/null +++ b/engines/kyra/engine/scene_rpg.cpp @@ -0,0 +1,658 @@ +/* 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. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraRpgEngine::setLevelShapesDim(int index, int16 &x1, int16 &x2, int dim) { + if (_lvlShapeLeftRight[index << 1] == -1) { + x1 = 0; + x2 = 22; + + int16 y1 = 0; + int16 y2 = 120; + + int m = index * 18; + + for (int i = 0; i < 18; i++) { + uint8 d = _visibleBlocks[i]->walls[_sceneDrawVarDown]; + uint8 a = _wllWallFlags[d]; + + if (a & 8) { + int t = _dscDim2[(m + i) << 1]; + + if (t > x1) { + x1 = t; + if (!(a & 0x10)) + setDoorShapeDim(index, y1, y2, -1); + } + + t = _dscDim2[((m + i) << 1) + 1]; + + if (t < x2) { + x2 = t; + if (!(a & 0x10)) + setDoorShapeDim(index, y1, y2, -1); + } + } else { + int t = _dscDim1[m + i]; + + if (!_wllVmpMap[d] || t == -40) + continue; + + if (t == -41) { + x1 = 22; + x2 = 0; + break; + } + + if (t > 0 && x2 > t) + x2 = t; + + if (t < 0 && x1 < -t) + x1 = -t; + } + + if (x2 < x1) + break; + } + + x1 += (_sceneXoffset >> 3); + x2 += (_sceneXoffset >> 3); + + + _lvlShapeTop[index] = y1; + _lvlShapeBottom[index] = y2; + _lvlShapeLeftRight[index << 1] = x1; + _lvlShapeLeftRight[(index << 1) + 1] = x2; + } else { + x1 = _lvlShapeLeftRight[index << 1]; + x2 = _lvlShapeLeftRight[(index << 1) + 1]; + } + + drawLevelModifyScreenDim(dim, x1, 0, x2, 15); +} + +void KyraRpgEngine::setDoorShapeDim(int index, int16 &y1, int16 &y2, int dim) { + uint8 a = _dscDimMap[index]; + + if (_flags.gameID != GI_EOB1 && dim == -1 && a != 3) + a++; + + uint8 b = a; + if (_flags.gameID == GI_EOB1) { + a += _dscDoorFrameIndex1[_currentLevel - 1]; + b += _dscDoorFrameIndex2[_currentLevel - 1]; + } + + y1 = _dscDoorFrameY1[a]; + y2 = _dscDoorFrameY2[b]; + + if (dim == -1) + return; + + const ScreenDim *cDim = screen()->getScreenDim(dim); + + screen()->modifyScreenDim(dim, cDim->sx, y1, cDim->w, y2 - y1); +} + +void KyraRpgEngine::drawLevelModifyScreenDim(int dim, int16 x1, int16 y1, int16 x2, int16 y2) { + screen()->modifyScreenDim(dim, x1, y1 << 3, x2 - x1, (y2 - y1) << 3); +} + +void KyraRpgEngine::generateBlockDrawingBuffer() { + _sceneDrawVarDown = _dscBlockMap[_currentDirection]; + _sceneDrawVarRight = _dscBlockMap[_currentDirection + 4]; + _sceneDrawVarLeft = _dscBlockMap[_currentDirection + 8]; + + /******************************************* + * _visibleBlocks map * + * * + * | | | | | | * + * 00 | 01 | 02 | 03 | 04 | 05 | 06 * + * ____|_____|_____|_____|_____|_____|_____ * + * | | | | | | * + * | 07 | 08 | 09 | 10 | 11 | * + * |_____|_____|_____|_____|_____| * + * | | | | * + * | 12 | 13 | 14 | * + * |_____|_____|_____| * + * | | * + * 15 | 16 | 17 * + * | (P) | * + ********************************************/ + + memset(_blockDrawingBuffer, 0, 660 * sizeof(uint16)); + + _wllProcessFlag = ((_currentBlock >> 5) + (_currentBlock & 0x1F) + _currentDirection) & 1; + + if (_wllProcessFlag) // floor and ceiling + generateVmpTileDataFlipped(0, 15, 1, -330, 22, 15); + else + generateVmpTileData(0, 15, 1, -330, 22, 15); + + assignVisibleBlocks(_currentBlock, _currentDirection); + + uint8 t = _visibleBlocks[0]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(-2, 3, t, 102, 3, 5); + + t = _visibleBlocks[6]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(21, 3, t, 102, 3, 5); + + t = _visibleBlocks[1]->walls[_sceneDrawVarRight]; + uint8 t2 = _visibleBlocks[2]->walls[_sceneDrawVarDown]; + + if (hasWall(t) && !(_wllWallFlags[t2] & 8)) + generateVmpTileData(2, 3, t, 102, 3, 5); + else if (t && (_wllWallFlags[t2] & 8)) + generateVmpTileData(2, 3, t2, 102, 3, 5); + + t = _visibleBlocks[5]->walls[_sceneDrawVarLeft]; + t2 = _visibleBlocks[4]->walls[_sceneDrawVarDown]; + + if (hasWall(t) && !(_wllWallFlags[t2] & 8)) + generateVmpTileDataFlipped(17, 3, t, 102, 3, 5); + else if (t && (_wllWallFlags[t2] & 8)) + generateVmpTileDataFlipped(17, 3, t2, 102, 3, 5); + + t = _visibleBlocks[2]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(8, 3, t, 97, 1, 5); + + t = _visibleBlocks[4]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(13, 3, t, 97, 1, 5); + + t = _visibleBlocks[1]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(-4, 3, t, 129, 6, 5); + + t = _visibleBlocks[5]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(20, 3, t, 129, 6, 5); + + t = _visibleBlocks[2]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(2, 3, t, 129, 6, 5); + + t = _visibleBlocks[4]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(14, 3, t, 129, 6, 5); + + t = _visibleBlocks[3]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(8, 3, t, 129, 6, 5); + + t = _visibleBlocks[7]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(0, 3, t, 117, 2, 6); + + t = _visibleBlocks[11]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(20, 3, t, 117, 2, 6); + + t = _visibleBlocks[8]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(6, 2, t, 81, 2, 8); + + t = _visibleBlocks[10]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(14, 2, t, 81, 2, 8); + + t = _visibleBlocks[8]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(-4, 2, t, 159, 10, 8); + + t = _visibleBlocks[10]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(16, 2, t, 159, 10, 8); + + t = _visibleBlocks[9]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(6, 2, t, 159, 10, 8); + + t = _visibleBlocks[12]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(3, 1, t, 45, 3, 12); + + t = _visibleBlocks[14]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(16, 1, t, 45, 3, 12); + + t = _visibleBlocks[12]->walls[_sceneDrawVarDown]; + if (!(_wllWallFlags[t] & 8)) + generateVmpTileData(-13, 1, t, 239, 16, 12); + + t = _visibleBlocks[14]->walls[_sceneDrawVarDown]; + if (!(_wllWallFlags[t] & 8)) + generateVmpTileData(19, 1, t, 239, 16, 12); + + t = _visibleBlocks[13]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(3, 1, t, 239, 16, 12); + + t = _visibleBlocks[15]->walls[_sceneDrawVarRight]; + t2 = _visibleBlocks[17]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileData(0, 0, t, 0, 3, 15); + if (t2) + generateVmpTileDataFlipped(19, 0, t2, 0, 3, 15); +} + +void KyraRpgEngine::generateVmpTileData(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) { + if (!_wllVmpMap[vmpMapIndex]) + return; + + uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330]; + + for (int i = 0; i < numBlocksY; i++) { + uint16 *bl = &_blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX]; + for (int ii = 0; ii < numBlocksX; ii++) { + if ((startBlockX + ii >= 0) && (startBlockX + ii < 22) && *vmp) + *bl = *vmp; + bl++; + vmp++; + } + } +} + +void KyraRpgEngine::generateVmpTileDataFlipped(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) { + if (!_wllVmpMap[vmpMapIndex]) + return; + + uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330]; + + for (int i = 0; i < numBlocksY; i++) { + for (int ii = 0; ii < numBlocksX; ii++) { + if ((startBlockX + ii) < 0 || (startBlockX + ii) > 21) + continue; + + uint16 v = vmp[i * numBlocksX + (numBlocksX - 1 - ii)]; + if (!v) + continue; + + if (v & 0x4000) + v -= 0x4000; + else + v |= 0x4000; + + _blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX + ii] = v; + } + } +} + +bool KyraRpgEngine::hasWall(int index) { + if (!index || (_wllWallFlags[index] & 8)) + return false; + return true; +} + +void KyraRpgEngine::assignVisibleBlocks(int block, int direction) { + for (int i = 0; i < 18; i++) { + uint16 t = (block + _dscBlockIndex[direction * 18 + i]) & 0x3FF; + _visibleBlockIndex[i] = t; + + _visibleBlocks[i] = &_levelBlockProperties[t]; + _lvlShapeLeftRight[i] = _lvlShapeLeftRight[18 + i] = -1; + } +} + +bool KyraRpgEngine::checkSceneUpdateNeed(int block) { + if (_sceneUpdateRequired) + return true; + + for (int i = 0; i < 15; i++) { + if (_visibleBlockIndex[i] == block) { + _sceneUpdateRequired = true; + return true; + } + } + + if (_currentBlock == block) { + _sceneUpdateRequired = true; + return true; + } + + return false; +} + +void KyraRpgEngine::drawVcnBlocks() { + uint8 *d = _sceneWindowBuffer; + uint16 *bdb = _blockDrawingBuffer; + uint16 *hiColorPal = screen()->get16bitPalette(); + + for (int y = 0; y < 15; y++) { + for (int x = 0; x < 22; x++) { + bool horizontalFlip = false; + uint16 vcnOffset = *bdb++; + uint16 vcnExtraOffsetWll = 0; + int wllVcnOffset = 0; + int wllVcnRmdOffset = 0; + + if (vcnOffset & 0x8000) { + // this renders a wall block over the transparent pixels of a floor/ceiling block + vcnExtraOffsetWll = vcnOffset - 0x8000; + vcnOffset = 0; + wllVcnRmdOffset = _wllVcnOffset; + } + + if (vcnOffset & 0x4000) { + horizontalFlip = true; + vcnOffset &= 0x3FFF; + } + + const uint8 *src = 0; + if (vcnOffset) { + src = &_vcnBlocks[vcnOffset << (4 + _vcnBpp)]; + wllVcnOffset = _wllVcnOffset; + } else { + // floor/ceiling blocks + vcnOffset = bdb[329]; + if (vcnOffset & 0x4000) { + horizontalFlip = true; + vcnOffset &= 0x3FFF; + } + + src = (_vcfBlocks ? _vcfBlocks : _vcnBlocks) + (vcnOffset << (4 + _vcnBpp)); + } + + uint8 shift = _vcnShift ? _vcnShift[vcnOffset] : _blockBrightness; + + if (horizontalFlip) { + for (int blockY = 0; blockY < 8; blockY++) { + src += ((_vcnBpp << 2) - 1); + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + *(uint16*)d = hiColorPal[*src--]; + d += 2; + } else { + uint8 bl = *src--; + *d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift]; + *d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift]; + } + } + src += ((_vcnBpp << 2) + 1); + d += 168 * _vcnBpp; + } + } else { + for (int blockY = 0; blockY < 8; blockY++) { + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + *(uint16*)d = hiColorPal[*src++]; + d += 2; + } else { + uint8 bl = *src++; + *d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift]; + *d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift]; + } + } + d += 168 * _vcnBpp; + } + } + d -= 1400 * _vcnBpp; + + if (vcnExtraOffsetWll) { + d -= 8 * _vcnBpp; + horizontalFlip = false; + + if (vcnExtraOffsetWll & 0x4000) { + vcnExtraOffsetWll &= 0x3FFF; + horizontalFlip = true; + } + + shift = _vcnShift ? _vcnShift[vcnExtraOffsetWll] : _blockBrightness; + src = &_vcnBlocks[vcnExtraOffsetWll << (4 + _vcnBpp)]; + uint8 *maskTable = _vcnTransitionMask ? &_vcnTransitionMask[vcnExtraOffsetWll << 5] : 0; + + if (horizontalFlip) { + for (int blockY = 0; blockY < 8; blockY++) { + src += ((_vcnBpp << 2) - 1); + maskTable += 3; + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + uint8 bl = *src--; + if (bl) + *(uint16*)d = hiColorPal[bl]; + d += 2; + } else { + uint8 bl = *src--; + uint8 mask = _vcnTransitionMask ? *maskTable-- : 0; + uint8 h = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift]; + uint8 l = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift]; + + if (_vcnTransitionMask) + *d = (*d & (mask & 0x0F)) | h; + else if (h) + *d = h; + d++; + + if (_vcnTransitionMask) + *d = (*d & (mask >> 4)) | l; + else if (l) + *d = l; + d++; + } + } + src += ((_vcnBpp << 2) + 1); + maskTable += 5; + d += 168 * _vcnBpp; + } + } else { + for (int blockY = 0; blockY < 8; blockY++) { + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + uint8 bl = *src++; + if (bl) + *(uint16*)d = hiColorPal[bl]; + d += 2; + } else { + uint8 bl = *src++; + uint8 mask = _vcnTransitionMask ? *maskTable++ : 0; + uint8 h = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift]; + uint8 l = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift]; + + if (_vcnTransitionMask) + *d = (*d & (mask >> 4)) | h; + else if (h) + *d = h; + d++; + + if (_vcnTransitionMask) + *d = (*d & (mask & 0x0F)) | l; + else if (l) + *d = l; + d++; + } + } + d += 168 * _vcnBpp; + } + } + d -= 1400 * _vcnBpp; + } + } + d += 1232 * _vcnBpp; + } + + screen()->copyBlockToPage(_sceneDrawPage1, _sceneXoffset, 0, 176, 120, _sceneWindowBuffer); +} + +uint16 KyraRpgEngine::calcNewBlockPosition(uint16 curBlock, uint16 direction) { + static const int16 blockPosTable[] = { -32, 1, 32, -1 }; + return (curBlock + blockPosTable[direction]) & 0x3FF; +} + +int KyraRpgEngine::clickedWallShape(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + snd_stopSpeech(true); + runLevelScript(block, 0x40); + + return 1; +} + +int KyraRpgEngine::clickedLeverOn(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + _levelBlockProperties[block].walls[direction]++; + _sceneUpdateRequired = true; + + if (_flags.gameID == GI_LOL) + snd_playSoundEffect(30, -1); + + runLevelScript(block, _clickedSpecialFlag); + + return 1; +} + +int KyraRpgEngine::clickedLeverOff(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + _levelBlockProperties[block].walls[direction]--; + _sceneUpdateRequired = true; + + if (_flags.gameID == GI_LOL) + snd_playSoundEffect(29, -1); + + runLevelScript(block, _clickedSpecialFlag); + return 1; +} + +int KyraRpgEngine::clickedWallOnlyScript(uint16 block) { + runLevelScript(block, _clickedSpecialFlag); + return 1; +} + +void KyraRpgEngine::processDoorSwitch(uint16 block, int openClose) { + if (block == _currentBlock) + return; + + if ((_flags.gameID == GI_LOL && (_levelBlockProperties[block].assignedObjects & 0x8000)) || (_flags.gameID != GI_LOL && (_levelBlockProperties[block].flags & 7))) + return; + + if (openClose == 0) { + for (int i = 0; i < 3; i++) { + if (_openDoorState[i].block != block) + continue; + openClose = -_openDoorState[i].state; + break; + } + } + + if (openClose == 0) { + openClose = (_wllWallFlags[_levelBlockProperties[block].walls[_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8 ? 0 : 1]] & 1) ? 1 : -1; + if (_flags.gameID != GI_LOL) + openClose *= -1; + } + + openCloseDoor(block, openClose); +} + +void KyraRpgEngine::openCloseDoor(int block, int openClose) { + int s1 = -1; + int s2 = -1; + + int c = (_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8) ? 0 : 1; + int v = _levelBlockProperties[block].walls[c]; + int flg = (_flags.gameID == GI_EOB1) ? 1 : ((openClose == 1) ? 0x10 : (openClose == -1 ? 0x20 : 0)); + + if ((_flags.gameID == GI_EOB1 && openClose == -1 && !(_wllWallFlags[v] & flg)) || (!(_flags.gameID == GI_EOB1 && openClose == -1) && (_wllWallFlags[v] & flg))) + return; + + for (int i = 0; i < 3; i++) { + if (_openDoorState[i].block == block) { + s1 = i; + break; + } else if (_openDoorState[i].block == 0 && s2 == -1) { + s2 = i; + } + } + + if (s1 != -1 || s2 != -1) { + if (s1 == -1) + s1 = s2; + + _openDoorState[s1].block = block; + _openDoorState[s1].state = openClose; + _openDoorState[s1].wall = c; + + flg = (-openClose == 1) ? 0x10 : (-openClose == -1 ? 0x20 : 0); + + if (_wllWallFlags[v] & flg) { + _levelBlockProperties[block].walls[c] += openClose; + _levelBlockProperties[block].walls[c ^ 2] += openClose; + + int snd = (openClose == -1) ? 4 : 3; + if (_flags.gameID == GI_LOL) { + snd_processEnvironmentalSoundEffect(snd + 28, _currentBlock); + if (!checkSceneUpdateNeed(block)) + updateEnvironmentalSfx(0); + } else { + updateEnvironmentalSfx(snd); + } + } + + enableTimer(_flags.gameID == GI_LOL ? 0 : 4); + + } else { + while (!(flg & _wllWallFlags[v])) + v += openClose; + + _levelBlockProperties[block].walls[c] = _levelBlockProperties[block].walls[c ^ 2] = v; + checkSceneUpdateNeed(block); + } +} + +void KyraRpgEngine::completeDoorOperations() { + for (int i = 0; i < 3; i++) { + if (!_openDoorState[i].block) + continue; + + uint16 b = _openDoorState[i].block; + + do { + _levelBlockProperties[b].walls[_openDoorState[i].wall] += _openDoorState[i].state; + _levelBlockProperties[b].walls[_openDoorState[i].wall ^ 2] += _openDoorState[i].state; + } while (!(_wllWallFlags[_levelBlockProperties[b].walls[_openDoorState[i].wall]] & 0x30)); + + _openDoorState[i].block = 0; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL diff --git a/engines/kyra/engine/scene_v1.cpp b/engines/kyra/engine/scene_v1.cpp new file mode 100644 index 0000000000..48958e5b90 --- /dev/null +++ b/engines/kyra/engine/scene_v1.cpp @@ -0,0 +1,360 @@ +/* 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 "kyra/kyra_v1.h" + +namespace Kyra { + +int KyraEngine_v1::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + x &= 0xFFFC; toX &= 0xFFFC; + y &= 0xFFFE; toY &= 0xFFFE; + x = (int16)x; y = (int16)y; toX = (int16)toX; toY = (int16)toY; + + if (x == toY && y == toY) { + moveTable[0] = 8; + return 0; + } + + int curX = x; + int curY = y; + int tempValue = 0; + int lastUsedEntry = 0; + int *pathTable1 = new int[0x7D0]; + int *pathTable2 = new int[0x7D0]; + assert(pathTable1 && pathTable2); + + while (true) { + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) + break; + moveTable[lastUsedEntry++] = newFacing; + break; + } + + if (lineIsPassable(curX, curY)) { + if (lastUsedEntry == moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + // debug drawing + /*if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + screen()->setPagePixel(0, curX, curY, 11); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + moveTable[lastUsedEntry++] = newFacing; + x = curX; + y = curY; + continue; + } + + int temp = 0; + while (true) { + newFacing = getFacingFromPointToPoint(curX, curY, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + // debug drawing + /*if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + screen()->setPagePixel(0, curX, curY, 8); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + + if (!lineIsPassable(curX, curY)) { + if (curX != toX || curY != toY) + continue; + } + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) { + tempValue = 0; + temp = 0; + break; + } + } + + temp = findSubPath(x, y, curX, curY, pathTable1, 1, 0x7D0); + tempValue = findSubPath(x, y, curX, curY, pathTable2, 0, 0x7D0); + if (curX == toX && curY == toY) { + if (temp == 0x7D00 && tempValue == 0x7D00) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + } + + if (temp != 0x7D00 || tempValue != 0x7D00) + break; + } + + if (temp < tempValue) { + if (lastUsedEntry + temp > moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable1, temp * sizeof(int)); + lastUsedEntry += temp; + } else { + if (lastUsedEntry + tempValue > moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable2, tempValue * sizeof(int)); + lastUsedEntry += tempValue; + } + x = curX; + y = curY; + if (curX == toX && curY == toY) + break; + } + + delete[] pathTable1; + delete[] pathTable2; + moveTable[lastUsedEntry] = 8; + return lastUsedEntry; +} + +int KyraEngine_v1::findSubPath(int x, int y, int toX, int toY, int *moveTable, int start, int end) { + // only used for debug specific code + //static uint16 unkTable[] = { 8, 5 }; + static const int8 facingTable1[] = { 7, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 0 }; + static const int8 facingTable2[] = { -1, 0, -1, 2, -1, 4, -1, 6, -1, 2, -1, 4, -1, 6, -1, 0 }; + static const int8 facingTable3[] = { 2, 4, 4, 6, 6, 0, 0, 2, 6, 6, 0, 0, 2, 2, 4, 4 }; + static const int8 addPosTableX[] = { -1, 0, -1, 4, -1, 0, -1, -4, -1, -4, -1, 0, -1, 4, -1, 0 }; + static const int8 addPosTableY[] = { -1, 2, -1, 0, -1, -2, -1, 0, -1, 0, -1, 2, -1, 0, -1, -2 }; + + // debug specific + /*++unkTable[start]; + while (screen()->getPalette(0)[unkTable[start]] != 0x0F) { + ++unkTable[start]; + }*/ + + int xpos1 = x, xpos2 = x; + int ypos1 = y, ypos2 = y; + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + int position = 0; + + while (position != end) { + int newFacing2 = newFacing; + while (true) { + changePosTowardsFacing(xpos1, ypos1, facingTable1[start * 8 + newFacing2]); + if (!lineIsPassable(xpos1, ypos1)) { + if (facingTable1[start * 8 + newFacing2] == newFacing) + return 0x7D00; + newFacing2 = facingTable1[start * 8 + newFacing2]; + xpos1 = x; + ypos1 = y; + continue; + } + newFacing = facingTable1[start * 8 + newFacing2]; + break; + } + // debug drawing + /*if (xpos1 >= 0 && ypos1 >= 0 && xpos1 < 320 && ypos1 < 200) { + screen()->setPagePixel(0, xpos1, ypos1, unkTable[start]); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + if (newFacing & 1) { + int temp = xpos1 + addPosTableX[newFacing + start * 8]; + if (toX == temp) { + temp = ypos1 + addPosTableY[newFacing + start * 8]; + if (toY == temp) { + moveTable[position++] = facingTable2[newFacing + start * 8]; + return position; + } + } + } + + moveTable[position++] = newFacing; + x = xpos1; + y = ypos1; + + if (x == toX && y == toY) + return position; + + if (xpos1 == xpos2 && ypos1 == ypos2) + break; + + newFacing = facingTable3[start * 8 + newFacing]; + } + + return 0x7D00; +} + +int KyraEngine_v1::getFacingFromPointToPoint(int x, int y, int toX, int toY) { + static const int facingTable[] = { + 1, 0, 1, 2, 3, 4, 3, 2, 7, 0, 7, 6, 5, 4, 5, 6 + }; + + int facingEntry = 0; + int ydiff = y - toY; + if (ydiff < 0) { + ++facingEntry; + ydiff = -ydiff; + } + facingEntry <<= 1; + + int xdiff = toX - x; + if (xdiff < 0) { + ++facingEntry; + xdiff = -xdiff; + } + + if (xdiff >= ydiff) { + int temp = ydiff; + ydiff = xdiff; + xdiff = temp; + + facingEntry <<= 1; + } else { + facingEntry <<= 1; + facingEntry += 1; + } + int temp = (ydiff + 1) >> 1; + + if (xdiff < temp) { + facingEntry <<= 1; + facingEntry += 1; + } else { + facingEntry <<= 1; + } + + assert(facingEntry < ARRAYSIZE(facingTable)); + return facingTable[facingEntry]; +} + + +int KyraEngine_v1::getOppositeFacingDirection(int dir) { + switch (dir) { + case 0: + return 2; + case 1: + return 1; + case 3: + return 7; + case 4: + return 6; + case 5: + return 5; + case 6: + return 4; + case 7: + return 3; + default: + break; + } + return 0; +} + +void KyraEngine_v1::changePosTowardsFacing(int &x, int &y, int facing) { + x += _addXPosTable[facing]; + y += _addYPosTable[facing]; +} + +int KyraEngine_v1::getMoveTableSize(int *moveTable) { + int tableSize = 0; + if (moveTable[0] == 8) + return 0; + + static const int facingTable[] = { + 4, 5, 6, 7, 0, 1, 2, 3 + }; + static const int unkTable[] = { + -1, -1, 1, 2, -1, 6, 7, -1, + -1, -1, -1, -1, 2, -1, 0, -1, + 1, -1, -1, -1, 3, 4, -1, 0, + 2, -1, -1, -1, -1, -1, 4, -1, + -1, 2, 3, -1, -1, -1, 5, 6, + 6, -1, 4, -1, -1, -1, -1, -1, + 7, 0, -1, 4, 5, -1, -1, -1, + -1, -1, 0, -1, 6, -1, -1, -1 + }; + + int *oldPosition = moveTable; + int *tempPosition = moveTable; + int *curPosition = moveTable + 1; + tableSize = 1; + + while (*curPosition != 8) { + if (*oldPosition == facingTable[*curPosition]) { + tableSize -= 2; + *oldPosition = 9; + *curPosition = 9; + + while (tempPosition != moveTable) { + --tempPosition; + if (*tempPosition != 9) + break; + } + + if (tempPosition == moveTable && *tempPosition == 9) { + while (*tempPosition == 9) + ++tempPosition; + + if (*tempPosition == 8) + return 0; + } + + oldPosition = tempPosition; + curPosition = oldPosition + 1; + + while (*curPosition == 9) + ++curPosition; + } else if (unkTable[*curPosition + *oldPosition * 8] != -1) { + --tableSize; + *oldPosition = unkTable[*curPosition + *oldPosition * 8]; + *curPosition = 9; + + if (tempPosition != oldPosition) { + curPosition = oldPosition; + oldPosition = tempPosition; + while (tempPosition != moveTable) { + --tempPosition; + if (*tempPosition != 9) + break; + } + } else { + do { + ++curPosition; + } while (*curPosition == 9); + } + } else { + tempPosition = oldPosition; + oldPosition = curPosition; + ++tableSize; + + do { + ++curPosition; + } while (*curPosition == 9); + } + } + + return tableSize; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_v2.cpp b/engines/kyra/engine/scene_v2.cpp new file mode 100644 index 0000000000..dad8188542 --- /dev/null +++ b/engines/kyra/engine/scene_v2.cpp @@ -0,0 +1,227 @@ +/* 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 "kyra/engine/kyra_v2.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_v2::freeSceneAnims() { + for (int i = 0; i < ARRAYSIZE(_sceneAnims); ++i) + _sceneAnims[i].flags = 0; + + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) { + if (_sceneAnimMovie[i]) + _sceneAnimMovie[i]->close(); + } +} + +void KyraEngine_v2::updateSpecialSceneScripts() { + uint32 nextTime = _system->getMillis() + _tickLength; + const int startScript = _lastProcessedSceneScript; + + while (_system->getMillis() <= nextTime) { + if (_sceneSpecialScriptsTimer[_lastProcessedSceneScript] <= _system->getMillis() && + !_specialSceneScriptState[_lastProcessedSceneScript]) { + _specialSceneScriptRunFlag = true; + + while (_specialSceneScriptRunFlag && _sceneSpecialScriptsTimer[_lastProcessedSceneScript] <= _system->getMillis()) { + if (!_emc->run(&_sceneSpecialScripts[_lastProcessedSceneScript])) + _specialSceneScriptRunFlag = false; + } + } + + if (!_emc->isValid(&_sceneSpecialScripts[_lastProcessedSceneScript])) { + _emc->start(&_sceneSpecialScripts[_lastProcessedSceneScript], _desc.firstAnimSceneScript + _lastProcessedSceneScript); + _specialSceneScriptRunFlag = false; + } + + ++_lastProcessedSceneScript; + if (_lastProcessedSceneScript >= 10) + _lastProcessedSceneScript = 0; + + if (_lastProcessedSceneScript == startScript) + return; + } +} + +void KyraEngine_v2::runSceneScript6() { + _emc->init(&_sceneScriptState, &_sceneScriptData); + + _sceneScriptState.regs[0] = _mainCharacter.sceneId; + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 6); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +#pragma mark - pathfinder + +int KyraEngine_v2::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + x &= ~3; toX &= ~3; + y &= ~1; toY &= ~1; + int size = KyraEngine_v1::findWay(x, y, toX, toY, moveTable, moveTableSize); + + if (size && !_smoothingPath) { + _smoothingPath = true; + int temp = pathfinderInitPositionTable(moveTable); + temp = pathfinderInitPositionIndexTable(temp, x, y); + pathfinderFinializePath(moveTable, temp, x, y, moveTableSize); + _smoothingPath = false; + } + + return _smoothingPath ? size : getMoveTableSize(moveTable); +} + +bool KyraEngine_v2::directLinePassable(int x, int y, int toX, int toY) { + Screen *scr = screen(); + + while (x != toX || y != toY) { + int facing = getFacingFromPointToPoint(x, y, toX, toY); + x += _addXPosTable[facing]; + y += _addYPosTable[facing]; + if (!scr->getShapeFlag1(x, y)) + return false; + } + + return true; +} + +int KyraEngine_v2::pathfinderInitPositionTable(int *moveTable) { + bool breakLoop = false; + int *moveTableCur = moveTable; + int oldEntry = *moveTableCur, curEntry = *moveTableCur; + int oldX = 0, newX = 0, oldY = 0, newY = 0; + int lastEntry = 0; + lastEntry = pathfinderAddToPositionTable(lastEntry, 0, 0); + + while (*moveTableCur != 8) { + oldEntry = curEntry; + + while (true) { + curEntry = *moveTableCur; + if (curEntry >= 0 && curEntry <= 7) + break; + + if (curEntry == 8) { + breakLoop = true; + break; + } else { + ++moveTableCur; + } + } + + if (breakLoop) + break; + + oldX = newX; + oldY = newY; + + newX += _addXPosTable[curEntry]; + newY += _addYPosTable[curEntry]; + + int temp = ABS(curEntry - oldEntry); + if (temp > 4) { + temp = 8 - temp; + } + + if (temp > 1 || oldEntry != curEntry) + lastEntry = pathfinderAddToPositionTable(lastEntry, oldX, oldY); + + ++moveTableCur; + } + + lastEntry = pathfinderAddToPositionTable(lastEntry, newX, newY); + _pathfinderPositionTable[lastEntry * 2 + 0] = -1; + _pathfinderPositionTable[lastEntry * 2 + 1] = -1; + return lastEntry; +} + +int KyraEngine_v2::pathfinderAddToPositionTable(int index, int v1, int v2) { + _pathfinderPositionTable[index << 1] = v1; + _pathfinderPositionTable[(index << 1) + 1] = v2; + ++index; + if (index >= 199) + --index; + return index; +} + +int KyraEngine_v2::pathfinderInitPositionIndexTable(int tableLen, int x, int y) { + int x1 = 0, y1 = 0; + int x2 = 0, y2 = 0; + int lastEntry = 0; + int index2 = tableLen - 1, index1 = 0; + while (index2 > index1) { + x1 = _pathfinderPositionTable[index1 * 2 + 0] + x; + y1 = _pathfinderPositionTable[index1 * 2 + 1] + y; + x2 = _pathfinderPositionTable[index2 * 2 + 0] + x; + y2 = _pathfinderPositionTable[index2 * 2 + 1] + y; + + if (directLinePassable(x1, y1, x2, y2)) { + lastEntry = pathfinderAddToPositionIndexTable(lastEntry, index2); + if (tableLen - 1 == index2) + break; + index1 = index2; + index2 = tableLen - 1; + } else if (index1 + 1 == index2) { + lastEntry = pathfinderAddToPositionIndexTable(lastEntry, index2); + index1 = index2; + index2 = tableLen - 1; + } else { + --index2; + } + } + return lastEntry; +} + +int KyraEngine_v2::pathfinderAddToPositionIndexTable(int index, int v) { + _pathfinderPositionIndexTable[index] = v; + ++index; + if (index >= 199) + --index; + return index; +} + +void KyraEngine_v2::pathfinderFinializePath(int *moveTable, int tableLen, int x, int y, int moveTableSize) { + int x1 = 0, y1 = 0; + int x2 = 0, y2 = 0; + int index1 = 0, index2 = 0; + int sizeLeft = moveTableSize; + for (int i = 0; i < tableLen; ++i) { + index2 = _pathfinderPositionIndexTable[i]; + x1 = _pathfinderPositionTable[index1 * 2 + 0] + x; + y1 = _pathfinderPositionTable[index1 * 2 + 1] + y; + x2 = _pathfinderPositionTable[index2 * 2 + 0] + x; + y2 = _pathfinderPositionTable[index2 * 2 + 1] + y; + + int wayLen = findWay(x1, y1, x2, y2, moveTable, sizeLeft); + moveTable += wayLen; + sizeLeft -= wayLen; // unlike the original we want to be sure that the size left is correct + index1 = index2; + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/sprites.cpp b/engines/kyra/engine/sprites.cpp new file mode 100644 index 0000000000..197d8eab4e --- /dev/null +++ b/engines/kyra/engine/sprites.cpp @@ -0,0 +1,575 @@ +/* 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 "kyra/engine/sprites.h" +#include "kyra/resource/resource.h" +#include "kyra/graphics/animator_lok.h" + +#include "common/system.h" + +namespace Kyra { + +Sprites::Sprites(KyraEngine_LoK *vm, OSystem *system) : _rnd("kyraSprites") { + _vm = vm; + _res = vm->resource(); + _screen = vm->screen(); + _system = system; + _dat = 0; + memset(_anims, 0, sizeof(_anims)); + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + _spriteDefStart = 0; + memset(_drawLayerTable, 0, sizeof(_drawLayerTable)); + _sceneAnimatorBeaconFlag = 0; +} + +Sprites::~Sprites() { + delete[] _dat; + freeSceneShapes(); + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].background) + delete[] _anims[i].background; + } +} + +void Sprites::setupSceneAnims() { + uint8 *data; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + delete[] _anims[i].background; + _anims[i].background = 0; + + if (_anims[i].script != 0) { + data = _anims[i].script; + + assert(READ_LE_UINT16(data) == 0xFF86); + data += 4; + + _anims[i].disable = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].unk2 = READ_LE_UINT16(data); + data += 4; + + if ((_vm->_northExitHeight & 0xFF) > READ_LE_UINT16(data)) + _anims[i].drawY = _vm->_northExitHeight & 0xFF; + else + _anims[i].drawY = READ_LE_UINT16(data); + data += 4; + + //sceneUnk2[i] = READ_LE_UINT16(data); + data += 4; + + _anims[i].x = READ_LE_UINT16(data); + data += 4; + _anims[i].y = READ_LE_UINT16(data); + data += 4; + _anims[i].width = *(data); + data += 4; + _anims[i].height = *(data); + data += 4; + _anims[i].sprite = READ_LE_UINT16(data); + data += 4; + _anims[i].flipX = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].width2 = *(data); + data += 4; + _anims[i].height2 = *(data); + data += 4; + _anims[i].unk1 = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].play = READ_LE_UINT16(data) != 0; + data += 2; + + _anims[i].script = data; + _anims[i].curPos = data; + + int bkgdWidth = _anims[i].width; + int bkgdHeight = _anims[i].height; + + if (_anims[i].width2 > 0) + bkgdWidth += (_anims[i].width2 >> 3) + 1; + + if (_anims[i].height2 > 0) + bkgdHeight += _anims[i].height2; + + _anims[i].background = new uint8[_screen->getRectSize(bkgdWidth + 1, bkgdHeight)]; + assert(_anims[i].background); + memset(_anims[i].background, 0, _screen->getRectSize(bkgdWidth + 1, bkgdHeight)); + } + } +} + +void Sprites::updateSceneAnims() { + uint32 currTime = _system->getMillis(); + bool update; + uint8 *data; + uint16 rndNr; + uint16 anim; + uint16 sound; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].script == 0 || !_anims[i].play || (_anims[i].nextRun != 0 && _anims[i].nextRun > currTime)) + continue; + + data = _anims[i].curPos; + update = true; + debugC(6, kDebugLevelSprites, "anim: %d 0x%.04X", i, READ_LE_UINT16(data)); + assert((data - _anims[i].script) < _anims[i].length); + switch (READ_LE_UINT16(data)) { + case 0xFF88: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + //debugC(6, kDebugLevelSprites, "Unused %i", READ_LE_UINT16(data)); + data += 2; + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = false; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8D: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image, flipped."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + data += 2; + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = true; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8A: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set time to wait"); + debugC(6, kDebugLevelSprites, "Time %i", READ_LE_UINT16(data)); + _anims[i].nextRun = _system->getMillis() + READ_LE_UINT16(data) * _vm->tickLength(); + _anims[i].nextRun -= _system->getMillis() - _anims[i].lastRefresh; + data += 2; + break; + case 0xFFB3: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set time to wait to random value"); + rndNr = READ_LE_UINT16(data) + _rnd.getRandomNumber( READ_LE_UINT16(data) + 2); + debugC(6, kDebugLevelSprites, "Minimum time %i", READ_LE_UINT16(data)); + data += 2; + debugC(6, kDebugLevelSprites, "Maximum time %i", READ_LE_UINT16(data)); + data += 2; + _anims[i].nextRun = _system->getMillis() + rndNr * _vm->tickLength(); + _anims[i].nextRun -= _system->getMillis() - _anims[i].lastRefresh; + break; + case 0xFF8C: + data += 2; + debugC(6, kDebugLevelSprites, "func: Wait until wait time has elapsed"); + update = (_anims[i].nextRun < currTime); + //assert( _anims[i].nextRun > _system->getMillis()); + break; + case 0xFF99: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of unknown animation property to 1"); + _anims[i].unk1 = 1; + break; + case 0xFF9A: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of unknown animation property to 0"); + _anims[i].unk1 = 0; + break; + case 0xFF97: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set default X coordinate of sprite"); + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF98: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set default Y coordinate of sprite"); + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF8B: + debugC(6, kDebugLevelSprites, "func: Jump to start of script section"); + _anims[i].curPos = _anims[i].script; + _anims[i].nextRun = _system->getMillis(); + update = false; + break; + case 0xFF8E: + data += 2; + debugC(6, kDebugLevelSprites, "func: Begin for () loop"); + debugC(6, kDebugLevelSprites, "Iterations: %i", READ_LE_UINT16(data)); + _anims[i].loopsLeft = READ_LE_UINT16(data); + data += 2; + _anims[i].loopStart = data; + break; + case 0xFF8F: + data += 2; + debugC(6, kDebugLevelSprites, "func: End for () loop"); + if (_anims[i].loopsLeft > 0) { + _anims[i].loopsLeft--; + data = _anims[i].loopStart; + } + break; + case 0xFF90: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image using default X and Y"); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = false; + data += 2; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF91: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image using default X and Y, flipped."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = true; + data += 2; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF92: + data += 2; + debugC(6, kDebugLevelSprites, "func: Increase value of default X-coordinate"); + debugC(6, kDebugLevelSprites, "Increment %i", READ_LE_UINT16(data)); + _anims[i].x += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF93: + data += 2; + debugC(6, kDebugLevelSprites, "func: Increase value of default Y-coordinate"); + debugC(6, kDebugLevelSprites, "Increment %i", READ_LE_UINT16(data)); + _anims[i].y += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF94: + data += 2; + debugC(6, kDebugLevelSprites, "func: Decrease value of default X-coordinate"); + debugC(6, kDebugLevelSprites, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].x -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF95: + data += 2; + debugC(6, kDebugLevelSprites, "func: Decrease value of default Y-coordinate"); + debugC(6, kDebugLevelSprites, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].y -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF96: + data += 2; + debugC(6, kDebugLevelSprites, "func: Stop animation"); + debugC(6, kDebugLevelSprites, "Animation index %i", READ_LE_UINT16(data)); + anim = READ_LE_UINT16(data); + data += 2; + _anims[anim].play = false; + _anims[anim].sprite = -1; + break; +/* case 0xFF97: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of animation property 34h to 0"); + break;*/ + case 0xFFAD: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's X coordinate"); + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->x1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAE: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's Y coordinate"); + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->y1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAF: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's sprite"); + debugC(6, kDebugLevelSprites, "Sprite %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->currentAnimFrame = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAA: + data += 2; + debugC(6, kDebugLevelSprites, "func: Reset Brandon's sprite"); + _vm->animator()->actors()->sceneAnimPtr = 0; + _vm->animator()->actors()->bkgdChangeFlag = 1; + _vm->animator()->actors()->refreshFlag = 1; + _vm->animator()->restoreAllObjectBackgrounds(); + _vm->animator()->flagAllObjectsForRefresh(); + _vm->animator()->updateAllObjectShapes(); + break; + case 0xFFAB: + data += 2; + debugC(6, kDebugLevelSprites, "func: Update Brandon's sprite"); + _vm->animator()->animRefreshNPC(0); + _vm->animator()->flagAllObjectsForRefresh(); + _vm->animator()->updateAllObjectShapes(); + break; + case 0xFFB0: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play sound"); + debugC(6, kDebugLevelSprites, "Sound index %i", READ_LE_UINT16(data)); + _vm->snd_playSoundEffect(READ_LE_UINT16(data)); + data += 2; + break; + case 0xFFB1: + data += 2; + _sceneAnimatorBeaconFlag = 1; + break; + case 0xFFB2: + data += 2; + _sceneAnimatorBeaconFlag = 0; + break; + case 0xFFB4: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play (at random) a certain sound at a certain percentage of time"); + debugC(6, kDebugLevelSprites, "Sound index %i", READ_LE_UINT16(data)); + sound = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Percentage %i", READ_LE_UINT16(data)); + rndNr = _rnd.getRandomNumber(100); + if (rndNr <= READ_LE_UINT16(data)) + _vm->snd_playSoundEffect(sound); + data += 2; + break; + case 0xFFA7: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play animation"); + debugC(6, kDebugLevelSprites, "Animation index %i", READ_LE_UINT16(data)); + _anims[READ_LE_UINT16(data)].play = 1; + data += 2; + break; + default: + warning("Unsupported anim command %X in script %i", READ_LE_UINT16(data), i); + data += 2; + } + + if (update) + _anims[i].curPos = data; + if (READ_LE_UINT16(data) == 0xFF87) + _anims[i].play = false; + } +} + +void Sprites::loadDat(const char *filename, SceneExits &exits) { + uint32 fileSize; + + delete[] _dat; + _spriteDefStart = 0; + + _res->exists(filename, true); + _dat = _res->fileData(filename, &fileSize); + + for (uint i = 0; i < MAX_NUM_ANIMS; ++i) + delete[] _anims[i].background; + + memset(_anims, 0, sizeof(_anims)); + uint8 nextAnim = 0; + + assert(fileSize > 0x6D); + + memcpy(_drawLayerTable, (_dat + 0x0D), 8); + _vm->_northExitHeight = READ_LE_UINT16(_dat + 0x15); + if (_vm->_northExitHeight & 1) + _vm->_northExitHeight += 1; + + // XXX + _vm->_paletteChanged = 1; + + if (_vm->gameFlags().platform == Common::kPlatformAmiga) { + if (_vm->queryGameFlag(0xA0)) + _screen->copyPalette(3, 4); + else + _screen->copyPalette(3, 0); + } else { + if (_vm->queryGameFlag(0xA0)) + _screen->copyPalette(1, 3); + else + _screen->copyPalette(1, 0); + + _screen->getPalette(1).copy(_dat + 0x17, 0, 20, 228); + } + uint8 *data = _dat + 0x6B; + + uint16 length = READ_LE_UINT16(data); + data += 2; + + if (length > 2) { + assert( length < fileSize); + uint8 *animstart; + uint8 *start = data; + + while (1) { + if (((uint16)(data - _dat) >= fileSize) || (data - start) >= length) + break; + + if (READ_LE_UINT16(data) == 0xFF83) { + //debugC(1, kDebugLevelSprites, "Body section end."); + data += 2; + break; + } + + switch (READ_LE_UINT16(data)) { + case 0xFF81: + data += 2; + //debugC(1, kDebugLevelSprites, "Body section start"); + break; + case 0xFF82: + data += 2; + //debugC(1, kDebugLevelSprites, "Unknown 0xFF82 section"); + break; + case 0xFF84: + data += 2; + _spriteDefStart = data; + while (READ_LE_UINT16(data) != 0xFF85) + data += 2; + data += 2; + break; + case 0xFF86: + assert(nextAnim < MAX_NUM_ANIMS); + _anims[nextAnim].script = data; + _anims[nextAnim].curPos = data; + _anims[nextAnim].sprite = -1; + _anims[nextAnim].play = true; + animstart = data; + data += 2; + while (READ_LE_UINT16(data) != 0xFF87) { + assert((uint16)(data - _dat) < fileSize); + data += 2; + } + _anims[nextAnim].length = data - animstart; + //debugC(1, kDebugLevelSprites, "Found an anim script of length %i", _anims[nextAnim].length); + nextAnim++; + data += 2; + break; + default: + warning("Unknown code in DAT file '%s' offset %d: %x", filename, int(data - _dat), READ_LE_UINT16(data)); + data += 2; + } + } + } else { + data += 2; + } + + assert(fileSize - (data - _dat) == 0xC); + + exits.northXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.northYPos = *data++ & 0xFFFE; + exits.eastXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.eastYPos = *data++ & 0xFFFE; + exits.southXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.southYPos = *data++ & 0xFFFE; + exits.westXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.westYPos = *data++ & 0xFFFE; +} + +void Sprites::freeSceneShapes() { + for (int i = 0; i < ARRAYSIZE(_sceneShapes); i++) { + delete[] _sceneShapes[i]; + _sceneShapes[i] = 0; + } +} + +void Sprites::loadSceneShapes() { + uint8 *data = _spriteDefStart; + int spriteNum, x, y, width, height; + + freeSceneShapes(); + memset( _sceneShapes, 0, sizeof(_sceneShapes)); + + if (_spriteDefStart == 0) + return; + + int bakPage = _screen->_curPage; + _screen->_curPage = 3; + + while (READ_LE_UINT16(data) != 0xFF85) { + spriteNum = READ_LE_UINT16(data); + assert(spriteNum < ARRAYSIZE(_sceneShapes)); + data += 2; + x = READ_LE_UINT16(data) * 8; + data += 2; + y = READ_LE_UINT16(data); + data += 2; + width = READ_LE_UINT16(data) * 8; + data += 2; + height = READ_LE_UINT16(data); + data += 2; + _sceneShapes[spriteNum] = _screen->encodeShape(x, y, width, height, 2); + } + _screen->_curPage = bakPage; +} + +void Sprites::refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag) { + Animator_LoK::AnimObject &anim = _vm->animator()->sprites()[animNum]; + anim.refreshFlag = 1; + anim.bkgdChangeFlag = 1; + + if (unkFlag) + anim.flags |= 0x0200; + else + anim.flags &= 0xFD00; + + if (flipX) + anim.flags |= 1; + else + anim.flags &= 0xFE; + + anim.sceneAnimPtr = _sceneShapes[shapeNum]; + anim.animFrameNumber = -1; + anim.x1 = x; + anim.y1 = y; +} + +int Sprites::getDrawLayer(int y) { + uint8 returnValue = 0; + for (int i = 0; i < ARRAYSIZE(_drawLayerTable); ++i) { + uint8 temp = _drawLayerTable[i]; + if (temp) { + if (temp <= y) + returnValue = i; + } + } + + if (returnValue <= 0) + returnValue = 1; + else if (returnValue >= 7) + returnValue = 6; + + return returnValue; +} +} // End of namespace Kyra diff --git a/engines/kyra/engine/sprites.h b/engines/kyra/engine/sprites.h new file mode 100644 index 0000000000..f68f36ffa4 --- /dev/null +++ b/engines/kyra/engine/sprites.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 KYRA_SPRITES_H +#define KYRA_SPRITES_H + +#include "kyra/engine/kyra_lok.h" +#include "common/random.h" + +namespace Kyra { + +#define MAX_NUM_ANIMS 11 + +struct Sprite { + uint16 x; + uint16 y; + uint16 width; + uint16 height; +}; + +struct Anim { + uint8 *script; + uint8 *curPos; + uint16 length; + int16 x; + int16 y; + bool flipX; + int8 sprite; + uint8 *loopStart; + uint16 loopsLeft; + uint32 nextRun; + uint32 lastRefresh; + bool play; + uint16 width; + uint16 height; + uint16 width2; + uint16 height2; + uint16 unk1; + uint16 drawY; + uint16 unk2; + uint8 *background; + bool disable; +}; + +class KyraEngine_LoK; + +class Sprites { +public: + Sprites(KyraEngine_LoK *vm, OSystem *system); + ~Sprites(); + + void updateSceneAnims(); + void setupSceneAnims(); + void loadDat(const char *filename, SceneExits &exits); + void loadSceneShapes(); + + Anim _anims[MAX_NUM_ANIMS]; + uint8 *_sceneShapes[50]; + + void refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag); + + int getDrawLayer(int y); + + int _sceneAnimatorBeaconFlag; +protected: + void freeSceneShapes(); + + KyraEngine_LoK *_vm; + Resource *_res; + OSystem *_system; + Screen *_screen; + uint8 *_dat; + Common::RandomSource _rnd; + uint8 *_spriteDefStart; + uint8 _drawLayerTable[8]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/sprites_eob.cpp b/engines/kyra/engine/sprites_eob.cpp new file mode 100644 index 0000000000..d7bfe7413d --- /dev/null +++ b/engines/kyra/engine/sprites_eob.cpp @@ -0,0 +1,1285 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/script/script_eob.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + + +namespace Kyra { + +void EoBCoreEngine::loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex) { + if (_flags.platform == Common::kPlatformFMTowns) { + Common::String tmp = Common::String::format("%s.MNT", filename); + Common::SeekableReadStream *s = _res->createReadStream(tmp); + if (!s) + error("Screen_EoB::loadMonsterShapes(): Failed to load file '%s'", tmp.c_str()); + + for (int i = 0; i < 6; i++) + _monsterShapes[monsterIndex + i] = loadTownsShape(s); + + for (int i = 0; i < 6; i++) { + for (int ii = 0; ii < 2; ii++) + s->read(_monsterPalettes[(monsterIndex >= 18 ? i + 6 : i) * 2 + ii], 16); + } + + if (hasDecorations) + loadMonsterDecoration(s, monsterIndex); + + delete s; + } else { + _screen->loadShapeSetBitmap(filename, 3, 3); + const uint16 *enc = &_encodeMonsterShpTable[encodeTableIndex << 2]; + + for (int i = 0; i < 6; i++, enc += 4) + _monsterShapes[monsterIndex + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, _cgaMappingDefault); + + generateMonsterPalettes(filename, monsterIndex); + + if (hasDecorations) { + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format("%s.DCR", filename)); + if (s) + loadMonsterDecoration(s, monsterIndex); + delete s; + } + } + _screen->_curPage = 0; +} + +void EoBCoreEngine::releaseMonsterShapes(int first, int num) { + for (int i = first; i < first + num; i++) { + delete[] _monsterShapes[i]; + _monsterShapes[i] = 0; + delete[] _monsterDecorations[i].shp; + _monsterDecorations[i].shp = 0; + } +} + +uint8 *EoBCoreEngine::loadTownsShape(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32LE(); + uint8 *shape= new uint8[size]; + stream->read(shape, size); + if (shape[0] == 1) + shape[0]++; + return shape; +} + +const uint8 *EoBCoreEngine::loadMonsterProperties(const uint8 *data) { + uint8 cmd = *data++; + while (cmd != 0xFF) { + EoBMonsterProperty *d = &_monsterProps[cmd]; + d->armorClass = (int8)*data++; + d->hitChance = (int8)*data++; + d->level = (int8)*data++; + d->hpDcTimes = *data++; + d->hpDcPips = *data++; + d->hpDcBase = *data++; + d->attacksPerRound = *data++; + d->dmgDc[0].times = *data++; + d->dmgDc[0].pips = *data++; + d->dmgDc[0].base = (int8)*data++; + d->dmgDc[1].times = *data++; + d->dmgDc[1].pips = *data++; + d->dmgDc[1].base = (int8)*data++; + d->dmgDc[2].times = *data++; + d->dmgDc[2].pips = *data++; + d->dmgDc[2].base = (int8)*data++; + d->immunityFlags = READ_LE_UINT16(data); + data += 2; + d->capsFlags = READ_LE_UINT16(data); + data += 2; + d->typeFlags = READ_LE_UINT16(data); + data += 2; + d->experience = READ_LE_UINT16(data); + data += 2; + + d->u30 = *data++; + d->sound1 = (int8)*data++; + d->sound2 = (int8)*data++; + d->numRemoteAttacks = *data++; + + if (*data++ != 0xFF) { + d->remoteWeaponChangeMode = *data++; + d->numRemoteWeapons = *data++; + + for (int i = 0; i < d->numRemoteWeapons; i++) { + d->remoteWeapons[i] = (int8)*data; + data += 2; + } + } + + d->tuResist = (int8)*data++; + d->dmgModifierEvade = *data++; + + for (int i = 0; i < 3; i++) + d->decorations[i] = *data++; + + cmd = *data++; + } + + return data; +} + +const uint8 *EoBCoreEngine::loadActiveMonsterData(const uint8 *data, int level) { + for (uint8 p = *data++; p != 0xFF; p = *data++) { + uint8 v = *data++; + _timer->setCountdown(0x20 + (p << 1), v); + _timer->setCountdown(0x21 + (p << 1), v); + } + + uint32 ct = _system->getMillis(); + for (int i = 0x20; i < 0x24; i++) { + int32 del = _timer->getDelay(i); + _timer->setNextRun(i, (i & 1) ? ct + (del >> 1) * _tickLength : ct + del * _tickLength); + } + _timer->resetNextRun(); + + if (_hasTempDataFlags & (1 << (level - 1))) + return data + 420; + + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + for (int i = 0; i < 30; i++, data += 14) { + if (*data == 0xFF) + continue; + + initMonster(data[0], data[1], READ_LE_UINT16(&data[2]), data[4], (int8)data[5], data[6], data[7], data[8], data[9], READ_LE_UINT16(&data[10]), READ_LE_UINT16(&data[12])); + _monsters[data[0]].flags |= 0x40; + } + + return data; +} + +void EoBCoreEngine::initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem) { + EoBMonsterInPlay *m = &_monsters[index]; + EoBMonsterProperty *p = &_monsterProps[type]; + memset(m, 0, sizeof(EoBMonsterInPlay)); + + if (!block) + return; + + unit <<= 1; + if (index & 1) + unit++; + + m->stepsTillRemoteAttack = _flags.gameID == GI_EOB2 ? rollDice(1, 3, 0) : 5; + m->type = type; + m->numRemoteAttacks = p->numRemoteAttacks; + m->curRemoteWeapon = 0; + m->unit = unit; + m->pos = pos; + m->shpIndex = shpIndex; + m->mode = mode; + m->spellStatusLeft = i; + m->dir = dir; + m->palette = _flags.gameID == GI_EOB2 ? (index % 3) : 0; + m->hitPointsCur = m->hitPointsMax = _flags.gameID == GI_EOB2 ? rollDice(p->hpDcTimes, p->hpDcPips, p->hpDcBase) : (p->level == -1 ? rollDice(1, 4, 0) : rollDice(p->level, 8, 0)); + m->randItem = randItem; + m->fixedItem = fixedItem; + m->sub = _currentSub; + + placeMonster(m, block, dir); +} + +void EoBCoreEngine::placeMonster(EoBMonsterInPlay *m, uint16 block, int dir) { + if (block != 0xFFFF) { + checkSceneUpdateNeed(m->block); + if (_levelBlockProperties[m->block].flags & 7) { + _levelBlockProperties[m->block].flags--; + if (_flags.gameID == GI_EOB2) + runLevelScript(m->block, 0x400); + } + m->block = block; + _levelBlockProperties[block].flags++; + if (_flags.gameID == GI_EOB2) + runLevelScript(m->block, 0x200); + } + + if (dir != -1) { + m->dir = dir; + block = m->block; + } + + checkSceneUpdateNeed(block); +} + +void EoBCoreEngine::killMonster(EoBMonsterInPlay *m, bool giveExperience) { + m->hitPointsCur = -1; + int pos = (m->pos == 4) ? rollDice(1, 4, -1) : m->pos; + + if (m->randItem) { + if (rollDice(1, 10, 0) == 1) + setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->randItem), pos); + } + + if (m->fixedItem) + setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->fixedItem), pos); + + if (giveExperience) + increasePartyExperience(_monsterProps[m->type].experience); + + if (killMonsterExtra(m)) { + placeMonster(m, 0, -1); + + if (m->mode == 8) + updateAttackingMonsterFlags(); + } +} + +int EoBCoreEngine::countSpecificMonsters(int type) { + int res = 0; + for (int i = 0; i < 30; i++) { + if (_monsters[i].type != type || _monsters[i].sub != _currentSub || _monsters[i].hitPointsCur < 0) + continue; + res++; + } + return res; +} + +void EoBCoreEngine::updateAttackingMonsterFlags() { + EoBMonsterInPlay *m2 = 0; + for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) { + if (m->mode != 8) + continue; + m->mode = 0; + m->dest = _currentBlock; + m2 = m; + } + + if (!m2) + return; + + if (m2->type == 7) + setScriptFlags(4); + + if (m2->type == 12) + setScriptFlags(0x800); +} + +const int8 *EoBCoreEngine::getMonstersOnBlockPositions(uint16 block) { + memset(_monsterBlockPosArray, -1, sizeof(_monsterBlockPosArray)); + for (int8 i = 0; i < 30; i++) { + if (_monsters[i].block != block) + continue; + assert(_monsters[i].pos < sizeof(_monsterBlockPosArray)); + _monsterBlockPosArray[_monsters[i].pos] = i; + } + return _monsterBlockPosArray; +} + +int EoBCoreEngine::getClosestMonster(int charIndex, int block) { + const int8 *pos = getMonstersOnBlockPositions(block); + if (pos[4] != -1) + return pos[4]; + + const uint8 *p = &_monsterProximityTable[(_currentDirection << 3) + ((charIndex & 1) << 2)]; + for (int i = 0; i < 4; i++) { + if (pos[p[i]] != -1) + return pos[p[i]]; + } + return -1; +} + +bool EoBCoreEngine::blockHasMonsters(uint16 block) { + return _levelBlockProperties[block].flags & 7 ? true : false; +} + +bool EoBCoreEngine::isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4) { + return (m->block == block && (m->pos == pos || (m->pos == 4 && checkPos4))) ? true : false; +} + +const int16 *EoBCoreEngine::findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent) { + static const uint8 cpos4[] = { 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1 }; + int include4 = (pos < 4) ? cpos4[(dir << 2) + pos] : 1; + int16 *dst = _foundMonstersArray; + + if (blockDamage) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block == block && (_monsters[i].pos != 4 || include4)) + *dst++ = i; + } + + } else if (singleTargetCheckAdjacent) { + int16 r = -1; + int f = 5; + + for (int i = 0; i < 30; i++) { + const uint8 *tbl = &_findBlockMonstersTable[(dir << 4) + (pos << 2)]; + + if (_monsters[i].block != block) + continue; + + if (_monsters[i].pos == pos) { + r = i; + break; + } + + for (int ii = 0; ii < 4; ii++) { + if (_monsters[i].pos == tbl[ii] && ii < f) { + f = ii; + r = i; + } + } + } + + *dst++ = r; + + } else { + for (int i = 0; i < 30; i++) { + if (isMonsterOnPos(&_monsters[i], block, pos, include4)) + *dst++ = i; + } + } + + *dst = -1; + return _foundMonstersArray; +} + +void EoBCoreEngine::drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl) { + const ScreenDim *d = _screen->getScreenDim(sd); + if (_flags.gameID == GI_EOB1) + x &= ~1; + _screen->drawShape(page, shape, x - (d->sx << 3), y - d->sy, sd, flipped | (ovl ? 2 : 0), ovl); +} + +void EoBCoreEngine::drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex) { + uint8 *ovl = 0; + + if (flags & 2) + ovl = _monsterFlashOverlay; + else if (_flags.gameID == GI_EOB2 && flags & 0x20) + ovl = _monsterStoneOverlay; + else if (palIndex != -1) + ovl = _monsterPalettes[palIndex]; + + drawBlockObject(flipped, 2, shape, x, y, 5, ovl); +} + +void EoBCoreEngine::flashMonsterShape(EoBMonsterInPlay *m) { + disableSysTimer(2); + _flashShapeTimer = 0; + drawScene(1); + m->flags &= 0xFD; + _flashShapeTimer = _system->getMillis() + _tickLength; + enableSysTimer(2); + + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::updateAllMonsterShapes() { + drawScene(1); + bool updateShp = false; + + for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) { + if (m->flags & 2) { + m->flags &= ~2; + updateShp = true; + if (m->hitPointsCur <= 0) + killMonster(m, true); + } + } + + if (updateShp) { + _sceneUpdateRequired = true; + _flashShapeTimer = _system->getMillis() + _tickLength; + } else { + _sceneUpdateRequired = false; + } + _preventMonsterFlash = false; +} + +void EoBCoreEngine::drawBlockItems(int index) { + uint16 o = _visibleBlocks[index]->drawObjects; + uint8 w = _visibleBlocks[index]->walls[_sceneDrawVarDown]; + uint8 flg = (index == 16) ? 0x80 : _wllWallFlags[w]; + + if (_wllVmpMap[w] && !(flg & 0x80)) + return; + + uint16 o2 = o = _items[o].next; + bool forceLoop = true; + static const int8 itemPosYNiche[] = { 0x25, 0x31, 0x38, 0x00 }; + static const int8 itemPosFin[] = { 0, -2, 1, -1, 2, 0, 1, -1 }; + int tile2 = 0; + + while (o != o2 || forceLoop) { + EoBItem *itm = &_items[o]; + if (itm->pos == 8 || itm->pos < 4) { + tile2 = -1; + + uint8 ps = (itm->pos == 8) ? 4 : _dscItemPosIndex[(_currentDirection << 2) + (itm->pos & 3)]; + uint16 wo = (index * 5 + ps) << 1; + int x = _dscShapeCoords[wo] + 88; + int y = 0; + + if (itm->pos == 8) { + x = _dscItemShpX[index]; + y = itemPosYNiche[_dscDimMap[index]]; + ps = 0; + } else { + y = _dscShapeCoords[wo + 1] + 124; + } + + int8 scaleSteps = (int8)_dscItemScaleIndex[(_dscDimMap[index] << 2) + ps]; + if ((flg & 8) && ps < 2 && scaleSteps) { + tile2 = _dscItemTileIndex[index]; + if (tile2 != -1) + setLevelShapesDim(tile2, _shpDmX1, _shpDmX2, 5); + y -= 4; + } + + if (scaleSteps >= 0) { + const uint8 *shp = _screen->scaleShape(_dscItemShapeMap[itm->icon] < _numLargeItemShapes ? _largeItemShapes[_dscItemShapeMap[itm->icon]] : (_dscItemShapeMap[itm->icon] < 15 ? 0 : _smallItemShapes[_dscItemShapeMap[itm->icon] - 15]), scaleSteps); + x = x + (itemPosFin[o & 7] << 1) - ((shp[2] << 3) >> 1); + y -= shp[1]; + + if (itm->pos != 8) + y += itemPosFin[(o >> 1) & 7]; + + drawBlockObject(0, 2, shp, x, y, 5); + _screen->setShapeFadingLevel(0); + } + } + + o = itm->next; + forceLoop = false; + if (tile2 != -1) + setLevelShapesDim(index, _shpDmX1, _shpDmX2, 5); + } +} + +void EoBCoreEngine::drawDoor(int index) { + int s = _visibleBlocks[index]->walls[_sceneDrawVarDown]; + + if (_flags.gameID == GI_EOB1 && s == 0x85) + s = 0; + + if (s >= _dscDoorShpIndexSize) + return; + + int type = _dscDoorShpIndex[s]; + int d = _dscDimMap[index]; + int w = _dscShapeCoords[(index * 5 + 4) << 1]; + + int x = 88 + w; + int y = 0; + + int16 y1 = 0; + int16 y2 = 0; + setDoorShapeDim(index, y1, y2, 5); + drawDoorIntern(type, index, x, y, w, s, d, y1, y2); + drawLevelModifyScreenDim(5, _shpDmX1, 0, _shpDmX2, 15); +} + +void EoBCoreEngine::drawMonsters(int index) { + static const uint8 distMap[] = { 2, 1, 0, 4 }; + static const uint8 yAdd[] = { 20, 12, 4, 4, 2, 0, 0 }; + + int blockDistance = distMap[_dscDimMap[index]]; + + uint16 bl = _visibleBlockIndex[index]; + if (!bl) + return; + + int drawObjDirIndex = _currentDirection * 5; + int cDirOffs = _currentDirection << 2; + + EoBMonsterInPlay *drawObj[5]; + memset(drawObj, 0, 5 * sizeof(EoBMonsterInPlay *)); + + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != bl) + continue; + drawObj[_drawObjPosIndex[drawObjDirIndex + _monsters[i].pos]] = &_monsters[i]; + } + + for (int i = 0; i < 5; i++) { + EoBMonsterInPlay *d = drawObj[i]; + if (!d) + continue; + + EoBMonsterProperty *p = &_monsterProps[d->type]; + + if (_flags.gameID == GI_EOB2 && (p->capsFlags & 0x100) && !(_partyEffectFlags & 0x220) && !(d->flags & 2)) + continue; + + int f = (d->animStep << 4) + cDirOffs + d->dir; + f = (p->capsFlags & 2) ? _monsterFrmOffsTable1[f] : _monsterFrmOffsTable2[f]; + + if (!blockDistance && d->curAttackFrame < 0) + f = d->curAttackFrame + 7; + + int subFrame = ABS(f); + int shpIndex = d->shpIndex ? 18 : 0; + int palIndex = d->palette ? ((((shpIndex == 18) ? subFrame + 5 : subFrame - 1) << 1) + (d->palette - 1)) : -1; + + const uint8 *shp = _screen->scaleShape(_monsterShapes[subFrame + shpIndex - 1], blockDistance); + + int v30 = (subFrame == 1 || subFrame > 3) ? 1 : 0; + int v1e = (d->pos == 4) ? 4 : _dscItemPosIndex[cDirOffs + d->pos]; + int posIndex = (index * 5 + v1e) << 1; + + int x = _dscShapeCoords[posIndex] + 88; + int y = _dscShapeCoords[posIndex + 1] + 127; + + if (p->u30 == 1) { + if (v30) { + if (_flags.gameID == GI_EOB2) + posIndex = ((posIndex >> 1) - v1e) << 1; + y = _dscShapeCoords[posIndex + 1] + 127 + yAdd[blockDistance + ((v1e == 4 || _flags.gameID == GI_EOB1) ? 0 : 3)]; + } else { + if (_flags.gameID == GI_EOB2) + posIndex = ((posIndex >> 1) - v1e + 4) << 1; + x = _dscShapeCoords[posIndex] + 88; + } + } + + int w = shp[2] << 3; + int h = shp[1]; + + x = x - (w >> 1) + (d->idleAnimState >> 4); + y = y - h + (d->idleAnimState & 0x0F); + + drawMonsterShape(shp, x, y, f >= 0 ? 0 : 1, d->flags, palIndex); + + if (_flags.gameID == GI_EOB1) { + _screen->setShapeFadingLevel(0); + continue; + } + + for (int ii = 0; ii < 3; ii++) { + if (!p->decorations[ii]) + continue; + + SpriteDecoration *dcr = &_monsterDecorations[(p->decorations[ii] - 1) * 6 + subFrame + shpIndex - 1]; + + if (!dcr->shp) + continue; + + shp = _screen->scaleShape(dcr->shp, blockDistance); + int dx = dcr->x; + int dy = dcr->y; + + for (int iii = 0; iii < blockDistance; iii++) { + dx = (dx << 1) / 3; + dy = (dy << 1) / 3; + } + + drawMonsterShape(shp, x + ((f < 0) ? (w - dx - (shp[2] << 3)) : dx), y + dy, f >= 0 ? 0 : 1, d->flags, -1); + } + _screen->setShapeFadingLevel(0); + } +} + +void EoBCoreEngine::drawWallOfForce(int index) { + int d = _dscDimMap[index]; + assert(d < 3); + int dH = _wallOfForceDsNumH[d]; + int dW = _wallOfForceDsNumW[d]; + int y = _wallOfForceDsY[d]; + int shpId = _wallOfForceShpId[d] + _teleporterPulse; + int h = _wallOfForceShapes[shpId][1]; + int w = _wallOfForceShapes[shpId][2] << 3; + + for (int i = 0; i < dH; i++) { + int x = _wallOfForceDsX[index]; + for (int ii = 0; ii < dW; ii++) { + drawBlockObject(0, 2, _wallOfForceShapes[shpId], x, y, 5); + x += w; + } + y += h; + shpId ^= 1; + } +} + +void EoBCoreEngine::drawFlyingObjects(int index) { + LevelBlockProperty *bl = _visibleBlocks[index]; + int blockIndex = _visibleBlockIndex[index]; + int w = bl->walls[_sceneDrawVarDown]; + + if (_wllVmpMap[w] && !(_wllWallFlags[w] & 0x80)) + return; + + EoBFlyingObject *drawObj[5]; + memset(drawObj, 0, 5 * sizeof(EoBFlyingObject *)); + + for (int i = 0; i < 10; i++) { + if (!_flyingObjects[i].enable || blockIndex != _flyingObjects[i].curBlock) + continue; + drawObj[_drawObjPosIndex[_currentDirection * 5 + (_flyingObjects[i].curPos & 3)]] = &_flyingObjects[i]; + } + + for (int i = 0; i < 5; i++) { + EoBFlyingObject *fo = drawObj[i]; + if (!fo) + continue; + + int p = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)]; + int x = _dscShapeCoords[(index * 5 + p) << 1] + 88; + int y = 39; + + int sclValue = _flightObjSclIndex[(index << 2) + p]; + int flipped = 0; + + if (sclValue < 0) { + _screen->setShapeFadingLevel(0); + continue; + } + + const uint8 *shp = 0; + bool noFade = false; + + if (fo->enable == 1) { + int shpIx = _dscItemShapeMap[_items[fo->item].icon]; + int dirOffs = (fo->direction == _currentDirection) ? 0 : ((fo->direction == (_currentDirection ^ 2)) ? 1 : -1); + + if (dirOffs == -1 || _flightObjShpMap[shpIx] == -1) { + shp = shpIx < _numLargeItemShapes ? _largeItemShapes[shpIx] : (shpIx < 15 ? 0 : _smallItemShapes[shpIx - 15]); + flipped = fo->direction == ((_currentDirection + 1) & 3) ? 1 : 0; + } else { + shp = (_flightObjShpMap[shpIx] + dirOffs) < _numThrownItemShapes ? _thrownItemShapes[_flightObjShpMap[shpIx] + dirOffs] : _spellShapes[_flightObjShpMap[shpIx - _numThrownItemShapes] + dirOffs]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + } + + } else { + noFade = true; + shp = (fo->objectType < _numThrownItemShapes) ? _thrownItemShapes[fo->objectType] : _spellShapes[fo->objectType - _numThrownItemShapes]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + + if (fo->flags & 0x40) { + x = _dscShapeCoords[(index * 5 + 4) << 1] + 88; + y = 44; + } + } + + assert(shp); + + shp = _screen->scaleShape(shp, sclValue); + + if (noFade) { + _screen->setShapeFadingLevel(0); + noFade = false; + } + + x -= (shp[2] << 2); + y -= (y == 44 ? (shp[1] >> 1) : shp[1]); + + drawBlockObject(flipped, 2, shp, x, y, 5); + _screen->setShapeFadingLevel(0); + } +} + +void EoBCoreEngine::drawTeleporter(int index) { + static const uint8 telprtX[] = { 0x28, 0x1C, 0x12 }; + static const uint8 telprtY[] = { 0x0D, 0x15, 0x1A }; + + int t = 2 - _dscDimMap[index]; + if (t < 0) + return; + + int16 x1 = _dscItemShpX[index] - telprtX[t]; + int16 y1 = telprtY[t]; + + for (int i = 0; i < 2; i++) { + + int16 x2 = 0; + int16 y2 = 0; + int d = (t << 1) + i; + if (!d) + x2 = y2 = -4; + + const uint8 *shp = _teleporterShapes[d ^ _teleporterPulse]; + + for (int ii = 0; ii < 13; ii++) + drawBlockObject(0, 2, shp, x1 + x2 + _teleporterShapeCoords[d * 26 + ii * 2], y1 + y2 + _teleporterShapeCoords[d * 26 + ii * 2 + 1], 5); + } +} + +void EoBCoreEngine::updateMonsters(int unit) { + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->unit == unit) { + if (m->hitPointsCur <= 0 || m->flags & 0x20) + continue; + if (m->directionChanged) { + m->directionChanged = 0; + continue; + } + + updateMonsterDest(m); + + if (m->mode > 0) + updateMonsterAttackMode(m); + + switch (m->mode) { + case 0: + updateMoveMonster(m); + break; + case 1: + updateMonsterFollowPath(m, 2); + break; + case 2: + updateMonsterFollowPath(m, -1); + break; + case 3: + updateMonsterFollowPath(m, 1); + break; + case 5: + updateMonstersStraying(m, -1); + break; + case 6: + updateMonstersStraying(m, 1); + break; + case 7: + case 10: + updateMonstersSpellStatus(m); + break; + default: + break; + } + + if (m->mode != 4 && m->mode != 7 && m->mode != 8) + m->animStep ^= 1; + + if (_monsterProps[m->type].u30 == 1) + setBlockMonsterDirection(m->block, m->dir); + } + } + checkFlyingObjects(); +} + +void EoBCoreEngine::updateMonsterDest(EoBMonsterInPlay *m) { + if (m->mode >= 7 && m->mode <= 10) + return; + int dist = getBlockDistance(m->block, _currentBlock); + if (dist >= 4) + return; + + int s = getNextMonsterDirection(m->block, _currentBlock) - (m->dir << 1) - 3; + + if (s < 0) + s += 8; + + if (s <= 2 && dist >= 2) + return; + + m->mode = 0; + m->dest = _currentBlock; +} + +void EoBCoreEngine::updateMonsterAttackMode(EoBMonsterInPlay *m) { + if (!(m->flags & 1) || m->mode == 10) + return; + if (m->mode == 8) { + turnFriendlyMonstersHostile(); + return; + } + m->mode = 0; + m->dest = _currentBlock; +} + +void EoBCoreEngine::updateAllMonsterDests() { + for (int i = 0; i < 30; i++) + updateMonsterDest(&_monsters[i]); +} + +void EoBCoreEngine::turnFriendlyMonstersHostile() { + EoBMonsterInPlay *m = 0; + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode == 8) { + _monsters[i].mode = 0; + _monsters[i].dest = _currentBlock; + m = &_monsters[i]; + } + } + + if (m) { + if (m->type == 7) + setScriptFlags(0x40000); + else if (m->type == 12) + setScriptFlags(0x8000000); + } +} + +int EoBCoreEngine::getNextMonsterDirection(int curBlock, int destBlock) { + uint8 c = destBlock % 32; + uint8 d = destBlock / 32; + uint8 e = curBlock % 32; + uint8 f = curBlock / 32; + + int r = 0; + + int s1 = f - d; + int d1 = ABS(s1); + s1 <<= 1; + int s2 = c - e; + int d2 = ABS(s2); + s2 <<= 1; + + if (s1 >= d2) + r |= 8; + if (-s1 >= d2) + r |= 4; + if (s2 >= d1) + r |= 2; + if (-s2 >= d1) + r |= 1; + + return _monsterDirChangeTable[r]; +} + +int EoBCoreEngine::getNextMonsterPos(EoBMonsterInPlay *m, int block) { + if ((_flags.gameID == GI_EOB1 && _monsterProps[m->type].u30 != 0) || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 2)) + return -1; + int d = findFreeMonsterPos(block, _monsterProps[m->type].u30); + if (d < 0) + return -1; + + int dir = m->dir; + if (_flags.gameID == GI_EOB2) { + if (_monsterProps[m->type].u30 == 1) { + if (d == 9) + return -1; + + int v = _monsterCloseAttUnkTable[d]; + if (v != -1) + ////// + m->dir = 0; + return v; + } + } else { + dir &= 1; + } + + for (int i = 0; i < 4; i++) { + int v = m->dir ^ _monsterCloseAttPosTable2[(dir << 2) + i]; + if (!(d & (1 << v))) + return v; + } + + return -1; +} + +int EoBCoreEngine::findFreeMonsterPos(int block, int size) { + int nm = _levelBlockProperties[block].flags & 7; + if (nm == 4) + return -2; + + int res = 0; + + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->block != block) + continue; + if (_monsterProps[m->type].u30 != size) + return -1; + + if (m->pos == 4 && !(_flags.gameID == GI_EOB2 && m->flags & 0x20)) + m->pos = (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1) ? 0 : _monsterCloseAttPosTable1[m->dir]; + + res |= (1 << m->pos); + if (--nm == 0) + break; + } + + return res; +} + +void EoBCoreEngine::updateMoveMonster(EoBMonsterInPlay *m) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + int d = getNextMonsterDirection(m->block, _currentBlock); + + if ((_flags.gameID == GI_EOB2) && (p->capsFlags & 0x800) && !(d & 1)) + d >>= 1; + else + d = m->dir; + + d = calcNewBlockPosition(m->block, d); + + if (m->dest == d && _currentBlock != d) { + m->mode = rollDice(1, 2, -1) + 5; + return; + } + + if (updateMonsterTryDistanceAttack(m)) + return; + + if (updateMonsterTryCloseAttack(m, d)) + return; + + m->curAttackFrame = 0; + walkMonster(m, m->dest); + + if (p->capsFlags & 8) + updateMonsterTryCloseAttack(m, -1); +} + +bool EoBCoreEngine::updateMonsterTryDistanceAttack(EoBMonsterInPlay *m) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + if (!m->numRemoteAttacks || ((_flags.gameID == GI_EOB1) && !(p->capsFlags & 0x40))) + return false; + + if ((_flags.gameID == GI_EOB1 && m->stepsTillRemoteAttack < 5) || (_flags.gameID == GI_EOB2 && (rollDice(1, 3) > m->stepsTillRemoteAttack))) { + m->stepsTillRemoteAttack++; + return false; + } + + if (getBlockDistance(m->block, _currentBlock) > 3 || getNextMonsterDirection(m->block, _currentBlock) != (m->dir << 1)) + return false; + + int d = m->dir; + int bl = calcNewBlockPosition(m->block, d); + + while (bl != _currentBlock) { + if (!(_wllWallFlags[_levelBlockProperties[bl].walls[d ^ 2]] & 3) || (_levelBlockProperties[bl].flags & 7)) + return false; + bl = calcNewBlockPosition(bl, d); + } + + Item itm = 0; + if (_flags.gameID == GI_EOB1) { + switch (m->type - 4) { + case 0: + launchMagicObject(-1, 9, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(31, m->block); + break; + case 10: + launchMagicObject(-1, _enemyMageSpellList[m->numRemoteAttacks], m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_enemyMageSfx[m->numRemoteAttacks], m->block); + break; + case 11: + itm = duplicateItem(60); + if (itm) { + if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type)) + _items[itm].block = -1; + } + break; + case 12: + launchMagicObject(-1, 0, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(85, m->block); + break; + case 13: + snd_processEnvironmentalSoundEffect(83, m->block); + _txt->printMessage(_monsterSpecAttStrings[1]); + for (int i = 0; i < 6; i++) + statusAttack(i, 4, _monsterSpecAttStrings[2], 1, 5, 9, 1); + break; + case 17: + d = rollDice(1, 4, -1); + if (d >= 3) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + _txt->printMessage(_monsterSpecAttStrings[0], -1, _characters[i].name); + inflictCharacterDamage(i, rollDice(2, 8, 1)); + } + snd_processEnvironmentalSoundEffect(108, m->block); + } else { + launchMagicObject(-1, _beholderSpellList[d], m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_beholderSfx[d], m->block); + } + break; + default: + break; + } + + } else { + int cw = 0; + if (p->remoteWeaponChangeMode == 1) { + cw = m->curRemoteWeapon++; + if (m->curRemoteWeapon == p->numRemoteWeapons) + m->curRemoteWeapon = 0; + } else if (p->remoteWeaponChangeMode == 2) { + cw = rollDice(1, p->numRemoteWeapons, -1); + } + + int s = p->remoteWeapons[cw]; + if (s >= 0) { + if (s < 20) { + monsterSpellCast(m, s); + } else if (s == 20) { + snd_processEnvironmentalSoundEffect(103, m->block); + _txt->printMessage(_monsterSpecAttStrings[0]); + for (int i = 0; i < 6; i++) + statusAttack(i, 4, _monsterSpecAttStrings[1], 1, 5, 9, 1); + } + } else { + itm = duplicateItem(-s); + if (itm) { + if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type)) + _items[itm].block = -1; + } + } + } + + if (m->numRemoteAttacks != 255) + m->numRemoteAttacks--; + m->stepsTillRemoteAttack = 0; + return true; +} + +bool EoBCoreEngine::updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block) { + if (block == -1) + block = calcNewBlockPosition(m->block, m->dir); + + if (block != _currentBlock) + return false; + + int r = (m->pos == 4 || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1)) ? 1 : _monsterCloseAttChkTable1[(m->dir << 2) + m->pos]; + + if (r) { + m->flags ^= 4; + if (!(m->flags & 4)) + return true; + + bool facing = (m->block == _visibleBlockIndex[13]); + + if (facing) { + disableSysTimer(2); + if (m->type == 4) + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + m->curAttackFrame = -2; + _flashShapeTimer = 0; + drawScene(1); + m->curAttackFrame = -1; + if (m->type != 4) + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + _flashShapeTimer = _system->getMillis() + 8 * _tickLength; + drawScene(1); + } else { + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + } + + monsterCloseAttack(m); + + if (facing) { + m->curAttackFrame = 0; + m->animStep ^= 1; + _sceneUpdateRequired = 1; + enableSysTimer(2); + _flashShapeTimer = _system->getMillis() + 8 * _tickLength; + } + } else { + int b = m->block; + if ((_levelBlockProperties[b].flags & 7) == 1) { + m->pos = 4; + } else { + b = getNextMonsterPos(m, b); + if (b >= 0) + m->pos = b; + } + checkSceneUpdateNeed(m->block); + } + + return true; +} + +void EoBCoreEngine::walkMonster(EoBMonsterInPlay *m, int destBlock) { + if (++_monsterStepCounter > 10) { + _monsterStepCounter = 0; + _monsterStepMode ^= 1; + } + + const int8 *tbl = _monsterStepMode ? _monsterStepTable3 : _monsterStepTable2; + + int s = m->dir << 1; + int b = m->block; + int d = getNextMonsterDirection(b, destBlock); + if (d == -1) + return; + + if (m->flags & 8) { + // Interestingly, the fear spell in EOB 1 does not expire. + // I don't know whether this is intended or not. + if (_flags.gameID == GI_EOB1) { + d ^= 4; + } else if (m->spellStatusLeft > 0) { + if (--m->spellStatusLeft == 0) + m->flags &= ~8; + else + d ^= 4; + } + } + + int d2 = (d - s) & 7; + + if (_flags.gameID == GI_EOB1) { + if ((b + _monsterStepTable0[d >> 1] == _currentBlock) && !(d & 1)) { + if (d2 >= 5) + s = m->dir - 1; + else if (d2 != 0) + s = m->dir + 1; + walkMonsterNextStep(m, -1, s & 3); + return; + } + } else if (_flags.gameID == GI_EOB2) { + if (b + _monsterStepTable0[d] == destBlock) { + if (d & 1) { + int e = _monsterStepTable1[((d - 1) << 1) + m->dir]; + if (e && (!(_monsterProps[m->type].capsFlags & 0x200) || (rollDice(1, 4) < 4))) { + if (walkMonsterNextStep(m, b + e, -1)) + return; + } + } else { + walkMonsterNextStep(m, -1, d >> 1); + return; + } + } + } + + if (d2) { + if (d2 >= 5) + s -= (1 + ((d & 1) ^ 1)); + else + s += (1 + ((d & 1) ^ 1)); + s &= 7; + } + + for (int i = 7; i > -1; i--) { + s = (s + tbl[i]) & 7; + uint16 b2 = (s & 1) ? 0 : calcNewBlockPosition(b, s >> 1); + if (!b2) + continue; + if (walkMonsterNextStep(m, b2, s >> 1)) + return; + } +} + +bool EoBCoreEngine::walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + int obl = m->block; + + if (destBlock != m->block && destBlock != -1) { + if (m->flags & 8) { + if (getBlockDistance(destBlock, _currentBlock) < getBlockDistance(m->block, _currentBlock)) + return false; + } + + if (destBlock == _currentBlock) + return false; + + if (direction == -1) + direction = m->dir; + + LevelBlockProperty *l = &_levelBlockProperties[destBlock]; + uint8 w = l->walls[direction ^ 2]; + + if (!(_wllWallFlags[w] & 4)) { + if (_flags.gameID == GI_EOB1 || !(p->capsFlags & 0x1000) || _wllShapeMap[w] != -1) + return false; + + if (_wllWallFlags[w] & 0x20) { + if (p->capsFlags & 4 && m->type == 1) + l->walls[direction] = l->walls[direction ^ 2] = 72; + else + openDoor(destBlock); + } + + if (direction != -1) { + m->dir = direction; + checkSceneUpdateNeed(m->block); + } + return true; + } + + if ((l->flags & 7) && destBlock) { + int pos = getNextMonsterPos(m, destBlock); + if (pos == -1) + return false; + m->pos = pos; + } + + placeMonster(m, destBlock, direction); + direction = -1; + } + + if (direction != -1) + m->dir = direction; + + checkSceneUpdateNeed(obl); + if (!_partyResting && p->sound2 > 0) + snd_processEnvironmentalSoundEffect(p->sound2, m->block); + + return true; +} + +void EoBCoreEngine::updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps) { + if (!walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) { + m->dir = (m->dir + turnSteps) & 3; + walkMonsterNextStep(m, -1, m->dir); + } +} + +void EoBCoreEngine::updateMonstersStraying(EoBMonsterInPlay *m, int a) { + if (m->f_9 >= 0) { + if (m->f_9 == 0) + updateMonsterFollowPath(m, -a); + + int8 d = (m->dir + a) & 3; + uint16 bl = calcNewBlockPosition(m->block, d); + uint8 flg = _wllWallFlags[_levelBlockProperties[bl].walls[_dscBlockMap[d]]] & 4; + + if (m->f_9 == 0) { + if (!flg) + m->f_9 = -1; + return; + } + + if (flg) { + walkMonsterNextStep(m, -1, d); + m->f_9 = -1; + return; + } + } + + if (walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) { + m->f_9 = 1; + } else { + walkMonsterNextStep(m, -1, (m->dir - a) & 3); + m->f_9 = 0; + } +} + +void EoBCoreEngine::updateMonstersSpellStatus(EoBMonsterInPlay *m) { + if (m->spellStatusLeft) { + if (!--m->spellStatusLeft) + m->mode = 0; + } +} + +void EoBCoreEngine::setBlockMonsterDirection(int block, int dir) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != block || _monsters[i].dir == dir) + continue; + _monsters[i].dir = dir; + _monsters[i].directionChanged = 1; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/sprites_lol.cpp b/engines/kyra/engine/sprites_lol.cpp new file mode 100644 index 0000000000..910447c45a --- /dev/null +++ b/engines/kyra/engine/sprites_lol.cpp @@ -0,0 +1,1558 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" + +namespace Kyra { + +void LoLEngine::loadMonsterShapes(const char *file, int monsterIndex, int animType) { + releaseMonsterShapes(monsterIndex); + _screen->loadBitmap(file, 3, 3, 0); + + const uint8 *p = _screen->getCPagePtr(2); + const uint8 *ts[16]; + + for (int i = 0; i < 16; i++) { + ts[i] = _screen->getPtrToShape(p, i); + + bool replaced = false; + int pos = monsterIndex << 4; + + for (int ii = 0; ii < i; ii++) { + if (ts[i] != ts[ii]) + continue; + + _monsterShapes[pos + i] = _monsterShapes[pos + ii]; + replaced = true; + break; + } + + if (!replaced) + _monsterShapes[pos + i] = _screen->makeShapeCopy(p, i); + + int size = _screen->getShapePaletteSize(_monsterShapes[pos + i]) << 3; + _monsterPalettes[pos + i] = new uint8[size]; + memset(_monsterPalettes[pos + i], 0, size); + } + + for (int i = 0; i < 4; i++) { + for (int ii = 0; ii < 16; ii++) { + uint8 **of = &_monsterDecorationShapes[monsterIndex * 192 + i * 48 + ii * 3]; + int s = (i << 4) + ii + 17; + of[0] = _screen->makeShapeCopy(p, s); + of[1] = _screen->makeShapeCopy(p, s + 1); + of[2] = _screen->makeShapeCopy(p, s + 2); + } + } + _monsterAnimType[monsterIndex] = animType & 0xFF; + + uint8 *palShape = _screen->makeShapeCopy(p, 16); + + _screen->clearPage(3); + _screen->drawShape(2, palShape, 0, 0, 0, 0); + + uint8 *tmpPal1 = new uint8[64]; + uint8 *tmpPal2 = new uint8[256]; + uint16 *tmpPal3 = new uint16[256]; + memset(tmpPal1, 0, 64); + + for (int i = 0; i < 64; i++) { + tmpPal1[i] = *p; + p += 320; + } + + p = _screen->getCPagePtr(2); + + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + uint16 sz = MIN(_screen->getShapeSize(_monsterShapes[pos]) - 10, 256); + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + memset(tmpPal3, 0xFF, 256 * sizeof(uint16)); + uint8 numCol = *tmpPal2; + + for (int ii = 0; ii < numCol; ii++) { + uint8 *cl = (uint8 *)memchr(tmpPal1, tmpPal2[1 + ii], 64); + if (!cl) + continue; + tmpPal3[ii] = (uint16)(cl - tmpPal1); + } + + for (int ii = 0; ii < 8; ii++) { + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + for (int iii = 0; iii < numCol; iii++) { + if (tmpPal3[iii] == 0xFFFF) + continue; + if (p[tmpPal3[iii] * 320 + ii + 1]) + tmpPal2[1 + iii] = p[tmpPal3[iii] * 320 + ii + 1]; + } + memcpy(_monsterPalettes[pos] + ii * numCol, &tmpPal2[1], numCol); + } + } + + delete[] tmpPal1; + delete[] tmpPal2; + delete[] tmpPal3; + delete[] palShape; +} + +void LoLEngine::releaseMonsterShapes(int monsterIndex) { + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + int pos2 = (monsterIndex << 4) + 16; + if (_monsterShapes[pos]) { + uint8 *t = _monsterShapes[pos]; + delete[] _monsterShapes[pos]; + for (int ii = pos; ii < pos2; ii++) { + if (_monsterShapes[ii] == t) + _monsterShapes[ii] = 0; + } + } + + if (_monsterPalettes[pos]) { + delete[] _monsterPalettes[pos]; + _monsterPalettes[pos] = 0; + } + } + + for (int i = 0; i < 192; i++) { + int pos = (monsterIndex * 192) + i; + if (_monsterDecorationShapes[pos]) { + delete[] _monsterDecorationShapes[pos]; + _monsterDecorationShapes[pos] = 0; + } + } +} + +int LoLEngine::deleteMonstersFromBlock(int block) { + int i = _levelBlockProperties[block].assignedObjects; + int cnt = 0; + uint16 next = 0; + + while (i) { + next = findObject(i)->nextAssignedObject; + if (!(i & 0x8000)) { + i = next; + continue; + } + + LoLMonster *m = &_monsters[i & 0x7FFF]; + + cnt++; + setMonsterMode(m, 14); + + checkSceneUpdateNeed(m->block); + + placeMonster(m, 0, 0); + + i = next; + } + return cnt; +} + +void LoLEngine::setMonsterMode(LoLMonster *monster, int mode) { + if (monster->mode == 13 && mode != 14) + return; + + if (mode == 7) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (monster->mode == 1 && mode == 7) { + for (int i = 0; i < 30; i++) { + if (monster->mode != 1) + continue; + monster->mode = mode; + monster->fightCurTick = 0; + monster->destX = _partyPosX; + monster->destY = _partyPosY; + setMonsterDirection(monster, calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY)); + } + } else { + monster->mode = mode; + monster->fightCurTick = 0; + if (mode == 14) + monster->hitPoints = 0; + if (mode == 13 && (monster->flags & 0x20)) { + monster->mode = 0; + monsterDropItems(monster); + if (_currentLevel != 29) + setMonsterMode(monster, 14); + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + checkSceneUpdateNeed(monster->block); + if (monster->mode == 14) + placeMonster(monster, 0, 0); + } + } +} + +bool LoLEngine::updateMonsterAdjustBlocks(LoLMonster *monster) { + static const uint8 dims[] = { 0, 13, 9, 3 }; + if (monster->properties->flags & 8) + return true; + + uint16 x1 = (monster->x & 0xFF00) | 0x80; + uint16 y1 = (monster->y & 0xFF00) | 0x80; + int x2 = _partyPosX; + int y2 = _partyPosY; + + uint16 dir = 0; + if (monster->properties->flags & 1) { + dir = monster->direction >> 1; + } else { + dir = calcMonsterDirection(x1, y1, x2, y2); + if ((monster->properties->flags & 2) && (dir == (monster->direction ^ 4))) + return false; + dir >>= 1; + } + + calcSpriteRelPosition(x1, y1, x2, y2, dir); + x2 >>= 8; + y2 >>= 8; + + if (y2 < 0 || y2 > 3) + return false; + + int t = (x2 < 0) ? -x2 : x2; + if (t > y2) + return false; + + for (int i = 0; i < 18; i++) + _visibleBlocks[i] = &_levelBlockProperties[(monster->block + _dscBlockIndex[dir + i]) & 0x3FF]; + + int16 fx1 = 0; + int16 fx2 = 0; + setLevelShapesDim(x2 + dims[y2], fx1, fx2, 13); + + return fx1 < fx2; +} + +void LoLEngine::placeMonster(LoLMonster *monster, uint16 x, uint16 y) { + bool cont = true; + int t = monster->block; + if (monster->block) { + removeAssignedObjectFromBlock(&_levelBlockProperties[t], ((uint16)monster->id) | 0x8000); + _levelBlockProperties[t].direction = 5; + checkSceneUpdateNeed(t); + } else { + cont = false; + } + + monster->block = calcBlockIndex(x, y); + + if (monster->x != x || monster->y != y) { + monster->x = x; + monster->y = y; + monster->currentSubFrame = (monster->currentSubFrame + 1) & 3; + } + + if (monster->block == 0) + return; + + assignObjectToBlock(&_levelBlockProperties[monster->block].assignedObjects, ((uint16)monster->id) | 0x8000); + _levelBlockProperties[monster->block].direction = 5; + checkSceneUpdateNeed(monster->block); + + // WORKAROUND: Some monsters in the white tower have sound id's of 0xFF. This is definitely a bug, since the + // last valid track number is 249 and there is no specific handling for 0xFF. Nonetheless this wouldn't + // cause problems in the original code, because it just so happens that the invalid memory address points + // to an entry in _ingameGMSoundIndex which just so happens to have a value of -1 + if (monster->properties->sounds[0] == 0 || monster->properties->sounds[0] == 255 || cont == false) + return; + + if ((!(monster->properties->flags & 0x100) || ((monster->currentSubFrame & 1) == 0)) && monster->block == t) + return; + + if (monster->block != t) + runLevelScriptCustom(monster->block, 0x800, -1, monster->id, 0, 0); + + if (_updateFlags & 1) + return; + + snd_processEnvironmentalSoundEffect(monster->properties->sounds[0], monster->block); +} + +int LoLEngine::calcMonsterDirection(uint16 x1, uint16 y1, uint16 x2, uint16 y2) { + int16 r = 0; + + int16 t1 = y1 - y2; + if (t1 < 0) { + r++; + t1 = -t1; + } + + r <<= 1; + + int16 t2 = x2 - x1; + if (t2 < 0) { + r++; + t2 = -t2; + } + + uint8 f = (t1 > t2) ? 1 : 0; + + if (t2 >= t1) + SWAP(t1, t2); + + r = (r << 1) | f; + + t1 = (t1 + 1) >> 1; + + f = (t1 > t2) ? 1 : 0; + r = (r << 1) | f; + + static const uint8 retVal[] = { 1, 2, 1, 0, 7, 6, 7, 0, 3, 2, 3, 4, 5, 6, 5, 4}; + return retVal[r]; +} + +void LoLEngine::setMonsterDirection(LoLMonster *monster, int dir) { + monster->direction = dir; + + if (!(dir & 1) || ((monster->direction - (monster->facing << 1)) >= 2)) + monster->facing = monster->direction >> 1; + + checkSceneUpdateNeed(monster->block); +} + +void LoLEngine::monsterDropItems(LoLMonster *monster) { + uint16 a = monster->assignedItems; + while (a) { + uint16 b = a; + a = _itemsInPlay[a].nextAssignedObject; + setItemPosition(b, monster->x, monster->y, 0, 1); + } +} + +int LoLEngine::checkBlockBeforeObjectPlacement(uint16 x, uint16 y, uint16 objectWidth, uint16 testFlag, uint16 wallFlag) { + _objectLastDirection = 0; + uint16 x2 = 0; + uint16 y2 = 0; + int xOffs = 0; + int yOffs = 0; + int flag = 0; + + int r = testBlockPassability(calcBlockIndex(x, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y, testFlag); + if (r) + return 4; + + if (x & 0x80) { + if (((x & 0xFF) + objectWidth) & 0xFF00) { + xOffs = 1; + _objectLastDirection = 2; + x2 = x + objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } else { + if (((x & 0xFF) - objectWidth) & 0xFF00) { + xOffs = -1; + _objectLastDirection = 6; + x2 = x - objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } + + if (y & 0x80) { + if (((y & 0xFF) + objectWidth) & 0xFF00) { + yOffs = 1; + _objectLastDirection = 4; + y2 = y + objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } else { + if (((y & 0xFF) - objectWidth) & 0xFF00) { + yOffs = -1; + _objectLastDirection = 0; + y2 = y - objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } + + if (!flag) + return 0; + + r = testBlockPassability(calcBlockIndex(x2, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y + yOffs, testFlag); + if (r) + return 4; + + return 0; +} + +int LoLEngine::testBlockPassability(int block, int x, int y, int objectWidth, int testFlag, int wallFlag) { + if (block == _currentBlock) + testFlag &= 0xFFFE; + + if (testFlag & 1) { + _monsterCurBlock = block; + if (testWallFlag(block, -1, wallFlag)) + return 1; + _monsterCurBlock = 0; + } + + if (!(testFlag & 2)) + return 0; + + uint16 obj = _levelBlockProperties[block].assignedObjects; + while (obj & 0x8000) { + LoLMonster *monster = &_monsters[obj & 0x7FFF]; + + if (monster->mode < 13) { + int r = checkDrawObjectSpace(x, y, monster->x, monster->y); + if ((objectWidth + monster->properties->maxWidth) > r) + return 2; + } + + obj = findObject(obj)->nextAssignedObject; + } + + return 0; +} + +int LoLEngine::calcMonsterSkillLevel(int id, int a) { + const uint16 *c = getCharacterOrMonsterStats(id); + int r = (a << 8) / c[4]; + + if (id & 0x8000) { + r = (r * _monsterModifiers2[3 + _monsterDifficulty]) >> 8; + } else { + if (_characters[id].skillLevels[1] > 7) + r = (r - (r >> 1)); + else if (_characters[id].skillLevels[1] > 3 && _characters[id].skillLevels[1] <= 7) + r = (r - (r >> 2)); + } + + return r; +} + +int LoLEngine::checkBlockOccupiedByParty(int x, int y, int testFlag) { + if ((testFlag & 4) && (_currentBlock == calcBlockIndex(x, y))) + return 1; + + return 0; +} + +void LoLEngine::drawBlockObjects(int blockArrayIndex) { + LevelBlockProperty *l = _visibleBlocks[blockArrayIndex]; + uint16 s = l->assignedObjects; + + if (l->direction != _currentDirection) { + l->drawObjects = 0; + l->direction = _currentDirection; + + while (s) { + reassignDrawObjects(_currentDirection, s, l, true); + s = findObject(s)->nextAssignedObject; + } + } + + s = l->drawObjects; + while (s) { + if (s & 0x8000) { + s &= 0x7FFF; + if (blockArrayIndex < 15) + drawMonster(s); + s = _monsters[s].nextDrawObject; + } else { + LoLItem *i = &_itemsInPlay[s]; + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + + if (i->flyingHeight >= 2 && blockArrayIndex >= 15) { + s = i->nextDrawObject; + continue; + } + + uint8 *shp = 0; + uint16 flg = 0; + + if (i->flyingHeight >= 2) + fy -= ((i->flyingHeight - 1) * 6); + + if ((_itemProperties[i->itemPropertyIndex].flags & 0x1000) && !(i->shpCurFrame_flg & 0xC000)) { + int shpIndex = _itemProperties[i->itemPropertyIndex].flags & 0x800 ? 7 : _itemProperties[i->itemPropertyIndex].shpIndex; + int ii = 0; + for (; ii < 8; ii++) { + if (!_flyingObjects[ii].enable) + continue; + + if (_flyingObjects[ii].item == s) + break; + } + + if (_flyingItemShapes[shpIndex].flipFlags && ((i->x ^ i->y) & 0x20)) + flg |= 0x20; + + flg |= _flyingItemShapes[shpIndex].drawFlags; + + if (ii != 8) { + switch (_currentDirection - (_flyingObjects[ii].direction >> 1) + 3) { + case 1: + case 5: + shpIndex = _flyingItemShapes[shpIndex].shapeFront; + break; + case 3: + shpIndex = _flyingItemShapes[shpIndex].shapeBack; + break; + case 2: + case 6: + flg |= 0x10; + // fall through + case 0: + case 4: + shpIndex = _flyingItemShapes[shpIndex].shapeLeft; + break; + default: + break; + } + + shp = _thrownShapes[shpIndex]; + } + + if (shp) + fy += (shp[2] >> 2); + + } else { + shp = (_itemProperties[i->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[i->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[i->itemPropertyIndex].shpIndex << 1]]; + } + + if (shp) + drawItemOrMonster(shp, 0, i->x, i->y, fx, fy, flg, -1, false); + s = i->nextDrawObject; + } + } +} + +void LoLEngine::drawMonster(uint16 id) { + LoLMonster *m = &_monsters[id]; + int16 flg = _monsterDirFlags[(_currentDirection << 2) + m->facing]; + int curFrm = getMonsterCurFrame(m, flg & 0xFFEF); + uint8 *shp = 0; + + if (curFrm == -1) { + shp = _monsterShapes[m->properties->shapeIndex << 4]; + calcDrawingLayerParameters(m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shp, 0); + } else { + int d = m->flags & 7; + bool flip = m->properties->flags & 0x200 ? true : false; + flg &= 0x10; + shp = _monsterShapes[(m->properties->shapeIndex << 4) + curFrm]; + + if (m->properties->flags & 0x800) + flg |= 0x20; + + uint8 *monsterPalette = d ? _monsterPalettes[(m->properties->shapeIndex << 4) + (curFrm & 0x0F)] + (shp[10] * (d - 1)) : 0; + uint8 *brightnessOverlay = drawItemOrMonster(shp, monsterPalette, m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], 0, 0, flg | 1, -1, flip); + + for (int i = 0; i < 4; i++) { + int v = m->equipmentShapes[i] - 1; + if (v == -1) + break; + + uint8 *shp2 = _monsterDecorationShapes[m->properties->shapeIndex * 192 + v * 48 + curFrm * 3]; + if (!shp2) + continue; + + drawDoorOrMonsterEquipment(shp2, 0, _shpDmX, _shpDmY, flg | 1, brightnessOverlay); + } + } + + if (!m->damageReceived) + return; + + int dW = _screen->getShapeScaledWidth(shp, _dmScaleW) >> 1; + int dH = _screen->getShapeScaledHeight(shp, _dmScaleH) >> 1; + + int bloodAmount = (m->mode == 13) ? (m->fightCurTick << 1) : (m->properties->hitPoints / (m->damageReceived & 0x7FFF)); + + shp = _gameShapes[6]; + + int bloodType = m->properties->flags & 0xC000; + if (bloodType == 0x4000) + bloodType = _flags.use16ColorMode ? 0xBB : 63; + else if (bloodType == 0x8000) + bloodType = _flags.use16ColorMode ? 0x55 : 15; + else if (bloodType == 0xC000) + bloodType = _flags.use16ColorMode ? 0x33 : 74; + else + bloodType = 0; + + uint8 *tbl = new uint8[256]; + if (bloodType) { + for (int i = 0; i < 256; i++) { + tbl[i] = i; + if (i < 2 || i > 7) + continue; + tbl[i] += bloodType; + } + } + + dW += m->hitOffsX; + dH += m->hitOffsY; + + bloodAmount = CLIP(bloodAmount, 1, 4); + + int sW = _dmScaleW / bloodAmount; + int sH = _dmScaleH / bloodAmount; + + _screen->drawShape(_sceneDrawPage1, shp, _shpDmX + dW, _shpDmY + dH, 13, 0x124, tbl, bloodType ? 1 : 0, sW, sH); + + delete[] tbl; +} + +int LoLEngine::getMonsterCurFrame(LoLMonster *m, uint16 dirFlags) { + int tmp = 0; + switch (_monsterAnimType[m->properties->shapeIndex]) { + case 0: + // default + if (dirFlags) { + return (m->mode == 13) ? -1 : (dirFlags + m->currentSubFrame); + } else { + if (m->damageReceived) + return 12; + + switch (m->mode - 5) { + case 0: + return (m->properties->flags & 4) ? 13 : 0; + case 3: + return (m->fightCurTick + 13); + case 6: + return 14; + case 8: + return -1; + default: + return m->currentSubFrame; + } + } + break; + case 1: + // monsters whose outward appearance reflects the damage they have taken + tmp = m->properties->hitPoints; + if (_flags.isTalkie) + tmp = (tmp * _monsterModifiers1[_monsterDifficulty]) >> 8; + if (m->hitPoints > (tmp >> 1)) + tmp = 0; + else if (m->hitPoints > (tmp >> 2)) + tmp = 4; + else + tmp = 8; + + switch (m->mode) { + case 8: + return (m->fightCurTick + tmp); + case 11: + return 12; + case 13: + return (m->fightCurTick + 12); + default: + return tmp; + } + + break; + case 2: + return (m->fightCurTick >= 13) ? 13 : m->fightCurTick; + case 3: + switch (m->mode) { + case 5: + return m->damageReceived ? 5 : 6; + case 8: + return (m->fightCurTick + 6); + case 11: + return 5; + default: + return m->damageReceived ? 5 : m->currentSubFrame; + } + + break; + default: + break; + } + + return 0; +} + +void LoLEngine::reassignDrawObjects(uint16 direction, uint16 itemIndex, LevelBlockProperty *l, bool flag) { + if (l->direction != direction) { + l->direction = 5; + return; + } + + LoLObject *newObject = findObject(itemIndex); + int r = calcObjectPosition(newObject, direction); + uint16 *b = &l->drawObjects; + LoLObject *lastObject = 0; + + while (*b) { + lastObject = findObject(*b); + + if (flag) { + if (calcObjectPosition(lastObject, direction) >= r) + break; + } else { + if (calcObjectPosition(lastObject, direction) > r) + break; + } + + b = &lastObject->nextDrawObject; + } + + newObject->nextDrawObject = *b; + *b = itemIndex; +} + +void LoLEngine::redrawSceneItem() { + assignVisibleBlocks(_currentBlock, _currentDirection); + _screen->fillRect(112, 0, 287, 119, 0); + + static const uint8 sceneClickTileIndex[] = { 13, 16}; + + int16 x1 = 0; + int16 x2 = 0; + + for (int i = 0; i < 2; i++) { + uint8 tile = sceneClickTileIndex[i]; + setLevelShapesDim(tile, x1, x2, 13); + uint16 s = _visibleBlocks[tile]->drawObjects; + + int t = (i << 7) + 1; + while (s) { + if (s & 0x8000) { + s = _monsters[s & 0x7FFF].nextDrawObject; + } else { + LoLItem *item = &_itemsInPlay[s]; + + if (item->shpCurFrame_flg & 0x4000) { + if (checkDrawObjectSpace(item->x, item->y, _partyPosX, _partyPosY) < 320) { + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + if (item->flyingHeight > 1) + fy -= ((item->flyingHeight - 1) * 6); + + uint8 *shp = (_itemProperties[item->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[item->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[item->itemPropertyIndex].shpIndex << 1]]; + + drawItemOrMonster(shp, 0, item->x, item->y, fx, fy, 0, t, 0); + _screen->updateScreen(); + } + } + + s = item->nextDrawObject; + t++; + } + } + } +} + +void LoLEngine::calcSpriteRelPosition(uint16 x1, uint16 y1, int &x2, int &y2, uint16 direction) { + int a = x2 - x1; + int b = y1 - y2; + + if (direction) { + if (direction != 2) + SWAP(a, b); + if (direction != 3) { + a = -a; + if (direction != 1) + b = -b; + } else { + b = -b; + } + } + + x2 = a; + y2 = b; +} + +void LoLEngine::drawDoor(uint8 *shape, uint8 *doorPalette, int index, int unk2, int w, int h, int flags) { + if (!shape) + return; + + uint8 c = _dscDoorY2[(_currentDirection << 5) + unk2]; + int r = (c / 5) + 5 * _dscDimMap[index]; + uint16 d = _dscShapeOvlIndex[r]; + uint16 t = (index << 5) + c; + + _shpDmY = _dscDoorMonsterY[t] + 120; + + if (flags & 1) { + // TODO / UNUSED + flags |= 1; + } + + int u = 0; + + if (flags & 2) { + uint8 dimW = _dscDimMap[index]; + _dmScaleW = _dscDoorMonsterScaleTable[dimW << 1]; + _dmScaleH = _dscDoorMonsterScaleTable[(dimW << 1) + 1]; + u = _dscDoor4[dimW]; + } + + d += 2; + + if (!_dmScaleW || !_dmScaleH) + return; + + int s = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (w) + w = (w * _dmScaleW) >> 8; + + if (h) + h = (h * _dmScaleH) >> 8; + + _shpDmX = _dscDoorMonsterX[t] + w + 200; + _shpDmY = _shpDmY + 4 - s + h - u; + + if (d > 7) + d = 7; + + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (d > bb) + d -= bb; + else + d = 0; + } + + uint8 *brightnessOverlay = _screen->getLevelOverlay(d); + int doorScaledWitdh = _screen->getShapeScaledWidth(shape, _dmScaleW); + + _shpDmX -= (doorScaledWitdh >> 1); + _shpDmY -= s; + + drawDoorOrMonsterEquipment(shape, doorPalette, _shpDmX, _shpDmY, flags, brightnessOverlay); +} + +void LoLEngine::drawDoorOrMonsterEquipment(uint8 *shape, uint8 *objectPalette, int x, int y, int flags, const uint8 *brightnessOverlay) { + int flg = 0; + + if (flags & 0x10) + flg |= 1; + + if (flags & 0x20) + flg |= 0x1000; + + if (flags & 0x40) + flg |= 2; + + if (flg & 0x1000) { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x9104, objectPalette, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x1104, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + } else { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x8104, objectPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x104, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + } +} + +uint8 *LoLEngine::drawItemOrMonster(uint8 *shape, uint8 *monsterPalette, int x, int y, int fineX, int fineY, int flags, int tblValue, bool vflip) { + uint8 *ovl2 = 0; + uint8 *brightnessOverlay = 0; + uint8 tmpOvl[16]; + + if (flags & 0x80) { + flags &= 0xFF7F; + ovl2 = monsterPalette; + monsterPalette = 0; + } else { + ovl2 = _screen->getLevelOverlay(_flags.use16ColorMode ? 5 : 4); + } + + int r = calcDrawingLayerParameters(x, y, _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shape, vflip); + + if (tblValue == -1) { + r = 7 - ((r / 3) - 1); + r = CLIP(r, 0, 7); + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (r > bb) + r -= bb; + else + r = 0; + } + brightnessOverlay = _screen->getLevelOverlay(r); + } else { + memset(tmpOvl + 1, tblValue, 15); + tmpOvl[0] = 0; + monsterPalette = tmpOvl; + brightnessOverlay = _screen->getLevelOverlay(7); + } + + int flg = flags & 0x10 ? 1 : 0; + if (flags & 0x20) + flg |= 0x1000; + if (flags & 0x40) + flg |= 2; + + if (_flags.use16ColorMode) { + if (_currentLevel != 22) + flg &= 0xDFFF; + + } else { + if (_currentLevel == 22) { + if (brightnessOverlay) + brightnessOverlay[255] = 0; + } else { + flg |= 0x2000; + } + } + + _shpDmX += ((_dmScaleW * fineX) >> 8); + _shpDmY += ((_dmScaleH * fineY) >> 8); + + int dH = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (flg & 0x1000) { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + } else { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + } + + _shpDmX -= (_screen->getShapeScaledWidth(shape, _dmScaleW) >> 1); + _shpDmY -= dH; + + return brightnessOverlay; +} + +int LoLEngine::calcDrawingLayerParameters(int x1, int y1, int &x2, int &y2, uint16 &w, uint16 &h, uint8 *shape, int vflip) { + calcSpriteRelPosition(_partyPosX, _partyPosY, x1, y1, _currentDirection); + + if (y1 < 0) { + w = h = x2 = y2 = 0; + return 0; + } + + int l = y1 >> 5; + y2 = _monsterScaleY[l]; + x2 = ((_monsterScaleX[l] * x1) >> 8) + 200; + w = h = (_shpDmY > 120) ? 0x100 : _monsterScaleWH[_shpDmY - 56]; + + if (vflip) + // objects aligned to the ceiling (like the "lobsters" in the mines) + y2 = ((120 - y2) >> 1) + (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + else + y2 -= (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + + return l; +} + +void LoLEngine::updateMonster(LoLMonster *monster) { + static const uint8 flags[] = { 1, 0, 1, 3, 3, 0, 0, 3, 4, 1, 0, 0, 4, 0, 0 }; + if (monster->mode > 14) + return; + + int f = flags[monster->mode]; + if ((monster->speedTick++ < monster->properties->speedTotalWaitTicks) && (!(f & 4))) + return; + + monster->speedTick = 0; + + if (monster->properties->flags & 0x40) { + monster->hitPoints += rollDice(1, 8); + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + } + + if (monster->flags & 8) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (f & 2) { + if (updateMonsterAdjustBlocks(monster)) { + setMonsterMode(monster, 7); + f &= 6; + } + } + + if ((f & 1) && (monster->flags & 0x10)) + setMonsterMode(monster, 7); + + if ((monster->mode != 11) && (monster->mode != 14)) { + if (!(_rnd.getRandomNumber(255) & 3)) { + monster->shiftStep = (monster->shiftStep + 1) & 0x0F; + checkSceneUpdateNeed(monster->block); + } + } + + switch (monster->mode) { + case 0: + case 1: + // friendly mode + if (monster->flags & 0x10) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode == 1) + setMonsterMode(&_monsters[i], 7); + } + } else if (monster->mode == 1) { + moveMonster(monster); + } + break; + + case 2: + moveMonster(monster); + break; + + case 3: + if (updateMonsterAdjustBlocks(monster)) + setMonsterMode(monster, 7); + for (int i = 0; i < 4; i++) { + if (calcNewBlockPosition(monster->block, i) == _currentBlock) + setMonsterMode(monster, 7); + } + break; + + case 4: + // straying around not tracing the party + moveStrayingMonster(monster); + break; + + case 5: + // second recovery phase after delivering an attack + // monsters will rearrange positions in this phase so as to allow a maximum + // number of monsters possible attacking at the same time + _partyAwake = true; + monster->fightCurTick--; + if ((monster->fightCurTick <= 0) || (checkDrawObjectSpace(_partyPosX, _partyPosY, monster->x, monster->y) > 256) || (monster->flags & 8)) + setMonsterMode(monster, 7); + else + alignMonsterToParty(monster); + break; + + case 6: + // same as mode 5, but without rearranging + if (--monster->fightCurTick <= 0) + setMonsterMode(monster, 7); + break; + + case 7: + // monster destination is set to current party position + // depending on the flag setting this gets updated each round + // monster can't change mode before arriving at destination and/or attacking the party + if (!chasePartyWithDistanceAttacks(monster)) + chasePartyWithCloseAttacks(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 8: + // first recovery phase after delivering an attack + if (++monster->fightCurTick > 2) { + setMonsterMode(monster, 5); + monster->fightCurTick = (int8)((((8 << 8) / monster->properties->fightingStats[4]) * _monsterModifiers3[_monsterDifficulty]) >> 8); + } + checkSceneUpdateNeed(monster->block); + break; + + case 9: + if (--monster->fightCurTick) { + chasePartyWithCloseAttacks(monster); + } else { + setMonsterMode(monster, 7); + monster->flags &= 0xFFF7; + } + break; + + case 12: + checkSceneUpdateNeed(monster->block); + if (++monster->fightCurTick > 13) + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + break; + + case 13: + // monster death + if (++monster->fightCurTick > 2) + killMonster(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 14: + monster->damageReceived = 0; + break; + + default: + break; + } + + if (monster->damageReceived) { + if (monster->damageReceived & 0x8000) + monster->damageReceived &= 0x7FFF; + else + monster->damageReceived = 0; + checkSceneUpdateNeed(monster->block); + } + + monster->flags &= 0xFFEF; +} + +void LoLEngine::moveMonster(LoLMonster *monster) { + static const int8 turnPos[] = { 0, 2, 6, 6, 0, 2, 4, 4, 2, 2, 4, 6, 0, 0, 4, 6, 0 }; + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else if (monster->direction != monster->destDirection) { + int i = (monster->facing << 2) + (monster->destDirection >> 1); + setMonsterDirection(monster, turnPos[i]); + } +} + +void LoLEngine::walkMonster(LoLMonster *monster) { + if (monster->properties->flags & 0x400) + return; + + int s = walkMonsterCalcNextStep(monster); + + if (s == -1) { + if (walkMonsterCheckDest(monster->x, monster->y, monster, 4) != 1) + return; + + _objectLastDirection ^= 4; + setMonsterDirection(monster, _objectLastDirection); + } else { + setMonsterDirection(monster, s); + if (monster->numDistAttacks) { + if (getBlockDistance(monster->block, _currentBlock) >= 2) { + if (checkForPossibleDistanceAttack(monster->block, monster->direction, 3, _currentBlock) != 5) { + if (monster->distAttackTick) + return; + } + } + } + } + + int fx = 0; + int fy = 0; + + getNextStepCoords(monster->x, monster->y, fx, fy, (s == -1) ? _objectLastDirection : s); + placeMonster(monster, fx, fy); +} + +bool LoLEngine::chasePartyWithDistanceAttacks(LoLMonster *monster) { + if (!monster->numDistAttacks) + return false; + + if (monster->distAttackTick > 0) { + monster->distAttackTick--; + return false; + } + + int dir = checkForPossibleDistanceAttack(monster->block, monster->facing, 4, _currentBlock); + if (dir == 5) + return false; + + int s = 0; + + if (monster->flags & 0x10) { + s = monster->properties->numDistWeapons ? rollDice(1, monster->properties->numDistWeapons) : 0; + } else { + s = monster->curDistWeapon++; + if (monster->curDistWeapon >= monster->properties->numDistWeapons) + monster->curDistWeapon = 0; + } + + int flyingObject = monster->properties->distWeapons[s]; + + if (flyingObject & 0xC000) { + if (getBlockDistance(monster->block, _currentBlock) > 1) { + int type = flyingObject & 0x4000 ? 0 : 1; + flyingObject = makeItem(flyingObject & 0x3FFF, 0, 0); + + if (flyingObject) { + if (!launchObject(type, flyingObject, monster->x, monster->y, 12, dir << 1, -1, monster->id | 0x8000, 0x3F)) + deleteItem(flyingObject); + } + } + } else if (!(flyingObject & 0x2000)) { + if (getBlockDistance(monster->block, _currentBlock) > 1) + return false; + + if (flyingObject == 1) { + snd_playSoundEffect(147, -1); + shakeScene(10, 2, 2, 1); + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + int item = removeCharacterItem(i, 15); + if (item) + setItemPosition(item, _partyPosX, _partyPosY, 0, 1); + + inflictDamage(i, 20, 0xFFFF, 0, 2); + } + + } else if (flyingObject == 3) { + // shriek + for (int i = 0; i < 30; i++) { + if (getBlockDistance(monster->block, _monsters[i].block) < 7) + setMonsterMode(monster, 7); + } + _txt->printMessage(2, "%s", getLangString(0x401A)); + + } else if (flyingObject == 4) { + launchMagicViper(); + + } else { + return false; + } + } + + if (monster->numDistAttacks != 255) + monster->numDistAttacks--; + + monster->distAttackTick = (monster->properties->fightingStats[4] * 8) >> 8; + + return true; +} + +void LoLEngine::chasePartyWithCloseAttacks(LoLMonster *monster) { + if (!(monster->flags & 8)) { + int dir = calcMonsterDirection(monster->x & 0xFF00, monster->y & 0xFF00, _partyPosX & 0xFF00, _partyPosY & 0xFF00); + int x1 = _partyPosX; + int y1 = _partyPosY; + + calcSpriteRelPosition(monster->x, monster->y, x1, y1, dir >> 1); + + int t = (x1 < 0) ? -x1 : x1; + if (y1 <= 160 && t <= 80) { + if ((monster->direction == dir) && (monster->facing == (dir >> 1))) { + int dst = getNearestPartyMemberFromPos(monster->x, monster->y); + snd_playSoundEffect(monster->properties->sounds[1], -1); + int m = monster->id | 0x8000; + int hit = battleHitSkillTest(m, dst, 0); + + if (hit) { + int mx = calcInflictableDamage(m, dst, hit); + int dmg = rollDice(2, mx); + inflictDamage(dst, dmg, m, 0, 0); + applyMonsterAttackSkill(monster, dst, dmg); + } + + setMonsterMode(monster, 8); + checkSceneUpdateNeed(monster->block); + + } else { + setMonsterDirection(monster, dir); + checkSceneUpdateNeed(monster->block); + } + return; + } + } + + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else { + setMonsterDirection(monster, monster->destDirection); + setMonsterMode(monster, (rollDice(1, 100) <= 50) ? 4 : 3); + } +} + +int LoLEngine::walkMonsterCalcNextStep(LoLMonster *monster) { + static const int8 walkMonsterTable1[] = { 7, -6, 5, -4, 3, -2, 1, 0 }; + static const int8 walkMonsterTable2[] = { -7, 6, -5, 4, -3, 2, -1, 0 }; + + if (++_monsterStepCounter > 10) { + _monsterStepCounter = 0; + _monsterStepMode ^= 1; + } + + const int8 *tbl = _monsterStepMode ? walkMonsterTable2 : walkMonsterTable1; + + int sx = monster->x; + int sy = monster->y; + int s = monster->direction; + int d = calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY); + + if (monster->flags & 8) + d ^= 4; + + d = (d - s) & 7; + + if (d >= 5) + s = (s - 1) & 7; + else if (d) + s = (s + 1) & 7; + + for (int i = 7; i > -1; i--) { + s = (s + tbl[i]) & 7; + + int fx = 0; + int fy = 0; + getNextStepCoords(sx, sy, fx, fy, s); + d = walkMonsterCheckDest(fx, fy, monster, 4); + + if (!d) + return s; + + if ((d != 1) || (s & 1) || (!(monster->properties->flags & 0x80))) + continue; + + uint8 w = _levelBlockProperties[_monsterCurBlock].walls[(s >> 1) ^ 2]; + + if (_wllWallFlags[w] & 0x20) { + if (_specialWallTypes[w] == 5) { + openCloseDoor(_monsterCurBlock, 1); + return -1; + } + } + + if (_wllWallFlags[w] & 8) + return -1; + } + + return -1; +} + +int LoLEngine::checkForPossibleDistanceAttack(uint16 monsterBlock, int direction, int distance, uint16 curBlock) { + int mdist = getBlockDistance(curBlock, monsterBlock); + + if (mdist > distance) + return 5; + + int dir = calcMonsterDirection(monsterBlock & 0x1F, monsterBlock >> 5, curBlock & 0x1F, curBlock >> 5); + if ((dir & 1) || (dir != (direction << 1))) + return 5; + + if (((monsterBlock & 0x1F) != (curBlock & 0x1F)) && ((monsterBlock & 0xFFE0) != (curBlock & 0xFFE0))) + return 5; + + if (distance < 0) + return 5; + + int p = monsterBlock; + + for (int i = 0; i < distance; i++) { + p = calcNewBlockPosition(p, direction); + + if (p == curBlock) + return direction; + + if (_wllWallFlags[_levelBlockProperties[p].walls[direction ^ 2]] & 2) + return 5; + + if (_levelBlockProperties[p].assignedObjects & 0x8000) + return 5; + } + + return 5; +} + +int LoLEngine::walkMonsterCheckDest(int x, int y, LoLMonster *monster, int unk) { + uint8 m = monster->mode; + monster->mode = 15; + + int objType = checkBlockBeforeObjectPlacement(x, y, monster->properties->maxWidth, 7, monster->properties->flags & 0x1000 ? 32 : unk); + + monster->mode = m; + return objType; +} + +void LoLEngine::getNextStepCoords(int16 srcX, int16 srcY, int &newX, int &newY, uint16 direction) { + static const int8 stepAdjustX[] = { 0, 32, 32, 32, 0, -32, -32, -32 }; + static const int8 stepAdjustY[] = { -32, -32, 0, 32, 32, 32, 0, -32 }; + + newX = (srcX + stepAdjustX[direction]) & 0x1FFF; + newY = (srcY + stepAdjustY[direction]) & 0x1FFF; +} + +void LoLEngine::alignMonsterToParty(LoLMonster *monster) { + uint8 mdir = monster->direction >> 1; + uint16 mx = monster->x; + uint16 my = monster->y; + uint16 *pos = (mdir & 1) ? &my : &mx; + bool centered = (*pos & 0x7F) == 0; + + bool posFlag = true; + if (monster->properties->maxWidth <= 63) { + if (centered) { + bool r = false; + + if (monster->nextAssignedObject & 0x8000) { + r = true; + } else { + uint16 id = _levelBlockProperties[monster->block].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + + if (id != monster->id) { + r = true; + } else { + for (int i = 0; i < 3; i++) { + mdir = (mdir + 1) & 3; + id = _levelBlockProperties[calcNewBlockPosition(monster->block, mdir)].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + if (id != 0xFFFF) { + r = true; + break; + } + } + } + } + + if (r) + posFlag = false; + } else { + posFlag = false; + } + } + + if (centered && posFlag) + return; + + if (posFlag) { + if (*pos & 0x80) + *pos -= 32; + else + *pos += 32; + } else { + if (*pos & 0x80) + *pos += 32; + else + *pos -= 32; + } + + if (walkMonsterCheckDest(mx, my, monster, 4)) + return; + + int fx = _partyPosX; + int fy = _partyPosY; + calcSpriteRelPosition(mx, my, fx, fy, monster->direction >> 1); + + if (fx < 0) + fx = -fx; + + if (fy > 160 || fx > 80) + return; + + placeMonster(monster, mx, my); +} + +void LoLEngine::moveStrayingMonster(LoLMonster *monster) { + int x = 0; + int y = 0; + + if (monster->fightCurTick) { + uint8 d = (monster->direction - monster->fightCurTick) & 6; + uint8 id = monster->id; + + for (int i = 0; i < 7; i++) { + getNextStepCoords(monster->x, monster->y, x, y, d); + + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + setMonsterDirection(monster, d); + if (!i) { + if (++id > 3) + monster->fightCurTick = 0; + } + return; + } + + d = (d + monster->fightCurTick) & 6; + } + setMonsterMode(monster, 3); + + } else { + monster->direction &= 6; + getNextStepCoords(monster->x, monster->y, x, y, monster->direction); + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + } else { + monster->fightCurTick = _rnd.getRandomBit() ? 2 : -2; + monster->direction = (monster->direction + monster->fightCurTick) & 6; + } + } +} + +void LoLEngine::killMonster(LoLMonster *monster) { + setMonsterMode(monster, 14); + monsterDropItems(monster); + checkSceneUpdateNeed(monster->block); + + uint8 w = _levelBlockProperties[monster->block].walls[0]; + uint16 f = _levelBlockProperties[monster->block].flags; + if (_wllVmpMap[w] == 0 && _wllShapeMap[w] == 0 && !(f & 0x40) && !(monster->properties->flags & 0x1000)) + _levelBlockProperties[monster->block].flags |= 0x80; + + placeMonster(monster, 0, 0); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/sprites_rpg.cpp b/engines/kyra/engine/sprites_rpg.cpp new file mode 100644 index 0000000000..87c2513c09 --- /dev/null +++ b/engines/kyra/engine/sprites_rpg.cpp @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" + +namespace Kyra { + +int KyraRpgEngine::getBlockDistance(uint16 block1, uint16 block2) { + int b1x = block1 & 0x1F; + int b1y = block1 >> 5; + int b2x = block2 & 0x1F; + int b2y = block2 >> 5; + + uint8 dy = ABS(b2y - b1y); + uint8 dx = ABS(b2x - b1x); + + if (dx > dy) + SWAP(dx, dy); + + return (dx >> 1) + dy; +} + +} // namespace Kyra + +#endif diff --git a/engines/kyra/engine/timer.cpp b/engines/kyra/engine/timer.cpp new file mode 100644 index 0000000000..9728838015 --- /dev/null +++ b/engines/kyra/engine/timer.cpp @@ -0,0 +1,304 @@ +/* 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 "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +namespace { +struct TimerResync : public Common::UnaryFunction<TimerEntry&, void> { + uint32 _tickLength, _curTime; + TimerResync(KyraEngine_v1 *vm, uint32 curTime) : _tickLength(vm->tickLength()), _curTime(curTime) {} + + void operator()(TimerEntry &entry) const { + if (entry.lastUpdate < 0) { + if ((uint32)(ABS(entry.lastUpdate)) >= entry.countdown * _tickLength) + entry.nextRun = 0; + else + entry.nextRun = _curTime + entry.lastUpdate + entry.countdown * _tickLength; + } else { + uint32 nextRun = entry.lastUpdate + entry.countdown * _tickLength; + if (_curTime < nextRun) + nextRun = 0; + entry.nextRun = nextRun; + } + } +}; + +struct TimerEqual : public Common::UnaryFunction<const TimerEntry&, bool> { + uint8 _id; + + TimerEqual(uint8 id) : _id(id) {} + + bool operator()(const TimerEntry &entry) const { + return entry.id == _id; + } +}; +} // end of anonymous namespace + +void TimerManager::pause(bool p) { + if (p) { + ++_isPaused; + + if (_isPaused == 1) { + _isPaused = true; + _pauseStart = _system->getMillis(); + } + } else if (!p && _isPaused > 0) { + --_isPaused; + + if (_isPaused == 0) { + const uint32 pausedTime = _system->getMillis() - _pauseStart; + _nextRun += pausedTime; + + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + pos->lastUpdate += pausedTime; + pos->nextRun += pausedTime; + } + } + } +} + +void TimerManager::reset() { + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) + delete pos->func; + + _timers.clear(); +} + +void TimerManager::addTimer(uint8 id, TimerFunc *func, int countdown, bool enabled) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + warning("Adding already existing timer %d", id); + return; + } + + TimerEntry newTimer; + + newTimer.id = id; + newTimer.countdown = countdown; + newTimer.enabled = enabled ? 1 : 0; + newTimer.lastUpdate = newTimer.nextRun = 0; + newTimer.func = func; + newTimer.pauseStartTime = 0; + + _timers.push_back(newTimer); +} + +void TimerManager::update() { + if (_system->getMillis() < _nextRun || _isPaused) + return; + + _nextRun += 99999; + + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + if (pos->enabled == 1 && pos->countdown >= 0) { + if (pos->nextRun <= _system->getMillis()) { + if (pos->func && pos->func->isValid()) { + (*pos->func)(pos->id); + } + + uint32 curTime = _system->getMillis(); + pos->lastUpdate = curTime; + pos->nextRun = curTime + pos->countdown * _vm->tickLength(); + } + + _nextRun = MIN(_nextRun, pos->nextRun); + } + } +} + +void TimerManager::resync() { + const uint32 curTime = _isPaused ? _pauseStart : _system->getMillis(); + + _nextRun = 0; // force rerun + Common::for_each(_timers.begin(), _timers.end(), TimerResync(_vm, curTime)); +} + +void TimerManager::resetNextRun() { + _nextRun = 0; +} + +void TimerManager::setCountdown(uint8 id, int32 countdown) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + timer->countdown = countdown; + + if (countdown >= 0) { + uint32 curTime = _system->getMillis(); + timer->lastUpdate = curTime; + timer->nextRun = curTime + countdown * _vm->tickLength(); + if (timer->enabled & 2) + timer->pauseStartTime = curTime; + + _nextRun = MIN(_nextRun, timer->nextRun); + } + } else { + warning("TimerManager::setCountdown: No timer %d", id); + } +} + +void TimerManager::setDelay(uint8 id, int32 countdown) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->countdown = countdown; + else + warning("TimerManager::setDelay: No timer %d", id); +} + +int32 TimerManager::getDelay(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return timer->countdown; + + warning("TimerManager::getDelay: No timer %d", id); + return -1; +} + +void TimerManager::setNextRun(uint8 id, uint32 nextRun) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + if (timer->enabled & 2) + timer->pauseStartTime = _system->getMillis(); + timer->nextRun = nextRun; + return; + } + + warning("TimerManager::setNextRun: No timer %d", id); +} + +uint32 TimerManager::getNextRun(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return timer->nextRun; + + warning("TimerManager::getNextRun: No timer %d", id); + return 0xFFFFFFFF; +} + +void TimerManager::pauseSingleTimer(uint8 id, bool p) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + + if (timer == _timers.end()) { + warning("TimerManager::pauseSingleTimer: No timer %d", id); + return; + } + + if (p) { + timer->pauseStartTime = _system->getMillis(); + timer->enabled |= 2; + } else if (timer->pauseStartTime) { + int32 elapsedTime = _system->getMillis() - timer->pauseStartTime; + timer->enabled &= (~2); + timer->lastUpdate += elapsedTime; + timer->nextRun += elapsedTime; + resetNextRun(); + timer->pauseStartTime = 0; + } +} + +bool TimerManager::isEnabled(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return (timer->enabled & 1); + + warning("TimerManager::isEnabled: No timer %d", id); + return false; +} + +void TimerManager::enable(uint8 id) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->enabled |= 1; + else + warning("TimerManager::enable: No timer %d", id); +} + +void TimerManager::disable(uint8 id) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->enabled &= (~1); + else + warning("TimerManager::disable: No timer %d", id); +} + +void TimerManager::loadDataFromFile(Common::SeekableReadStream &file, int version) { + const uint32 loadTime = _isPaused ? _pauseStart : _system->getMillis(); + + if (version <= 7) { + _nextRun = 0; + for (int i = 0; i < 32; ++i) { + uint8 enabled = file.readByte(); + int32 countdown = file.readSint32BE(); + uint32 nextRun = file.readUint32BE(); + + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(i)); + if (timer != _timers.end()) { + timer->enabled = enabled; + timer->countdown = countdown; + + if (nextRun) { + timer->nextRun = nextRun + loadTime; + timer->lastUpdate = timer->nextRun - countdown * _vm->tickLength(); + } else { + timer->nextRun = loadTime; + timer->lastUpdate = loadTime - countdown * _vm->tickLength(); + } + } else { + warning("Loading timer data for non existing timer %d", i); + } + } + } else { + int entries = file.readByte(); + for (int i = 0; i < entries; ++i) { + uint8 id = file.readByte(); + + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + timer->enabled = file.readByte(); + timer->countdown = file.readSint32BE(); + timer->lastUpdate = file.readSint32BE(); + } else { + warning("Loading timer data for non existing timer %d", id); + file.seek(7, SEEK_CUR); + } + } + + resync(); + } +} + +void TimerManager::saveDataToFile(Common::WriteStream &file) const { + const uint32 saveTime = _isPaused ? _pauseStart : _system->getMillis(); + + file.writeByte(count()); + for (CIterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + file.writeByte(pos->id); + file.writeByte(pos->enabled); + file.writeSint32BE(pos->countdown); + file.writeSint32BE(pos->lastUpdate - saveTime); + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer.h b/engines/kyra/engine/timer.h new file mode 100644 index 0000000000..a753707b8a --- /dev/null +++ b/engines/kyra/engine/timer.h @@ -0,0 +1,106 @@ +/* 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 KYRA_TIMER_H +#define KYRA_TIMER_H + +#include "kyra/kyra_v1.h" + +#include "common/list.h" +#include "common/stream.h" +#include "common/func.h" + +namespace Kyra { + +typedef Common::Functor1<int, void> TimerFunc; + +struct TimerEntry { + uint8 id; + int32 countdown; + int8 enabled; + + int32 lastUpdate; + uint32 nextRun; + + TimerFunc *func; + + uint32 pauseStartTime; +}; + +class TimerManager { +public: + TimerManager(KyraEngine_v1 *vm, OSystem *sys) : _vm(vm), _system(sys), _timers(), _nextRun(0), _isPaused(0), _pauseStart(0) {} + ~TimerManager() { reset(); } + + void pause(bool p); + + void reset(); + + void addTimer(uint8 id, TimerFunc *func, int countdown, bool enabled); + + int count() const { return _timers.size(); } + + void update(); + + void resetNextRun(); + + void setCountdown(uint8 id, int32 countdown); + void setDelay(uint8 id, int32 countdown); + int32 getDelay(uint8 id) const; + void setNextRun(uint8 id, uint32 nextRun); + uint32 getNextRun(uint8 id) const; + + void pauseSingleTimer(uint8 id, bool p); + + bool isEnabled(uint8 id) const; + void enable(uint8 id); + void disable(uint8 id); + + void loadDataFromFile(Common::SeekableReadStream &file, int version); + void saveDataToFile(Common::WriteStream &file) const; + +private: + void resync(); + + KyraEngine_v1 *_vm; + OSystem *_system; + Common::List<TimerEntry> _timers; + uint32 _nextRun; + + uint _isPaused; + uint32 _pauseStart; + + typedef Common::List<TimerEntry>::iterator Iterator; + typedef Common::List<TimerEntry>::const_iterator CIterator; +}; + +class PauseTimer { +public: + PauseTimer(TimerManager &timer) : _timer(timer) { _timer.pause(true); } + ~PauseTimer() { _timer.pause(false); } +private: + TimerManager &_timer; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/timer_eob.cpp b/engines/kyra/engine/timer_eob.cpp new file mode 100644 index 0000000000..8cac8d8abc --- /dev/null +++ b/engines/kyra/engine/timer_eob.cpp @@ -0,0 +1,361 @@ +/* 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 "kyra/engine/eobcommon.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +#ifdef ENABLE_EOB + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem<int, void, EoBCoreEngine>(this, &EoBCoreEngine::x) + +void EoBCoreEngine::setupTimers() { + _timer->addTimer(0, TimerV2(timerProcessCharacterExchange), 9, false); + _timer->addTimer(1, TimerV2(timerProcessFlyingObjects), 3, true); + _timer->addTimer(0x20, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x21, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x22, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x23, TimerV2(timerProcessMonsters), 20, true); + _timer->setNextRun(0x20, _system->getMillis()); + _timer->setNextRun(0x21, _system->getMillis() + 7 * _tickLength); + _timer->setNextRun(0x22, _system->getMillis() + 14 * _tickLength); + _timer->setNextRun(0x23, _system->getMillis() + 14 * _tickLength); + _timer->addTimer(0x30, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x31, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x32, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x33, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x34, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x35, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(4, TimerV2(timerProcessDoors), 5, true); + _timer->addTimer(5, TimerV2(timerUpdateTeleporters), 10, true); + _timer->addTimer(6, TimerV2(timerUpdateFoodStatus), 1080, true); + _timer->addTimer(7, TimerV2(timerUpdateMonsterIdleAnim), 25, true); + _timer->resetNextRun(); +} + +void EoBCoreEngine::setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer) { + uint32 ntime = _system->getMillis() + countdown * _tickLength; + uint8 timerId = 0x30 | (charIndex & 0x0F); + EoBCharacter *c = &_characters[charIndex]; + + if (!_timer->isEnabled(timerId)) { + c->timers[0] = ntime; + c->events[0] = evnt; + _timer->setCountdown(timerId, countdown); + enableTimer(timerId); + return; + } + + if (ntime < _timer->getNextRun(timerId)) + _timer->setNextRun(timerId, ntime); + _timer->resetNextRun(); + + if (updateExistingTimer) { + bool updated = false; + int d = -1; + + for (int i = 0; i < 10 && updated == false; i++) { + if (d == -1 && !c->timers[i]) + d = i; + + if (!updated && c->events[i] == evnt) { + d = i; + updated = true; + } + } + + assert(d != -1); + + c->timers[d] = ntime; + c->events[d] = evnt; + } else { + for (int i = 0; i < 10; i++) { + if (c->timers[i]) + continue; + c->timers[i] = ntime; + c->events[i] = evnt; + return; + } + } +} + +void EoBCoreEngine::deleteCharEventTimer(int charIndex, int evnt) { + EoBCharacter *c = &_characters[charIndex]; + for (int i = 0; i < 10; i++) { + if (c->events[i] == evnt) { + c->events[i] = 0; + c->timers[i] = 0; + } + } + setupCharacterTimers(); +} + +void EoBCoreEngine::setupCharacterTimers() { + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_characters[i]; + if (!testCharacter(i, 1)) + continue; + + uint32 nextTimer = 0xFFFFFFFF; + + for (int ii = 0; ii < 10; ii++) { + if (c->timers[ii] && c->timers[ii] < nextTimer) + nextTimer = c->timers[ii]; + } + uint32 ctime = _system->getMillis(); + + if (nextTimer == 0xFFFFFFFF) + _timer->disable(0x30 | i); + else { + enableTimer(0x30 | i); + _timer->setCountdown(0x30 | i, (nextTimer - ctime) / _tickLength); + } + } + _timer->resetNextRun(); +} + +void EoBCoreEngine::advanceTimers(uint32 millis) { + uint32 ct = _system->getMillis(); + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_characters[i]; + for (int ii = 0; ii < 10; ii++) { + if (c->timers[ii] > ct) { + uint32 chrt = c->timers[ii] - ct; + c->timers[ii] = chrt > millis ? ct + chrt - millis : ct; + } + } + } + + setupCharacterTimers(); + + if (_scriptTimersMode & 1) { + for (int i = 0; i < _scriptTimersCount; i++) { + if (_scriptTimers[i].next > ct) { + uint32 chrt = _scriptTimers[i].next - ct; + _scriptTimers[i].next = chrt > millis ? ct + chrt - millis : ct; + } + } + } + + for (int i = 0; i < 5; i++) { + if (_wallsOfForce[i].duration > ct) { + uint32 chrt = _wallsOfForce[i].duration - ct; + _wallsOfForce[i].duration = chrt > millis ? ct + chrt - millis : ct; + } + } +} + +void EoBCoreEngine::timerProcessCharacterExchange(int timerNum) { + _charExchangeSwap ^= 1; + if (_charExchangeSwap) { + int index = _exchangeCharacterId; + _exchangeCharacterId = -1; + gui_drawCharPortraitWithStats(index); + _exchangeCharacterId = index; + } else { + gui_drawCharPortraitWithStats(_exchangeCharacterId); + } +} + +void EoBCoreEngine::timerProcessFlyingObjects(int timerNum) { + static const uint8 dirPosIndex[] = { 0x82, 0x83, 0x00, 0x01, 0x01, 0x80, 0x03, 0x82, 0x02, 0x03, 0x80, 0x81, 0x81, 0x00, 0x83, 0x02 }; + for (int i = 0; i < 10; i++) { + EoBFlyingObject *fo = &_flyingObjects[i]; + if (!fo->enable) + continue; + + bool endFlight = fo->distance == 0; + + uint8 pos = dirPosIndex[(fo->direction << 2) + (fo->curPos & 3)]; + uint16 bl = fo->curBlock; + bool newBl = (pos & 0x80) ? true : false; + + if (newBl) { + bl = calcNewBlockPosition(fo->curBlock, fo->direction); + pos &= 3; + fo->starting = 0; + } + + if (updateObjectFlight(fo, bl, pos)) { + if (newBl) + runLevelScript(bl, 0x10); + if (updateFlyingObjectHitTest(fo, bl, pos)) + endFlight = true; + } else { + if (fo->flags & 0x20) { + if (!updateFlyingObjectHitTest(fo, fo->curBlock, fo->curPos)) + explodeObject(fo, fo->curBlock, fo->item); + } + endFlight = true; + } + + if (endFlight) + endObjectFlight(fo); + + _sceneUpdateRequired = true; + } +} + +void EoBCoreEngine::timerProcessMonsters(int timerNum) { + updateMonsters(timerNum & 0x0F); +} + +void EoBCoreEngine::timerSpecialCharacterUpdate(int timerNum) { + int charIndex = timerNum & 0x0F; + EoBCharacter *c = &_characters[charIndex]; + uint32 ctime = _system->getMillis(); + + for (int i = 0; i < 10; i++) { + if (!c->timers[i]) + continue; + if (c->timers[i] > ctime) + continue; + + c->timers[i] = 0; + int evt = c->events[i]; + + if (evt < 0) { + removeCharacterEffect(-evt, charIndex, 1); + continue; + } + + int od = _screen->curDimIndex(); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + + switch (evt) { + case 2: + case 3: + setCharEventTimer(charIndex, (c->effectFlags & 0x10000) ? 9 : 36, evt + 2, 1); + // fall through + case 0: + case 1: + case 4: + case 5: + setWeaponSlotStatus(charIndex, evt / 2, evt & 1); + break; + + case 6: + c->damageTaken = 0; + gui_drawCharPortraitWithStats(charIndex); + break; + + case 7: + _txt->printMessage(_characterStatusStrings7[0], -1, c->name); + c->strengthCur = c->strengthMax; + c->strengthExtCur = c->strengthExtMax; + if (_currentControlMode == 2) + gui_drawCharPortraitWithStats(charIndex); + break; + + case 8: + if (c->flags & 2) { + calcAndInflictCharacterDamage(charIndex, 0, 0, 5, 0x400, 5, 3); + setCharEventTimer(charIndex, 546, 8, 1); + } else { + c->flags &= ~2; + gui_drawCharPortraitWithStats(charIndex); + } + break; + + case 9: + if (c->flags & 4) { + _txt->printMessage(_characterStatusStrings9[0], -1, c->name); + c->flags &= ~4; + gui_drawCharPortraitWithStats(charIndex); + } + break; + + case 11: + if (c->disabledSlots & 4) { + c->disabledSlots &= ~4; + if (_openBookChar == charIndex && _updateFlags) + gui_drawSpellbook(); + } + break; + + case 12: + c->effectFlags &= ~0x1000; + if (_characterStatusStrings12) + _txt->printMessage(_characterStatusStrings12[0], -1, c->name); + break; + + default: + break; + } + + _screen->setScreenDim(od); + _screen->setFont(of); + } + + uint32 nextTimer = 0xFFFFFFFF; + for (int i = 0; i < 10; i++) { + if (c->timers[i] && c->timers[i] < nextTimer) + nextTimer = c->timers[i]; + } + + if (nextTimer == 0xFFFFFFFF) + _timer->disable(timerNum); + else + _timer->setCountdown(timerNum, (nextTimer - ctime) / _tickLength); +} + +void EoBCoreEngine::timerUpdateTeleporters(int timerNum) { + _teleporterPulse ^= 1; + for (int i = 0; i < 18; i++) { + uint8 w = _visibleBlocks[i]->walls[_sceneDrawVarDown]; + if ((w == _teleporterWallId) || (_flags.gameID == GI_EOB2 && w == 74)) { + _sceneUpdateRequired = true; + return; + } + } +} + +void EoBCoreEngine::timerUpdateFoodStatus(int timerNum) { + for (int i = 0; i < 6; i++) { + // Ring of Sustenance check + if (checkInventoryForRings(i, 2)) + continue; + EoBCharacter *c = &_characters[i]; + if (c->food != 0 && c->flags & 1 && c->hitPointsCur > -10) { + c->food--; + gui_drawFoodStatusGraph(i); + } + } +} + +void EoBCoreEngine::timerUpdateMonsterIdleAnim(int timerNum) { + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->mode == 7 || m->mode == 10 || (m->flags & 0x20) || (rollDice(1, 2, 0) != 1)) + continue; + m->idleAnimState = (rollDice(1, 2, 0) << 4) | rollDice(1, 2, 0); + checkSceneUpdateNeed(m->block); + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/timer_hof.cpp b/engines/kyra/engine/timer_hof.cpp new file mode 100644 index 0000000000..1973e2e593 --- /dev/null +++ b/engines/kyra/engine/timer_hof.cpp @@ -0,0 +1,110 @@ +/* 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 "kyra/engine/kyra_hof.h" +#include "kyra/engine/timer.h" + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem<int, void, KyraEngine_HoF>(this, &KyraEngine_HoF::x) + +void KyraEngine_HoF::setupTimers() { + _timer->addTimer(0, 0, 5, 1); + _timer->addTimer(1, TimerV2(timerFadeOutMessage), -1, 1); + _timer->addTimer(2, TimerV2(timerCauldronAnimation), 1, 1); + _timer->addTimer(3, TimerV2(timerFunc4), 1, 0); + _timer->addTimer(4, TimerV2(timerFunc5), 1, 0); + _timer->addTimer(5, TimerV2(timerBurnZanthia), 1, 0); +} + +void KyraEngine_HoF::timerFadeOutMessage(int arg) { + if (_shownMessage) + _fadeMessagePalette = 1; +} + +void KyraEngine_HoF::timerCauldronAnimation(int arg) { + int animation = -1; + + // HACK: We don't allow inventory animations while the inventory is backed off, which means not shown usually. + // This prevents for example that the cauldron animation is shown in the meanwhile scene with Marco and the Hand in Chapter 2. + if (_inventorySaved) + return; + + if (queryGameFlag(2) && _mainCharacter.sceneId != 34 && _mainCharacter.sceneId != 73 && !_invWsa.wsa && !_invWsa.running) { + if (animation == -1) + animation = _rnd.getRandomNumberRng(1, 6); + + char filename[13]; + strcpy(filename, "CAULD00.WSA"); + filename[5] = (animation / 10) + '0'; + filename[6] = (animation % 10) + '0'; + loadInvWsa(filename, 0, 8, 0, -1, -1, 1); + } +} + +void KyraEngine_HoF::timerFunc4(int arg) { + _timer->disable(3); + setGameFlag(0xD8); +} + +void KyraEngine_HoF::timerFunc5(int arg) { + _timer->disable(4); + _screen->hideMouse(); + _specialSceneScriptState[5] = 1; + for (int i = 68; i <= 75; ++i) { + updateSceneAnim(4, i); + delay(6); + } + _deathHandler = 4; +} + +void KyraEngine_HoF::timerBurnZanthia(int arg) { + _timer->disable(5); + _screen->hideMouse(); + snd_playSoundEffect(0x2D); + runAnimationScript("_ZANBURN.EMC", 0, 1, 1, 0); + _deathHandler = 7; + snd_playWanderScoreViaMap(0x53, 1); +} + +void KyraEngine_HoF::setTimer1DelaySecs(int secs) { + if (secs == -1) + secs = 32000; + + _timer->setCountdown(1, secs * 60); +} + +void KyraEngine_HoF::setWalkspeed(uint8 newSpeed) { + if (!_timer) + return; + + if (newSpeed < 5) + newSpeed = 3; + else + newSpeed = 5; + + _configWalkspeed = newSpeed; + _timer->setDelay(0, newSpeed); +} + + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_lok.cpp b/engines/kyra/engine/timer_lok.cpp new file mode 100644 index 0000000000..47f8d0c80b --- /dev/null +++ b/engines/kyra/engine/timer_lok.cpp @@ -0,0 +1,192 @@ +/* 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 "kyra/engine/kyra_lok.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/timer.h" + +namespace Kyra { + +#define TimerV1(x) new Common::Functor1Mem<int, void, KyraEngine_LoK>(this, &KyraEngine_LoK::x) + +void KyraEngine_LoK::setupTimers() { + for (int i = 0; i <= 4; ++i) + _timer->addTimer(i, 0, -1, 1); + + _timer->addTimer(5, 0, 5, 1); + _timer->addTimer(6, 0, 7, 1); + _timer->addTimer(7, 0, 8, 1); + _timer->addTimer(8, 0, 9, 1); + _timer->addTimer(9, 0, 7, 1); + + for (int i = 10; i <= 13; ++i) + _timer->addTimer(i, 0, 420, 1); + + _timer->addTimer(14, TimerV1(timerAsWillowispTimeout), 600, 1); + _timer->addTimer(15, TimerV1(timerUpdateHeadAnims), 11, 1); + _timer->addTimer(16, TimerV1(timerTulipCreator), 7200, 1); + _timer->addTimer(17, TimerV1(timerRubyCreator), 7200, 1); + _timer->addTimer(18, TimerV1(timerAsInvisibleTimeout), 600, 1); + _timer->addTimer(19, TimerV1(timerRedrawAmulet), 600, 1); + + _timer->addTimer(20, 0, 7200, 1); + _timer->addTimer(21, TimerV1(timerLavenderRoseCreator), 18000, 1); + _timer->addTimer(22, 0, 7200, 1); + + _timer->addTimer(23, 0, 10800, 1); + _timer->addTimer(24, TimerV1(timerAcornCreator), 10800, 1); + _timer->addTimer(25, 0, 10800, 1); + _timer->addTimer(26, TimerV1(timerBlueberryCreator), 10800, 1); + _timer->addTimer(27, 0, 10800, 1); + + _timer->addTimer(28, 0, 21600, 1); + _timer->addTimer(29, 0, 7200, 1); + _timer->addTimer(30, 0, 10800, 1); + + _timer->addTimer(31, TimerV1(timerFadeText), -1, 1); + _timer->addTimer(32, TimerV1(timerWillowispFrameTimer), 9, 1); + _timer->addTimer(33, TimerV1(timerInvisibleFrameTimer), 3, 1); +} + +void KyraEngine_LoK::timerUpdateHeadAnims(int timerNum) { + static const int8 frameTable[] = { + 4, 5, 4, 5, 4, 5, 0, 1, + 4, 5, 4, 4, 6, 4, 8, 1, + 9, 4, -1 + }; + + if (_talkingCharNum < 0) + return; + + _currHeadShape = frameTable[_currentHeadFrameTableIndex]; + ++_currentHeadFrameTableIndex; + + if (frameTable[_currentHeadFrameTableIndex] == -1) + _currentHeadFrameTableIndex = 0; + + _animator->animRefreshNPC(0); + _animator->animRefreshNPC(_talkingCharNum); +} + +void KyraEngine_LoK::timerTulipCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x1C) + return; + + setItemCreationFlags(17, 3); +} + +void KyraEngine_LoK::timerRubyCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x23) + return; + + setItemCreationFlags(22, 4); +} + +void KyraEngine_LoK::timerLavenderRoseCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x06) + return; + + setItemCreationFlags(0, 4); +} + +void KyraEngine_LoK::timerAcornCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x1F) + return; + + setItemCreationFlags(72, 5); +} + +void KyraEngine_LoK::timerBlueberryCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x28) + return; + + setItemCreationFlags(26, 7); +} + +void KyraEngine_LoK::setItemCreationFlags(int offset, int count) { + int rndNr = _rnd.getRandomNumberRng(0, count); + + for (int i = 0; i <= count; i++) { + if (!queryGameFlag(rndNr + offset)) { + setGameFlag(rndNr + offset); + break; + } else { + rndNr++; + if (rndNr > count) + rndNr = 0; + } + } +} + +void KyraEngine_LoK::timerFadeText(int timerNum) { + _fadeText = true; +} + +void KyraEngine_LoK::timerWillowispFrameTimer(int timerNum) { + if (_brandonStatusBit & 2) + _brandonStatusBit0x02Flag = 1; +} + +void KyraEngine_LoK::timerInvisibleFrameTimer(int timerNum) { + if (_brandonStatusBit & 0x20) + _brandonStatusBit0x20Flag = 1; +} + +void KyraEngine_LoK::setTextFadeTimerCountdown(int16 countdown) { + if (countdown == -1) + countdown = 32000; + + _timer->setCountdown(31, countdown * 60); +} + +void KyraEngine_LoK::timerAsInvisibleTimeout(int timerNum) { + if (_brandonStatusBit & 0x20) { + checkAmuletAnimFlags(); + _timer->setCountdown(18, -1); + } +} + +void KyraEngine_LoK::timerAsWillowispTimeout(int timerNum) { + if (_brandonStatusBit & 0x2) { + checkAmuletAnimFlags(); + _timer->setCountdown(14, -1); + } +} + +void KyraEngine_LoK::timerRedrawAmulet(int timerNum) { + if (queryGameFlag(0xF1)) { + drawAmulet(); + _timer->setCountdown(19, -1); + } +} + +void KyraEngine_LoK::setWalkspeed(uint8 newSpeed) { + if (!_timer) + return; + + static const uint8 speeds[] = { 11, 9, 6, 5, 3 }; + + assert(newSpeed < ARRAYSIZE(speeds)); + _timer->setDelay(5, speeds[newSpeed]); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_lol.cpp b/engines/kyra/engine/timer_lol.cpp new file mode 100644 index 0000000000..8ece68afa4 --- /dev/null +++ b/engines/kyra/engine/timer_lol.cpp @@ -0,0 +1,206 @@ +/* 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. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem<int, void, LoLEngine>(this, &LoLEngine::x) + +void LoLEngine::setupTimers() { + _timer->addTimer(0, TimerV2(timerProcessDoors), 15, true); + _timer->addTimer(0x10, TimerV2(timerProcessMonsters), 6, true); + _timer->addTimer(0x11, TimerV2(timerProcessMonsters), 6, true); + _timer->setNextRun(0x11, _system->getMillis() + 3 * _tickLength); + _timer->addTimer(3, TimerV2(timerSpecialCharacterUpdate), 15, true); + _timer->addTimer(4, TimerV2(timerProcessFlyingObjects), 1, true); + _timer->addTimer(0x50, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(0x51, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(0x52, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(8, TimerV2(timerRegeneratePoints), 1200, true); + _timer->addTimer(9, TimerV2(timerUpdatePortraitAnimations), 10, true); + _timer->addTimer(10, TimerV2(timerUpdateLampState), 360, true); + _timer->addTimer(11, TimerV2(timerFadeMessageText), 360, false); + _timer->resetNextRun(); +} + +void LoLEngine::timerProcessMonsters(int timerNum) { + for (int i = timerNum & 0x0F; i < 30; i += 2) + updateMonster(&_monsters[i]); +} + +void LoLEngine::timerSpecialCharacterUpdate(int timerNum) { + int eventsLeft = 0; + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + for (int ii = 0; ii < 5; ii++) { + if (!(_characters[i].characterUpdateEvents[ii])) + continue; + + if (--_characters[i].characterUpdateDelay[ii] > 0) { + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + continue; + } + + switch (_characters[i].characterUpdateEvents[ii] - 1) { + case 0: + if (_characters[i].weaponHit) { + _characters[i].weaponHit = 0; + _characters[i].characterUpdateDelay[ii] = calcMonsterSkillLevel(i, 6); + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + } else { + _characters[i].flags &= 0xFFFB; + } + + gui_drawCharPortraitWithStats(i); + break; + + case 1: + _characters[i].damageSuffered = 0; + gui_drawCharPortraitWithStats(i); + break; + + case 2: + _characters[i].flags &= 0xFFBF; + gui_drawCharPortraitWithStats(i); + break; + + case 3: + eventsLeft = rollDice(1, 2); + if (inflictDamage(i, eventsLeft, 0x8000, 0, 0x80)) { + _txt->printMessage(2, getLangString(0x4022), _characters[i].name); + _characters[i].characterUpdateDelay[ii] = 10; + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + } + break; + + case 4: + _characters[i].flags &= 0xFEFF; + _txt->printMessage(0, getLangString(0x4027), _characters[i].name); + gui_drawCharPortraitWithStats(i); + break; + + case 5: + setTemporaryFaceFrame(i, 0, 0, 1); + break; + + case 6: + _characters[i].flags &= 0xEFFF; + gui_drawCharPortraitWithStats(i); + break; + + case 7: + restoreSwampPalette(); + break; + + default: + break; + } + + if (_characters[i].characterUpdateDelay[ii] <= 0) + _characters[i].characterUpdateEvents[ii] = 0; + } + } + + if (eventsLeft) + _timer->enable(3); + else + _timer->disable(3); +} + +void LoLEngine::timerProcessFlyingObjects(int timerNum) { + for (int i = 0; i < 8; i++) { + if (!_flyingObjects[i].enable) + continue; + updateFlyingObject(&_flyingObjects[i]); + } +} + +void LoLEngine::timerRunSceneAnimScript(int timerNum) { + runLevelScript(0x401 + (timerNum & 0x0F), -1); +} + +void LoLEngine::timerRegeneratePoints(int timerNum) { + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + // check for Duble ring + int hInc = (_characters[i].flags & 8) ? 0 : (itemEquipped(i, 228) ? 4 : 1); + // check for Talba ring + int mInc = _drainMagic ? ((_characters[i].magicPointsMax >> 5) * -1) : + ((_characters[i].flags & 8) ? 0 : (itemEquipped(i, 227) ? (_characters[i].magicPointsMax / 10) : 1)); + + _characters[i].magicPointsCur = CLIP<int16>(_characters[i].magicPointsCur + mInc, 0, _characters[i].magicPointsMax); + + if (!(_characters[i].flags & 0x80)) + increaseCharacterHitpoints(i, hInc, false); + + gui_drawCharPortraitWithStats(i); + } +} + +void LoLEngine::timerUpdatePortraitAnimations(int skipUpdate) { + if (skipUpdate != 1) + skipUpdate = 0; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || (_characters[i].flags & 8) || (_characters[i].curFaceFrame > 1)) + continue; + + if (_characters[i].curFaceFrame != 1) { + if (--_characters[i].nextAnimUpdateCountdown <= 0 && !skipUpdate) { + _characters[i].curFaceFrame = 1; + gui_drawCharPortraitWithStats(i); + _timer->setCountdown(9, 10); + } + } else { + _characters[i].curFaceFrame = 0; + gui_drawCharPortraitWithStats(i); + _characters[i].nextAnimUpdateCountdown = rollDice(1, 12) + 6; + } + } +} + +void LoLEngine::timerUpdateLampState(int timerNum) { + if ((_flagsTable[31] & 0x08) && (_flagsTable[31] & 0x04) && _brightness && _lampOilStatus) + _lampOilStatus--; +} + +void LoLEngine::timerFadeMessageText(int timerNum) { + _timer->disable(timerNum); + initTextFading(0, 0); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/timer_mr.cpp b/engines/kyra/engine/timer_mr.cpp new file mode 100644 index 0000000000..544e36afa9 --- /dev/null +++ b/engines/kyra/engine/timer_mr.cpp @@ -0,0 +1,101 @@ +/* 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 "kyra/engine/kyra_mr.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +#define TimerV3(x) new Common::Functor1Mem<int, void, KyraEngine_MR>(this, &KyraEngine_MR::x) + +void KyraEngine_MR::setupTimers() { + _timer->addTimer(0, TimerV3(timerRestoreCommandLine), -1, 1); + for (int i = 1; i <= 3; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), -1, 0); + _timer->addTimer(4, TimerV3(timerFleaDeath), -1, 0); + for (int i = 5; i <= 11; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), -1, 0); + for (int i = 12; i <= 13; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), 0, 0); +} + +void KyraEngine_MR::timerRestoreCommandLine(int arg) { + if (_shownMessage) + _restoreCommandLine = true; +} + +void KyraEngine_MR::timerRunSceneScript7(int arg) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + _emc->start(&_sceneScriptState, 7); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_MR::timerFleaDeath(int arg) { + _timer->setCountdown(4, 5400); + saveGameStateIntern(999, "Autosave", 0); + _screen->hideMouse(); + _timer->disable(4); + runAnimationScript("FLEADTH1.EMC", 0, 0, 1, 1); + runAnimationScript("FLEADTH2.EMC", 0, 0, 1, 0); + showBadConscience(); + delay(60, true); + const char *str1 = (const char *)getTableEntry(_cCodeFile, 130); + const char *str2 = (const char *)getTableEntry(_cCodeFile, 131); + if (str1 && str2) { + badConscienceChat(str1, 204, 130); + badConscienceChat(str2, 204, 131); + } + delay(60, true); + hideBadConscience(); + runAnimationScript("FLEADTH3.EMC", 0, 0, 0, 1); + _deathHandler = 9; + _screen->showMouse(); +} + +void KyraEngine_MR::setWalkspeed(uint8 speed) { + if (speed < 5) + speed = 3; + else + speed = 5; + + _mainCharacter.walkspeed = speed; +} + +void KyraEngine_MR::setCommandLineRestoreTimer(int secs) { + if (secs == -1) + secs = 32000; + _timer->setCountdown(0, secs*60); +} + +void KyraEngine_MR::setNextIdleAnimTimer() { + _nextIdleAnim = _system->getMillis() + _rnd.getRandomNumberRng(10, 15) * 1000; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_rpg.cpp b/engines/kyra/engine/timer_rpg.cpp new file mode 100644 index 0000000000..572829eb64 --- /dev/null +++ b/engines/kyra/engine/timer_rpg.cpp @@ -0,0 +1,90 @@ +/* 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. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraRpgEngine::enableSysTimer(int sysTimer) { + if (sysTimer != 2) + return; + + for (int i = 0; i < getNumClock2Timers(); i++) + _timer->pauseSingleTimer(getClock2Timer(i), false); +} + +void KyraRpgEngine::disableSysTimer(int sysTimer) { + if (sysTimer != 2) + return; + + for (int i = 0; i < getNumClock2Timers(); i++) + _timer->pauseSingleTimer(getClock2Timer(i), true); +} + +void KyraRpgEngine::enableTimer(int id) { + _timer->enable(id); + _timer->setCountdown(id, _timer->getDelay(id)); +} + +void KyraRpgEngine::timerProcessDoors(int timerNum) { + for (int i = 0; i < 3; i++) { + uint16 b = _openDoorState[i].block; + if (!b) + continue; + + int v = _openDoorState[i].state; + int c = _openDoorState[i].wall; + + _levelBlockProperties[b].walls[c] += v; + _levelBlockProperties[b].walls[c ^ 2] += v; + + int snd = 3; + int flg = _wllWallFlags[_levelBlockProperties[b].walls[c]]; + if (flg & 0x20) + snd = 5; + else if (v == -1) + snd = 4; + + if (_flags.gameID == GI_LOL) { + if (!(_updateFlags & 1)) { + snd_processEnvironmentalSoundEffect(snd + 28, b); + if (!checkSceneUpdateNeed(b)) + updateEnvironmentalSfx(0); + } + } else { + checkSceneUpdateNeed(b); + updateEnvironmentalSfx(snd); + } + + if (flg & 0x30) + _openDoorState[i].block = 0; + } +} + +} // namespace Kyra + +#endif diff --git a/engines/kyra/engine/util.cpp b/engines/kyra/engine/util.cpp new file mode 100644 index 0000000000..ae5b833858 --- /dev/null +++ b/engines/kyra/engine/util.cpp @@ -0,0 +1,148 @@ +/* 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 "kyra/engine/util.h" + +namespace Kyra { + +int Util::decodeString1(const char *src, char *dst) { + static const uint8 decodeTable1[] = { + 0x20, 0x65, 0x74, 0x61, 0x69, 0x6E, 0x6F, 0x73, 0x72, 0x6C, 0x68, + 0x63, 0x64, 0x75, 0x70, 0x6D + }; + + static const uint8 decodeTable2[] = { + 0x74, 0x61, 0x73, 0x69, 0x6F, 0x20, 0x77, 0x62, 0x20, 0x72, 0x6E, + 0x73, 0x64, 0x61, 0x6C, 0x6D, 0x68, 0x20, 0x69, 0x65, 0x6F, 0x72, + 0x61, 0x73, 0x6E, 0x72, 0x74, 0x6C, 0x63, 0x20, 0x73, 0x79, 0x6E, + 0x73, 0x74, 0x63, 0x6C, 0x6F, 0x65, 0x72, 0x20, 0x64, 0x74, 0x67, + 0x65, 0x73, 0x69, 0x6F, 0x6E, 0x72, 0x20, 0x75, 0x66, 0x6D, 0x73, + 0x77, 0x20, 0x74, 0x65, 0x70, 0x2E, 0x69, 0x63, 0x61, 0x65, 0x20, + 0x6F, 0x69, 0x61, 0x64, 0x75, 0x72, 0x20, 0x6C, 0x61, 0x65, 0x69, + 0x79, 0x6F, 0x64, 0x65, 0x69, 0x61, 0x20, 0x6F, 0x74, 0x72, 0x75, + 0x65, 0x74, 0x6F, 0x61, 0x6B, 0x68, 0x6C, 0x72, 0x20, 0x65, 0x69, + 0x75, 0x2C, 0x2E, 0x6F, 0x61, 0x6E, 0x73, 0x72, 0x63, 0x74, 0x6C, + 0x61, 0x69, 0x6C, 0x65, 0x6F, 0x69, 0x72, 0x61, 0x74, 0x70, 0x65, + 0x61, 0x6F, 0x69, 0x70, 0x20, 0x62, 0x6D + }; + + int size = 0; + uint cChar = 0; + while ((cChar = *src++) != 0) { + if (cChar & 0x80) { + cChar &= 0x7F; + int index = (cChar & 0x78) >> 3; + *dst++ = decodeTable1[index]; + ++size; + assert(cChar < sizeof(decodeTable2)); + cChar = decodeTable2[cChar]; + } + + *dst++ = cChar; + ++size; + } + + *dst++ = 0; + return size; +} + +void Util::decodeString2(const char *src, char *dst) { + if (!src || !dst) + return; + + char out = 0; + while ((out = *src) != 0) { + if (*src == 0x1B) { + ++src; + out = *src + 0x7F; + } + *dst++ = out; + ++src; + } + + *dst = 0; +} + +void Util::convertDOSToISO(char *str) { + uint8 *s = (uint8 *)str; + + for (; *s; ++s) { + if (*s >= 128) { + uint8 c = _charMapDOSToISO[*s - 128]; + + if (!c) + c = 0x20; + + *s = c; + } + } +} + +void Util::convertISOToDOS(char *str) { + while (*str) + convertISOToDOS(*str++); +} + +void Util::convertISOToDOS(char &c) { + uint8 code = (uint8)c; + if (code >= 128) { + code = _charMapISOToDOS[code - 128]; + if (!code) + code = 0x20; + } + + c = code; +} + +// CP850 to ISO-8859-1 (borrowed from engines/saga/font_map.cpp) +const uint8 Util::_charMapDOSToISO[128] = { + 199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, + 239, 238, 236, 196, 197, 201, 230, 198, 244, 246, 242, + 251, 249, 255, 214, 220, 248, 163, 216, 215, 0, 225, + 237, 243, 250, 241, 209, 170, 186, 191, 174, 172, 189, + 188, 161, 171, 187, 0, 0, 0, 0, 0, 193, 194, + 192, 169, 0, 0, 0, 0, 162, 165, 0, 0, 0, + 0, 0, 0, 0, 227, 195, 0, 0, 0, 0, 0, + 0, 0, 164, 240, 208, 202, 203, 200, 0, 205, 206, + 207, 0, 0, 0, 0, 166, 204, 0, 211, 223, 212, + 210, 245, 213, 181, 254, 222, 218, 219, 217, 253, 221, + 175, 180, 173, 177, 0, 190, 182, 167, 247, 184, 176, + 168, 183, 185, 179, 178, 0, 160 +}; + +// ISO-8859-1 to CP850 +const uint8 Util::_charMapISOToDOS[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 173, 189, 156, 207, 190, 221, 245, 249, 184, 166, 174, + 170, 240, 169, 238, 248, 241, 253, 252, 239, 230, 244, + 250, 247, 251, 167, 175, 172, 171, 243, 168, 183, 181, + 182, 199, 142, 143, 146, 128, 212, 144, 210, 211, 222, + 214, 215, 216, 209, 165, 227, 224, 226, 229, 153, 158, + 157, 235, 233, 234, 154, 237, 232, 225, 133, 160, 131, + 198, 132, 134, 145, 135, 138, 130, 136, 137, 141, 161, + 140, 139, 208, 164, 149, 162, 147, 228, 148, 246, 155, + 151, 163, 150, 129, 236, 231, 152 +}; + +} // End of namespace Kyra diff --git a/engines/kyra/engine/util.h b/engines/kyra/engine/util.h new file mode 100644 index 0000000000..130768f89d --- /dev/null +++ b/engines/kyra/engine/util.h @@ -0,0 +1,48 @@ +/* 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 KYRA_UTIL_H +#define KYRA_UTIL_H + +#include "common/scummsys.h" + +namespace Kyra { + +class Util { +public: + static int decodeString1(const char *src, char *dst); + static void decodeString2(const char *src, char *dst); + + // Since our current GUI font uses ISO-8859-1, this + // conversion functionallty uses that as a base. + static void convertDOSToISO(char *str); + static void convertISOToDOS(char *str); + static void convertISOToDOS(char &c); + +private: + static const uint8 _charMapDOSToISO[128]; + static const uint8 _charMapISOToDOS[128]; +}; + +} // End of namespace Kyra + +#endif |