diff options
Diffstat (limited to 'engines/kyra/engine/lol.cpp')
-rw-r--r-- | engines/kyra/engine/lol.cpp | 4513 |
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 |