/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "kyra/engine/kyra_lok.h"
#include "kyra/sequence/seqplayer_lok.h"
#include "kyra/resource/resource.h"
#include "kyra/engine/sprites.h"
#include "kyra/graphics/wsamovie.h"
#include "kyra/graphics/animator_lok.h"
#include "kyra/engine/timer.h"
#include "kyra/sound/sound.h"

#include "common/system.h"
#include "common/savefile.h"
#include "common/list.h"

namespace Kyra {

void KyraEngine_LoK::seq_demo() {
	snd_playTheme(0, 2);

	_screen->loadBitmap("START.CPS", 7, 7, &_screen->getPalette(0));
	_screen->copyRegion(0, 0, 0, 0, 320, 200, 6, 0, Screen::CR_NO_P_CHECK);
	_screen->updateScreen();
	_screen->fadeFromBlack();
	delay(60 * _tickLength);
	_screen->fadeToBlack();

	_screen->clearPage(0);
	_screen->loadBitmap("TOP.CPS", 7, 7, nullptr);
	_screen->loadBitmap("BOTTOM.CPS", 5, 5, &_screen->getPalette(0));
	_screen->copyRegion(0, 91, 0, 8, 320, 103, 6, 0);
	_screen->copyRegion(0, 0, 0, 111, 320, 64, 6, 0);
	_screen->updateScreen();
	_screen->fadeFromBlack();

	_seq->playSequence(_seq_WestwoodLogo, true);
	delay(60 * _tickLength);
	_seq->playSequence(_seq_KyrandiaLogo, true);

	_screen->fadeToBlack();
	_screen->clearPage(2);
	_screen->clearPage(0);

	_seq->playSequence(_seq_Demo1, true);

	_screen->clearPage(0);
	_seq->playSequence(_seq_Demo2, true);

	_screen->clearPage(0);
	_seq->playSequence(_seq_Demo3, true);

	_screen->clearPage(0);
	_seq->playSequence(_seq_Demo4, true);

	_screen->clearPage(0);
	_screen->loadBitmap("FINAL.CPS", 7, 7, &_screen->getPalette(0));
	_screen->_curPage = 0;
	_screen->copyRegion(0, 0, 0, 0, 320, 200, 6, 0);
	_screen->updateScreen();
	_screen->fadeFromBlack();
	delay(60 * _tickLength);
	_screen->fadeToBlack();
	_sound->haltTrack();
}

void KyraEngine_LoK::seq_intro() {
	if (_flags.isTalkie)
		_res->loadPakFile("INTRO.VRM");

	static const IntroProc introProcTable[] = {
		&KyraEngine_LoK::seq_introPublisherLogos,
		&KyraEngine_LoK::seq_introLogos,
		&KyraEngine_LoK::seq_introStory,
		&KyraEngine_LoK::seq_introMalcolmTree,
		&KyraEngine_LoK::seq_introKallakWriting,
		&KyraEngine_LoK::seq_introKallakMalcolm
	};

	Common::InSaveFile *in;
	if ((in = _saveFileMan->openForLoading(getSavegameFilename(0)))) {
		delete in;
		_skipIntroFlag = true;
	} else {
		_skipIntroFlag = !_flags.isDemo;
	}

	_seq->setCopyViewOffs(true);
	_screen->setFont(_defaultFont);
	if (_flags.platform == Common::kPlatformDOS)
		snd_playTheme(0, 2);
	_text->setTalkCoords(144);

	for (int i = 0; i < ARRAYSIZE(introProcTable) && !seq_skipSequence(); ++i) {
		if ((this->*introProcTable[i])() && !shouldQuit()) {
			resetSkipFlag();
			_screen->fadeToBlack();
			_screen->clearPage(0);
		}
	}

	_screen->setFont(_defaultFont);

	_text->setTalkCoords(136);
	delay(30 * _tickLength);
	_seq->setCopyViewOffs(false);
	_sound->haltTrack();
	_sound->voiceStop();

	if (_flags.isTalkie)
		_res->unloadPakFile("INTRO.VRM");
}

bool KyraEngine_LoK::seq_introPublisherLogos() {
	if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
		_screen->loadBitmap("LOGO.CPS", 3, 3, &_screen->getPalette(0));
		_screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0);
		_screen->updateScreen();
		_screen->fadeFromBlack();
		delay(90 * _tickLength);
		if (!_abortIntroFlag) {
			_screen->fadeToBlack();
			snd_playWanderScoreViaMap(_flags.platform == Common::kPlatformFMTowns ? 57 : 2, 0);
		}
	} else if (_flags.platform == Common::kPlatformMacintosh && _res->exists("MP_GOLD.CPS")) {
		_screen->loadPalette("MP_GOLD.COL", _screen->getPalette(0));
		_screen->loadBitmap("MP_GOLD.CPS", 3, 3, nullptr);
		_screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0);
		_screen->updateScreen();
		_screen->fadeFromBlack();
		delay(120 * _tickLength);
		if (!_abortIntroFlag)
			_screen->fadeToBlack();
	}

	return _abortIntroFlag;
}

bool KyraEngine_LoK::seq_introLogos() {
	_screen->clearPage(0);

	if (_flags.platform == Common::kPlatformAmiga) {
		_screen->loadPaletteTable("INTRO.PAL", 0);
		_screen->loadBitmap("BOTTOM.CPS", 3, 5, nullptr);
		_screen->loadBitmap("TOP.CPS", 3, 3, nullptr);
		_screen->copyRegion(0, 0, 0, 111, 320, 64, 2, 0);
		_screen->copyRegion(0, 91, 0, 8, 320, 109, 2, 0);
		_screen->copyRegion(0, 0, 0, 0, 320, 190, 0, 2);
	} else {
		_screen->loadBitmap("TOP.CPS", 7, 7, nullptr);
		_screen->loadBitmap("BOTTOM.CPS", 5, 5, &_screen->getPalette(0));
		_screen->copyRegion(0, 91, 0, 8, 320, 103, 6, 0);
		_screen->copyRegion(0, 0, 0, 111, 320, 64, 6, 0);
	}

	_screen->_curPage = 0;
	_screen->updateScreen();
	_screen->fadeFromBlack();

	if (_seq->playSequence(_seq_WestwoodLogo, skipFlag()) || shouldQuit())
		return true;

	delay(60 * _tickLength);

	if (_flags.platform == Common::kPlatformAmiga) {
		_screen->copyPalette(0, 1);
		_screen->setScreenPalette(_screen->getPalette(0));
	}

	Screen::FontId of = _screen->setFont(Screen::FID_8_FNT);

	if (_seq->playSequence(_seq_KyrandiaLogo, skipFlag()) || shouldQuit())
		return true;

	_screen->setFont(of);
	_screen->fillRect(0, 179, 319, 199, 0);

	if (shouldQuit())
		return false;

	if (_flags.platform == Common::kPlatformAmiga) {
		_screen->copyPalette(0, 2);
		_screen->fadeToBlack();
		_screen->copyRegion(0, 0, 0, 0, 320, 200, 4, 0);
		_screen->fadeFromBlack();
	} else {
		_screen->copyRegion(0, 91, 0, 8, 320, 104, 6, 2);
		_screen->copyRegion(0, 0, 0, 112, 320, 64, 6, 2);

		uint32 start = _system->getMillis();
		bool doneFlag = false;
		int oldDistance = 0;

		do {
			uint32 now = _system->getMillis();

			// The smallest y2 we ever draw the screen for is 65.
			int distance = (now - start) / (_tickLength << 1);
			if (distance > 112) {
				distance = 112;
				doneFlag = true;
			}

			if (distance > oldDistance) {
				int y1 = 8 + distance;
				int h1 = 168 - distance;
				int y2 = 176 - distance;
				int h2 = distance;

				_screen->copyRegion(0, y1, 0, 8, 320, h1, 2, 0);
				if (h2 > 0)
					_screen->copyRegion(0, 64, 0, y2, 320, h2, 4, 0);
				_screen->updateScreen();
			}

			oldDistance = distance;
			delay(8);
		} while (!doneFlag && !shouldQuit() && !_abortIntroFlag);
	}

	if (_abortIntroFlag || shouldQuit())
		return true;

	return _seq->playSequence(_seq_Forest, true);
}

bool KyraEngine_LoK::seq_introStory() {
	_screen->clearPage(3);
	_screen->clearPage(0);

	// HACK: The Italian fan translation uses an special text screen here
	// so we show it even when text is disabled
	if (!textEnabled() && speechEnabled() && _flags.lang != Common::IT_ITA)
		return false;

	bool success = false;
	static const char *pattern[] = { "", "_ENG", "_FRE", "_GER", "_SPA", "_ITA", "_HEB" };
	for (int i = 0; i < ARRAYSIZE(pattern) && !success; ++i) {
		Common::String tryFile = Common::String::format("TEXT%s.CPS", pattern[i]);
		if ((success = _res->exists(tryFile.c_str())))
			_screen->loadBitmap(tryFile.c_str(), 3, 3, &_screen->getPalette(0));
	}

	if (!success)
		warning("no story graphics file found");

	if (_flags.platform == Common::kPlatformAmiga)
		_screen->setScreenPalette(_screen->getPalette(4));
	else
		_screen->setScreenPalette(_screen->getPalette(0));
	_screen->copyPage(3, 0);

	if (_flags.lang == Common::JA_JPN) {
		const int y1 = 175;
		int x1, x2, y2, col1;
		const char *s1, *s2;

		if (_flags.platform == Common::kPlatformFMTowns) {
			s1 = _seq_textsTable[18];
			s2 = _seq_textsTable[19];
			x1 = (Screen::SCREEN_W - _screen->getTextWidth(s1)) / 2;
			x2 = (Screen::SCREEN_W - _screen->getTextWidth(s2)) / 2;
			uint8 colorMap[] = { 0, 15, 12, 12 };
			_screen->setTextColor(colorMap, 0, 3);
			y2 = 184;
			col1 = 5;

		} else {
			s1 = _storyStrings[0];
			s2 = _storyStrings[1];
			x1 = x2 = 54;
			y2 = 185;
			col1 = 15;
		}

		_screen->printText(s1, x1, y1, col1, 8);
		_screen->printText(s2, x2, y2, col1, 8);
	}

	_screen->updateScreen();
	delay(360 * _tickLength);

	_sound->beginFadeOut();

	return _abortIntroFlag;
}

bool KyraEngine_LoK::seq_introMalcolmTree() {
	_screen->_curPage = 0;
	_screen->clearPage(3);
	return _seq->playSequence(_seq_MalcolmTree, true);
}

bool KyraEngine_LoK::seq_introKallakWriting() {
	_seq->makeHandShapes();
	_screen->setAnimBlockPtr(5060);
	_screen->_charSpacing = -2;
	_screen->clearPage(3);
	const bool skipped = _seq->playSequence(_seq_KallakWriting, true);
	_seq->freeHandShapes();

	return skipped;
}

bool KyraEngine_LoK::seq_introKallakMalcolm() {
	_screen->clearPage(3);
	return _seq->playSequence(_seq_KallakMalcolm, true);
}

void KyraEngine_LoK::seq_createAmuletJewel(int jewel, int page, int noSound, int drawOnly) {
	static const uint16 specialJewelTable[] = {
		0x167, 0x162, 0x15D, 0x158, 0x153, 0xFFFF
	};
	static const uint16 specialJewelTable1[] = {
		0x14F, 0x154, 0x159, 0x15E, 0x163, 0xFFFF
	};
	static const uint16 specialJewelTable2[] = {
		0x150, 0x155, 0x15A, 0x15F, 0x164, 0xFFFF
	};
	static const uint16 specialJewelTable3[] = {
		0x151, 0x156, 0x15B, 0x160, 0x165, 0xFFFF
	};
	static const uint16 specialJewelTable4[] = {
		0x152, 0x157, 0x15C, 0x161, 0x166, 0xFFFF
	};
	if (!noSound)
		snd_playSoundEffect(0x5F);
	_screen->hideMouse();
	if (!drawOnly) {
		for (int i = 0; specialJewelTable[i] != 0xFFFF; ++i) {
			_screen->drawShape(page, _shapes[specialJewelTable[i]], _amuletX2[jewel], _amuletY2[jewel], 0, 0);
			_screen->updateScreen();
			delayWithTicks(3);
		}

		const uint16 *opcodes = nullptr;
		switch (jewel - 1) {
		case 0:
			opcodes = specialJewelTable1;
			break;

		case 1:
			opcodes = specialJewelTable2;
			break;

		case 2:
			opcodes = specialJewelTable3;
			break;

		case 3:
			opcodes = specialJewelTable4;
			break;

		default:
			break;
		}

		if (opcodes) {
			for (int i = 0; opcodes[i] != 0xFFFF; ++i) {
				_screen->drawShape(page, _shapes[opcodes[i]], _amuletX2[jewel], _amuletY2[jewel], 0, 0);
				_screen->updateScreen();
				delayWithTicks(3);
			}
		}
	}
	_screen->drawShape(page, _shapes[323 + jewel], _amuletX2[jewel], _amuletY2[jewel], 0, 0);
	_screen->updateScreen();
	_screen->showMouse();
	setGameFlag(0x55 + jewel);
}

void KyraEngine_LoK::seq_brandonHealing() {
	if (!(_deathHandler & 8))
		return;
	if (_currentCharacter->sceneId == 210) {
		if (_beadStateVar == 4 || _beadStateVar == 6)
			return;
	}
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_healingShapeTable);
	setupShapes123(_healingShapeTable, 22, 0);
	_animator->setBrandonAnimSeqSize(3, 48);
	snd_playSoundEffect(0x53);
	for (int i = 123; i <= 144; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}
	for (int i = 125; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}
	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_brandonHealing2() {
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_healingShape2Table);
	setupShapes123(_healingShape2Table, 30, 0);
	resetBrandonPoisonFlags();
	_animator->setBrandonAnimSeqSize(3, 48);
	snd_playSoundEffect(0x50);
	for (int i = 123; i <= 152; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}
	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();
	assert(_poisonGone);
	characterSays(2010, _poisonGone[0], 0, -2);
	characterSays(2011, _poisonGone[1], 0, -2);
}

void KyraEngine_LoK::seq_poisonDeathNow(int now) {
	if (!(_brandonStatusBit & 1))
		return;
	++_poisonDeathCounter;
	if (now)
		_poisonDeathCounter = 2;
	if (_poisonDeathCounter >= 2) {
		snd_playWanderScoreViaMap(1, 1);
		assert(_thePoison);
		characterSays(7000, _thePoison[0], 0, -2);
		characterSays(7001, _thePoison[1], 0, -2);
		seq_poisonDeathNowAnim();
		_deathHandler = 3;
	} else {
		assert(_thePoison);
		characterSays(7002, _thePoison[2], 0, -2);
		characterSays(7004, _thePoison[3], 0, -2);
	}
}

void KyraEngine_LoK::seq_poisonDeathNowAnim() {
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_posionDeathShapeTable);
	setupShapes123(_posionDeathShapeTable, 20, 0);
	_animator->setBrandonAnimSeqSize(8, 48);

	_currentCharacter->currentAnimFrame = 124;
	_animator->animRefreshNPC(0);
	delayWithTicks(30);

	_currentCharacter->currentAnimFrame = 123;
	_animator->animRefreshNPC(0);
	delayWithTicks(30);

	for (int i = 125; i <= 139; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	delayWithTicks(60);

	for (int i = 140; i <= 142; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	delayWithTicks(60);

	_animator->resetBrandonAnimSeqSize();
	freeShapes123();
	_animator->restoreAllObjectBackgrounds();
	_currentCharacter->x1 = _currentCharacter->x2 = -1;
	_currentCharacter->y1 = _currentCharacter->y2 = -1;
	_animator->preserveAllBackgrounds();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_playFluteAnimation() {
	_screen->hideMouse();
	checkAmuletAnimFlags();
	setupShapes123(_fluteAnimShapeTable, 36, 0);
	_animator->setBrandonAnimSeqSize(3, 75);
	for (int i = 123; i <= 130; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(2);
	}

	int delayTime = 0, soundType = 0;
	if (queryGameFlag(0x85)) {
		snd_playSoundEffect(0x63);
		delayTime = 9;
		soundType = 3;
	} else if (!queryGameFlag(0x86)) {
		snd_playSoundEffect(0x61);
		delayTime = 2;
		soundType = 1;
		setGameFlag(0x86);
	} else {
		snd_playSoundEffect(0x62);
		delayTime = 2;
		soundType = 2;
	}

	for (int i = 131; i <= 158; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(delayTime);
	}

	for (int i = 126; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(delayTime);
	}
	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();

	if (soundType == 1) {
		assert(_fluteString);
		characterSays(1000, _fluteString[0], 0, -2);
	} else if (soundType == 2) {
		assert(_fluteString);
		characterSays(1001, _fluteString[1], 0, -2);
	}
}

void KyraEngine_LoK::seq_winterScroll1() {
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_winterScrollTable);
	assert(_winterScroll1Table);
	assert(_winterScroll2Table);
	setupShapes123(_winterScrollTable, 7, 0);
	_animator->setBrandonAnimSeqSize(5, 66);

	for (int i = 123; i <= 129; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	freeShapes123();
	snd_playSoundEffect(0x20);

	uint8 numFrames, midpoint;
	if (_flags.isTalkie) {
		numFrames = 18;
		midpoint = 136;
	} else {
		numFrames = 35;
		midpoint = 147;
	}
	setupShapes123(_winterScroll1Table, numFrames, 0);
	for (int i = 123; i < midpoint; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	if (_currentCharacter->sceneId == 41 && !queryGameFlag(0xA2)) {
		snd_playSoundEffect(0x20);
		_sprites->_anims[0].play = false;
		_animator->sprites()[0].active = 0;
		_sprites->_anims[1].play = true;
		_animator->sprites()[1].active = 1;

		if (_flags.platform != Common::kPlatformAmiga)
			setGameFlag(0xA2);
	}

	for (int i = midpoint; i < 123 + numFrames; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	if (_currentCharacter->sceneId == 117 && !queryGameFlag(0xB3)) {
		for (int i = 0; i <= 7; ++i) {
			_sprites->_anims[i].play = false;
			_animator->sprites()[i].active = 0;
		}

		if (_flags.platform == Common::kPlatformAmiga) {
			_screen->copyPalette(0, 11);
		} else {
			_screen->getPalette(0).copy(palTable2()[0], 0, 20, 228);
			_screen->fadePalette(_screen->getPalette(0), 72);
			_screen->setScreenPalette(_screen->getPalette(0));
			setGameFlag(0xB3);
		}
	} else {
		delayWithTicks(120);
	}

	freeShapes123();
	setupShapes123(_winterScroll2Table, 4, 0);

	for (int i = 123; i <= 126; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_winterScroll2() {
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_winterScrollTable);
	setupShapes123(_winterScrollTable, 7, 0);
	_animator->setBrandonAnimSeqSize(5, 66);

	for (int i = 123; i <= 128; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	delayWithTicks(120);

	for (int i = 127; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_makeBrandonInv() {
	if (_deathHandler == 8)
		return;

	if (_currentCharacter->sceneId == 210) {
		if (_beadStateVar == 4 || _beadStateVar == 6)
			return;
	}

	_screen->hideMouse();
	checkAmuletAnimFlags();
	_brandonStatusBit |= 0x20;
	_timer->setCountdown(18, 2700);
	_brandonStatusBit |= 0x40;
	snd_playSoundEffect(0x77);
	_brandonInvFlag = 0;
	while (_brandonInvFlag <= 0x100) {
		_animator->animRefreshNPC(0);
		delayWithTicks(10);
		_brandonInvFlag += 0x10;
	}
	_brandonStatusBit &= 0xFFBF;
	_screen->showMouse();
}

void KyraEngine_LoK::seq_makeBrandonNormal() {
	_screen->hideMouse();
	_brandonStatusBit |= 0x40;
	snd_playSoundEffect(0x77);
	_brandonInvFlag = 0x100;
	while (_brandonInvFlag >= 0) {
		_animator->animRefreshNPC(0);
		delayWithTicks(10);
		_brandonInvFlag -= 0x10;
	}
	_brandonInvFlag = 0;
	_brandonStatusBit &= 0xFF9F;
	_screen->showMouse();
}

void KyraEngine_LoK::seq_makeBrandonNormal2() {
	_screen->hideMouse();
	assert(_brandonToWispTable);
	setupShapes123(_brandonToWispTable, 26, 0);
	_animator->setBrandonAnimSeqSize(5, 48);
	_brandonStatusBit &= 0xFFFD;
	snd_playSoundEffect(0x6C);
	for (int i = 138; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}
	_animator->setBrandonAnimSeqSize(3, 48);
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);

	if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245)
		_screen->fadeSpecialPalette(31, 234, 13, 4);
	else if (_currentCharacter->sceneId >= 118 && _currentCharacter->sceneId <= 186)
		_screen->fadeSpecialPalette(14, 228, 15, 4);

	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_makeBrandonWisp() {
	if (_deathHandler == 8)
		return;

	if (_currentCharacter->sceneId == 210) {
		if (_beadStateVar == 4 || _beadStateVar == 6)
			return;
	}
	_screen->hideMouse();
	checkAmuletAnimFlags();
	assert(_brandonToWispTable);
	setupShapes123(_brandonToWispTable, 26, 0);
	_animator->setBrandonAnimSeqSize(5, 48);
	snd_playSoundEffect(0x6C);
	for (int i = 123; i <= 138; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}
	_brandonStatusBit |= 2;

	if (_currentCharacter->sceneId >= 109 && _currentCharacter->sceneId <= 198)
		_timer->setCountdown(14, 18000);
	else
		_timer->setCountdown(14, 7200);

	_animator->_brandonDrawFrame = 113;
	_brandonStatusBit0x02Flag = 1;
	_currentCharacter->currentAnimFrame = 113;
	_animator->animRefreshNPC(0);
	_animator->updateAllObjectShapes();

	if (_flags.platform == Common::kPlatformAmiga) {
		if ((_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245) ||
		        (_currentCharacter->sceneId >= 118 && _currentCharacter->sceneId <= 186))
			_screen->fadePalette(_screen->getPalette(10), 0x54);
	} else {
		if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245)
			_screen->fadeSpecialPalette(30, 234, 13, 4);
		else if (_currentCharacter->sceneId >= 118 && _currentCharacter->sceneId <= 186)
			_screen->fadeSpecialPalette(14, 228, 15, 4);
	}

	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_dispelMagicAnimation() {
	if (_deathHandler == 8)
		return;
	if (_currentCharacter->sceneId == 210) {
		if (_beadStateVar == 4 || _beadStateVar == 6)
			return;
	}
	_screen->hideMouse();
	// TODO
#if 0
	// FIXME: This condition is always false. Is this a typo or a bug in the original?
	if (_currentCharacter->sceneId == 210 && _currentCharacter->sceneId < 160) {
		_currentCharacter->facing = 3;
	}
#endif
	if (_malcolmFlag == 7 && _beadStateVar == 3) {
		_beadStateVar = 6;
		_unkEndSeqVar5 = 2;
		_malcolmFlag = 10;
	}
	checkAmuletAnimFlags();
	setGameFlag(0xEE);
	assert(_magicAnimationTable);
	setupShapes123(_magicAnimationTable, 5, 0);
	_animator->setBrandonAnimSeqSize(8, 49);
	snd_playSoundEffect(0x15);
	for (int i = 123; i <= 127; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	delayWithTicks(120);

	for (int i = 127; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(10);
	}
	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_fillFlaskWithWater(int item, int type) {
	int newItem = -1;

	static const uint8 flaskTable1[] = { 0x46, 0x48, 0x4A, 0x4C };
	static const uint8 flaskTable2[] = { 0x47, 0x49, 0x4B, 0x4D };

	if (item >= 60 && item <= 77) {
		assert(_flaskFull);
		characterSays(8006, _flaskFull[0], 0, -2);
	} else if (item == 78) {
		assert(type >= 0 && type < ARRAYSIZE(flaskTable1));
		newItem = flaskTable1[type];
	} else if (item == 79) {
		assert(type >= 0 && type < ARRAYSIZE(flaskTable2));
		newItem = flaskTable2[type];
	}

	if (newItem == -1)
		return;

	setMouseItem(newItem);
	_itemInHand = newItem;

	assert(_fullFlask);
	assert(type < _fullFlask_Size && type >= 0);

	static const uint16 voiceEntries[] = {
		0x1F40, 0x1F41, 0x1F42, 0x1F45
	};
	assert(type < ARRAYSIZE(voiceEntries));

	characterSays(voiceEntries[type], _fullFlask[type], 0, -2);
}

void KyraEngine_LoK::seq_playDrinkPotionAnim(int item, int makeFlaskEmpty, int flags) {
	if (_flags.platform == Common::kPlatformAmiga) {
		uint8 r, g, b;

		switch (item) {
		case 60: case 61:
			// 0xC22
			r = 50;
			g = 8;
			b = 8;
			break;

		case 62: case 63: case 76:
		case 77:
			// 0x00E
			r = 0;
			g = 0;
			b = 58;
			break;

		case 64: case 65:
			// 0xFF5
			r = 63;
			g = 63;
			b = 21;
			break;

		case 66:
			// 0x090
			r = 0;
			g = 37;
			b = 0;
			break;

		case 67:
			// 0xC61
			r = 50;
			g = 25;
			b = 4;
			break;

		case 68:
			// 0xE2E
			r = 58;
			g = 8;
			b = 58;
			break;

		case 69:
			// 0xBBB
			r = 46;
			g = 46;
			b = 46;
			break;

		default:
			// 0xFFF
			r = 63;
			g = 63;
			b = 63;
		}

		_screen->setPaletteIndex(16, r, g, b);
	} else {
		uint8 red, green, blue;

		switch (item) {
		case 60: case 61:
			red = 63;
			green = blue = 6;
			break;

		case 62: case 63:
			red = green = 0;
			blue = 67;
			break;

		case 64: case 65:
			red = 84;
			green = 78;
			blue = 14;
			break;

		case 66:
			red = blue = 0;
			green = 48;
			break;

		case 67:
			red = 100;
			green = 48;
			blue = 23;
			break;

		case 68:
			red = 73;
			green = 0;
			blue = 89;
			break;

		case 69:
			red = green = 73;
			blue = 86;
			break;

		default:
			red = 33;
			green = 66;
			blue = 100;
		}

		red   = red * 0x3F / 100;
		green = green * 0x3F / 100;
		blue  = blue * 0x3F / 100;

		_screen->setPaletteIndex(0xFE, red, green, blue);
	}

	_screen->hideMouse();
	checkAmuletAnimFlags();
	_currentCharacter->facing = 5;
	_animator->animRefreshNPC(0);
	assert(_drinkAnimationTable);
	setupShapes123(_drinkAnimationTable, 9, flags);
	_animator->setBrandonAnimSeqSize(5, 54);

	for (int i = 123; i <= 131; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(5);
	}

	snd_playSoundEffect(0x34);

	for (int i = 0; i < 2; ++i) {
		_currentCharacter->currentAnimFrame = 130;
		_animator->animRefreshNPC(0);
		delayWithTicks(7);
		_currentCharacter->currentAnimFrame = 131;
		_animator->animRefreshNPC(0);
		delayWithTicks(7);
	}

	if (makeFlaskEmpty)
		_screen->setPaletteIndex(0xFE, 30, 30, 30);

	for (int i = 131; i >= 123; --i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(5);
	}

	_animator->resetBrandonAnimSeqSize();
	_currentCharacter->currentAnimFrame = 7;
	_animator->animRefreshNPC(0);
	freeShapes123();

	if (_flags.platform != Common::kPlatformAmiga)
		_screen->setPaletteIndex(0xFE, 30, 30, 30);

	_screen->showMouse();
}

int KyraEngine_LoK::seq_playEnd() {
	if (_endSequenceSkipFlag)
		return 0;

	if (_deathHandler == 8)
		return 0;

	_screen->_curPage = 2;
	if (_endSequenceNeedLoading) {
		snd_playWanderScoreViaMap(50, 1);
		setupPanPages();

		if (_flags.platform == Common::kPlatformAmiga || _flags.platform == Common::kPlatformMacintosh) {
			_sound->loadSoundFile(kMusicFinale);
			_sound->selectAudioResourceSet(kMusicFinale);
			// The original started song 0 directly here. Since our player
			// uses 0, 1 for stop and fade we start song 0 with 2
			_sound->playTrack(2);
		}

		_finalA = createWSAMovie();
		assert(_finalA);
		_finalA->open("finala.wsa", 1, nullptr);

		_finalB = createWSAMovie();
		assert(_finalB);
		_finalB->open("finalb.wsa", 1, nullptr);

		_finalC = createWSAMovie();
		assert(_finalC);
		_endSequenceNeedLoading = 0;
		_finalC->open("finalc.wsa", 1, nullptr);

		_screen->_curPage = 0;
		_beadStateVar = 0;
		_malcolmFlag = 0;
		_unkEndSeqVar2 = _system->getMillis() + 600 * _tickLength;

		_screen->copyRegion(312, 0, 312, 0, 8, 136, 0, 2);
	}

	// TODO: better handling. This timer shouldn't count when the menu is open or something.
	if (_unkEndSeqVar2 != -1) {
		if (_system->getMillis() > (uint32)_unkEndSeqVar2) {
			_unkEndSeqVar2 = -1;
			if (!_malcolmFlag)
				_malcolmFlag = 1;
		}
	}

	if (handleMalcolmFlag()) {
		_beadStateVar = 0;
		_malcolmFlag = 12;
		handleMalcolmFlag();
		handleBeadState();
		closeFinalWsa();
		if (_deathHandler == 8) {
			_screen->_curPage = 0;
			checkAmuletAnimFlags();
			seq_brandonToStone();
			delay(60 * _tickLength);
			return 1;
		} else {
			_endSequenceSkipFlag = 1;
			if (_text->printed())
				_text->restoreTalkTextMessageBkgd(2, 0);

			_screen->_curPage = 0;
			_screen->hideMouse();

			if (_flags.platform != Common::kPlatformAmiga)
				_screen->fadeSpecialPalette(32, 228, 20, 60);

			delay(60 * _tickLength);

			_screen->loadBitmap("GEMHEAL.CPS", 3, 3, &_screen->getPalette(0));
			_screen->setScreenPalette(_screen->getPalette(0));
			_screen->shuffleScreen(8, 8, 304, 128, 2, 0, 1, 0);

			uint32 nextTime = _system->getMillis() + 120 * _tickLength;

			_finalA = createWSAMovie();
			assert(_finalA);
			_finalA->open("finald.wsa", 1, nullptr);

			delayUntil(nextTime);
			snd_playSoundEffect(0x40);
			for (int i = 0; i < 22; ++i) {
				delayUntil(nextTime);
				if (i == 4)
					snd_playSoundEffect(0x3E);
				else if (i == 20)
					snd_playSoundEffect(_flags.platform == Common::kPlatformPC98 ? 0x13 : 0x0E);
				nextTime = _system->getMillis() + 8 * _tickLength;
				_finalA->displayFrame(i, 0, 8, 8, 0, nullptr, nullptr);
				_screen->updateScreen();
			}

			nextTime = _system->getMillis() + 300 * _tickLength;
			delete _finalA;
			_finalA = nullptr;
			delayUntil(nextTime);

			seq_playEnding();
			return 1;
		}
	} else {
		handleBeadState();
		_screen->bitBlitRects();
		_screen->updateScreen();
		_screen->_curPage = 0;
	}
	return 0;
}

void KyraEngine_LoK::seq_brandonToStone() {
	_screen->hideMouse();
	assert(_brandonStoneTable);
	setupShapes123(_brandonStoneTable, 14, 0);
	_animator->setBrandonAnimSeqSize(5, 51);

	for (int i = 123; i <= 136; ++i) {
		_currentCharacter->currentAnimFrame = i;
		_animator->animRefreshNPC(0);
		delayWithTicks(8);
	}

	_animator->resetBrandonAnimSeqSize();
	freeShapes123();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_playEnding() {
	if (shouldQuit())
		return;
	_screen->hideMouse();
	_screen->_curPage = 0;
	_screen->fadeToBlack();

	if (_flags.platform == Common::kPlatformAmiga) {
		_screen->loadBitmap("GEMCUT.CPS", 3, 3, &_screen->getPalette(0));
		_screen->copyRegion(232, 136, 176, 56, 56, 56, 2, 2);
		_screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0);
		_screen->copyRegion(0, 0, 0, 0, 320, 200, 0, 2, Screen::CR_NO_P_CHECK);
	} else {
		_screen->loadBitmap("REUNION.CPS", 3, 3, &_screen->getPalette(0));
		_screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0);
	}

	_screen->_curPage = 0;
	// XXX
	assert(_homeString);
	drawSentenceCommand(_homeString[0], 179);

	_screen->getPalette(2).clear();
	_screen->setScreenPalette(_screen->getPalette(2));

	_seqPlayerFlag = true;
	_seq->playSequence(_seq_Reunion, false);
	_screen->fadeToBlack();
	_seqPlayerFlag = false;

	_screen->showMouse();

	// To avoid any remaining input events, we remove the queue
	// over here.
	_eventList.clear();

	if (_flags.platform == Common::kPlatformAmiga) {
		_screen->_charSpacing = -2;
		_screen->setCurPage(2);

		_screen->getPalette(2).clear();
		_screen->setScreenPalette(_screen->getPalette(2));

		while (!shouldQuit()) {
			seq_playCreditsAmiga();
			delayUntil(_system->getMillis() + 300 * _tickLength);
		}
	} else {
		seq_playCredits();
	}
}

namespace {
struct CreditsLine {
	int16 x, y;
	Screen::FontId font;
	uint8 *str;
};
} // end of anonymous namespace

void KyraEngine_LoK::seq_playCredits() {
	static const uint8 colorMap[] = { 0, 0, 0xC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	static const char stringTermsDef[] = { 0x5, 0xD, 0x0};
	static const char stringTermsMac[] = { 0x5, 0xA, 0x0 };
	const char *stringTerms = (_flags.platform == Common::kPlatformMacintosh) ? stringTermsMac : stringTermsDef;

	typedef Common::List<CreditsLine> CreditsLineList;
	CreditsLineList lines;

	_screen->disableDualPaletteMode();
	_screen->hideMouse();

	Screen::FontId font1, font2;
	int alignX3 = 157;
	int alignX4 = 161;
	int alignXOffs = 0;
	int lineHeight = 10;
	int fin = 175;

	if (_flags.lang == Common::ZH_TWN) {
		font1 = font2 = Screen::FID_CHINESE_FNT;
		alignX3 = alignX4 = 150;
		alignXOffs = 10;
		lineHeight = 16;
		fin = 160;
	} else if (!_flags.isTalkie) {
		_screen->loadFont(Screen::FID_CRED6_FNT, "CREDIT6.FNT");
		_screen->loadFont(Screen::FID_CRED8_FNT, "CREDIT8.FNT");
		font1 = Screen::FID_CRED6_FNT;
		font2 = Screen::FID_CRED8_FNT;
	} else {
		font1 = font2 = Screen::FID_8_FNT;
	}

	_screen->setFont(font2);
	_screen->loadBitmap("CHALET.CPS", 4, 4, &_screen->getPalette(0));

	_screen->setCurPage(0);
	_screen->clearCurPage();
	_screen->setTextColorMap(colorMap);
	_screen->_charSpacing = -1;

	// we only need this for the FM-TOWNS version
	if (_flags.platform == Common::kPlatformFMTowns && _configMusic == 1)
		snd_playWanderScoreViaMap(53, 1);

	uint8 *buffer = nullptr;
	uint32 size = 0;

	buffer = _res->fileData("CREDITS.TXT", &size);
	if (!buffer) {
		int sizeTmp = 0;
		const uint8 *bufferTmp = _staticres->loadRawData(k1CreditsStrings, sizeTmp);
		if (!bufferTmp)
			error("KyraEngine_LoK::seq_playCredits(): Unable to find credits data (neither in file 'CREDITS.TXT' nor in static data");

		buffer = new uint8[sizeTmp];
		assert(buffer);
		memcpy(buffer, bufferTmp, sizeTmp);
		size = sizeTmp;
		_staticres->unloadId(k1CreditsStrings);
	}

	uint8 *nextString = buffer;
	uint8 *currentString = buffer;
	int currentY = 200;

	do {
		currentString = nextString;
		nextString = (uint8 *)strpbrk((char *)currentString, stringTerms);
		if (!nextString)
			nextString = (uint8 *)strchr((char *)currentString, 0);

		CreditsLine line;

		int lineEndCode = nextString[0];
		*nextString = 0;
		if (lineEndCode != 0)
			nextString++;

		int alignment = 0;
		if (*currentString == 3 || *currentString == 4) {
			alignment = *currentString;
			currentString++;
		}

		if (*currentString == 1) {
			currentString++;
			_screen->setFont(font1);
		} else if (*currentString == 2) {
			currentString++;
			_screen->setFont(font2);
		}

		line.font = _screen->_currentFont;

		if (alignment == 3)
			line.x = alignX3 - _screen->getTextWidth((const char *)currentString);
		else if (alignment == 4)
			line.x = alignX4;
		else
			line.x = (320  - _screen->getTextWidth((const char *)currentString)) / 2 + 1 - alignXOffs;

		line.y = currentY;
		if (lineEndCode != 5)
			currentY += lineHeight;

		line.str = currentString;

		lines.push_back(line);
	} while (*nextString);

	_screen->setCurPage(2);

	_screen->getPalette(2).clear();
	_screen->setScreenPalette(_screen->getPalette(2));

	_screen->copyRegion(0, 32, 0, 32, 320, 128, 4, 0, Screen::CR_NO_P_CHECK);
	_screen->fadePalette(_screen->getPalette(0), 0x5A);

	bool finished = false;
	int bottom = 201;
	while (!finished && !shouldQuit()) {
		uint32 startLoop = _system->getMillis();

		if (bottom > fin) {
			_screen->copyRegion(0, 32, 0, 32, 320, 128, 4, 2, Screen::CR_NO_P_CHECK);
			bottom = 0;

			for (CreditsLineList::iterator it = lines.begin(); it != lines.end();) {
				if (it->y < 0) {
					it = lines.erase(it);
					continue;
				}

				if (it->y < 200) {
					if (it->font != _screen->_currentFont)
						_screen->setFont(it->font);

					_screen->printText((const char *)it->str, it->x, it->y, 15, 0);
				}

				it->y--;
				if (it->y > bottom)
					bottom = it->y;

				++it;
			}

			_screen->copyRegion(0, 32, 0, 32, 320, 128, 2, 0, Screen::CR_NO_P_CHECK);
			_screen->updateScreen();
		}

		if (checkInput(nullptr, false)) {
			removeInputTop();
			finished = true;
		}

		uint32 now = _system->getMillis();
		uint32 nextLoop = startLoop + _tickLength * 5;

		if (nextLoop > now)
			_system->delayMillis(nextLoop - now);
	}

	delete[] buffer;

	_screen->fadeToBlack();
	_screen->clearCurPage();
	_screen->showMouse();
}

void KyraEngine_LoK::seq_playCreditsAmiga() {
	_screen->setFont(Screen::FID_8_FNT);

	_screen->loadBitmap("CHALET.CPS", 4, 2, &_screen->getPalette(0));
	_screen->copyPage(2, 0);

	_screen->getPalette(0).fill(16, 1, 63);
	_screen->fadePalette(_screen->getPalette(0), 0x5A);
	_screen->updateScreen();

	const char *theEnd = "THE END";

	const int width = _screen->getTextWidth(theEnd) + 1;
	int x = (320 - width) / 2 + 1;

	_screen->copyRegion(x, 8, x, 8, width, 56, 0, 2, Screen::CR_NO_P_CHECK);
	_screen->copyRegion(x, 8, 0, 8, width, 11, 0, 2, Screen::CR_NO_P_CHECK);
	_screen->printText(theEnd, 0, 10, 31, 0);

	for (int y = 18, h = 1; y >= 10 && !shouldQuit(); --y, ++h) {
		uint32 endTime = _system->getMillis() + 3 * _tickLength;

		_screen->copyRegion(0, y, x, 8, width, h, 2, 0, Screen::CR_NO_P_CHECK);
		_screen->updateScreen();

		delayUntil(endTime);
	}

	for (int y = 8; y <= 62 && !shouldQuit(); ++y) {
		uint32 endTime = _system->getMillis() + 3 * _tickLength;

		_screen->copyRegion(x, y, 0, 8, width, 11, 2, 2, Screen::CR_NO_P_CHECK);
		_screen->printText(theEnd, 0, 9, 31, 0);
		_screen->copyRegion(0, 8, x, y, width, 11, 2, 0, Screen::CR_NO_P_CHECK);
		_screen->updateScreen();

		delayUntil(endTime);
	}

	int size = 0;
	const char *creditsData = (const char *)_staticres->loadRawData(k1CreditsStrings, size);

	char stringBuffer[81];
	memset(stringBuffer, 0, sizeof(stringBuffer));

	const char *cur = creditsData;
	char *specialString = stringBuffer;
	bool fillRectFlag = false, subWidth = false, centerFlag = false;
	x = 0;
	int specialX = 0;

	const int fontHeight = _screen->getFontHeight();

	do {
		char code = *cur;

		if (code == 3) {
			fillRectFlag = subWidth = true;
		} else if (code == 5) {
			centerFlag = true;
		} else if (code == 4) {
			if (fillRectFlag) {
				_screen->fillRect(0, 0, 319, 20, 0);

				if (subWidth)
					specialX = 157 - _screen->getTextWidth(stringBuffer);

				_screen->printText(stringBuffer, specialX + 8, 0, 31, 0);
			}

			specialString = stringBuffer;
			*specialString = 0;

			x = 161;
		} else if (code == 13) {
			if (!fillRectFlag)
				_screen->fillRect(0, 0, 319, 20, 0);

			uint32 nextTime = _system->getMillis() + 8 * _tickLength;

			if (centerFlag)
				x = (320 - _screen->getTextWidth(stringBuffer)) / 2 - 8;

			_screen->printText(stringBuffer, x + 8, 0, 31, 0);

			for (int i = 0; i < fontHeight && !shouldQuit(); ++i) {
				_screen->copyRegion(0, 141, 0, 140, 320, 59, 0, 0, Screen::CR_NO_P_CHECK);
				_screen->copyRegion(0, i, 0, 198, 320, 3, 2, 0, Screen::CR_NO_P_CHECK);
				_screen->updateScreen();

				delayUntil(nextTime);
				nextTime = _system->getMillis() + 8 * _tickLength;
			}

			specialString = stringBuffer;
			*specialString = 0;

			centerFlag = fillRectFlag = false;
		} else {
			*specialString++ = code;
			*specialString = 0;
		}

		if (checkInput(nullptr, false)) {
			removeInputTop();
			break;
		}
	} while (++cur != (creditsData + size) && !shouldQuit());
}

bool KyraEngine_LoK::seq_skipSequence() const {
	return shouldQuit() || _abortIntroFlag;
}

int KyraEngine_LoK::handleMalcolmFlag() {
	switch (_malcolmFlag) {
	case 1:
		_malcolmFrame = 0;
		_malcolmFlag = 2;
		_malcolmTimer2 = 0;
		// fall through

	case 2:
		if (_system->getMillis() >= _malcolmTimer2) {
			_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
			_screen->updateScreen();
			_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
			++_malcolmFrame;
			if (_malcolmFrame > 13) {
				_malcolmFlag = 3;
				_malcolmTimer1 = _system->getMillis() + 180 * _tickLength;
			}
		}
		break;

	case 3:
		if (_system->getMillis() < _malcolmTimer1) {
			if (_system->getMillis() >= _malcolmTimer2) {
				_malcolmFrame = _rnd.getRandomNumberRng(14, 17);
				_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
				_screen->updateScreen();
				_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
			}
		} else {
			_malcolmFlag = 4;
			_malcolmFrame = 18;
		}
		break;

	case 4:
		if (_system->getMillis() >= _malcolmTimer2) {
			_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
			_screen->updateScreen();
			_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
			++_malcolmFrame;
			if (_malcolmFrame > 25) {
				_malcolmFrame = 26;
				_malcolmFlag = 5;
				_beadStateVar = 1;
			}
		}
		break;

	case 5:
		if (_system->getMillis() >= _malcolmTimer2) {
			_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
			_screen->updateScreen();
			_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
			++_malcolmFrame;
			if (_malcolmFrame > 31) {
				_malcolmFrame = 32;
				_malcolmFlag = 6;
			}
		}
		break;

	case 6:
		if (_unkEndSeqVar4) {
			if (_malcolmFrame <= 33 && _system->getMillis() >= _malcolmTimer2) {
				_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
				_screen->updateScreen();
				_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
				++_malcolmFrame;
				if (_malcolmFrame > 33) {
					_malcolmFlag = 7;
					_malcolmFrame = 32;
					_unkEndSeqVar5 = 0;
				}
			}
		}
		break;

	case 7:
		if (_unkEndSeqVar5 == 1) {
			_malcolmFlag = 8;
			_malcolmFrame = 34;
		} else if (_unkEndSeqVar5 == 2) {
			_malcolmFlag = 3;
			_malcolmTimer1 = _system->getMillis() + 180 * _tickLength;
		}
		break;

	case 8:
		if (_system->getMillis() >= _malcolmTimer2) {
			_finalA->displayFrame(_malcolmFrame, 0, 8, 46, 0, nullptr, nullptr);
			_screen->updateScreen();
			_malcolmTimer2 = _system->getMillis() + 8 * _tickLength;
			++_malcolmFrame;
			if (_malcolmFrame > 37) {
				_malcolmFlag = 0;
				_deathHandler = 8;
				return 1;
			}
		}
		break;

	case 9:
		snd_playSoundEffect(12);
		snd_playSoundEffect(12);
		for (int i = 0; i < 18; ++i) {
			_malcolmTimer2 = _system->getMillis() + 4 * _tickLength;
			_finalC->displayFrame(i, 0, 16, 50, 0, nullptr, nullptr);
			_screen->updateScreen();
			delayUntil(_malcolmTimer2);
		}
		if (_flags.platform == Common::kPlatformMacintosh)
			_sound->playTrack(4);
		else
			snd_playWanderScoreViaMap(51, 1);
		delay(60 * _tickLength);
		_malcolmFlag = 0;
		return 1;

	case 10:
		if (!_beadStateVar) {
			handleBeadState();
			_screen->bitBlitRects();
			assert(_veryClever);
			_text->printTalkTextMessage(_veryClever[0], 60, 31, 5, 0, 2);
			_malcolmTimer2 = _system->getMillis() + 180 * _tickLength;
			_malcolmFlag = 11;
		}
		break;

	case 11:
		if (_system->getMillis() >= _malcolmTimer2) {
			_text->restoreTalkTextMessageBkgd(2, 0);
			_malcolmFlag = 3;
			_malcolmTimer1 = _system->getMillis() + 180 * _tickLength;
		}
		break;

	default:
		break;
	}

	return 0;
}

int KyraEngine_LoK::handleBeadState() {
	static const int table1[] = {
		-1, -2, -4, -5, -6, -7, -6, -5,
		-4, -2, -1,  0,  1,  2,  4,  5,
		 6,  7,  6,  5,  4,  2,  1,  0, 0
	};

	static const int table2[] = {
		0, 0, 1, 1, 2, 2, 3, 3,
		4, 4, 5, 5, 5, 5, 4, 4,
		3, 3, 2, 2, 1, 1, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0
	};

	switch (_beadStateVar) {
	case 0:
		if (_beadState1.x != -1 && _endSequenceBackUpRect) {
			_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
			_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
		}

		_beadState1.x = -1;
		_beadState1.tableIndex = 0;
		_beadStateTimer1 = 0;
		_beadStateTimer2 = 0;
		_lastDisplayedPanPage = 0;
		return 1;

	case 1:
		if (_beadState1.x != -1) {
			if (_endSequenceBackUpRect) {
				_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
				_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
			}
			_beadState1.x = -1;
			_beadState1.tableIndex = 0;
		}
		_beadStateVar = 2;
		break;

	case 2:
		if (_system->getMillis() >= _beadStateTimer1) {
			int x = 0, y = 0;
			_beadStateTimer1 = _system->getMillis() + 4 * _tickLength;
			if (_beadState1.x == -1) {
				assert(_panPagesTable);
				_beadState1.width2 = _animator->fetchAnimWidth(_panPagesTable[19], 256);
				_beadState1.width = ((_beadState1.width2 + 7) >> 3) + 1;
				_beadState1.height = _animator->fetchAnimHeight(_panPagesTable[19], 256);
				if (!_endSequenceBackUpRect) {
					_endSequenceBackUpRect = new uint8[(_beadState1.width * _beadState1.height) << 3]();
					assert(_endSequenceBackUpRect);
				}
				x = _beadState1.x = 60;
				y = _beadState1.y = 40;
				initBeadState(x, y, x, 25, 8, &_beadState2);
			} else {
				if (processBead(_beadState1.x, _beadState1.y, x, y, &_beadState2)) {
					_beadStateVar = 3;
					_beadStateTimer2 = _system->getMillis() + 240 * _tickLength;
					_unkEndSeqVar4 = 0;
					_beadState1.dstX = _beadState1.x;
					_beadState1.dstY = _beadState1.y;
					return 0;
				} else {
					_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
					_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
					_beadState1.x = x;
					_beadState1.y = y;
				}
			}

			_screen->copyRegionToBuffer(_screen->_curPage, x, y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
			_screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0);

			if (_lastDisplayedPanPage > 17)
				_lastDisplayedPanPage = 0;

			_screen->addBitBlitRect(x, y, _beadState1.width2, _beadState1.height);
		}
		break;

	case 3:
		if (_system->getMillis() >= _beadStateTimer1) {
			_beadStateTimer1 = _system->getMillis() + 4 * _tickLength;
			_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
			_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);

			_beadState1.x = _beadState1.dstX + table1[_beadState1.tableIndex];
			_beadState1.y = _beadState1.dstY + table2[_beadState1.tableIndex];
			_screen->copyRegionToBuffer(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);

			_screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], _beadState1.x, _beadState1.y, 0, 0);
			if (_lastDisplayedPanPage >= 17)
				_lastDisplayedPanPage = 0;

			_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);

			++_beadState1.tableIndex;
			if (_beadState1.tableIndex > 24) {
				_beadState1.tableIndex = 0;
				_unkEndSeqVar4 = 1;
			}
			if (_system->getMillis() > _beadStateTimer2 && _malcolmFlag == 7 && !_unkAmuletVar && !_text->printed()) {
				snd_playSoundEffect(0x0B);
				if (_currentCharacter->x1 > 233 && _currentCharacter->x1 < 305 && _currentCharacter->y1 > 85 && _currentCharacter->y1 < 105 &&
				        (_brandonStatusBit & 0x20)) {
					_beadState1.unk8 = 290;
					_beadState1.unk9 = 40;
					_beadStateVar = 5;
				} else {
					_beadStateVar = 4;
					_beadState1.unk8 = _currentCharacter->x1 - 4;
					_beadState1.unk9 = _currentCharacter->y1 - 30;
				}

				if (_text->printed())
					_text->restoreTalkTextMessageBkgd(2, 0);

				initBeadState(_beadState1.x, _beadState1.y, _beadState1.unk8, _beadState1.unk9, 12, &_beadState2);
				_lastDisplayedPanPage = 18;
			}
		}
		break;

	case 4:
		if (_system->getMillis() >= _beadStateTimer1) {
			int x = 0, y = 0;
			_beadStateTimer1 = _system->getMillis() + _tickLength;
			if (processBead(_beadState1.x, _beadState1.y, x, y, &_beadState2)) {
				if (_brandonStatusBit & 20) {
					_unkEndSeqVar5 = 2;
					_beadStateVar = 6;
				} else {
					snd_playWanderScoreViaMap(52, 1);
					snd_playSoundEffect(0x0C);
					_unkEndSeqVar5 = 1;
					_beadStateVar = 0;
				}
			} else {
				_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
				_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
				_beadState1.x = x;
				_beadState1.y = y;
				_screen->copyRegionToBuffer(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
				_screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0);
				if (_lastDisplayedPanPage > 17) {
					_lastDisplayedPanPage = 0;
				}
				_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
			}
		}
		break;

	case 5:
		if (_system->getMillis() >= _beadStateTimer1) {
			_beadStateTimer1 = _system->getMillis() + _tickLength;
			int x = 0, y = 0;
			if (processBead(_beadState1.x, _beadState1.y, x, y, &_beadState2)) {
				if (_beadState2.dstX == 290) {
					_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
					uint32 nextRun = 0;
					for (int i = 0; i < 8; ++i) {
						nextRun = _system->getMillis() + _tickLength;
						_finalB->displayFrame(i, 0, 224, 8, 0, nullptr, nullptr);
						_screen->updateScreen();
						delayUntil(nextRun);
					}
					snd_playSoundEffect(0x0D);
					for (int i = 7; i >= 0; --i) {
						nextRun = _system->getMillis() + _tickLength;
						_finalB->displayFrame(i, 0, 224, 8, 0, nullptr, nullptr);
						_screen->updateScreen();
						delayUntil(nextRun);
					}
					initBeadState(_beadState1.x, _beadState1.y, 63, 60, 12, &_beadState2);
				} else {
					_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
					_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
					_beadState1.x = -1;
					_beadState1.tableIndex = 0;
					_beadStateVar = 0;
					_malcolmFlag = 9;
				}
			} else {
				_screen->copyBlockToPage(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
				_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
				_beadState1.x = x;
				_beadState1.y = y;
				_screen->copyRegionToBuffer(_screen->_curPage, _beadState1.x, _beadState1.y, _beadState1.width << 3, _beadState1.height, _endSequenceBackUpRect);
				_screen->drawShape(2, _panPagesTable[_lastDisplayedPanPage++], x, y, 0, 0);
				if (_lastDisplayedPanPage > 17)
					_lastDisplayedPanPage = 0;
				_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
			}
		}
		break;

	case 6:
		_screen->drawShape(2, _panPagesTable[19], _beadState1.x, _beadState1.y, 0, 0);
		_screen->addBitBlitRect(_beadState1.x, _beadState1.y, _beadState1.width2, _beadState1.height);
		_beadStateVar = 0;
		break;

	default:
		break;
	}

	return 0;
}

void KyraEngine_LoK::initBeadState(int x, int y, int x2, int y2, int unk, BeadState *ptr) {
	ptr->unk9 = unk;
	int xDiff = x2 - x;
	int yDiff = y2 - y;
	int unk1 = 0, unk2 = 0;
	if (xDiff > 0)
		unk1 = 1;
	else if (xDiff == 0)
		unk1 = 0;
	else
		unk1 = -1;


	if (yDiff > 0)
		unk2 = 1;
	else if (yDiff == 0)
		unk2 = 0;
	else
		unk2 = -1;

	xDiff = ABS(xDiff);
	yDiff = ABS(yDiff);

	ptr->y = 0;
	ptr->x = 0;
	ptr->width = xDiff;
	ptr->height = yDiff;
	ptr->dstX = x2;
	ptr->dstY = y2;
	ptr->width2 = unk1;
	ptr->unk8 = unk2;
}

int KyraEngine_LoK::processBead(int x, int y, int &x2, int &y2, BeadState *ptr) {
	if (x == ptr->dstX && y == ptr->dstY)
		return 1;

	int xPos = x, yPos = y;
	if (ptr->width >= ptr->height) {
		for (int i = 0; i < ptr->unk9; ++i) {
			ptr->y += ptr->height;
			if (ptr->y >= ptr->width) {
				ptr->y -= ptr->width;
				yPos += ptr->unk8;
			}
			xPos += ptr->width2;
		}
	} else {
		for (int i = 0; i < ptr->unk9; ++i) {
			ptr->x += ptr->width;
			if (ptr->x >= ptr->height) {
				ptr->x -= ptr->height;
				xPos += ptr->width2;
			}
			yPos += ptr->unk8;
		}
	}

	int temp = ABS(x - ptr->dstX);
	if (ptr->unk9 > temp)
		xPos = ptr->dstX;
	temp = ABS(y - ptr->dstY);
	if (ptr->unk9 > temp)
		yPos = ptr->dstY;
	x2 = xPos;
	y2 = yPos;
	return 0;
}

void KyraEngine_LoK::setupPanPages() {
	_screen->savePageToDisk("BKGD.PG", 2);
	_screen->loadBitmap("BEAD.CPS", 3, 3, nullptr);
	if (_flags.platform == Common::kPlatformMacintosh || _flags.platform == Common::kPlatformAmiga) {
		int pageBackUp = _screen->_curPage;
		_screen->_curPage = 2;

		delete[] _panPagesTable[19];
		_panPagesTable[19] = _screen->encodeShape(0, 0, 16, 9, 0);
		assert(_panPagesTable[19]);

		int curX = 16;
		for (int i = 0; i < 19; ++i) {
			delete[] _panPagesTable[i];
			_panPagesTable[i] = _screen->encodeShape(curX, 0, 8, 5, 0);
			assert(_panPagesTable[i]);
			curX += 8;
		}

		_screen->_curPage = pageBackUp;
	} else {
		for (int i = 0; i <= 19; ++i) {
			delete[] _panPagesTable[i];
			_panPagesTable[i] = _seq->setPanPages(3, i);
			assert(_panPagesTable[i]);
		}
	}
	_screen->loadPageFromDisk("BKGD.PG", 2);
}

void KyraEngine_LoK::freePanPages() {
	delete[] _endSequenceBackUpRect;
	_endSequenceBackUpRect = nullptr;
	for (int i = 0; i <= 19; ++i) {
		delete[] _panPagesTable[i];
		_panPagesTable[i] = nullptr;
	}
}

void KyraEngine_LoK::closeFinalWsa() {
	delete _finalA;
	_finalA = nullptr;
	delete _finalB;
	_finalB = nullptr;
	delete _finalC;
	_finalC = nullptr;
	freePanPages();
	_endSequenceNeedLoading = 1;
}

void KyraEngine_LoK::updateKyragemFading() {
	if (_flags.platform == Common::kPlatformAmiga) {
		// The AMIGA version seems to have no fading for the Kyragem. The code does not
		// alter the screen palette.
		//
		// TODO: Check this in the original.
		return;
	}

	static const uint8 kyraGemPalette[0x28] = {
		0x3F, 0x3B, 0x38, 0x34, 0x32, 0x2F, 0x2C, 0x29, 0x25, 0x22,
		0x1F, 0x1C, 0x19, 0x16, 0x12, 0x0F, 0x0C, 0x0A, 0x06, 0x03,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	if (_system->getMillis() < _kyragemFadingState.timerCount)
		return;

	_kyragemFadingState.timerCount = _system->getMillis() + 4 * _tickLength;

	int palPos = 684;
	for (int i = 0; i < 20; ++i) {
		_screen->getPalette(0)[palPos++] = kyraGemPalette[i + _kyragemFadingState.rOffset];
		_screen->getPalette(0)[palPos++] = kyraGemPalette[i + _kyragemFadingState.gOffset];
		_screen->getPalette(0)[palPos++] = kyraGemPalette[i + _kyragemFadingState.bOffset];
	}

	_screen->setScreenPalette(_screen->getPalette(0));

	switch (_kyragemFadingState.nextOperation) {
	case 0:
		--_kyragemFadingState.bOffset;
		if (_kyragemFadingState.bOffset >= 1)
			return;
		_kyragemFadingState.nextOperation = 1;
		break;

	case 1:
		++_kyragemFadingState.rOffset;
		if (_kyragemFadingState.rOffset < 19)
			return;
		_kyragemFadingState.nextOperation = 2;
		break;

	case 2:
		--_kyragemFadingState.gOffset;
		if (_kyragemFadingState.gOffset >= 1)
			return;
		_kyragemFadingState.nextOperation = 3;
		break;

	case 3:
		++_kyragemFadingState.bOffset;
		if (_kyragemFadingState.bOffset < 19)
			return;
		_kyragemFadingState.nextOperation = 4;
		break;

	case 4:
		--_kyragemFadingState.rOffset;
		if (_kyragemFadingState.rOffset >= 1)
			return;
		_kyragemFadingState.nextOperation = 5;
		break;

	case 5:
		++_kyragemFadingState.gOffset;
		if (_kyragemFadingState.gOffset < 19)
			return;
		_kyragemFadingState.nextOperation = 0;
		break;

	default:
		break;
	}

	_kyragemFadingState.timerCount = _system->getMillis() + 120 * _tickLength;
}

void KyraEngine_LoK::drawJewelPress(int jewel, int drawSpecial) {
	_screen->hideMouse();
	int shape = 0;

	if (drawSpecial)
		shape = 0x14E;
	else
		shape = jewel + 0x149;

	snd_playSoundEffect(0x45);
	_screen->drawShape(0, _shapes[shape], _amuletX2[jewel], _amuletY2[jewel], 0, 0);
	_screen->updateScreen();
	delayWithTicks(2);

	if (drawSpecial)
		shape = 0x148;
	else
		shape = jewel + 0x143;

	_screen->drawShape(0, _shapes[shape], _amuletX2[jewel], _amuletY2[jewel], 0, 0);
	_screen->updateScreen();
	_screen->showMouse();
}

void KyraEngine_LoK::drawJewelsFadeOutStart() {
	static const uint16 jewelTable1[] = { 0x164, 0x15F, 0x15A, 0x155, 0x150, 0xFFFF };
	static const uint16 jewelTable2[] = { 0x163, 0x15E, 0x159, 0x154, 0x14F, 0xFFFF };
	static const uint16 jewelTable3[] = { 0x166, 0x160, 0x15C, 0x157, 0x152, 0xFFFF };
	static const uint16 jewelTable4[] = { 0x165, 0x161, 0x15B, 0x156, 0x151, 0xFFFF };
	for (int i = 0; jewelTable1[i] != 0xFFFF; ++i) {
		if (queryGameFlag(0x57))
			_screen->drawShape(0, _shapes[jewelTable1[i]], _amuletX2[2], _amuletY2[2], 0, 0);
		if (queryGameFlag(0x59))
			_screen->drawShape(0, _shapes[jewelTable3[i]], _amuletX2[4], _amuletY2[4], 0, 0);
		if (queryGameFlag(0x56))
			_screen->drawShape(0, _shapes[jewelTable2[i]], _amuletX2[1], _amuletY2[1], 0, 0);
		if (queryGameFlag(0x58))
			_screen->drawShape(0, _shapes[jewelTable4[i]], _amuletX2[3], _amuletY2[3], 0, 0);
		_screen->updateScreen();
		delayWithTicks(3);
	}
}

void KyraEngine_LoK::drawJewelsFadeOutEnd(int jewel) {
	static const uint16 jewelTable[] = { 0x153, 0x158, 0x15D, 0x162, 0x148, 0xFFFF };
	int newDelay = 0;

	switch (jewel - 1) {
	case 2:
		if (_currentCharacter->sceneId >= 109 && _currentCharacter->sceneId <= 198)
			newDelay = 18900;
		else
			newDelay = 8100;
		break;

	default:
		newDelay = 3600;
	}

	setGameFlag(0xF1);
	_timer->setCountdown(19, newDelay);
	_screen->hideMouse();
	for (int i = 0; jewelTable[i] != 0xFFFF; ++i) {
		uint16 shape = jewelTable[i];
		if (queryGameFlag(0x57))
			_screen->drawShape(0, _shapes[shape], _amuletX2[2], _amuletY2[2], 0, 0);
		if (queryGameFlag(0x59))
			_screen->drawShape(0, _shapes[shape], _amuletX2[4], _amuletY2[4], 0, 0);
		if (queryGameFlag(0x56))
			_screen->drawShape(0, _shapes[shape], _amuletX2[1], _amuletY2[1], 0, 0);
		if (queryGameFlag(0x58))
			_screen->drawShape(0, _shapes[shape], _amuletX2[3], _amuletY2[3], 0, 0);

		_screen->updateScreen();
		delayWithTicks(3);
	}
	_screen->showMouse();
}

} // End of namespace Kyra
