aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/engine
diff options
context:
space:
mode:
authorathrxx2019-01-26 01:31:34 +0100
committerathrxx2019-03-06 20:48:15 +0100
commit1dfdcc7252ac83643cae7a7447c025da2af63843 (patch)
treeb6736d006bf67d5264dd171c336f0915695d1f88 /engines/kyra/engine
parent8b53d20b51771680c3d31aa02c0285b7a8be4e85 (diff)
downloadscummvm-rg350-1dfdcc7252ac83643cae7a7447c025da2af63843.tar.gz
scummvm-rg350-1dfdcc7252ac83643cae7a7447c025da2af63843.tar.bz2
scummvm-rg350-1dfdcc7252ac83643cae7a7447c025da2af63843.zip
KYRA: cleanup dir
Reorganize all files in sub directories. The file placement isn't as intuitive as it might be for other engines, which is probably the reason why this hasn't been done before.
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