aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/engine/lol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/kyra/engine/lol.cpp')
-rw-r--r--engines/kyra/engine/lol.cpp4513
1 files changed, 4513 insertions, 0 deletions
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