/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#ifdef ENABLE_EOB

#include "kyra/eobcommon.h"
#include "kyra/resource.h"
#include "kyra/script_eob.h"
#include "kyra/timer.h"
#include "kyra/sound.h"

#include "common/system.h"


namespace Kyra {

void EoBCoreEngine::loadLevel(int level, int sub) {
	_currentLevel = level;
	_currentSub = sub;
	uint32 end = _system->getMillis() + 500;

	readLevelFileData(level);

	Common::String gfxFile;
	// Work around for issue with corrupt (incomplete) monster property data
	// when loading a savegame saved in a sub level
	for (int i = 0; i <= sub; i++)
		gfxFile = initLevelData(i);

	const uint8 *data = _screen->getCPagePtr(5);
	const uint8 *pos = data + READ_LE_UINT16(data);
	uint16 len = READ_LE_UINT16(pos);
	uint16 len2 = len;
	pos += 2;

	if (_flags.gameID == GI_EOB2) {
		if (*pos++ == 0xEC)
			pos = loadActiveMonsterData(pos, level);
		else if (!(_hasTempDataFlags & (1 << (level - 1))))
			memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay));

		len2 = len - (pos - data);
		_inf->loadData(pos, len2);
	} else {
		_inf->loadData(data, READ_LE_UINT16(data));
	}

	_screen->setCurPage(2);
	addLevelItems();

	if (_flags.gameID == GI_EOB2) {
		pos = data + len;
		len2 = READ_LE_UINT16(pos);
		pos += 2;
	}

	for (uint16 i = 0; i < len2; i++) {
		LevelBlockProperty *p = &_levelBlockProperties[READ_LE_UINT16(pos)];
		pos += 2;
		if (_flags.gameID == GI_EOB2) {
			p->flags |= READ_LE_UINT16(pos);
			pos += 2;
		} else {
			p->flags |= *pos++;
		}
		p->assignedObjects = READ_LE_UINT16(pos);
		pos += 2;
	}

	// WORKAROUND for bug #3596547 (EOB1: Door Buttons Don't Work)
	if (_flags.gameID == GI_EOB1 && level == 7 && _levelBlockProperties[0x035C].assignedObjects == 0x0E89)
		_levelBlockProperties[0x035C].assignedObjects = 0x0E8D;

	loadVcnData(gfxFile.c_str(), (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[level - 1]] : 0);
	_screen->loadEoBBitmap("INVENT", _cgaMappingInv, 5, 3, 2);
	delayUntil(end);
	snd_stopSound();

	enableSysTimer(2);
	_sceneDrawPage1 = 2;
	_sceneDrawPage2 = 1;
	_screen->setCurPage(0);
}

void EoBCoreEngine::readLevelFileData(int level) {
	Common::String file;
	Common::SeekableReadStream *s = 0;
	static const char *const suffix[] = { "INF", "DRO", "ELO", 0 };

	for (const char *const *sf = suffix; *sf && !s; sf++) {
		file = Common::String::format("LEVEL%d.%s", level, *sf);
		s = _res->createReadStream(file);
	}

	if (!s)
		error("Failed to load level file LEVEL%d.INF/DRO/ELO", level);

	if (s->readUint16LE() + 2 == s->size()) {
		if (s->readUint16LE() == 4) {
			delete s;
			s = 0;
			_screen->loadBitmap(file.c_str(), 5, 5, 0);
		}
	}

	if (s) {
		s->seek(0);
		_screen->loadFileDataToPage(s, 5, 15000);
		delete s;
	}
}

Common::String EoBCoreEngine::initLevelData(int sub) {
	const uint8 *data = _screen->getCPagePtr(5) + 2;
	const uint8 *pos = data;

	int slen = (_flags.gameID == GI_EOB1) ? 12 : 13;

	for (int i = 0; i < sub; i++)
		pos = data + READ_LE_UINT16(pos);

	pos += 2;
	if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) {
		if (_flags.gameID == GI_EOB1)
			pos -= 3;

		loadBlockProperties((const char *)pos);
		pos += slen;

		const char *vmpPattern = (_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.EMP" : "%s.VMP";
		Common::SeekableReadStream *s = _res->createReadStream(Common::String::format(vmpPattern, (const char *)pos));
		uint16 size = s->readUint16LE();
		delete[] _vmpPtr;
		_vmpPtr = new uint16[size];
		for (int i = 0; i < size; i++)
			_vmpPtr[i] = s->readUint16LE();
		delete s;

		const char *paletteFilePattern = (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA) ? "%s.EGA" : "%s.PAL";

		Common::String tmpStr = Common::String::format(paletteFilePattern, (const char *)pos);
		_curGfxFile = (const char *)pos;
		pos += slen;

		if (*pos++ != 0xFF && _flags.gameID == GI_EOB2) {
			tmpStr = Common::String::format(paletteFilePattern, (const char *)pos);
			pos += 13;
		}

		if (_flags.gameID == GI_EOB1) {
			pos += 11;
			_screen->setShapeFadeMode(0, false);
			_screen->setShapeFadeMode(1, false);
		}

		if (_flags.gameID == GI_EOB2 || _configRenderMode != Common::kRenderEGA)
			_screen->loadPalette(tmpStr.c_str(), _screen->getPalette(0));

		if (_configRenderMode != Common::kRenderCGA) {
			Palette backupPal(256);
			backupPal.copy(_screen->getPalette(0), 224, 32, 224);
			_screen->getPalette(0).fill(224, 32, 0x3F);
			uint8 *src = _screen->getPalette(0).getData();

			_screen->createFadeTable(src, _screen->getFadeTable(0), 4, 75);     // green
			_screen->createFadeTable(src, _screen->getFadeTable(1), 12, 200);   // black
			_screen->createFadeTable(src, _screen->getFadeTable(2), 10, 85);    // blue
			_screen->createFadeTable(src, _screen->getFadeTable(3), 11, 125);   // light blue

			_screen->getPalette(0).copy(backupPal, 224, 32, 224);
			_screen->createFadeTable(src, _screen->getFadeTable(4), 12, 85);    // grey (shadow)
			_screen->setFadeTableIndex(4);
			if (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA)
				_screen->setScreenPalette(_screen->getPalette(0));
		}
	}

	if (_flags.gameID == GI_EOB2) {
		delay(3 * _tickLength);
		_sound->loadSoundFile((const char *)pos);
		pos += 13;
	}

	releaseDoorShapes();
	releaseMonsterShapes(0, 36);
	releaseDecorations();

	if (_flags.gameID == GI_EOB1) {
		loadDoorShapes(pos[0], pos[1], pos[2], pos[3]);
		pos += 4;
		_scriptTimersMode = *pos++;
		_scriptTimers[0].ticks = READ_LE_UINT16(pos);
		_scriptTimers[0].func = 0;
		_scriptTimers[0].next = _system->getMillis() + _scriptTimers[0].ticks * _tickLength;
		pos += 2;
	} else {
		for (int i = 0; i < 2; i++) {
			int a = (*pos == 0xEC) ? 0 : ((*pos == 0xEA) ? 1 : -1);
			pos++;
			if (a == -1)
				continue;
			toggleWallState(pos[13], a);
			_doorType[pos[13]] = pos[14];
			_noDoorSwitch[pos[13]] = pos[15];
			pos = loadDoorShapes((const char *)pos, pos[13], pos + 16);
		}
	}

	_stepsUntilScriptCall = READ_LE_UINT16(pos);
	pos += 2;
	_stepCounter = 0;

	for (int i = 0; i < 2; i++) {
		if (_flags.gameID == GI_EOB1) {
			if (*pos != 0xFF)
				loadMonsterShapes((const char *)(pos + 1), i * 18, false, *pos * 18);
			pos += 13;
		} else {
			if (*pos++ != 0xEC)
				continue;
			loadMonsterShapes((const char *)(pos + 2), pos[1] * 18, pos[15] ? true : false, *pos * 18);
			pos += 16;
		}
	}

	if (_flags.gameID == GI_EOB1)
		pos = loadActiveMonsterData(pos, _currentLevel) - 1;
	else
		pos = loadMonsterProperties(pos);

	if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) {
		int num = READ_LE_UINT16(pos);
		pos += 2;

		for (int i = 0; i < num; i++) {
			if (*pos++ == 0xEC) {
				loadDecorations((const char *)pos, (const char *)(pos + slen));
				pos += (slen << 1);
			} else {
				assignWallsAndDecorations(pos[0], pos[1], (int8)pos[2], pos[3], pos[4]);
				pos += 5;
			}
		}
	}

	if (_flags.gameID == GI_EOB2)
		initScriptTimers(pos);

	return _curGfxFile;
}

void EoBCoreEngine::addLevelItems() {
	for (int i = 0; i < 1024; i++)
		_levelBlockProperties[i].drawObjects = 0;

	for (int i = 0; i < 600; i++) {
		if (_items[i].level != _currentLevel || _items[i].block <= 0)
			continue;
		setItemPosition((Item *)&_levelBlockProperties[_items[i].block & 0x3FF].drawObjects, _items[i].block, i, _items[i].pos);
	}
}

void EoBCoreEngine::loadVcnData(const char *file, const uint8 *cgaMapping) {
	if (file)
		strcpy(_lastBlockDataFile, file);

	const char *filePattern = (_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.ECN" : "%s.VCN";
	_screen->loadBitmap(Common::String::format(filePattern, _lastBlockDataFile).c_str(), 3, 3, 0);
	const uint8 *pos = _screen->getCPagePtr(3);

	uint32 vcnSize = READ_LE_UINT16(pos) << 5;
	pos += 2;

	const uint8 *colMap = pos;
	pos += 32;

	delete[] _vcnBlocks;
	_vcnBlocks = new uint8[vcnSize];

	if (_configRenderMode == Common::kRenderCGA) {
		uint8 *tmp = _screen->encodeShape(0, 0, 1, 8, false, cgaMapping);
		delete[] tmp;

		delete[] _vcnTransitionMask;
		_vcnTransitionMask = new uint8[vcnSize];
		uint8 tblSwitch = 1;
		uint8 *dst = _vcnBlocks;
		uint8 *dst2 = _vcnTransitionMask;

		while (dst < _vcnBlocks + vcnSize) {
			const uint16 *table = _screen->getCGADitheringTable((tblSwitch++) & 1);
			for (int ii = 0; ii < 2; ii++) {
				*dst++ = (table[pos[0]] & 0x000F) | ((table[pos[0]] & 0x0F00) >> 4);
				*dst++ = (table[pos[1]] & 0x000F) | ((table[pos[1]] & 0x0F00) >> 4);
				*dst2++ = ((pos[0] & 0xF0 ? 0x30 : 0) | (pos[0] & 0x0F ? 0x03 : 0)) ^ 0x33;
				*dst2++ = ((pos[1] & 0xF0 ? 0x30 : 0) | (pos[1] & 0x0F ? 0x03 : 0)) ^ 0x33;
				pos += 2;
			}
		}
	} else {
		if (!(_flags.gameID == GI_EOB1 && _configRenderMode == Common::kRenderEGA))
			memcpy(_vcnColTable, colMap, 32);
		memcpy(_vcnBlocks, pos, vcnSize);
	}
}

void EoBCoreEngine::loadBlockProperties(const char *mazFile) {
	memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty));
	const uint8 *p = getBlockFileData(mazFile) + 6;

	if (_hasTempDataFlags & (1 << (_currentLevel - 1))) {
		restoreBlockTempData(_currentLevel);
		return;
	}

	for (int i = 0; i < 1024; i++) {
		for (int ii = 0; ii < 4; ii++)
			_levelBlockProperties[i].walls[ii] = *p++;
	}
}

const uint8 *EoBCoreEngine::getBlockFileData(int) {
	Common::SeekableReadStream *s = _res->createReadStream(_curBlockFile);
	_screen->loadFileDataToPage(s, 15, s->size());
	delete s;
	return _screen->getCPagePtr(15);
}

Common::String EoBCoreEngine::getBlockFileName(int levelIndex, int sub) {
	readLevelFileData(levelIndex);
	const uint8 *data = _screen->getCPagePtr(5) + 2;
	const uint8 *pos = data;

	for (int i = 0; i < sub; i++)
		pos = data + READ_LE_UINT16(pos);

	pos += 2;

	if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) {
		if (_flags.gameID == GI_EOB1)
			pos -= 3;

		return Common::String((const char *)pos);
	}

	return Common::String();
}

const uint8 *EoBCoreEngine::getBlockFileData(const char *mazFile) {
	_curBlockFile = mazFile;
	return getBlockFileData();
}

void EoBCoreEngine::loadDecorations(const char *cpsFile, const char *decFile) {
	_screen->loadShapeSetBitmap(cpsFile, 5, 3);
	Common::SeekableReadStream *s = _res->createReadStream(decFile);

	_levelDecorationDataSize = s->readUint16LE();
	delete[] _levelDecorationData;
	_levelDecorationData = new LevelDecorationProperty[_levelDecorationDataSize];
	memset(_levelDecorationData, 0, _levelDecorationDataSize * sizeof(LevelDecorationProperty));

	for (int i = 0; i < _levelDecorationDataSize; i++) {
		LevelDecorationProperty *l = &_levelDecorationData[i];
		for (int ii = 0; ii < 10; ii++) {
			l->shapeIndex[ii] = s->readByte();
			if (l->shapeIndex[ii] == 0xFF)
				l->shapeIndex[ii] = 0xFFFF;
		}
		l->next = s->readByte();
		l->flags = s->readByte();
		for (int ii = 0; ii < 10; ii++)
			l->shapeX[ii] = s->readSint16LE();
		for (int ii = 0; ii < 10; ii++)
			l->shapeY[ii] = s->readSint16LE();
	}

	int len = s->readUint16LE();
	delete[] _levelDecorationRects;
	_levelDecorationRects = new EoBRect8[len];
	for (int i = 0; i < len; i++) {
		EoBRect8 *l = &_levelDecorationRects[i];
		l->x = s->readUint16LE();
		l->y = s->readUint16LE();
		l->w = s->readUint16LE();
		l->h = s->readUint16LE();
	}

	delete s;
}

void EoBCoreEngine::assignWallsAndDecorations(int wallIndex, int vmpIndex, int decIndex, int specialType, int flags) {
	_wllVmpMap[wallIndex] = vmpIndex;
	for (int i = 0; i < 6; i++) {
		for (int ii = 0; ii < 10; ii++) {
			if (_characters[i].events[ii] == -57)
				spellCallback_start_trueSeeing();
		}
	}
	_wllShapeMap[wallIndex] = _mappedDecorationsCount + 1;
	_specialWallTypes[wallIndex] = specialType;
	_wllWallFlags[wallIndex] = flags ^ 4;

	if (decIndex == -1) {
		_wllShapeMap[wallIndex] = 0;
		return;
	}

	do {
		assert(decIndex < _levelDecorationDataSize);
		memcpy(&_levelDecorationProperties[_mappedDecorationsCount], &_levelDecorationData[decIndex], sizeof(LevelDecorationProperty));

		for (int i = 0; i < 10; i++) {
			uint16 t = _levelDecorationProperties[_mappedDecorationsCount].shapeIndex[i];
			if (t == 0xFFFF)
				continue;

			if (_levelDecorationShapes[t])
				continue;

			EoBRect8 *r = &_levelDecorationRects[t];
			if (r->w == 0 || r->h == 0)
				error("Error trying to make decoration %d (x: %d, y: %d, w: %d, h: %d)", decIndex, r->x, r->y, r->w, r->h);

			_levelDecorationShapes[t] = _screen->encodeShape(r->x, r->y, r->w, r->h, false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0);
		}

		decIndex = _levelDecorationProperties[_mappedDecorationsCount++].next;

		if (decIndex)
			_levelDecorationProperties[_mappedDecorationsCount - 1].next = _mappedDecorationsCount + 1;
		else
			decIndex = -1;

	} while (decIndex != -1);
}

void EoBCoreEngine::releaseDecorations() {
	if (_levelDecorationShapes) {
		for (int i = 0; i < 400; i++) {
			delete[] _levelDecorationShapes[i];
			_levelDecorationShapes[i] = 0;
		}
	}
	_mappedDecorationsCount = 0;
}

void EoBCoreEngine::releaseDoorShapes() {
	for (int i = 0; i < 6; i++) {
		delete[] _doorShapes[i];
		_doorShapes[i] = 0;
		delete[] _doorSwitches[i].shp;
		_doorSwitches[i].shp = 0;
	}
}

void EoBCoreEngine::toggleWallState(int wall, int toggle) {
	wall = wall * 10 + 3;

	for (int i = 0; i < 9 ; i++) {
		if (i == 4)
			continue;

		if (toggle)
			_wllWallFlags[wall + i] |= 2;
		else
			_wllWallFlags[wall + i] &= 0xFD;
	}
}

void EoBCoreEngine::drawScene(int refresh) {
	generateBlockDrawingBuffer();
	drawVcnBlocks();
	drawSceneShapes();

	if (_sceneDrawPage2) {
		if (refresh)
			_screen->fillRect(0, 0, 176, 120, 12);

		if (!_loading)
			_screen->setScreenPalette(_screen->getPalette(0));

		_sceneDrawPage2 = 0;
	}

	uint32 ct = _system->getMillis();
	if (_flashShapeTimer > ct) {
		int diff = _flashShapeTimer - ct;
		while ((diff > 0) && !shouldQuit()) {
			updateInput();
			uint32 step = MIN<uint32>(diff, _tickLength / 5);
			_system->delayMillis(step);
			diff -= step;
		}
	}

	if (_sceneDefaultUpdate)
		delayUntil(_drawSceneTimer);

	if (refresh && !_partyResting)
		_screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK);

	updateEnvironmentalSfx(0);

	if (!_dialogueField && refresh && !_updateFlags)
		gui_drawCompass(false);

	if (refresh && !_partyResting && !_loading)
		_screen->updateScreen();

	if (_sceneDefaultUpdate) {
		_sceneDefaultUpdate = false;
		_drawSceneTimer = _system->getMillis() + 4 * _tickLength;
	}

	_sceneUpdateRequired = false;
}

void EoBCoreEngine::drawSceneShapes(int start) {
	for (int i = start; i < 18; i++) {
		uint8 t = _dscTileIndex[i];
		uint8 s = _visibleBlocks[t]->walls[_sceneDrawVarDown];

		_shpDmX1 = 0;
		_shpDmX2 = 0;

		setLevelShapesDim(t, _shpDmX1, _shpDmX2, _sceneShpDim);

		if (_shpDmX2 <= _shpDmX1)
			continue;

		drawDecorations(t);

		if (_visibleBlocks[t]->drawObjects)
			drawBlockItems(t);

		if (t < 15) {
			uint16 w = _wllWallFlags[s];

			if (w & 8)
				drawDoor(t);

			if (_visibleBlocks[t]->flags & 7) {
				const ScreenDim *dm = _screen->getScreenDim(5);
				_screen->modifyScreenDim(5, dm->sx, _lvlShapeTop[t], dm->w, _lvlShapeBottom[t] - _lvlShapeTop[t]);
				drawMonsters(t);
				drawLevelModifyScreenDim(5, _lvlShapeLeftRight[(t << 1)], 0, _lvlShapeLeftRight[(t << 1) + 1], 15);
			}

			if (_flags.gameID == GI_EOB2 && s == 74)
				drawWallOfForce(t);
		}

		drawFlyingObjects(t);

		if (s == _teleporterWallId)
			drawTeleporter(t);
	}
}

void EoBCoreEngine::drawDecorations(int index) {
	for (int i = 1; i >= 0; i--) {
		int s = index * 2 + i;
		if (_dscWallMapping[s]) {
			int16 d = *_dscWallMapping[s];
			int8 l = _wllShapeMap[_visibleBlocks[index]->walls[d]];

			uint8 *shapeData = 0;

			int x = 0;

			while (l > 0) {
				l--;
				int8 ix = _dscShapeIndex[s];
				uint8 shpIx = ABS(ix) - 1;
				uint8 flg = _levelDecorationProperties[l].flags;

				if ((i == 0) && (flg & 1 || ((flg & 2) && _wllProcessFlag)))
					ix = -ix;

				if (_levelDecorationProperties[l].shapeIndex[shpIx] == 0xFFFF) {
					l = _levelDecorationProperties[l].next;
					continue;
				}

				shapeData = _levelDecorationShapes[_levelDecorationProperties[l].shapeIndex[shpIx]];
				if (shapeData) {
					x = 0;
					if (i == 0) {
						if (flg & 4)
							x += _dscShapeCoords[(index * 5 + 4) << 1];
						else
							x += _dscShapeX[index];
					}

					if (ix < 0) {
						x += (176 - _levelDecorationProperties[l].shapeX[shpIx] - (shapeData[2] << 3));
						drawBlockObject(1, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim);
					} else {
						x += _levelDecorationProperties[l].shapeX[shpIx];
						drawBlockObject(0, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim);

					}
				}
				l = _levelDecorationProperties[l].next;
				continue;
			}
		}
	}
}

int EoBCoreEngine::calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction) {
	uint16 b = calcNewBlockPosition(curBlock, direction);
	int w = _levelBlockProperties[b].walls[direction ^ 2];
	int f = _wllWallFlags[w];

	assert((_flags.gameID == GI_EOB1 && w < 70) || (_flags.gameID == GI_EOB2 && w < 80));

	if (_flags.gameID == GI_EOB2 && w == 74 && _currentBlock == curBlock) {
		for (int i = 0; i < 5; i++) {
			if (_wallsOfForce[i].block == b) {
				destroyWallOfForce(i);
				f = _wllWallFlags[0];
			}
		}
	}

	if (!(f & 1) || _levelBlockProperties[b].flags & 7)
		return -1;

	return b;
}

void EoBCoreEngine::notifyBlockNotPassable() {
	_txt->printMessage(_warningStrings[0]);
	snd_playSoundEffect(29);
	removeInputTop();
}

void EoBCoreEngine::moveParty(uint16 block) {
	updateAllMonsterDests();
	uint16 old = _currentBlock;
	_currentBlock = block;

	runLevelScript(old, 2);

	if (++_moveCounter > 3) {
		_txt->printMessage("\r");
		_moveCounter = 0;
	}

	runLevelScript(block, 1);

	if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26)
		memset(_levelBlockProperties[block].walls, 0, 4);

	updateAllMonsterDests();
	_stepCounter++;
	//_keybControlUnk = -1;
	_sceneUpdateRequired = true;

	checkFlyingObjects();
}

int EoBCoreEngine::clickedDoorSwitch(uint16 block, uint16 direction) {
	uint8 v = _visibleBlocks[13]->walls[_sceneDrawVarDown];
	SpriteDecoration *d = &_doorSwitches[((v > 12 && v < 23) || v == 31) ? 3 : 0];
	int x1 = d->x + _dscShapeCoords[138] - 4;
	int y1 = d->y - 4;

	if (_flags.gameID == GI_EOB1 && _currentLevel >= 4 && _currentLevel <= 6) {
		if (v >= 30)
			x1 += 4;
		else
			x1 += ((v - _dscDoorXE[v]) * 9);
	}

	if (!posWithinRect(_mouseX, _mouseY, x1, y1, x1 + (d->shp[2] << 3) + 8, y1 + d->shp[1] + 8) && (_clickedSpecialFlag == 0x40))
		return clickedDoorNoPry(block, direction);

	processDoorSwitch(block, 0);
	snd_playSoundEffect(6);

	return 1;
}

int EoBCoreEngine::clickedNiche(uint16 block, uint16 direction) {
	uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]];
	if (!clickedShape(v))
		return 0;

	if (_itemInHand) {
		if (_dscItemShapeMap[_items[_itemInHand].icon] <= 14) {
			_txt->printMessage(_pryDoorStrings[5]);
		} else {
			setItemPosition((Item *)&_levelBlockProperties[block & 0x3FF].drawObjects, block, _itemInHand, 8);
			runLevelScript(block, 4);
			setHandItem(0);
			_sceneUpdateRequired = true;
		}
	} else {
		int d = getQueuedItem((Item *)&_levelBlockProperties[block].drawObjects, 8, -1);
		if (!d)
			return 1;
		runLevelScript(block, 8);
		setHandItem(d);
		_sceneUpdateRequired = true;
	}

	return 1;
}

int EoBCoreEngine::clickedDoorPry(uint16 block, uint16 direction) {
	if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40))
		return 0;

	int d = -1;
	for (int i = 0; i < 6; i++)  {
		if (!testCharacter(i, 0x0D))
			continue;
		if (d >= 0) {
			int s1 = _characters[i].strengthCur + _characters[i].strengthExtCur;
			int s2 = _characters[d].strengthCur + _characters[d].strengthExtCur;
			if (s1 >= s2)
				d = i;
		} else {
			d = i;
		}
	}

	if (d == -1) {
		_txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 1 : 0]);
		return 1;
	}

	static const uint8 forceDoorChanceTable[] = { 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12, 13 };
	int s = _characters[d].strengthCur > 18 ? 18 : _characters[d].strengthCur;

	if (rollDice(1, 20) < forceDoorChanceTable[s]) {
		_txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 2 : 1]);
		_levelBlockProperties[block].walls[direction] = _levelBlockProperties[block].walls[direction ^ 2] =
		            (_levelBlockProperties[block].walls[direction] == (_flags.gameID == GI_EOB2 ? 51 : 30)) ? 8 : 18;
		openDoor(block);
	} else {
		_txt->printMessage(_pryDoorStrings[3]);
	}

	return 1;
}

int EoBCoreEngine::clickedDoorNoPry(uint16 block, uint16 direction) {
	if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40))
		return 0;

	if (!(_wllWallFlags[_levelBlockProperties[block].walls[direction]] & 0x20))
		return 0;
	_txt->printMessage(_pryDoorStrings[6]);
	return 1;
}

int EoBCoreEngine::specialWallAction(int block, int direction) {
	direction ^= 2;
	uint8 type = _specialWallTypes[_levelBlockProperties[block].walls[direction]];
	if (!type || !(_clickedSpecialFlag & (((_levelBlockProperties[block].flags & 0xF8) >> 3) | 0xE0)))
		return 0;

	int res = 0;
	switch (type) {
	case 1:
		res = clickedDoorSwitch(block, direction);
		break;

	case 2:
	case 8:
		res = clickedWallShape(block, direction);
		break;

	case 3:
		res = clickedLeverOn(block, direction);
		break;

	case 4:
		res = clickedLeverOff(block, direction);
		break;

	case 5:
		res =  clickedDoorPry(block, direction);
		break;

	case 6:
		res = clickedDoorNoPry(block, direction);
		break;

	case 7:
	case 9:
		res = clickedWallOnlyScript(block);
		break;

	case 10:
		res = clickedNiche(block, direction);
		break;

	default:
		break;
	}

	_clickedSpecialFlag = 0;
	_sceneUpdateRequired = true;

	return res;
}

void EoBCoreEngine::openDoor(int block) {
	openCloseDoor(block, 1);
}

void EoBCoreEngine::closeDoor(int block) {
	if (block == _currentBlock || _levelBlockProperties[block].flags & 7)
		return;
	openCloseDoor(block, -1);
}

} // End of namespace Kyra

#endif // ENABLE_EOB