aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engines/kyra/engine')
-rw-r--r--engines/kyra/engine/chargen.cpp1982
-rw-r--r--engines/kyra/engine/darkmoon.cpp493
-rw-r--r--engines/kyra/engine/darkmoon.h139
-rw-r--r--engines/kyra/engine/eob.cpp573
-rw-r--r--engines/kyra/engine/eob.h125
-rw-r--r--engines/kyra/engine/eobcommon.cpp2536
-rw-r--r--engines/kyra/engine/eobcommon.h1185
-rw-r--r--engines/kyra/engine/item.h41
-rw-r--r--engines/kyra/engine/items_eob.cpp732
-rw-r--r--engines/kyra/engine/items_hof.cpp424
-rw-r--r--engines/kyra/engine/items_lok.cpp960
-rw-r--r--engines/kyra/engine/items_lol.cpp594
-rw-r--r--engines/kyra/engine/items_mr.cpp536
-rw-r--r--engines/kyra/engine/items_v2.cpp100
-rw-r--r--engines/kyra/engine/kyra_hof.cpp1933
-rw-r--r--engines/kyra/engine/kyra_hof.h697
-rw-r--r--engines/kyra/engine/kyra_lok.cpp990
-rw-r--r--engines/kyra/engine/kyra_lok.h816
-rw-r--r--engines/kyra/engine/kyra_mr.cpp1426
-rw-r--r--engines/kyra/engine/kyra_mr.h670
-rw-r--r--engines/kyra/engine/kyra_rpg.cpp366
-rw-r--r--engines/kyra/engine/kyra_rpg.h388
-rw-r--r--engines/kyra/engine/kyra_v1.cpp698
-rw-r--r--engines/kyra/engine/kyra_v2.cpp244
-rw-r--r--engines/kyra/engine/kyra_v2.h400
-rw-r--r--engines/kyra/engine/lol.cpp4513
-rw-r--r--engines/kyra/engine/lol.h1345
-rw-r--r--engines/kyra/engine/magic_eob.cpp1381
-rw-r--r--engines/kyra/engine/scene_eob.cpp862
-rw-r--r--engines/kyra/engine/scene_hof.cpp737
-rw-r--r--engines/kyra/engine/scene_lok.cpp1301
-rw-r--r--engines/kyra/engine/scene_lol.cpp1577
-rw-r--r--engines/kyra/engine/scene_mr.cpp787
-rw-r--r--engines/kyra/engine/scene_rpg.cpp658
-rw-r--r--engines/kyra/engine/scene_v1.cpp360
-rw-r--r--engines/kyra/engine/scene_v2.cpp227
-rw-r--r--engines/kyra/engine/sprites.cpp575
-rw-r--r--engines/kyra/engine/sprites.h99
-rw-r--r--engines/kyra/engine/sprites_eob.cpp1285
-rw-r--r--engines/kyra/engine/sprites_lol.cpp1558
-rw-r--r--engines/kyra/engine/sprites_rpg.cpp46
-rw-r--r--engines/kyra/engine/timer.cpp304
-rw-r--r--engines/kyra/engine/timer.h106
-rw-r--r--engines/kyra/engine/timer_eob.cpp361
-rw-r--r--engines/kyra/engine/timer_hof.cpp110
-rw-r--r--engines/kyra/engine/timer_lok.cpp192
-rw-r--r--engines/kyra/engine/timer_lol.cpp206
-rw-r--r--engines/kyra/engine/timer_mr.cpp101
-rw-r--r--engines/kyra/engine/timer_rpg.cpp90
-rw-r--r--engines/kyra/engine/util.cpp148
-rw-r--r--engines/kyra/engine/util.h48
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