/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/debug.h"
#include "common/events.h"
#include "common/file.h"
#include "common/random.h"
#include "common/fs.h"
#include "common/keyboard.h"
#include "common/substream.h"
#include "common/str.h"

#include "graphics/cursorman.h"
#include "graphics/surface.h"
#include "graphics/palette.h"
#include "graphics/pixelformat.h"

#include "engines/util.h"
#include "engines/advancedDetector.h"

#include "audio/audiostream.h"

#include "prince/prince.h"
#include "prince/font.h"
#include "prince/graphics.h"
#include "prince/script.h"
#include "prince/debugger.h"
#include "prince/object.h"
#include "prince/mob.h"
#include "prince/sound.h"
#include "prince/variatxt.h"
#include "prince/flags.h"
#include "prince/font.h"
#include "prince/mhwanh.h"
#include "prince/cursor.h"
#include "prince/archive.h"
#include "prince/hero.h"
#include "prince/resource.h"
#include "prince/animation.h"
#include "prince/option_text.h"
#include "prince/curve_values.h"
#include "prince/detection.h"

namespace Prince {

void PrinceEngine::debugEngine(const char *s, ...) {
	char buf[STRINGBUFLEN];
	va_list va;

	va_start(va, s);
	vsnprintf(buf, STRINGBUFLEN, s, va);
	va_end(va);

	debug("Prince::Engine %s", buf);
}

PrinceEngine::PrinceEngine(OSystem *syst, const PrinceGameDescription *gameDesc) :
	Engine(syst), _gameDescription(gameDesc), _graph(nullptr), _script(nullptr), _interpreter(nullptr), _flags(nullptr),
	_locationNr(0), _debugger(nullptr), _midiPlayer(nullptr), _room(nullptr),
	_cursor1(nullptr), _cursor2(nullptr), _cursor3(nullptr), _font(nullptr),
	_suitcaseBmp(nullptr), _roomBmp(nullptr), _cursorNr(0), _picWindowX(0), _picWindowY(0), _randomSource("prince"),
	_invLineX(134), _invLineY(176), _invLine(5), _invLines(3), _invLineW(70), _invLineH(76), _maxInvW(72), _maxInvH(76),
	_invLineSkipX(2), _invLineSkipY(3), _showInventoryFlag(false), _inventoryBackgroundRemember(false),
	_mst_shadow(0), _mst_shadow2(0), _candleCounter(0), _invX1(53), _invY1(18), _invWidth(536), _invHeight(438),
	_invCurInside(false), _optionsFlag(false), _optionEnabled(0), _invExamY(120), _invMaxCount(2), _invCounter(0),
	_optionsMob(-1), _currentPointerNumber(1), _selectedMob(-1), _selectedItem(0), _selectedMode(0),
	_optionsWidth(210), _optionsHeight(170), _invOptionsWidth(210), _invOptionsHeight(130), _optionsStep(20),
	_invOptionsStep(20), _optionsNumber(7), _invOptionsNumber(5), _optionsColor1(236), _optionsColor2(252),
	_dialogWidth(600), _dialogHeight(0), _dialogLineSpace(10), _dialogColor1(220), _dialogColor2(223),
	_dialogFlag(false), _dialogLines(0), _dialogText(nullptr), _mouseFlag(1),
	_roomPathBitmap(nullptr), _roomPathBitmapTemp(nullptr), _coordsBufEnd(nullptr), _coordsBuf(nullptr), _coords(nullptr),
	_traceLineLen(0), _rembBitmapTemp(nullptr), _rembBitmap(nullptr), _rembMask(0), _rembX(0), _rembY(0), _fpX(0), _fpY(0),
	_checkBitmapTemp(nullptr), _checkBitmap(nullptr), _checkMask(0), _checkX(0), _checkY(0), _traceLineFirstPointFlag(false),
	_tracePointFirstPointFlag(false), _coordsBuf2(nullptr), _coords2(nullptr), _coordsBuf3(nullptr), _coords3(nullptr),
	_shanLen(0), _directionTable(nullptr), _currentMidi(0), _lightX(0), _lightY(0), _curveData(nullptr), _curvPos(0),
	_creditsData(nullptr), _creditsDataSize(0), _currentTime(0), _zoomBitmap(nullptr), _shadowBitmap(nullptr), _transTable(nullptr),
	_flcFrameSurface(nullptr), _shadScaleValue(0), _shadLineLen(0), _scaleValue(0), _dialogImage(nullptr), _mobTranslationData(nullptr),
	_mobTranslationSize(0) {

	// Debug/console setup
	DebugMan.addDebugChannel(DebugChannel::kScript, "script", "Prince Script debug channel");
	DebugMan.addDebugChannel(DebugChannel::kEngine, "engine", "Prince Engine debug channel");

	DebugMan.enableDebugChannel("script");

	memset(_audioStream, 0, sizeof(_audioStream));

	gDebugLevel = 10;
}

PrinceEngine::~PrinceEngine() {
	DebugMan.clearAllDebugChannels();

	delete _rnd;
	delete _debugger;
	delete _cursor1;
	delete _cursor3;
	delete _midiPlayer;
	delete _script;
	delete _flags;
	delete _interpreter;
	delete _font;
	delete _roomBmp;
	delete _suitcaseBmp;
	delete _variaTxt;
	free(_talkTxt);
	free(_invTxt);
	free(_dialogDat);
	delete _graph;
	delete _room;

	if (_cursor2 != nullptr) {
		_cursor2->free();
		delete _cursor2;
	}

	for (uint i = 0; i < _objList.size(); i++) {
		delete _objList[i];
	}
	_objList.clear();

	free(_objSlot);

	for (uint32 i = 0; i < _pscrList.size(); i++) {
		delete _pscrList[i];
	}
	_pscrList.clear();

	for (uint i = 0; i < _maskList.size(); i++) {
		free(_maskList[i]._data);
	}
	_maskList.clear();

	_drawNodeList.clear();

	clearBackAnimList();
	_backAnimList.clear();

	freeAllNormAnims();
	_normAnimList.clear();

	for (uint i = 0; i < _allInvList.size(); i++) {
		_allInvList[i]._surface->free();
		delete _allInvList[i]._surface;
	}
	_allInvList.clear();

	_optionsPic->free();
	delete _optionsPic;

	_optionsPicInInventory->free();
	delete _optionsPicInInventory;

	for (uint i = 0; i < _mainHero->_moveSet.size(); i++) {
		delete _mainHero->_moveSet[i];
	}

	for (uint i = 0; i < _secondHero->_moveSet.size(); i++) {
		delete _secondHero->_moveSet[i];
	}

	delete _mainHero;
	delete _secondHero;

	free(_roomPathBitmap);
	free(_roomPathBitmapTemp);
	free(_coordsBuf);

	_mobPriorityList.clear();

	freeAllSamples();

	free(_zoomBitmap);
	free(_shadowBitmap);
	free(_transTable);

	free(_curveData);

	free(_shadowLine);

	free(_creditsData);

	if (_dialogImage != nullptr) {
		_dialogImage->free();
		delete _dialogImage;
	}

	free(_mobTranslationData);
}

GUI::Debugger *PrinceEngine::getDebugger() {
	return _debugger;
}

void PrinceEngine::init() {

	const Common::FSNode gameDataDir(ConfMan.get("path"));

	debugEngine("Adding all path: %s", gameDataDir.getPath().c_str());

	PtcArchive *all = new PtcArchive();
	if (!all->open("all/databank.ptc"))
		error("Can't open all/databank.ptc");

	PtcArchive *voices = new PtcArchive();
	if (!voices->open("data/voices/databank.ptc"))
		error("Can't open data/voices/databank.ptc");

	PtcArchive *sound = new PtcArchive();
	if (!sound->open("sound/databank.ptc"))
		error("Can't open sound/databank.ptc");

	PtcArchive *translation = new PtcArchive();
	if (getLanguage() != Common::PL_POL && getLanguage() != Common::DE_DEU) {
		if (!translation->openTranslation("all/prince_translation.dat"))
			error("Can't open prince_translation.dat");
	}

	SearchMan.addSubDirectoryMatching(gameDataDir, "all");

	SearchMan.add("all", all);
	SearchMan.add("voices", voices);
	SearchMan.add("sound", sound);
	if (getLanguage() != Common::PL_POL && getLanguage() != Common::DE_DEU) {
		SearchMan.add("translation", translation);
	}

	_graph = new GraphicsMan(this);

	_rnd = new Common::RandomSource("prince");

	_midiPlayer = new MusicPlayer(this);

	if (getLanguage() == Common::DE_DEU) {
		_font = new Font();
		Resource::loadResource(_font, "font3.raw", true);
	} else {
		_font = new Font();
		Resource::loadResource(_font, "font1.raw", true);
	}

	_suitcaseBmp = new MhwanhDecoder();
	Resource::loadResource(_suitcaseBmp, "walizka", true);

	_script = new Script(this);
	Resource::loadResource(_script, "skrypt.dat", true);

	_flags = new InterpreterFlags();
	_interpreter = new Interpreter(this, _script, _flags);

	_debugger = new Debugger(this, _flags);

	_variaTxt = new VariaTxt();
	if (getLanguage() == Common::PL_POL || getLanguage() == Common::DE_DEU) {
		Resource::loadResource(_variaTxt, "variatxt.dat", true);
	} else {
		Resource::loadResource(_variaTxt, "variatxt_translate.dat", true);
	}

	_cursor1 = new Cursor();
	Resource::loadResource(_cursor1, "mouse1.cur", true);

	_cursor3 = new Cursor();
	Resource::loadResource(_cursor3, "mouse2.cur", true);

	Common::SeekableReadStream *talkTxtStream;
	if (getLanguage() == Common::PL_POL || getLanguage() == Common::DE_DEU) {
		talkTxtStream = SearchMan.createReadStreamForMember("talktxt.dat");
	} else {
		talkTxtStream = SearchMan.createReadStreamForMember("talktxt_translate.dat");
	}
	if (!talkTxtStream) {
		error("Can't load talkTxtStream");
		return;
	}
	_talkTxtSize = talkTxtStream->size();
	_talkTxt = (byte *)malloc(_talkTxtSize);
	talkTxtStream->read(_talkTxt, _talkTxtSize);

	delete talkTxtStream;

	Common::SeekableReadStream *invTxtStream;
	if (getLanguage() == Common::PL_POL || getLanguage() == Common::DE_DEU) {
		invTxtStream = SearchMan.createReadStreamForMember("invtxt.dat");
	} else {
		invTxtStream = SearchMan.createReadStreamForMember("invtxt_translate.dat");
	}
	if (!invTxtStream) {
		error("Can't load invTxtStream");
		return;
	}
	_invTxtSize = invTxtStream->size();
	_invTxt = (byte *)malloc(_invTxtSize);
	invTxtStream->read(_invTxt, _invTxtSize);

	delete invTxtStream;

	loadAllInv();

	Common::SeekableReadStream *dialogDatStream = SearchMan.createReadStreamForMember("dialog.dat");
	if (!dialogDatStream) {
		error("Can't load dialogDatStream");
		return;
	}
	_dialogDatSize = dialogDatStream->size();
	_dialogDat = (byte *)malloc(_dialogDatSize);
	dialogDatStream->read(_dialogDat, _dialogDatSize);

	delete dialogDatStream;

	_optionsPic = new Graphics::Surface();
	_optionsPic->create(_optionsWidth, _optionsHeight, Graphics::PixelFormat::createFormatCLUT8());
	Common::Rect picRect(0, 0, _optionsWidth, _optionsHeight);
	_optionsPic->fillRect(picRect, _graph->kShadowColor);

	_optionsPicInInventory = new Graphics::Surface();
	_optionsPicInInventory->create(_invOptionsWidth, _invOptionsHeight, Graphics::PixelFormat::createFormatCLUT8());
	Common::Rect invPicRect(0, 0, _invOptionsWidth, _invOptionsHeight);
	_optionsPicInInventory->fillRect(invPicRect, _graph->kShadowColor);

	_roomBmp = new Image::BitmapDecoder();

	_room = new Room();

	_mainHero = new Hero(this, _graph);
	_secondHero = new Hero(this, _graph);
	_secondHero->_maxBoredom = 140;
	_secondHero->loadAnimSet(3);

	_roomPathBitmap = (byte *)malloc(kPathBitmapLen);
	_roomPathBitmapTemp = (byte *)malloc(kPathBitmapLen);
	_coordsBuf = (byte *)malloc(kTracePts * 4);
	_coords = _coordsBuf;
	_coordsBufEnd = _coordsBuf + kTracePts * 4 - 4;

	BackgroundAnim tempBackAnim;
	tempBackAnim._seq._currRelative = 0;
	for (int i = 0; i < kMaxBackAnims; i++) {
		_backAnimList.push_back(tempBackAnim);
	}

	Anim tempAnim;
	tempAnim._animData = nullptr;
	tempAnim._shadowData = nullptr;
	for (int i = 0; i < kMaxNormAnims; i++) {
		_normAnimList.push_back(tempAnim);
	}

	_objSlot = (uint16 *)malloc(kMaxObjects * sizeof(uint16));
	for (int i = 0; i < kMaxObjects; i++) {
		_objSlot[i] = 0xFF;
	}

	_zoomBitmap = (byte *)malloc(kZoomBitmapLen);
	_shadowBitmap = (byte *)malloc(2 * kShadowBitmapSize);
	_transTable = (byte *)malloc(kTransTableSize);

	_curveData = (int16 *)malloc(2 * kCurveLen * sizeof(int16));

	_shadowLine = (byte *)malloc(kShadowLineArraySize);

	Common::SeekableReadStream *creditsDataStream;
	if (getLanguage() == Common::PL_POL || getLanguage() == Common::DE_DEU) {
		creditsDataStream = SearchMan.createReadStreamForMember("credits.dat");
	} else {
		creditsDataStream = SearchMan.createReadStreamForMember("credits_translate.dat");
	}
	if (!creditsDataStream) {
		error("Can't load creditsDataStream");
		return;
	}
	_creditsDataSize = creditsDataStream->size();
	_creditsData = (byte *)malloc(_creditsDataSize);
	creditsDataStream->read(_creditsData, _creditsDataSize);
	delete creditsDataStream;

	if (getLanguage() != Common::PL_POL && getLanguage() != Common::DE_DEU) {
		loadMobTranslationTexts();
	}
}

void PrinceEngine::showLogo() {
	MhwanhDecoder logo;
	if (Resource::loadResource(&logo, "logo.raw", true)) {
		loadSample(0, "LOGO.WAV");
		playSample(0, 0);
		_graph->draw(_graph->_frontScreen, logo.getSurface());
		_graph->change();
		_graph->update(_graph->_frontScreen);
		setPalette(logo.getPalette());

		uint32 logoStart = _system->getMillis();
		while (_system->getMillis() < logoStart + 5000) {
			Common::Event event;
			Common::EventManager *eventMan = _system->getEventManager();
			while (eventMan->pollEvent(event)) {
				switch (event.type) {
				case Common::EVENT_KEYDOWN:
					if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
						stopSample(0);
						return;
					}
					break;
				case Common::EVENT_LBUTTONDOWN:
					stopSample(0);
					return;
				default:
					break;
				}
			}

			if (shouldQuit()) {
				return;
			}
		}
	}
}

Common::Error PrinceEngine::run() {
	syncSoundSettings();
	int startGameSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
	init();
	if (startGameSlot == -1) {
		showLogo();
	} else {
		loadLocation(59); // load intro location - easiest way to set everything up
		loadGame(startGameSlot);
	}
	mainLoop();
	return Common::kNoError;
}

void PrinceEngine::pauseEngineIntern(bool pause) {
	Engine::pauseEngineIntern(pause);
	if (pause) {
		_midiPlayer->pause();
	}
	else {
		_midiPlayer->resume();
	}
}

bool AnimListItem::loadFromStream(Common::SeekableReadStream &stream) {
	int32 pos = stream.pos();

	uint16 type = stream.readUint16LE();
	if (type == 0xFFFF) {
		return false;
	}
	_type = type;
	_fileNumber = stream.readUint16LE();
	_startPhase = stream.readUint16LE();
	_endPhase = stream.readUint16LE();
	_loopPhase = stream.readUint16LE();
	_x = stream.readSint16LE();
	_y = stream.readSint16LE();
	_loopType = stream.readUint16LE();
	_nextAnim = stream.readUint16LE();
	_flags = stream.readUint16LE();

	//debug("AnimListItem type %d, fileNumber %d, x %d, y %d, flags %d", _type, _fileNumber, _x, _y, _flags);
	//debug("startPhase %d, endPhase %d, loopPhase %d", _startPhase, _endPhase, _loopPhase);

	// 32 byte aligment
	stream.seek(pos + 32);

	return true;
}

bool PrinceEngine::loadLocation(uint16 locationNr) {

	blackPalette();

	_flicPlayer.close();

	memset(_textSlots, 0, sizeof(_textSlots));
	freeAllSamples();

	debugEngine("PrinceEngine::loadLocation %d", locationNr);
	const Common::FSNode gameDataDir(ConfMan.get("path"));
	SearchMan.remove(Common::String::format("%02d", _locationNr));

	_locationNr = locationNr;
	_debugger->_locationNr = locationNr;

	_flags->setFlagValue(Flags::CURRROOM, _locationNr);
	_interpreter->stopBg();

	changeCursor(0);

	const Common::String locationNrStr = Common::String::format("%02d", _locationNr);
	debugEngine("loadLocation %s", locationNrStr.c_str());

	PtcArchive *locationArchive = new PtcArchive();
	if (!locationArchive->open(locationNrStr + "/databank.ptc"))
		error("Can't open location %s", locationNrStr.c_str());

	SearchMan.add(locationNrStr, locationArchive);

	loadMusic(_locationNr);

	// load location background, replace old one
	Resource::loadResource(_roomBmp, "room", true);
	if (_roomBmp->getSurface()) {
		_sceneWidth = _roomBmp->getSurface()->w;
	}

	loadZoom(_zoomBitmap, kZoomBitmapLen, "zoom");
	loadShadow(_shadowBitmap, kShadowBitmapSize, "shadow", "shadow2");
	loadTrans(_transTable, "trans");
	loadPath("path");

	for (uint32 i = 0; i < _pscrList.size(); i++) {
		delete _pscrList[i];
	}
	_pscrList.clear();
	Resource::loadResource(_pscrList, "pscr.lst", false);

	loadMobPriority("mobpri");

	_mobList.clear();
	if (getGameType() == kPrinceDataDE) {
		const Common::String mobLstName = Common::String::format("mob%02d.lst", _locationNr);
		debug("name: %s", mobLstName.c_str());
		Resource::loadResource(_mobList, mobLstName.c_str(), false);
	} else if (getGameType() == kPrinceDataPL) {
		Resource::loadResource(_mobList, "mob.lst", false);
	}
	if (getLanguage() != Common::PL_POL && getLanguage() != Common::DE_DEU) {
		// update Mob texts for translated version
		setMobTranslationTexts();
	}

	_animList.clear();
	Resource::loadResource(_animList, "anim.lst", false);

	for (uint32 i = 0; i < _objList.size(); i++) {
		delete _objList[i];
	}
	_objList.clear();
	Resource::loadResource(_objList, "obj.lst", false);

	_room->loadRoom(_script->getRoomOffset(_locationNr));

	for (uint i = 0; i < _maskList.size(); i++) {
		free(_maskList[i]._data);
	}
	_maskList.clear();
	_script->loadAllMasks(_maskList, _room->_nak);

	_picWindowX = 0;

	_lightX = _script->getLightX(_locationNr);
	_lightY = _script->getLightY(_locationNr);
	setShadowScale(_script->getShadowScale(_locationNr));

	for (uint i = 0; i < _mobList.size(); i++) {
		_mobList[i]._visible = _script->getMobVisible(_room->_mobs, i);
	}

	_script->installObjects(_room->_obj);

	freeAllNormAnims();

	clearBackAnimList();
	_script->installBackAnims(_backAnimList, _room->_backAnim);

	_graph->makeShadowTable(70, _graph->_shadowTable70);
	_graph->makeShadowTable(50, _graph->_shadowTable50);

	_mainHero->freeOldMove();
	_secondHero->freeOldMove();

	_mainHero->scrollHero();

	return true;
}

void PrinceEngine::setShadowScale(int32 shadowScale) {
	shadowScale = 100 - shadowScale;
	if (!shadowScale) {
		_shadScaleValue = 10000;
	} else {
		_shadScaleValue = 10000 / shadowScale;
	}
}

void PrinceEngine::plotShadowLinePoint(int x, int y, int color, void *data) {
	PrinceEngine *vm = (PrinceEngine *)data;
	WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4], x);
	WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4 + 2], y);
	vm->_shadLineLen++;
}

void PrinceEngine::changeCursor(uint16 curId) {
	_debugger->_cursorNr = curId;
	_mouseFlag = curId;
	_flags->setFlagValue(Flags::MOUSEENABLED, curId);

	const Graphics::Surface *curSurface = nullptr;

	switch (curId) {
	case 0:
		CursorMan.showMouse(false);
		_optionsFlag = 0;
		_selectedMob = -1;
		return;
	case 1:
		curSurface = _cursor1->getSurface();
		break;
	case 2:
		curSurface = _cursor2;
		break;
	case 3:
		curSurface = _cursor3->getSurface();
		Common::Point mousePos = _system->getEventManager()->getMousePos();
		mousePos.x = CLIP(mousePos.x, (int16) 315, (int16) 639);
		mousePos.y = CLIP(mousePos.y, (int16) 0, (int16) 170);
		_system->warpMouse(mousePos.x, mousePos.y);
		break;
	}

	CursorMan.replaceCursorPalette(_roomBmp->getPalette(), 0, 255);
	CursorMan.replaceCursor(
		curSurface->getBasePtr(0, 0),
		curSurface->w, curSurface->h,
		0, 0,
		255, false,
		&curSurface->format
	);
	CursorMan.showMouse(true);
}

void PrinceEngine::makeInvCursor(int itemNr) {
	const Graphics::Surface *cur1Surface = _cursor1->getSurface();
	int cur1W = cur1Surface->w;
	int cur1H = cur1Surface->h;
	const Common::Rect cur1Rect(0, 0, cur1W, cur1H);

	const Graphics::Surface *itemSurface = _allInvList[itemNr].getSurface();
	int itemW = itemSurface->w;
	int itemH = itemSurface->h;

	int cur2W = cur1W + itemW / 2;
	int cur2H = cur1H + itemH / 2;

	if (_cursor2 != nullptr) {
		_cursor2->free();
		delete _cursor2;
	}
	_cursor2 = new Graphics::Surface();
	_cursor2->create(cur2W, cur2H, Graphics::PixelFormat::createFormatCLUT8());
	Common::Rect cur2Rect(0, 0, cur2W, cur2H);
	_cursor2->fillRect(cur2Rect, 255);
	_cursor2->copyRectToSurface(*cur1Surface, 0, 0, cur1Rect);

	const byte *src1 = (const byte *)itemSurface->getBasePtr(0, 0);
	byte *dst1 = (byte *)_cursor2->getBasePtr(cur1W, cur1H);

	if (itemH % 2) {
		itemH--;
	}
	if (itemW % 2) {
		itemW--;
	}

	for (int y = 0; y < itemH; y++) {
		const byte *src2 = src1;
		byte *dst2 = dst1;
		if (y % 2 == 0) {
			for (int x = 0; x < itemW; x++, src2++) {
				if (x % 2 == 0) {
					if (*src2) {
						*dst2 = *src2;
					} else {
						*dst2 = 255;
					}
					dst2++;
				}
			}
			dst1 += _cursor2->pitch;
		}
		src1 += itemSurface->pitch;
	}
}

bool PrinceEngine::loadMusic(int musNumber) {
	uint8 midiNumber = MusicPlayer::_musRoomTable[musNumber];
	if (midiNumber) {
		if (midiNumber != 100) {
			 if (_currentMidi != midiNumber) {
				_currentMidi = midiNumber;
				const char *musName = MusicPlayer::_musTable[_currentMidi];
				_midiPlayer->loadMidi(musName);
			 }
		}
	} else {
		stopMusic();
	}
	return true;
}

void PrinceEngine::stopMusic() {
	if (_midiPlayer->isPlaying()) {
		_midiPlayer->stop();
	}
}

bool PrinceEngine::playNextFLCFrame() {
	if (!_flicPlayer.isVideoLoaded())
		return false;

	const Graphics::Surface *s = _flicPlayer.decodeNextFrame();
	if (s) {
		_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, s, 255);
		_graph->change();
		_flcFrameSurface = s;
	} else if (_flicLooped) {
		_flicPlayer.rewind();
		playNextFLCFrame();
	} else if (_flcFrameSurface) {
		_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, _flcFrameSurface, 255);
		_graph->change();
	}

	return true;
}

void PrinceEngine::playSample(uint16 sampleId, uint16 loopType) {
	if (_audioStream[sampleId]) {
		if (_mixer->isSoundIDActive(sampleId)) {
			return;
		}
		_audioStream[sampleId]->rewind();
		if (sampleId < 28) {
			_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle[sampleId], _audioStream[sampleId], sampleId, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
		} else {
			_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle[sampleId], _audioStream[sampleId], sampleId, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
		}
	}
}

void PrinceEngine::stopSample(uint16 sampleId) {
	_mixer->stopID(sampleId);
}

void PrinceEngine::stopAllSamples() {
	_mixer->stopAll();
}

void PrinceEngine::freeSample(uint16 sampleId) {
	stopSample(sampleId);
	if (_audioStream[sampleId] != nullptr) {
		delete _audioStream[sampleId];
		_audioStream[sampleId] = nullptr;
	}
}

void PrinceEngine::freeAllSamples() {
	for (int sampleId = 0; sampleId < kMaxSamples; sampleId++) {
		freeSample(sampleId);
	}
}

bool PrinceEngine::loadSample(uint32 sampleSlot, const Common::String &streamName) {
	// FIXME: This is just a workaround streamName is a path
	// SOUND\\SCIERKA1.WAV for now only last path component is used
	Common::String normalizedPath = lastPathComponent(streamName, '\\');

	// WALKAROUND: Wrong name in script, not existing sound in data files
	if (!normalizedPath.compareTo("9997BEKA.WAV")) {
		return 0;
	}

	debugEngine("loadSample slot %d, name %s", sampleSlot, normalizedPath.c_str());

	freeSample(sampleSlot);
	Common::SeekableReadStream *sampleStream = SearchMan.createReadStreamForMember(normalizedPath);
	if (sampleStream == nullptr) {
		delete sampleStream;
		error("Can't load sample %s to slot %d", normalizedPath.c_str(), sampleSlot);
	}
	_audioStream[sampleSlot] = Audio::makeWAVStream(sampleStream, DisposeAfterUse::NO);
	delete sampleStream;
	return true;
}

bool PrinceEngine::loadVoice(uint32 slot, uint32 sampleSlot, const Common::String &streamName) {
	debugEngine("Loading wav %s slot %d", streamName.c_str(), slot);

	if (slot >= kMaxTexts) {
		error("Text slot bigger than MAXTEXTS %d", kMaxTexts - 1);
		return false;
	}

	freeSample(sampleSlot);
	Common::SeekableReadStream *sampleStream = SearchMan.createReadStreamForMember(streamName);
	if (sampleStream == nullptr) {
		debug("Can't open %s", streamName.c_str());
		return false;
	}

	uint32 id = sampleStream->readUint32LE();
	if (id != MKTAG('F', 'F', 'I', 'R')) {
		error("It's not RIFF file %s", streamName.c_str());
		return false;
	}

	sampleStream->skip(0x20);
	id = sampleStream->readUint32LE();
	if (id != MKTAG('a', 't', 'a', 'd')) {
		error("No data section in %s id %04x", streamName.c_str(), id);
		return false;
	}

	id = sampleStream->readUint32LE();
	debugEngine("SetVoice slot %d time %04x", slot, id);
	id <<= 3;
	id /= 22050;
	id += 2;

	_textSlots[slot]._time = id;
	if (!slot) {
		_mainHero->_talkTime = id;
	} else if (slot == 1) {
		_secondHero->_talkTime = id;
	}

	debugEngine("SetVoice slot %d time %04x", slot, id);
	sampleStream->seek(SEEK_SET);
	_audioStream[sampleSlot] = Audio::makeWAVStream(sampleStream, DisposeAfterUse::NO);
	delete sampleStream;
	return true;
}

void PrinceEngine::setVoice(uint16 slot, uint32 sampleSlot, uint16 flag) {
	Common::String sampleName;
	uint32 currentString = _interpreter->getCurrentString();

	if (currentString >= 80000) {
		uint32 nr = currentString - 80000;
		sampleName = Common::String::format("%02d0%02d-%02d.WAV", nr / 100, nr % 100, flag);
	} else if (currentString >= 70000) {
		sampleName = Common::String::format("inv%02d-01.WAV", currentString - 70000);
	} else if (currentString >= 60000) {
		sampleName = Common::String::format("M%04d-%02d.WAV", currentString - 60000, flag);
	} else if (currentString >= 2000) {
		return;
	} else if (flag >= 100) {
		sampleName = Common::String::format("%03d-%03d.WAV", currentString, flag);
	} else {
		sampleName = Common::String::format("%03d-%02d.WAV", currentString, flag);
	}

	loadVoice(slot, sampleSlot, sampleName);
}

bool PrinceEngine::loadAnim(uint16 animNr, bool loop) {
	Common::String streamName = Common::String::format("AN%02d", animNr);
	Common::SeekableReadStream *flicStream = SearchMan.createReadStreamForMember(streamName);

	if (!flicStream) {
		error("Can't open %s", streamName.c_str());
		return false;
	}

	if (!_flicPlayer.loadStream(flicStream)) {
		error("Can't load flic stream %s", streamName.c_str());
	}

	debugEngine("%s loaded", streamName.c_str());
	_flicLooped = loop;
	_flicPlayer.start();
	playNextFLCFrame();
	return true;
}

bool PrinceEngine::loadZoom(byte *zoomBitmap, uint32 dataSize, const char *resourceName) {
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(resourceName);
	if (!stream) {
		delete stream;
		return false;
	}
	if (stream->read(zoomBitmap, dataSize) != dataSize) {
		free(zoomBitmap);
		delete stream;
		return false;
	}
	delete stream;
	return true;
}

bool PrinceEngine::loadShadow(byte *shadowBitmap, uint32 dataSize, const char *resourceName1, const char *resourceName2) {
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(resourceName1);
	if (!stream) {
		delete stream;
		return false;
	}

	if (stream->read(shadowBitmap, dataSize) != dataSize) {
		free(shadowBitmap);
		delete stream;
		return false;
	}

	Common::SeekableReadStream *stream2 = SearchMan.createReadStreamForMember(resourceName2);
	if (!stream2) {
		delete stream;
		delete stream2;
		return false;
	}

	byte *shadowBitmap2 = shadowBitmap + dataSize;
	if (stream2->read(shadowBitmap2, dataSize) != dataSize) {
		free(shadowBitmap);
		delete stream;
		delete stream2;
		return false;
	}

	delete stream;
	delete stream2;
	return true;
}

bool PrinceEngine::loadTrans(byte *transTable, const char *resourceName) {
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(resourceName);
	if (!stream) {
		delete stream;
		for (int i = 0; i < 256; i++) {
			for (int j = 0; j < 256; j++) {
				transTable[i * 256 + j] = j;
			}
		}
		return true;
	}
	if (stream->read(transTable, kTransTableSize) != kTransTableSize) {
		delete stream;
		return false;
	}
	delete stream;
	return true;
}

bool PrinceEngine::loadPath(const char *resourceName) {
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(resourceName);
	if (!stream) {
		delete stream;
		return false;
	}
	if (stream->read(_roomPathBitmap, kPathBitmapLen) != kPathBitmapLen) {
		delete stream;
		return false;
	}
	delete stream;
	return true;
}

bool PrinceEngine::loadAllInv() {
	for (int i = 0; i < kMaxInv; i++) {
		InvItem tempInvItem;

		const Common::String invStreamName = Common::String::format("INV%02d", i);
		Common::SeekableReadStream *invStream = SearchMan.createReadStreamForMember(invStreamName);
		if (!invStream) {
			delete invStream;
			return true;
		}

		tempInvItem._x = invStream->readUint16LE();
		tempInvItem._y = invStream->readUint16LE();
		int width = invStream->readUint16LE();
		int height = invStream->readUint16LE();
		tempInvItem._surface = new Graphics::Surface();
		tempInvItem._surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());

		for (int h = 0; h < tempInvItem._surface->h; h++) {
			invStream->read(tempInvItem._surface->getBasePtr(0, h), tempInvItem._surface->w);
		}

		_allInvList.push_back(tempInvItem);
		delete invStream;
	}

	return true;
}

bool PrinceEngine::loadMobPriority(const char *resourceName) {
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(resourceName);
	if (!stream) {
		delete stream;
		return false;
	}

	_mobPriorityList.clear();
	uint mobId;
	while (1) {
		mobId = stream->readUint32LE();
		if (mobId == 0xFFFFFFFF) {
			break;
		}
		_mobPriorityList.push_back(mobId);
	}
	delete stream;
	return true;
}

void PrinceEngine::loadMobTranslationTexts() {
	Common::SeekableReadStream *mobTranslationStream = SearchMan.createReadStreamForMember("mob_translate.dat");
	if (!mobTranslationStream) {
		error("Can't load mob_translate.dat");
	}
	_mobTranslationSize = mobTranslationStream->size();
	_mobTranslationData = (byte *)malloc(_mobTranslationSize);
	mobTranslationStream->read(_mobTranslationData, _mobTranslationSize);
	delete mobTranslationStream;
}

void PrinceEngine::setMobTranslationTexts() {
	int locationOffset = READ_UINT16(_mobTranslationData + (_locationNr - 1) * 2);
	if (locationOffset) {
		byte *locationText = _mobTranslationData + locationOffset;
		for (uint i = 0; i < _mobList.size(); i++) {
			byte c;
			locationText++;
			_mobList[i]._name.clear();
			while ((c = *locationText)) {
				_mobList[i]._name += c;
				locationText++;
			}
			locationText++;
			_mobList[i]._examText.clear();
			c = *locationText;
			locationText++;
			if (c) {
				_mobList[i]._examText += c;
				do {
					c = *locationText;
					_mobList[i]._examText += c;
					locationText++;
				} while (c != 255);
			}
		}
	}
}

void PrinceEngine::keyHandler(Common::Event event) {
	uint16 nChar = event.kbd.keycode;
	switch (nChar) {
	case Common::KEYCODE_d:
		if (event.kbd.hasFlags(Common::KBD_CTRL)) {
			getDebugger()->attach();
		}
		break;
	case Common::KEYCODE_z:
		if (_flags->getFlagValue(Flags::POWERENABLED)) {
			_flags->setFlagValue(Flags::MBFLAG, 1);
		}
		break;
	case Common::KEYCODE_x:
		if (_flags->getFlagValue(Flags::POWERENABLED)) {
			_flags->setFlagValue(Flags::MBFLAG, 2);
		}
		break;
	case Common::KEYCODE_ESCAPE:
		_flags->setFlagValue(Flags::ESCAPED2, 1);
		break;
	}
}

int PrinceEngine::getMob(Common::Array<Mob> &mobList, bool usePriorityList, int posX, int posY) {

	Common::Point pointPos(posX, posY);

	int mobListSize;
	if (usePriorityList) {
		mobListSize = _mobPriorityList.size();
	} else {
		mobListSize = mobList.size();
	}

	for (int mobNumber = 0; mobNumber < mobListSize; mobNumber++) {
		Mob *mob = nullptr;
		if (usePriorityList) {
			mob = &mobList[_mobPriorityList[mobNumber]];
		} else {
			mob = &mobList[mobNumber];
		}

		if (mob->_visible) {
			continue;
		}

		int type = mob->_type & 7;
		switch (type) {
		case 0:
		case 1:
			//normal_mob
			if (!mob->_rect.contains(pointPos)) {
				continue;
			}
			break;
		case 3:
			//mob_obj
			if (mob->_mask < kMaxObjects) {
				int nr = _objSlot[mob->_mask];
				if (nr != 0xFF) {
					Object &obj = *_objList[nr];
					Common::Rect objectRect(obj._x, obj._y, obj._x + obj._width, obj._y + obj._height);
					if (objectRect.contains(pointPos)) {
						Graphics::Surface *objSurface = obj.getSurface();
						byte *pixel = (byte *)objSurface->getBasePtr(posX - obj._x, posY - obj._y);
						if (*pixel != 255) {
							break;
						}
					}
				}
			}
			continue;
			break;
		case 2:
		case 5:
			//check_ba_mob
			if (!_backAnimList[mob->_mask].backAnims.empty()) {
				int currentAnim = _backAnimList[mob->_mask]._seq._currRelative;
				Anim &backAnim = _backAnimList[mob->_mask].backAnims[currentAnim];
				if (backAnim._animData != nullptr) {
					if (!backAnim._state) {
						Common::Rect backAnimRect(backAnim._currX, backAnim._currY, backAnim._currX + backAnim._currW, backAnim._currY + backAnim._currH);
						if (backAnimRect.contains(pointPos)) {
							int phase = backAnim._showFrame;
							int phaseFrameIndex = backAnim._animData->getPhaseFrameIndex(phase);
							Graphics::Surface *backAnimSurface = backAnim._animData->getFrame(phaseFrameIndex);
							byte pixel = *(byte *)backAnimSurface->getBasePtr(posX - backAnim._currX, posY - backAnim._currY);
							if (pixel != 255) {
								if (type == 5) {
									if (mob->_rect.contains(pointPos)) {
										break;
									}
								} else {
									break;
								}
							}
						}
					}
				}
			}
			continue;
			break;
		default:
			//not_part_ba
			continue;
			break;
		}

		if (usePriorityList) {
			return _mobPriorityList[mobNumber];
		} else {
			return mobNumber;
		}
	}
	return -1;
}

int PrinceEngine::checkMob(Graphics::Surface *screen, Common::Array<Mob> &mobList, bool usePriorityList) {
	if (_mouseFlag == 0 || _mouseFlag == 3) {
		return -1;
	}
	Common::Point mousePos = _system->getEventManager()->getMousePos();
	int mobNumber = getMob(mobList, usePriorityList, mousePos.x + _picWindowX, mousePos.y);

	if (mobNumber != -1) {
		Common::String mobName = mobList[mobNumber]._name;

		if (getLanguage() == Common::DE_DEU) {
			for (uint i = 0; i < mobName.size(); i++) {
				switch (mobName[i]) {
				case '\xc4':
					mobName.setChar('\x83', i);
					break;
				case '\xd6':
					mobName.setChar('\x84', i);
					break;
				case '\xdc':
					mobName.setChar('\x85', i);
					break;
				case '\xdf':
					mobName.setChar('\x7f', i);
					break;
				case '\xe4':
					mobName.setChar('\x80', i);
					break;
				case '\xf6':
					mobName.setChar('\x81', i);
					break;
				case '\xfc':
					mobName.setChar('\x82', i);
					break;
				}
			}
		}

		uint16 textW = getTextWidth(mobName.c_str());

		uint16 x = mousePos.x - textW / 2;
		if (x > screen->w) {
			x = 0;
		}

		if (x + textW > screen->w) {
			x = screen->w - textW;
		}

		uint16 y = mousePos.y - _font->getFontHeight();
		if (y > screen->h) {
			y = _font->getFontHeight() - 2;
		}

		_font->drawString(screen, mobName, x, y, screen->w, 216);
	}

	return mobNumber;
}

void PrinceEngine::printAt(uint32 slot, uint8 color, char *s, uint16 x, uint16 y) {
	debugC(1, DebugChannel::kEngine, "PrinceEngine::printAt slot %d, color %d, x %02d, y %02d, str %s", slot, color, x, y, s);

	if (getLanguage() == Common::DE_DEU)
		correctStringDEU(s);

	Text &text = _textSlots[slot];
	text._str = s;
	text._x = x;
	text._y = y;
	text._color = color;
	int lines = calcTextLines(s);
	text._time = calcTextTime(lines);
}

int PrinceEngine::calcTextLines(const char *s) {
	int lines = 1;
	while (*s) {
		if (*s == '\n') {
			lines++;
		}
		s++;
	}
	return lines;
}

int PrinceEngine::calcTextTime(int numberOfLines) {
	return numberOfLines * 30;
}

void PrinceEngine::correctStringDEU(char *s) {
	while (*s) {
		switch (*s) {
		case '\xc4':
			*s = '\x83';
			break;
		case '\xd6':
			*s = '\x84';
			break;
		case '\xdc':
			*s = '\x85';
			break;
		case '\xdf':
			*s = '\x7f';
			break;
		case '\xe4':
			*s = '\x80';
			break;
		case '\xf6':
			*s = '\x81';
			break;
		case '\xfc':
			*s = '\x82';
			break;
		}
		s++;
	}
}

uint32 PrinceEngine::getTextWidth(const char *s) {
	uint16 textW = 0;
	while (*s) {
		textW += _font->getCharWidth(*s) + _font->getKerningOffset(0, 0);
		s++;
	}
	return textW;
}

void PrinceEngine::showTexts(Graphics::Surface *screen) {
	for (uint32 slot = 0; slot < kMaxTexts; slot++) {

		if (_showInventoryFlag && slot) {
			// only slot 0 for inventory
			break;
		}

		Text& text = _textSlots[slot];
		if (!text._str && !text._time) {
			continue;
		}

		int x = text._x;
		int y = text._y;

		if (!_showInventoryFlag) {
			x -= _picWindowX;
			y -= _picWindowY;
		}

		Common::Array<Common::String> lines;
		_font->wordWrapText(text._str, _graph->_frontScreen->w, lines);

		int wideLine = 0;
		for (uint i = 0; i < lines.size(); i++) {
			int textLen = getTextWidth(lines[i].c_str());
			if (textLen > wideLine) {
				wideLine = textLen;
			}
		}

		int leftBorderText = 6;
		if (x + wideLine / 2 >  kNormalWidth - leftBorderText) {
			x = kNormalWidth - leftBorderText - wideLine / 2;
		}

		if (x - wideLine / 2 < leftBorderText) {
			x = leftBorderText + wideLine / 2;
		}

		int textSkip = 2;
		for (uint i = 0; i < lines.size(); i++) {
			int drawX = x - getTextWidth(lines[i].c_str()) / 2;
			int drawY = y - 10 - (lines.size() - i) * (_font->getFontHeight() - textSkip);
			if (drawX < 0) {
				drawX = 0;
			}
			if (drawY < 0) {
				drawY = 0;
			}
			_font->drawString(screen, lines[i], drawX, drawY, screen->w, text._color);
		}

		text._time--;
		if (!text._time) {
			text._str = nullptr;
		}
	}
}

bool PrinceEngine::spriteCheck(int sprWidth, int sprHeight, int destX, int destY) {
	destX -= _picWindowX;
	destY -= _picWindowY;

	 // if x1 is on visible part of screen
	if (destX < 0) {
		if (destX + sprWidth < 1) {
			//x2 is negative - out of window
			return false;
		}
	}
	 // if x1 is outside of screen on right side
	if (destX >= kNormalWidth) {
		return false;
	}

	if (destY < 0) {
		if (destY + sprHeight < 1) {
			//y2 is negative - out of window
			return false;
		}
	}
	if (destY >= kNormalHeight) {
		return false;
	}

	return true;
}

// CheckNak
void PrinceEngine::checkMasks(int x1, int y1, int sprWidth, int sprHeight, int z) {
	int x2 = x1 + sprWidth - 1;
	int y2 = y1 + sprHeight - 1;
	if (x1 < 0) {
		x1 = 0;
	}
	for (uint i = 0; i < _maskList.size(); i++) {
		if (!_maskList[i]._state && !_maskList[i]._flags) {
			if (_maskList[i]._z > z) {
				if (_maskList[i]._x1 <= x2 && _maskList[i]._x2 >= x1) {
					if (_maskList[i]._y1 <= y2 && _maskList[i]._y2 >= y1) {
						_maskList[i]._state = 1;
					}
				}
			}
		}
	}
}

// ClsNak
void PrinceEngine::clsMasks() {
	for (uint i = 0; i < _maskList.size(); i++) {
		if (_maskList[i]._state) {
			_maskList[i]._state = 0;
		}
	}
}

// InsertNakladki
void PrinceEngine::insertMasks(Graphics::Surface *originalRoomSurface) {
	for (uint i = 0; i < _maskList.size(); i++) {
		if (_maskList[i]._state) {
			if (_maskList[i]._data != nullptr) {
				showMask(i, originalRoomSurface);
			} else {
				error("insertMasks() - Wrong mask data- nr %d", i);
			}
		}
	}
}

// ShowNak
void PrinceEngine::showMask(int maskNr, Graphics::Surface *originalRoomSurface) {
	if (!_maskList[maskNr]._flags) {
		if (spriteCheck(_maskList[maskNr]._width, _maskList[maskNr]._height, _maskList[maskNr]._x1, _maskList[maskNr]._y1)) {
			int destX = _maskList[maskNr]._x1 - _picWindowX;
			int destY = _maskList[maskNr]._y1 - _picWindowY;
			DrawNode newDrawNode;
			newDrawNode.posX = destX;
			newDrawNode.posY = destY;
			newDrawNode.posZ = _maskList[maskNr]._z;
			newDrawNode.width = _maskList[maskNr]._width;
			newDrawNode.height = _maskList[maskNr]._height;
			newDrawNode.s = nullptr;
			newDrawNode.originalRoomSurface = originalRoomSurface;
			newDrawNode.data = _maskList[maskNr].getMask();
			newDrawNode.drawFunction = &_graph->drawMaskDrawNode;
			_drawNodeList.push_back(newDrawNode);
		}
	}
}

void PrinceEngine::showSprite(Graphics::Surface *spriteSurface, int destX, int destY, int destZ) {
	if (spriteCheck(spriteSurface->w, spriteSurface->h, destX, destY)) {
		destX -= _picWindowX;
		destY -= _picWindowY;
		DrawNode newDrawNode;
		newDrawNode.posX = destX;
		newDrawNode.posY = destY;
		newDrawNode.posZ = destZ;
		newDrawNode.width = 0;
		newDrawNode.height = 0;
		newDrawNode.s = spriteSurface;
		newDrawNode.originalRoomSurface = nullptr;
		newDrawNode.data = _transTable;
		newDrawNode.drawFunction = &_graph->drawTransparentWithTransDrawNode;
		_drawNodeList.push_back(newDrawNode);
	}
}

void PrinceEngine::showSpriteShadow(Graphics::Surface *shadowSurface, int destX, int destY, int destZ) {
	if (spriteCheck(shadowSurface->w, shadowSurface->h, destX, destY)) {
		destX -= _picWindowX;
		destY -= _picWindowY;
		DrawNode newDrawNode;
		newDrawNode.posX = destX;
		newDrawNode.posY = destY;
		newDrawNode.posZ = destZ;
		newDrawNode.width = 0;
		newDrawNode.height = 0;
		newDrawNode.s = shadowSurface;
		newDrawNode.originalRoomSurface = nullptr;
		newDrawNode.data = _graph->_shadowTable70;
		newDrawNode.drawFunction = &_graph->drawAsShadowDrawNode;
		_drawNodeList.push_back(newDrawNode);
	}
}

void PrinceEngine::showAnim(Anim &anim) {
	//ShowFrameCode
	//ShowAnimFrame
	int phase = anim._showFrame;
	int phaseFrameIndex = anim._animData->getPhaseFrameIndex(phase);
	int x = anim._x + anim._animData->getPhaseOffsetX(phase);
	int y = anim._y + anim._animData->getPhaseOffsetY(phase);
	int animFlag = anim._flags;
	int checkMaskFlag = (animFlag & 1);
	int maxFrontFlag = (animFlag & 2);
	int specialZFlag = anim._nextAnim;
	int z = anim._nextAnim;
	Graphics::Surface *animSurface = anim._animData->getFrame(phaseFrameIndex);
	int frameWidth = animSurface->w;
	int frameHeight = animSurface->h;
	int shadowZ = 0;

	if (checkMaskFlag) {
		if (!anim._nextAnim) {
			z = y + frameHeight - 1;
		}
		checkMasks(x, y, frameWidth, frameHeight, z);
	}

	if (specialZFlag) {
		z = specialZFlag;
	} else if (maxFrontFlag) {
		z = kMaxPicHeight + 1;
	} else {
		z = y + frameHeight - 1;
	}
	shadowZ = z;

	anim._currX = x;
	anim._currY = y;
	anim._currW = frameWidth;
	anim._currH = frameHeight;
	showSprite(animSurface, x, y, z);

	// make_special_shadow
	if ((anim._flags & 0x80)) {
		if (animSurface) {
			DrawNode newDrawNode;
			newDrawNode.posX = x;
			newDrawNode.posY = y + animSurface->h - anim._shadowBack;
			newDrawNode.posZ = Hero::kHeroShadowZ;
			newDrawNode.width = 0;
			newDrawNode.height = 0;
			newDrawNode.scaleValue = _scaleValue;
			newDrawNode.originalRoomSurface = nullptr;
			newDrawNode.data = this;
			newDrawNode.drawFunction = &Hero::showHeroShadow;
			newDrawNode.s = animSurface;
			_drawNodeList.push_back(newDrawNode);
		}
	}

	//ShowFrameCodeShadow
	//ShowAnimFrameShadow
	if (anim._shadowData != nullptr) {
		int shadowPhaseFrameIndex = anim._shadowData->getPhaseFrameIndex(phase);
		int shadowX = anim._shadowData->getBaseX() + anim._shadowData->getPhaseOffsetX(phase);
		int shadowY = anim._shadowData->getBaseY() + anim._shadowData->getPhaseOffsetY(phase);
		Graphics::Surface *shadowSurface = anim._shadowData->getFrame(shadowPhaseFrameIndex);
		int shadowFrameWidth = shadowSurface->w;
		int shadowFrameHeight = shadowSurface->h;

		if (checkMaskFlag) {
			checkMasks(shadowX, shadowY, shadowFrameWidth, shadowFrameHeight, shadowY + shadowFrameWidth - 1);
		}

		if (!shadowZ) {
			if (maxFrontFlag) {
				shadowZ = kMaxPicHeight + 1;
			} else {
				shadowZ = shadowY + shadowFrameWidth - 1;
			}
		}
		showSpriteShadow(shadowSurface, shadowX, shadowY, shadowZ);
	}
}

void PrinceEngine::showNormAnims() {
	for (int i = 0; i < kMaxNormAnims; i++) {
		Anim &anim = _normAnimList[i];
		if (anim._animData != nullptr) {
			int phaseCount = anim._animData->getPhaseCount();
			if (!anim._state) {
				if (anim._frame == anim._lastFrame - 1) {
					if (anim._loopType) {
						if (anim._loopType == 1) {
							anim._frame = anim._loopFrame;
						} else {
							continue;
						}
					}
				} else {
					anim._frame++;
				}
				anim._showFrame = anim._frame;
				if (anim._showFrame >= phaseCount) {
					anim._showFrame = phaseCount - 1;
				}
				showAnim(anim);
			}
		}
	}
}

void PrinceEngine::setBackAnim(Anim &backAnim) {
	int start = backAnim._basaData._start;
	if (start != -1) {
		backAnim._frame = start;
		backAnim._showFrame = start;
		backAnim._loopFrame = start;
	}
	int end = backAnim._basaData._end;
	if (end != -1) {
		backAnim._lastFrame = end;
	}
	backAnim._state = 0;
}

void PrinceEngine::showBackAnims() {
	for (int i = 0; i < kMaxBackAnims; i++) {
		BAS &seq = _backAnimList[i]._seq;
		int activeSubAnim = seq._currRelative;
		if (!_backAnimList[i].backAnims.empty()) {
			if (_backAnimList[i].backAnims[activeSubAnim]._animData != nullptr) {
				if (!_backAnimList[i].backAnims[activeSubAnim]._state) {
					seq._counter++;
					if (seq._type == 2) {
						if (!seq._currRelative) {
							if (seq._counter >= seq._data) {
								if (seq._anims > 2) {
									seq._currRelative = _randomSource.getRandomNumber(seq._anims - 2) + 1;
									activeSubAnim = seq._currRelative;
									seq._current = _backAnimList[i].backAnims[activeSubAnim]._basaData._num;
								}
								setBackAnim(_backAnimList[i].backAnims[activeSubAnim]);
								seq._counter = 0;
							}
						}
					}

					if (seq._type == 3) {
						if (!seq._currRelative) {
							if (seq._counter < seq._data2) {
								continue;
							} else {
								setBackAnim(_backAnimList[i].backAnims[activeSubAnim]);
							}
						}
					}

					if (_backAnimList[i].backAnims[activeSubAnim]._frame == _backAnimList[i].backAnims[activeSubAnim]._lastFrame - 1) {
						_backAnimList[i].backAnims[activeSubAnim]._frame = _backAnimList[i].backAnims[activeSubAnim]._loopFrame;
						switch (seq._type) {
						case 1:
							if (seq._anims > 1) {
								int rnd;
								do {
									rnd = _randomSource.getRandomNumber(seq._anims - 1);
								} while (rnd == seq._currRelative);
								seq._currRelative = rnd;
								seq._current = _backAnimList[i].backAnims[rnd]._basaData._num;
								activeSubAnim = rnd;
								setBackAnim(_backAnimList[i].backAnims[activeSubAnim]);
								seq._counter = 0;
							}
							break;
						case 2:
							if (seq._currRelative) {
								seq._currRelative = 0;
								seq._current = _backAnimList[i].backAnims[0]._basaData._num;
								activeSubAnim = 0;
								setBackAnim(_backAnimList[i].backAnims[activeSubAnim]);
								seq._counter = 0;
							}
							break;
						case 3:
							seq._currRelative = 0;
							seq._current = _backAnimList[i].backAnims[0]._basaData._num;
							seq._counter = 0;
							seq._data2 = _randomSource.getRandomNumber(seq._data - 1);
							continue; // for bug in original game
							break;
						}
					} else {
						_backAnimList[i].backAnims[activeSubAnim]._frame++;
					}
					_backAnimList[i].backAnims[activeSubAnim]._showFrame = _backAnimList[i].backAnims[activeSubAnim]._frame;
					showAnim(_backAnimList[i].backAnims[activeSubAnim]);
				}
			}
		}
	}
}

void PrinceEngine::removeSingleBackAnim(int slot) {
	if (!_backAnimList[slot].backAnims.empty()) {
		for (uint j = 0; j < _backAnimList[slot].backAnims.size(); j++) {
			if (_backAnimList[slot].backAnims[j]._animData != nullptr) {
				delete _backAnimList[slot].backAnims[j]._animData;
				_backAnimList[slot].backAnims[j]._animData = nullptr;
			}
			if (_backAnimList[slot].backAnims[j]._shadowData != nullptr) {
				delete _backAnimList[slot].backAnims[j]._shadowData;
				_backAnimList[slot].backAnims[j]._shadowData = nullptr;
			}
		}
		_backAnimList[slot].backAnims.clear();
		_backAnimList[slot]._seq._currRelative = 0;
	}
}

void PrinceEngine::clearBackAnimList() {
	for (int i = 0; i < kMaxBackAnims; i++) {
		removeSingleBackAnim(i);
	}
}

void PrinceEngine::grabMap() {
	_graph->_frontScreen->copyFrom(*_roomBmp->getSurface());
	showObjects();
	runDrawNodes();
	_graph->_mapScreen->copyFrom(*_graph->_frontScreen);
}

void PrinceEngine::initZoomIn(int slot) {
	freeZoomObject(slot);
	Object *object = _objList[slot];
	if (object != nullptr) {
		Graphics::Surface *zoomSource = object->getSurface();
		if (zoomSource != nullptr) {
			object->_flags |= 0x8000;
			object->_zoomSurface = new Graphics::Surface();
			object->_zoomSurface->create(zoomSource->w, zoomSource->h, Graphics::PixelFormat::createFormatCLUT8());
			object->_zoomSurface->fillRect(Common::Rect(zoomSource->w, zoomSource->h), 0xFF);
			object->_zoomTime = 20;
		}
	}
}

void PrinceEngine::initZoomOut(int slot) {
	freeZoomObject(slot);
	Object *object = _objList[slot];
	if (object != nullptr) {
		Graphics::Surface *zoomSource = object->getSurface();
		if (zoomSource != nullptr) {
			object->_flags |= 0x4000;
			object->_zoomSurface = new Graphics::Surface();
			object->_zoomSurface->copyFrom(*zoomSource);
			object->_zoomTime = 10;
		}
	}
}

void PrinceEngine::doZoomIn(int slot) {
	Object *object = _objList[slot];
	if (object != nullptr) {
		Graphics::Surface *orgSurface = object->getSurface();
		if (orgSurface != nullptr) {
			byte *src1 = (byte *)orgSurface->getBasePtr(0, 0);
			byte *dst1 = (byte *)object->_zoomSurface->getBasePtr(0, 0);
			int x = 0;
			int surfaceHeight = orgSurface->h;
			for (int y = 0; y < surfaceHeight; y++) {
				byte *src2 = src1;
				byte *dst2 = dst1;
				int w = orgSurface->w - x;
				src2 += x;
				dst2 += x;
				while (w > 0) {
					int randVal = _randomSource.getRandomNumber(zoomInStep - 1);
					if (randVal < w) {
						*(dst2 + randVal) = *(src2 + randVal);
						src2 += zoomInStep;
						dst2 += zoomInStep;
					} else if (y + 1 != surfaceHeight) {
						*(dst1 + orgSurface->pitch + randVal - w) = *(src1 + orgSurface->pitch + randVal - w);
					}
					w -= zoomInStep;
				}
				x = -1 * w;
				src1 += orgSurface->pitch;
				dst1 += orgSurface->pitch;
			}
		}
	}
}

void PrinceEngine::doZoomOut(int slot) {
	Object *object = _objList[slot];
	if (object != nullptr) {
		Graphics::Surface *orgSurface = object->getSurface();
		if (orgSurface != nullptr) {
			byte *dst1 = (byte *)object->_zoomSurface->getBasePtr(0, 0);
			int x = 0;
			int surfaceHeight = orgSurface->h;
			for (int y = 0; y < surfaceHeight; y++) {
				byte *dst2 = dst1;
				int w = orgSurface->w - x;
				dst2 += x;
				while (w > 0) {
					int randVal = _randomSource.getRandomNumber(zoomInStep - 1);
					if (randVal < w) {
						*(dst2 + randVal) = 255;
						dst2 += zoomInStep;
					} else if (y + 1 != surfaceHeight) {
						*(dst1 + orgSurface->pitch + randVal - w) = 255;
					}
					w -= zoomInStep;
				}
				x = -1 * w;
				dst1 += orgSurface->pitch;
			}
		}
	}
}

void PrinceEngine::freeZoomObject(int slot) {
	Object *object = _objList[slot];
	if (object != nullptr) {
		if (object->_zoomSurface != nullptr) {
			object->_zoomSurface->free();
			delete object->_zoomSurface;
			object->_zoomSurface = nullptr;
		}
	}
}

void PrinceEngine::showObjects() {
	for (int i = 0; i < kMaxObjects; i++) {
		int nr = _objSlot[i];
		if (nr != 0xFF) {
			Graphics::Surface *objSurface = nullptr;
			if ((_objList[nr]->_flags & 0x8000)) {
				_objList[nr]->_zoomTime--;
				if (!_objList[nr]->_zoomTime) {
					freeZoomObject(nr);
					_objList[nr]->_flags &= 0x7FFF;
					objSurface = _objList[nr]->getSurface();
				} else {
					doZoomIn(nr);
					objSurface = _objList[nr]->_zoomSurface;
				}
			} else if ((_objList[nr]->_flags & 0x4000)) {
				_objList[nr]->_zoomTime--;
				if (!_objList[nr]->_zoomTime) {
					freeZoomObject(nr);
					_objList[nr]->_flags &= 0xBFFF;
					objSurface = _objList[nr]->getSurface();
				} else {
					doZoomOut(nr);
					objSurface = _objList[nr]->_zoomSurface;
				}
			} else {
				objSurface = _objList[nr]->getSurface();
			}

			if (objSurface != nullptr) {
				if (spriteCheck(objSurface->w, objSurface->h, _objList[nr]->_x, _objList[nr]->_y)) {
					int destX = _objList[nr]->_x - _picWindowX;
					int destY = _objList[nr]->_y - _picWindowY;
					DrawNode newDrawNode;
					newDrawNode.posX = destX;
					newDrawNode.posY = destY;
					newDrawNode.posZ = _objList[nr]->_z;
					newDrawNode.width = 0;
					newDrawNode.height = 0;
					newDrawNode.s = objSurface;
					newDrawNode.originalRoomSurface = nullptr;
					if ((_objList[nr]->_flags & 0x2000)) {
						newDrawNode.data = nullptr;
						newDrawNode.drawFunction = &_graph->drawBackSpriteDrawNode;
					} else {
						newDrawNode.data = _transTable;
						if (_flags->getFlagValue(Flags::NOANTIALIAS)) {
							newDrawNode.drawFunction = &_graph->drawTransparentDrawNode;
						} else {
							newDrawNode.drawFunction = &_graph->drawTransparentWithTransDrawNode;
						}
					}
					_drawNodeList.push_back(newDrawNode);
				}

				if ((_objList[nr]->_flags & 1)) {
					checkMasks(_objList[nr]->_x, _objList[nr]->_y, objSurface->w, objSurface->h, _objList[nr]->_z);
				}
			}
		}
	}
}

void PrinceEngine::showParallax() {
	if (!_pscrList.empty()) {
		for (uint i = 0; i < _pscrList.size(); i++) {
			Graphics::Surface *pscrSurface = _pscrList[i]->getSurface();
			if (pscrSurface != nullptr) {
				int x = _pscrList[i]->_x - (_pscrList[i]->_step * _picWindowX / 4);
				int y = _pscrList[i]->_y;
				int z = PScr::kPScrZ;
				if (spriteCheck(pscrSurface->w, pscrSurface->h, x, y)) {
					showSprite(pscrSurface, x, y, z);
				}
			}
		}
	}
}

bool PrinceEngine::compareDrawNodes(DrawNode d1, DrawNode d2) {
	if (d1.posZ < d2.posZ) {
		return true;
	}
	return false;
}

void PrinceEngine::runDrawNodes() {
	Common::sort(_drawNodeList.begin(), _drawNodeList.end(), compareDrawNodes);

	for (uint i = 0; i < _drawNodeList.size(); i++) {
		(*_drawNodeList[i].drawFunction)(_graph->_frontScreen, &_drawNodeList[i]);
	}
	_graph->change();
}

void PrinceEngine::drawScreen() {
	if (!_showInventoryFlag || _inventoryBackgroundRemember) {
		clsMasks();

		_mainHero->showHero();
		_mainHero->scrollHero();
		_mainHero->drawHero();

		_secondHero->showHero();
		_secondHero->_drawX -= _picWindowX;
		_secondHero->drawHero();

		const Graphics::Surface *roomSurface;
		if (_locationNr != 50) {
			roomSurface = _roomBmp->getSurface();
		} else {
			roomSurface = _graph->_mapScreen;
		}
		Graphics::Surface visiblePart;
		if (roomSurface) {
			visiblePart = roomSurface->getSubArea(Common::Rect(_picWindowX, 0, roomSurface->w, roomSurface->h));
			_graph->draw(_graph->_frontScreen, &visiblePart);
		}

		showBackAnims();

		showNormAnims();

		playNextFLCFrame();

		showObjects();

		if (roomSurface) {
			insertMasks(&visiblePart);
		}

		showParallax();

		runDrawNodes();

		_drawNodeList.clear();

		if (!_inventoryBackgroundRemember && !_dialogFlag) {
			if (!_optionsFlag) {
				_selectedMob = checkMob(_graph->_frontScreen, _mobList, true);
			}
			showTexts(_graph->_frontScreen);
			checkOptions();
		} else {
			_inventoryBackgroundRemember = false;
		}

		showPower();

		getDebugger()->onFrame();

	} else {
		displayInventory();
	}
}

void PrinceEngine::blackPalette() {
	byte *paletteBackup = (byte *)malloc(256 * 3);
	byte *blackPalette1 = (byte *)malloc(256 * 3);

	int fadeStep = kFadeStep - 1;
	for (int i = 0; i < kFadeStep; i++) {
		_system->getPaletteManager()->grabPalette(paletteBackup, 0, 256);
		for (int j = 0; j < 256; j++) {
			blackPalette1[3 * j] = paletteBackup[3 * j] * fadeStep / 4;
			blackPalette1[3 * j + 1] = paletteBackup[3 * j + 1] * fadeStep / 4;
			blackPalette1[3 * j + 2] = paletteBackup[3 * j + 2] * fadeStep / 4;
		}
		fadeStep--;
		_graph->setPalette(blackPalette1);
		_system->updateScreen();
		Common::Event event;
		Common::EventManager *eventMan = _system->getEventManager();
		eventMan->pollEvent(event);
		if (shouldQuit()) {
			free(paletteBackup);
			free(blackPalette1);
			return;
		}
		pausePrinceEngine();
	}
	free(paletteBackup);
	free(blackPalette1);
}

void PrinceEngine::setPalette(const byte *palette) {
	if (palette != nullptr) {
		byte *blackPalette_ = (byte *)malloc(256 * 3);
		int fadeStep = 0;
		for (int i = 0; i <= kFadeStep; i++) {
			for (int j = 0; j < 256; j++) {
				blackPalette_[3 * j] = palette[3 * j] * fadeStep / 4;
				blackPalette_[3 * j + 1] = palette[3 * j + 1] * fadeStep / 4;
				blackPalette_[3 * j + 2] = palette[3 * j + 2] * fadeStep / 4;
			}
			fadeStep++;
			_graph->setPalette(blackPalette_);
			_system->updateScreen();
			Common::Event event;
			Common::EventManager *eventMan = _system->getEventManager();
			eventMan->pollEvent(event);
			if (shouldQuit()) {
				_graph->setPalette(palette);
				free(blackPalette_);
				return;
			}
			pausePrinceEngine();
		}
		_graph->setPalette(palette);
		free(blackPalette_);
	}
}

void PrinceEngine::pausePrinceEngine(int fps) {
	int delay = 1000 / fps - int32(_system->getMillis() - _currentTime);
	delay = delay < 0 ? 0 : delay;
	_system->delayMillis(delay);
	_currentTime = _system->getMillis();
}

void PrinceEngine::addInv(int heroId, int item, bool addItemQuiet) {
	Hero *hero = nullptr;
	if (!heroId) {
		hero = _mainHero;
	} else if (heroId == 1) {
		hero = _secondHero;
	}
	if (hero != nullptr) {
		if (hero->_inventory.size() < kMaxItems) {
			if (item != 0x7FFF) {
				hero->_inventory.push_back(item);
			}
			if (!addItemQuiet) {
				addInvObj();
			}
			_interpreter->setResult(0);
		} else {
			_interpreter->setResult(1);
		}
	}
}

void PrinceEngine::remInv(int heroId, int item) {
	Hero *hero = nullptr;
	if (!heroId) {
		hero = _mainHero;
	} else if (heroId == 1) {
		hero = _secondHero;
	}
	if (hero != nullptr) {
		for (uint i = 0; i < hero->_inventory.size(); i++) {
			if (hero->_inventory[i] == item) {
				hero->_inventory.remove_at(i);
				_interpreter->setResult(0);
				return;
			}
		}
	}
	_interpreter->setResult(1);
}

void PrinceEngine::clearInv(int heroId) {
	switch (heroId) {
	case 0:
		_mainHero->_inventory.clear();
		break;
	case 1:
		_secondHero->_inventory.clear();
		break;
	default:
		error("clearInv() - wrong hero slot");
		break;
	}
}

void PrinceEngine::swapInv(int heroId) {
	Common::Array<int> tempInv;
	Hero *hero = nullptr;
	if (!heroId) {
		hero = _mainHero;
	} else if (heroId == 1) {
		hero = _secondHero;
	}
	if (hero != nullptr) {
		for (uint i = 0; i < hero->_inventory.size(); i++) {
			tempInv.push_back(hero->_inventory[i]);
		}
		hero->_inventory.clear();
		for (uint i = 0; i < hero->_inventory2.size(); i++) {
			hero->_inventory.push_back(hero->_inventory2[i]);
		}
		hero->_inventory2.clear();
		for (uint i = 0; i < tempInv.size(); i++) {
			hero->_inventory2.push_back(tempInv[i]);
		}
		tempInv.clear();
	}
}

void PrinceEngine::addInvObj() {
	changeCursor(0);
	prepareInventoryToView();

	_inventoryBackgroundRemember = true;
	drawScreen();

	Graphics::Surface *suitcase = _suitcaseBmp->getSurface();

	if (!_flags->getFlagValue(Flags::CURSEBLINK)) {

		loadSample(27, "PRZEDMIO.WAV");
		playSample(27, 0);

		_mst_shadow2 = 1;

		while (_mst_shadow2 < 512) {
			rememberScreenInv();
			_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);
			drawInvItems();
			_graph->update(_graph->_screenForInventory);
			_mst_shadow2 += 50;
			Common::Event event;
			Common::EventManager *eventMan = _system->getEventManager();
			eventMan->pollEvent(event);
			if (shouldQuit()) {
				return;
			}
			pausePrinceEngine();
		}
		while (_mst_shadow2 > 256) {
			rememberScreenInv();
			_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);
			drawInvItems();
			_graph->update(_graph->_screenForInventory);
			_mst_shadow2 -= 42;
			Common::Event event;
			Common::EventManager *eventMan = _system->getEventManager();
			eventMan->pollEvent(event);
			if (shouldQuit()) {
				return;
			}
			pausePrinceEngine();
		}
	} else {
		//CURSEBLINK:
		for (int i = 0; i < 3; i++) {
			_mst_shadow2 = 256;
			while (_mst_shadow2 < 512) {
				rememberScreenInv();
				_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);
				drawInvItems();
				_graph->update(_graph->_screenForInventory);
				_mst_shadow2 += 50;
				Common::Event event;
				Common::EventManager *eventMan = _system->getEventManager();
				eventMan->pollEvent(event);
				if (shouldQuit()) {
					return;
				}
				pausePrinceEngine();
			}
			while (_mst_shadow2 > 256) {
				rememberScreenInv();
				_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);
				drawInvItems();
				_graph->update(_graph->_screenForInventory);
				_mst_shadow2 -= 50;
				Common::Event event;
				Common::EventManager *eventMan = _system->getEventManager();
				eventMan->pollEvent(event);
				if (shouldQuit()) {
					return;
				}
				pausePrinceEngine();
			}
		}
	}
	_mst_shadow2 = 0;
	for (int i = 0; i < 20; i++) {
		rememberScreenInv();
		_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);
		drawInvItems();
		_graph->update(_graph->_screenForInventory);
		Common::Event event;
		Common::EventManager *eventMan = _system->getEventManager();
		eventMan->pollEvent(event);
		if (shouldQuit()) {
			return;
		}
		pausePrinceEngine();
	}
}

void PrinceEngine::rememberScreenInv() {
	_graph->_screenForInventory->copyFrom(*_graph->_frontScreen);
}

void PrinceEngine::inventoryFlagChange(bool inventoryState) {
	if (inventoryState) {
		_showInventoryFlag = true;
		_inventoryBackgroundRemember = true;
	} else {
		_showInventoryFlag = false;
	}
}

void PrinceEngine::prepareInventoryToView() {
	_invMobList.clear();
	int invItem = _mainHero->_inventory.size();
	_invLine =  invItem / 3;
	if (invItem % 3) {
		_invLine++;
	}
	if (_invLine < 4) {
		_invLine = 4;
	}
	_maxInvW = (374 - 2 * _invLine) / _invLine;
	_invLineW = _maxInvW - 2;

	int currInvX = _invLineX;
	int currInvY = _invLineY;

	Common::MemoryReadStream stream(_invTxt, _invTxtSize);
	byte c;

	uint item = 0;
	for (int i = 0; i < _invLines; i++) {
		for (int j = 0; j < _invLine; j++) {
			Mob tempMobItem;
			if (item < _mainHero->_inventory.size()) {
				int itemNr = _mainHero->_inventory[item];
				tempMobItem._visible = 0;
				tempMobItem._mask =  itemNr;
				tempMobItem._rect = Common::Rect(currInvX + _picWindowX, currInvY, currInvX + _picWindowX + _invLineW  - 1, currInvY + _invLineH - 1);
				tempMobItem._type = 0; // to work with checkMob()

				tempMobItem._name = "";
				tempMobItem._examText = "";
				int txtOffset = READ_LE_UINT32(&_invTxt[itemNr * 8]);
				int examTxtOffset = READ_LE_UINT32(&_invTxt[itemNr * 8 + 4]);

				stream.seek(txtOffset);
				while ((c = stream.readByte())) {
					tempMobItem._name += c;
				}

				stream.seek(examTxtOffset);
				while ((c = stream.readByte())) {
					tempMobItem._examText += c;
				}
				_invMobList.push_back(tempMobItem);
			}
			currInvX += _invLineW + _invLineSkipX;
			item++;
		}
		currInvX = _invLineX;
		currInvY += _invLineSkipY + _invLineH;
	}
}

void PrinceEngine::drawInvItems() {
	int currInvX = _invLineX;
	int currInvY = _invLineY;
	uint item = 0;
	for (int i = 0; i < _invLines; i++) {
		for (int j = 0; j < _invLine; j++) {
			if (item < _mainHero->_inventory.size()) {
				int itemNr = _mainHero->_inventory[item];
				_mst_shadow = 0;
				if (_mst_shadow2) {
					if (!_flags->getFlagValue(Flags::CURSEBLINK)) {
						if (item + 1 == _mainHero->_inventory.size()) { // last item in inventory
							_mst_shadow = 1;
						}
					} else if (itemNr == 1 || itemNr == 3 || itemNr == 4 || itemNr == 7) {
						_mst_shadow = 1;
					}
				}

				int drawX = currInvX;
				int drawY = currInvY;
				Graphics::Surface *itemSurface = nullptr;
				if (itemNr != 68) {
					itemSurface = _allInvList[itemNr].getSurface();
					if (itemSurface->h < _maxInvH) {
						drawY += (_maxInvH - itemSurface->h) / 2;
					}
				} else {
					// candle item:
					if (_candleCounter == 8) {
						_candleCounter = 0;
					}
					itemNr = _candleCounter;
					_candleCounter++;
					itemNr &= 7;
					itemNr += 71;
					itemSurface = _allInvList[itemNr].getSurface();
					drawY += _allInvList[itemNr]._y + (_maxInvH - 76) / 2 - 200;
				}
				if (itemSurface->w < _maxInvW) {
					drawX += (_maxInvW - itemSurface->w) / 2;
				}
				if (!_mst_shadow) {
					_graph->drawTransparentSurface(_graph->_screenForInventory, drawX, drawY, itemSurface);
				} else {
					_mst_shadow = _mst_shadow2;
					_graph->drawTransparentWithBlendSurface(_graph->_screenForInventory, drawX, drawY, itemSurface);
				}
			}
			currInvX += _invLineW + _invLineSkipX;
			item++;
		}
		currInvX = _invLineX;
		currInvY += _invLineSkipY + _invLineH;
	}
}

void PrinceEngine::walkTo() {
	if (_mainHero->_visible) {
		_mainHero->freeHeroAnim();
		_mainHero->freeOldMove();
		_interpreter->storeNewPC(_script->_scriptInfo.usdCode);
		int destX, destY;
		if (_optionsMob != -1) {
			destX = _mobList[_optionsMob]._examPosition.x;
			destY = _mobList[_optionsMob]._examPosition.y;
			_mainHero->_destDirection = _mobList[_optionsMob]._examDirection;
		} else {
			Common::Point mousePos = _system->getEventManager()->getMousePos();
			destX = mousePos.x + _picWindowX;
			destY = mousePos.y + _picWindowY;
			_mainHero->_destDirection = 0;
		}
		_mainHero->_coords = makePath(kMainHero, _mainHero->_middleX, _mainHero->_middleY, destX, destY);
		if (_mainHero->_coords != nullptr) {
			_mainHero->_currCoords = _mainHero->_coords;
			_mainHero->_dirTab = _directionTable;
			_mainHero->_currDirTab = _directionTable;
			_directionTable = nullptr;
			_mainHero->_state = Hero::kHeroStateMove;
			moveShandria();
		}
	}
}

void PrinceEngine::moveRunHero(int heroId, int x, int y, int dir, bool runHeroFlag) {
	Hero *hero = nullptr;
	if (!heroId) {
		hero = _mainHero;
	} else if (heroId == 1) {
		hero = _secondHero;
	}

	if (hero != nullptr) {
		if (dir) {
			hero->_destDirection = dir;
		}
		if (x || y) {
			hero->freeOldMove();
			hero->_coords = makePath(heroId, hero->_middleX, hero->_middleY, x, y);
			if (hero->_coords != nullptr) {
				hero->_currCoords = hero->_coords;
				hero->_dirTab = _directionTable;
				hero->_currDirTab = _directionTable;
				_directionTable = nullptr;
				if (runHeroFlag) {
					hero->_state = Hero::kHeroStateRun;
				} else {
					hero->_state = Hero::kHeroStateMove;
				}
				if (heroId == kMainHero && _mouseFlag) {
					moveShandria();
				}
			}
		} else {
			hero->freeOldMove();
			hero->_state = Hero::kHeroStateTurn;
		}
		hero->freeHeroAnim();
		hero->_visible = 1;
	}
}

void PrinceEngine::leftMouseButton() {
	_flags->setFlagValue(Flags::ESCAPED2, 1); // skip intro animation
	_flags->setFlagValue(Flags::LMOUSE, 1);
	if (_flags->getFlagValue(Flags::POWERENABLED)) {
		_flags->setFlagValue(Flags::MBFLAG, 1);
	}
	if (_mouseFlag) {
		int option = 0;
		int optionEvent = -1;

		if (_optionsFlag) {
			if (_optionEnabled < _optionsNumber && _optionEnabled != -1) {
				option = _optionEnabled;
				_optionsFlag = 0;
			} else {
				return;
			}
		} else {
			_optionsMob = _selectedMob;
			if (_optionsMob == -1) {
				walkTo();
				return;
			}
			option = 0;
		}
		//do_option
		if (_currentPointerNumber != 2) {
			//skip_use_code
			int optionScriptOffset = _room->getOptionOffset(option);
			if (optionScriptOffset != 0) {
				optionEvent = _script->scanMobEvents(_optionsMob, optionScriptOffset);
			}
			if (optionEvent == -1) {
				if (!option) {
					walkTo();
					return;
				} else {
					optionEvent = _script->getOptionStandardOffset(option);
				}
			}
		} else if (_selectedMode) {
			//give_item
			if (_room->_itemGive) {
				optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemGive, _selectedItem);
			}
			if (optionEvent == -1) {
				//standard_giveitem
				optionEvent = _script->_scriptInfo.stdGiveItem;
			}
		} else {
			if (_room->_itemUse) {
				optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemUse, _selectedItem);
				_flags->setFlagValue(Flags::SELITEM, _selectedItem);
			}
			if (optionEvent == -1) {
				//standard_useitem
				optionEvent = _script->_scriptInfo.stdUseItem;
			}
		}
		_interpreter->storeNewPC(optionEvent);
		_flags->setFlagValue(Flags::CURRMOB, _selectedMob);
		_selectedMob = -1;
		_optionsMob = -1;
	} else {
		if (!_flags->getFlagValue(Flags::POWERENABLED)) {
			if (!_flags->getFlagValue(Flags::NOCLSTEXT)) {
				for (int slot = 0; slot < kMaxTexts; slot++) {
					if (slot != 9) {
						Text& text = _textSlots[slot];
						if (!text._str) {
							continue;
						}
						text._str = 0;
						text._time = 0;
					}
				}
				_mainHero->_talkTime = 0;
				_secondHero->_talkTime = 0;
			}
		}
	}
}

void PrinceEngine::rightMouseButton() {
	if (_flags->getFlagValue(Flags::POWERENABLED)) {
		_flags->setFlagValue(Flags::MBFLAG, 2);
	}
	if (_mouseFlag && _mouseFlag != 3) {
		_mainHero->freeOldMove();
		_secondHero->freeOldMove();
		_interpreter->storeNewPC(0);
		if (_currentPointerNumber < 2) {
			enableOptions(true);
		} else {
			_currentPointerNumber = 1;
			changeCursor(1);
		}
	}
}

void PrinceEngine::inventoryLeftMouseButton() {
	if (!_mouseFlag) {
		_textSlots[0]._time = 0;
		_textSlots[0]._str = nullptr;
		stopSample(28);
	}

	if (_optionsFlag == 1) {
		if (_selectedMob != -1)  {
			if (_optionEnabled < _invOptionsNumber) {
				_optionsFlag = 0;
			} else {
				return;
			}
		} else {
			error("PrinceEngine::inventoryLeftMouseButton() - optionsFlag = 1, selectedMob = 0");
			if (_currentPointerNumber == 2) {
				changeCursor(1);
				_currentPointerNumber = 1;
				_selectedMob = -1;
				_optionsMob = -1;
				return;
			} else {
				return;
			}
		}
	} else {
		if (_selectedMob != -1) {
			if (_currentPointerNumber != 2) {
				if (_invMobList[_selectedMob]._mask != 29) {
					_optionEnabled = 0;
				} else {
					// map item
					_optionEnabled = 1;
				}
			} else {
				//use_item_on_item
				int invObjUU = _script->scanMobEventsWithItem(_invMobList[_selectedMob]._mask, _script->_scriptInfo.invObjUU, _selectedItem);
				if (invObjUU == -1) {
					int textNr = 80011; // "I can't do it."
					if (_selectedItem == 31 || _invMobList[_selectedMob]._mask == 31) {
						textNr = 80020; // "Nothing is happening."
					}
					_interpreter->setCurrentString(textNr);
					printAt(0, 216, (char *)_variaTxt->getString(textNr - 80000), kNormalWidth / 2, 100);
					setVoice(0, 28, 1);
					playSample(28, 0);
					_selectedMob = -1;
					_optionsMob = -1;
					return;
				} else {
					_interpreter->storeNewPC(invObjUU);
					_flags->setFlagValue(Flags::CURRMOB, _invMobList[_selectedMob]._mask);
					_showInventoryFlag = false;
				}
			}
		} else {
			return;
		}
	}
	//do_option
	if (_optionEnabled == 0) {
		int invObjExamEvent = _script->scanMobEvents(_invMobList[_selectedMob]._mask, _script->_scriptInfo.invObjExam);
		if (invObjExamEvent == -1) {
			// do_standard
			printAt(0, 216, (char *)_invMobList[_selectedMob]._examText.c_str(), kNormalWidth / 2, _invExamY);
			_interpreter->setCurrentString(_invMobList[_selectedMob]._mask + 70000);
			setVoice(0, 28, 1);
			playSample(28, 0);
			// disableuseuse
			changeCursor(0);
			_currentPointerNumber = 1;
		} else {
			_interpreter->storeNewPC(invObjExamEvent);
			_flags->setFlagValue(Flags::CURRMOB, _invMobList[_selectedMob]._mask);
			_showInventoryFlag = false;
		}
	} else if (_optionEnabled == 1) {
		// not_examine
		int invObjUse = _script->scanMobEvents(_invMobList[_selectedMob]._mask, _script->_scriptInfo.invObjUse);
		if (invObjUse == -1) {
			// do_standard_use
			_selectedMode = 0;
			_selectedItem = _invMobList[_selectedMob]._mask;
			makeInvCursor(_invMobList[_selectedMob]._mask);
			_currentPointerNumber = 2;
			changeCursor(2);
		} else {
			_interpreter->storeNewPC(invObjUse);
			_flags->setFlagValue(Flags::CURRMOB, _invMobList[_selectedMob]._mask);
			_showInventoryFlag = false;
		}
	} else if (_optionEnabled == 4) {
		// do_standard_give
		_selectedMode = 1;
		_selectedItem = _invMobList[_selectedMob]._mask;
		makeInvCursor(_invMobList[_selectedMob]._mask);
		_currentPointerNumber = 2;
		changeCursor(2);
	} else {
		// use_item_on_item
		int invObjUU = _script->scanMobEventsWithItem(_invMobList[_selectedMob]._mask, _script->_scriptInfo.invObjUU, _selectedItem);
		if (invObjUU == -1) {
			int textNr = 80011; // "I can't do it."
			if (_selectedItem == 31 || _invMobList[_selectedMob]._mask == 31) {
				textNr = 80020; // "Nothing is happening."
			}
			_interpreter->setCurrentString(textNr);
			printAt(0, 216, (char *)_variaTxt->getString(textNr - 80000), kNormalWidth / 2, 100);
			setVoice(0, 28, 1);
			playSample(28, 0);
		} else {
			_interpreter->storeNewPC(invObjUU);
			_flags->setFlagValue(Flags::CURRMOB, _invMobList[_selectedMob]._mask);
			_showInventoryFlag = false;
		}
	}
	_selectedMob = -1;
	_optionsMob = -1;
}

void PrinceEngine::inventoryRightMouseButton() {
	if (_textSlots[0]._str == nullptr) {
		enableOptions(false);
	}
}

void PrinceEngine::enableOptions(bool checkType) {
	if (_optionsFlag != 1) {
		changeCursor(1);
		_currentPointerNumber = 1;
		if (_selectedMob != -1) {
			if (checkType) {
				if (_mobList[_selectedMob]._type & 0x100) {
					return;
				}
			}
			Common::Point mousePos = _system->getEventManager()->getMousePos();
			int x1 = mousePos.x - _optionsWidth / 2;
			int x2 = mousePos.x + _optionsWidth / 2;
			if (x1 < 0) {
				x1 = 0;
				x2 = _optionsWidth;
			} else if (x2 >= kNormalWidth) {
				x1 = kNormalWidth - _optionsWidth;
				x2 = kNormalWidth;
			}
			int y1 = mousePos.y - 10;
			if (y1 < 0) {
				y1 = 0;
			}
			if (y1 + _optionsHeight >= kNormalHeight) {
				y1 = kNormalHeight - _optionsHeight;
			}
			_optionsMob = _selectedMob;
			_optionsX = x1;
			_optionsY = y1;
			_optionsFlag = 1;
		}
	}
}

void PrinceEngine::checkOptions() {
	if (_optionsFlag) {
		Common::Rect optionsRect(_optionsX, _optionsY, _optionsX + _optionsWidth, _optionsY + _optionsHeight);
		Common::Point mousePos = _system->getEventManager()->getMousePos();
		if (!optionsRect.contains(mousePos)) {
			_optionsFlag = 0;
			_selectedMob = -1;
			return;
		}
		_graph->drawAsShadowSurface(_graph->_frontScreen, _optionsX, _optionsY, _optionsPic, _graph->_shadowTable50);

		_optionEnabled = -1;
		int optionsYCord = mousePos.y - (_optionsY + 16);
		if (optionsYCord >= 0) {
			int selectedOptionNr = optionsYCord / _optionsStep;
			if (selectedOptionNr < _optionsNumber) {
				_optionEnabled = selectedOptionNr;
			}
		}
		int optionsColor;
		int textY = _optionsY + 16;
		for (int i = 0; i < _optionsNumber; i++) {
			if (i != _optionEnabled) {
				optionsColor = _optionsColor1;
			} else {
				optionsColor = _optionsColor2;
			}
			Common::String optText;
			switch(getLanguage()) {
			case Common::PL_POL:
				optText = optionsTextPL[i];
				break;
			case Common::DE_DEU:
				optText = optionsTextDE[i];
				break;
			case Common::EN_ANY:
				optText = optionsTextEN[i];
				break;
			default:
				break;
			};
			uint16 textW = getTextWidth(optText.c_str());
			uint16 textX = _optionsX + _optionsWidth / 2 - textW / 2;
			_font->drawString(_graph->_frontScreen, optText, textX, textY, textW, optionsColor);
			textY += _optionsStep;
		}
	}
}

void PrinceEngine::checkInvOptions() {
	if (_optionsFlag) {
		Common::Rect optionsRect(_optionsX, _optionsY, _optionsX + _invOptionsWidth, _optionsY + _invOptionsHeight);
		Common::Point mousePos = _system->getEventManager()->getMousePos();
		if (!optionsRect.contains(mousePos)) {
			_optionsFlag = 0;
			_selectedMob = -1;
			return;
		}
		_graph->drawAsShadowSurface(_graph->_screenForInventory, _optionsX, _optionsY, _optionsPicInInventory, _graph->_shadowTable50);

		_optionEnabled = -1;
		int optionsYCord = mousePos.y - (_optionsY + 16);
		if (optionsYCord >= 0) {
			int selectedOptionNr = optionsYCord / _invOptionsStep;
			if (selectedOptionNr < _invOptionsNumber) {
				_optionEnabled = selectedOptionNr;
			}
		}
		int optionsColor;
		int textY = _optionsY + 16;
		for (int i = 0; i < _invOptionsNumber; i++) {
			if (i != _optionEnabled) {
				optionsColor = _optionsColor1;
			} else {
				optionsColor = _optionsColor2;
			}
			Common::String invText;
			switch(getLanguage()) {
			case Common::PL_POL:
				invText = invOptionsTextPL[i];
				break;
			case Common::DE_DEU:
				invText = invOptionsTextDE[i];
				break;
			case Common::EN_ANY:
				invText = invOptionsTextEN[i];
				break;
			default:
				error("Unknown game language %d", getLanguage());
				break;
			};
			uint16 textW = getTextWidth(invText.c_str());
			uint16 textX = _optionsX + _invOptionsWidth / 2 - textW / 2;
			_font->drawString(_graph->_screenForInventory, invText, textX, textY, _graph->_screenForInventory->w, optionsColor);
			textY += _invOptionsStep;
		}
	}
}

void PrinceEngine::displayInventory() {

	_mainHero->freeOldMove();
	_secondHero->freeOldMove();

	_interpreter->setFgOpcodePC(0);

	stopAllSamples();

	prepareInventoryToView();

	while (!shouldQuit()) {

		if (_textSlots[0]._str != nullptr) {
			changeCursor(0);
		} else {
			changeCursor(_currentPointerNumber);

			Common::Rect inventoryRect(_invX1, _invY1, _invX1 + _invWidth, _invY1 + _invHeight);
			Common::Point mousePos = _system->getEventManager()->getMousePos();

			if (!_invCurInside && inventoryRect.contains(mousePos)) {
				_invCurInside = true;
			}

			if (_invCurInside && !inventoryRect.contains(mousePos)) {
				inventoryFlagChange(false);
				_invCurInside = false;
				break;
			}
		}

		rememberScreenInv();

		Graphics::Surface *suitcase = _suitcaseBmp->getSurface();
		_graph->drawTransparentSurface(_graph->_screenForInventory, 0, 0, suitcase);

		drawInvItems();

		showTexts(_graph->_screenForInventory);

		if (!_optionsFlag && _textSlots[0]._str == nullptr) {
			_selectedMob = checkMob(_graph->_screenForInventory, _invMobList, false);
		}

		checkInvOptions();

		Common::Event event;
		Common::EventManager *eventMan = _system->getEventManager();
		while (eventMan->pollEvent(event)) {
			switch (event.type) {
			case Common::EVENT_KEYDOWN:
				keyHandler(event);
				break;
			case Common::EVENT_LBUTTONDOWN:
				inventoryLeftMouseButton();
				break;
			case Common::EVENT_RBUTTONDOWN:
				inventoryRightMouseButton();
				break;
			default:
				break;
			}
		}

		if (!_showInventoryFlag) {
			break;
		}

		if (shouldQuit())
			return;

		getDebugger()->onFrame();
		_graph->update(_graph->_screenForInventory);
		pausePrinceEngine();
	}

	if (_currentPointerNumber == 2) {
		_flags->setFlagValue(Flags::SELITEM, _selectedItem);
	} else {
		_flags->setFlagValue(Flags::SELITEM, 0);
	}
}

void PrinceEngine::createDialogBox(int dialogBoxNr) {
	_dialogLines = 0;
	int amountOfDialogOptions = 0;
	int dialogDataValue = (int)READ_LE_UINT32(_dialogData);

	byte c;
	int sentenceNumber;
	_dialogText = _dialogBoxAddr[dialogBoxNr];
	byte *dialogText = _dialogText;

	while ((sentenceNumber = *dialogText) != 0xFF) {
		dialogText++;
		if (!(dialogDataValue & (1 << sentenceNumber))) {
			_dialogLines += calcTextLines((const char *)dialogText);
			amountOfDialogOptions++;
		}
		do {
			c = *dialogText;
			dialogText++;
		} while (c);
	}

	_dialogHeight = _font->getFontHeight() * _dialogLines + _dialogLineSpace * (amountOfDialogOptions + 1);
	_dialogImage = new Graphics::Surface();
	_dialogImage->create(_dialogWidth, _dialogHeight, Graphics::PixelFormat::createFormatCLUT8());
	Common::Rect dBoxRect(0, 0, _dialogWidth, _dialogHeight);
	_dialogImage->fillRect(dBoxRect, _graph->kShadowColor);
}

void PrinceEngine::dialogRun() {

	_dialogFlag = true;

	while (!shouldQuit()) {

		_interpreter->stepBg();
		drawScreen();

		int dialogX = (640 - _dialogWidth) / 2;
		int dialogY = 460 - _dialogHeight;
		_graph->drawAsShadowSurface(_graph->_frontScreen, dialogX, dialogY, _dialogImage, _graph->_shadowTable50);

		int dialogSkipLeft = 14;
		int dialogSkipUp = 10;

		int dialogTextX = dialogX + dialogSkipLeft;
		int dialogTextY = dialogY + dialogSkipUp;

		Common::Point mousePos = _system->getEventManager()->getMousePos();

		byte c;
		int sentenceNumber;
		byte *dialogText = _dialogText;
		byte *dialogCurrentText = nullptr;
		int dialogSelected = -1;
		int dialogDataValue = (int)READ_LE_UINT32(_dialogData);

		while ((sentenceNumber = *dialogText) != 0xFF) {
			dialogText++;
			int actualColor = _dialogColor1;

			if (!(dialogDataValue & (1 << sentenceNumber))) {
				if (getLanguage() == Common::DE_DEU) {
					correctStringDEU((char *)dialogText);
				}
				Common::Array<Common::String> lines;
				_font->wordWrapText((const char *)dialogText, _graph->_frontScreen->w, lines);

				Common::Rect dialogOption(dialogTextX, dialogTextY - dialogSkipUp / 2, dialogX + _dialogWidth - dialogSkipLeft, dialogTextY + lines.size() * _font->getFontHeight() + dialogSkipUp / 2 - 1);
				if (dialogOption.contains(mousePos)) {
					actualColor = _dialogColor2;
					dialogSelected = sentenceNumber;
					dialogCurrentText = dialogText;
				}

				for (uint j = 0; j < lines.size(); j++) {
					_font->drawString(_graph->_frontScreen, lines[j], dialogTextX, dialogTextY, _graph->_frontScreen->w, actualColor);
					dialogTextY += _font->getFontHeight();
				}
				dialogTextY += _dialogLineSpace;
			}
			do {
				c = *dialogText;
				dialogText++;
			} while (c);
		}

		Common::Event event;
		Common::EventManager *eventMan = _system->getEventManager();
		while (eventMan->pollEvent(event)) {
			switch (event.type) {
			case Common::EVENT_KEYDOWN:
				keyHandler(event);
				break;
			case Common::EVENT_LBUTTONDOWN:
				if (dialogSelected != -1) {
					dialogLeftMouseButton(dialogCurrentText, dialogSelected);
					_dialogFlag = false;
				}
				break;
			default:
				break;
			}
		}

		if (shouldQuit()) {
			return;
		}

		if (!_dialogFlag) {
			break;
		}

		getDebugger()->onFrame();
		_graph->update(_graph->_frontScreen);
		pausePrinceEngine();
	}
	_dialogImage->free();
	delete _dialogImage;
	_dialogImage = nullptr;
	_dialogFlag = false;
}

void PrinceEngine::dialogLeftMouseButton(byte *string, int dialogSelected) {
	_interpreter->setString(string);
	talkHero(0);

	int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
	dialogDataValue |= (1u << dialogSelected);
	WRITE_LE_UINT32(_dialogData, dialogDataValue);

	_flags->setFlagValue(Flags::BOXSEL, dialogSelected + 1);
	setVoice(0, 28, dialogSelected + 1);

	_flags->setFlagValue(Flags::VOICE_H_LINE, _dialogOptLines[dialogSelected * 4]);
	_flags->setFlagValue(Flags::VOICE_A_LINE, _dialogOptLines[dialogSelected * 4 + 1]);
	_flags->setFlagValue(Flags::VOICE_B_LINE, _dialogOptLines[dialogSelected * 4 + 2]);

	_interpreter->setString(_dialogOptAddr[dialogSelected]);
}

void PrinceEngine::talkHero(int slot) {
	// heroSlot = textSlot (slot 0 or 1)
	Text &text = _textSlots[slot];
	int lines = calcTextLines((const char *)_interpreter->getString());
	int time = lines * 30;

	if (slot == 0) {
		text._color = 220; // TODO - test this
		_mainHero->_state = Hero::kHeroStateTalk;
		_mainHero->_talkTime = time;
		text._x = _mainHero->_middleX;
		text._y = _mainHero->_middleY - _mainHero->_scaledFrameYSize;
	} else {
		text._color = _flags->getFlagValue(Flags::KOLOR); // TODO - test this
		_secondHero->_state = Hero::kHeroStateTalk;
		_secondHero->_talkTime = time;
		text._x = _secondHero->_middleX;
		text._y = _secondHero->_middleY - _secondHero->_scaledFrameYSize;
	}
	text._time = time;
	if (getLanguage() == Common::DE_DEU) {
		correctStringDEU((char *)_interpreter->getString());
	}
	text._str = (const char *)_interpreter->getString();
	_interpreter->increaseString();
}

void PrinceEngine::doTalkAnim(int animNumber, int slot, AnimType animType) {
	Text &text = _textSlots[slot];
	int lines = calcTextLines((const char *)_interpreter->getString());
	int time = lines * 30;
	if (animType == kNormalAnimation) {
		Anim &normAnim = _normAnimList[animNumber];
		if (normAnim._animData != nullptr) {
			if (!normAnim._state) {
				if (normAnim._currW && normAnim._currH) {
					text._color = _flags->getFlagValue(Flags::KOLOR);
					text._x = normAnim._currX + normAnim._currW / 2;
					text._y = normAnim._currY - 10;
				}
			}
		}
	} else if (animType == kBackgroundAnimation) {
		if (!_backAnimList[animNumber].backAnims.empty()) {
			int currAnim = _backAnimList[animNumber]._seq._currRelative;
			Anim &backAnim = _backAnimList[animNumber].backAnims[currAnim];
			if (backAnim._animData != nullptr) {
				if (!backAnim._state) {
					if (backAnim._currW && backAnim._currH) {
						text._color = _flags->getFlagValue(Flags::KOLOR);
						text._x = backAnim._currX + backAnim._currW / 2;
						text._y = backAnim._currY - 10;
					}
				}
			}
		}
	} else {
		error("doTalkAnim() - wrong animType: %d", animType);
	}
	text._time = time;
	if (getLanguage() == Common::DE_DEU) {
		correctStringDEU((char *)_interpreter->getString());
	}
	text._str = (const char *)_interpreter->getString();
	_interpreter->increaseString();
}

void PrinceEngine::freeNormAnim(int slot) {
	if (!_normAnimList.empty()) {
		_normAnimList[slot]._state = 1;
		if (_normAnimList[slot]._animData != nullptr) {
			delete _normAnimList[slot]._animData;
			_normAnimList[slot]._animData = nullptr;
		}
		if (_normAnimList[slot]._shadowData != nullptr) {
			delete _normAnimList[slot]._shadowData;
			_normAnimList[slot]._shadowData = nullptr;
		}
	}
}

void PrinceEngine::freeAllNormAnims() {
	for (int i = 0; i < kMaxNormAnims; i++) {
		freeNormAnim(i);
	}
}

void PrinceEngine::getCurve() {
	_flags->setFlagValue(Flags::TORX1, _curveData[_curvPos]);
	_flags->setFlagValue(Flags::TORY1, _curveData[_curvPos + 1]);
	_curvPos += 2;
}

void PrinceEngine::makeCurve() {
	_curvPos = 0;
	int x1 = _flags->getFlagValue(Flags::TORX1);
	int y1 = _flags->getFlagValue(Flags::TORY1);
	int x2 = _flags->getFlagValue(Flags::TORX2);
	int y2 = _flags->getFlagValue(Flags::TORY2);

	for (int i = 0; i < kCurveLen; i++) {
		int sum1 = x1 * curveValues[i][0];
		sum1 += (x2 + (x1 - x2) / 2) * curveValues[i][1];
		sum1 += x2 * curveValues[i][2];
		sum1 += x2 * curveValues[i][3];

		int sum2 = y1 * curveValues[i][0];
		sum2 += (y2 - 20) * curveValues[i][1];
		sum2 += (y2 - 10) * curveValues[i][2];
		sum2 += y2 * curveValues[i][3];

		_curveData[i * 2] = (sum1 >> 15);
		_curveData[i * 2 + 1] = (sum2 >> 15);
	}
}

void PrinceEngine::mouseWeirdo() {
	if (_mouseFlag == 3) {
		int weirdDir = _randomSource.getRandomNumber(3);
		Common::Point mousePos = _system->getEventManager()->getMousePos();
		switch (weirdDir) {
		case 0:
			mousePos.x += kCelStep;
			break;
		case 1:
			mousePos.x -= kCelStep;
			break;
		case 2:
			mousePos.y += kCelStep;
			break;
		case 3:
			mousePos.y -= kCelStep;
			break;
		}
		mousePos.x = CLIP(mousePos.x, (int16) 315, (int16) 639);
		_flags->setFlagValue(Flags::MXFLAG, mousePos.x);
		mousePos.y = CLIP(mousePos.y, (int16) 0, (int16) 170);
		_flags->setFlagValue(Flags::MYFLAG, mousePos.y);
		_system->warpMouse(mousePos.x, mousePos.y);
	}
}

void PrinceEngine::showPower() {
	if (_flags->getFlagValue(Flags::POWERENABLED)) {
		int power = _flags->getFlagValue(Flags::POWER);

		byte *dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarPosY);
		for (int y = 0; y < kPowerBarHeight; y++) {
			byte *dst2 = dst;
			for (int x = 0; x < kPowerBarWidth; x++, dst2++) {
				*dst2 = kPowerBarBackgroundColor;
			}
			dst += _graph->_frontScreen->pitch;
		}

		if (power) {
			dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarGreenPosY);
			for (int y = 0; y < kPowerBarGreenHeight; y++) {
				byte *dst2 = dst;
				for (int x = 0; x < power + 1; x++, dst2++) {
					if (x < 58) {
						*dst2 = kPowerBarGreenColor1;
					} else {
						*dst2 = kPowerBarGreenColor2;
					}
				}
				dst += _graph->_frontScreen->pitch;
			}
		}

		_graph->change();
	}
}

void PrinceEngine::scrollCredits() {
	byte *scrollAdress = _creditsData;
	while (!shouldQuit()) {
		for (int scrollPos = 0; scrollPos > -23; scrollPos--) {
			const Graphics::Surface *roomSurface = _roomBmp->getSurface();
			if (roomSurface) {
				_graph->draw(_graph->_frontScreen, roomSurface);
			}
			char *s = (char *)scrollAdress;
			int drawY = scrollPos;
			for (int i = 0; i < 22; i++) {
				Common::String line;
				char *linePos = s;
				while ((*linePos != 13)) {
					line += *linePos;
					linePos++;
				}
				if (!line.empty()) {
					int drawX = (kNormalWidth - getTextWidth(line.c_str())) / 2;
					_font->drawString(_graph->_frontScreen, line, drawX, drawY, _graph->_frontScreen->w, 217);
				}

				char letter1;
				bool gotIt1 = false;
				do {
					letter1 = *s;
					s++;
					if (letter1 == 13) {
						if (*s == 10) {
							s++;
						}
						if (*s != 35) {
							gotIt1 = true;
						}
						break;
					}
				} while (letter1 != 35);

				if (gotIt1) {
					drawY += 23;
				} else {
					break;
				}
			}
			Common::Event event;
			Common::EventManager *eventMan = _system->getEventManager();
			while (eventMan->pollEvent(event)) {
				if (event.type == Common::EVENT_KEYDOWN) {
					if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
						blackPalette();
						return;
					}
				}
			}
			if (shouldQuit()) {
				return;
			}
			_graph->change();
			_graph->update(_graph->_frontScreen);
			pausePrinceEngine(kFPS * 2);
		}
		char letter2;
		byte *scan2 = scrollAdress;
		bool gotIt2 = false;
		do {
			letter2 = *scan2;
			scan2++;
			if (letter2 == 13) {
				if (*scan2 == 10) {
					scan2++;
				}
				if (*scan2 != 35) {
					gotIt2 = true;
				}
				break;
			}
		} while (letter2 != 35);
		if (gotIt2) {
			scrollAdress = scan2;
		} else {
			break;
		}
	}
	blackPalette();
}

// Modified version of Graphics::drawLine() to allow breaking the loop and return value
int PrinceEngine::drawLine(int x0, int y0, int x1, int y1, int (*plotProc)(int, int, void *), void *data) {
	// Bresenham's line algorithm, as described by Wikipedia
	const bool steep = ABS(y1 - y0) > ABS(x1 - x0);

	if (steep) {
		SWAP(x0, y0);
		SWAP(x1, y1);
	}

	const int delta_x = ABS(x1 - x0);
	const int delta_y = ABS(y1 - y0);
	const int delta_err = delta_y;
	int x = x0;
	int y = y0;
	int err = 0;

	const int x_step = (x0 < x1) ? 1 : -1;
	const int y_step = (y0 < y1) ? 1 : -1;

	int stopFlag = 0;
	if (steep)
		stopFlag = (*plotProc)(y, x, data);
	else
		stopFlag = (*plotProc)(x, y, data);

	while (x != x1 && !stopFlag) {
		x += x_step;
		err += delta_err;
		if (2 * err > delta_x) {
			y += y_step;
			err -= delta_x;
		}
		if (steep)
			stopFlag = (*plotProc)(y, x, data);
		else
			stopFlag = (*plotProc)(x, y, data);
	}
	return stopFlag;
}

int PrinceEngine::getPixelAddr(byte *pathBitmap, int x, int y) {
	int mask = 128 >> (x & 7);
	byte value = pathBitmap[x / 8 + y * 80];
	return (mask & value);
}

void PrinceEngine::findPoint(int x, int y) {
	_fpX = x;
	_fpY = y;

	if (getPixelAddr(_roomPathBitmap, x, y)) {
		return;
	}

	int fpL = x;
	int fpU = y;
	int fpR = x;
	int fpD = y;

	while (1) {
		if (fpD != kMaxPicHeight) {
			if (getPixelAddr(_roomPathBitmap, x, fpD)) {
				_fpX = x;
				_fpY = fpD;
				break;
			}
			fpD++;
		}
		if (fpU) {
			if (getPixelAddr(_roomPathBitmap, x, fpU)) {
				_fpX = x;
				_fpY = fpU;
				break;
			}
			fpU--;
		}
		if (fpL) {
			if (getPixelAddr(_roomPathBitmap, fpL, y)) {
				_fpX = fpL;
				_fpY = y;
				break;
			}
			fpL--;
		}
		if (fpR != _sceneWidth) {
			if (getPixelAddr(_roomPathBitmap, fpR, y)) {
				_fpX = fpR;
				_fpY = y;
				break;
			}
			fpR++;
		}
		if (!fpU && (fpD == kMaxPicHeight)) {
			if (!fpL && (fpR == _sceneWidth)) {
				break;
			}
		}
	}
}

Direction PrinceEngine::makeDirection(int x1, int y1, int x2, int y2) {
	if (x1 != x2) {
		if (y1 != y2) {
			if (x1 > x2) {
				if (y1 > y2) {
					if (x1 - x2 >= y1 - y2) {
						return kDirLU;
					} else {
						return kDirUL;
					}
				} else {
					if (x1 - x2 >= y2 - y1) {
						return kDirLD;
					} else {
						return kDirDL;
					}
				}
			} else {
				if (y1 > y2) {
					if (x2 - x1 >= y1 - y2) {
						return kDirRU;
					} else {
						return kDirUR;
					}
				} else {
					if (x2 - x1 >= y2 - y1) {
						return kDirRD;
					} else {
						return kDirDR;
					}
				}
			}
		} else {
			if (x1 >= x2) {
				return kDirL;
			} else {
				return kDirR;
			}
		}
	} else {
		if (y1 >= y2) {
			return kDirU;
		} else {
			return kDirD;
		}
	}
}

void PrinceEngine::specialPlot(int x, int y) {
	if (_coords < _coordsBufEnd) {
		WRITE_LE_UINT16(_coords, x);
		_coords += 2;
		WRITE_LE_UINT16(_coords, y);
		_coords += 2;
		specialPlot2(x, y);
	}
}

void PrinceEngine::specialPlot2(int x, int y) {
	int mask = 128 >> (x & 7);
	_roomPathBitmapTemp[x / 8 + y * 80] |= mask;
}

void PrinceEngine::specialPlotInside(int x, int y) {
	if (_coords < _coordsBufEnd) {
		WRITE_LE_UINT16(_coords, x);
		_coords += 2;
		WRITE_LE_UINT16(_coords, y);
		_coords += 2;
	}
}

int PrinceEngine::plotTraceLine(int x, int y, void *data) {
	PrinceEngine *traceLine = (PrinceEngine *)data;
	if (!traceLine->_traceLineFirstPointFlag) {
		if (!traceLine->getPixelAddr(traceLine->_roomPathBitmapTemp, x, y)) {
			if (traceLine->getPixelAddr(traceLine->_roomPathBitmap, x, y)) {
				traceLine->specialPlotInside(x, y);
				traceLine->_traceLineLen++;
				return 0;
			} else {
				return -1;
			}
		} else {
			return 1;
		}
	} else {
		traceLine->_traceLineFirstPointFlag = false;
		return 0;
	}
}

int PrinceEngine::leftDownDir() {
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::leftDir() {
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::leftUpDir() {
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::rightDownDir() {
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::rightDir() {
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::rightUpDir() {
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::upLeftDir() {
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::upDir() {
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::upRightDir() {
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::downLeftDir() {
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::downDir() {
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::downRightDir() {
	if (!checkRightDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDownDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkRightUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	if (!checkLeftUpDir()) {
		specialPlot(_checkX, _checkY);
		return 0;
	}
	return -1;
}

int PrinceEngine::cpe() {
	if ((*(_checkBitmap - kPBW) & _checkMask)) {
		if ((*(_checkBitmap + kPBW) & _checkMask)) {
			int value;
			switch (_checkMask) {
			case 128:
				value = READ_LE_UINT16(_checkBitmap - 1);
				value &= 0x4001;
				if (value != 0x4001) {
					return 0;
				}
				break;
			case 64:
				value = *_checkBitmap;
				value &= 0xA0;
				if (value != 0xA0) {
					return 0;
				}
				break;
			case 32:
				value = *_checkBitmap;
				value &= 0x50;
				if (value != 0x50) {
					return 0;
				}
				break;
			case 16:
				value = *_checkBitmap;
				value &= 0x28;
				if (value != 0x28) {
					return 0;
				}
				break;
			case 8:
				value = *_checkBitmap;
				value &= 0x14;
				if (value != 0x14) {
					return 0;
				}
				break;
			case 4:
				value = *_checkBitmap;
				value &= 0xA;
				if (value != 0xA) {
					return 0;
				}
				break;
			case 2:
				value = *_checkBitmap;
				value &= 0x5;
				if (value != 0x5) {
					return 0;
				}
				break;
			case 1:
				value = READ_LE_UINT16(_checkBitmap);
				value &= 0x8002;
				if (value != 0x8002) {
					return 0;
				}
				break;
			default:
				error("Wrong _checkMask value - cpe()");
				break;
			}
			_checkX = _rembX;
			_checkY = _rembY;
			_checkBitmapTemp = _rembBitmapTemp;
			_checkBitmap = _rembBitmap;
			_checkMask = _rembMask;
			return -1;
		}
		return 0;
	}
	return 0;
}

int PrinceEngine::checkLeftDownDir() {
	if (_checkX && _checkY != (kMaxPicHeight / 2 - 1)) {
		int tempMask = _checkMask;
		if (tempMask != 128) {
			tempMask <<= 1;
			if ((*(_checkBitmap + kPBW) & tempMask)) {
				if (!(*(_checkBitmapTemp + kPBW) & tempMask)) {
					_checkBitmap += kPBW;
					_checkBitmapTemp += kPBW;
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap + kPBW - 1) & 1)) {
				if (!(*(_checkBitmapTemp + kPBW - 1) & 1)) {
					_checkBitmap += (kPBW - 1);
					_checkBitmapTemp += (kPBW - 1);
					_checkMask = 1;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX--;
		_checkY++;
		return cpe();
	} else {
		return -1;
	}
}

int PrinceEngine::checkLeftDir() {
	if (_checkX) {
		int tempMask = _checkMask;
		if (tempMask != 128) {
			tempMask <<= 1;
			if ((*(_checkBitmap) & tempMask)) {
				if (!(*(_checkBitmapTemp) & tempMask)) {
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap - 1) & 1)) {
				if (!(*(_checkBitmapTemp - 1) & 1)) {
					_checkBitmap--;
					_checkBitmapTemp--;
					_checkMask = 1;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX--;
		return cpe();
	} else {
		return -1;
	}
}

int PrinceEngine::checkDownDir() {
	if (_checkY != (kMaxPicHeight / 2 - 1)) {
		if ((*(_checkBitmap + kPBW) & _checkMask)) {
			if (!(*(_checkBitmapTemp + kPBW) & _checkMask)) {
				_checkBitmap += kPBW;
				_checkBitmapTemp += kPBW;
				_checkY++;
				return cpe();
			} else {
				return 1;
			}
		} else {
			return -1;
		}
	} else {
		return -1;
	}
}

int PrinceEngine::checkUpDir() {
	if (_checkY) {
		if ((*(_checkBitmap - kPBW) & _checkMask)) {
			if (!(*(_checkBitmapTemp - kPBW) & _checkMask)) {
				_checkBitmap -= kPBW;
				_checkBitmapTemp -= kPBW;
				_checkY--;
				return cpe();
			} else {
				return 1;
			}
		} else {
			return -1;
		}
	} else {
		return -1;
	}
}

int PrinceEngine::checkRightDir() {
	if (_checkX != (kMaxPicWidth / 2 - 1)) {
		int tempMask = _checkMask;
		if (tempMask != 1) {
			tempMask >>= 1;
			if ((*(_checkBitmap) & tempMask)) {
				if (!(*(_checkBitmapTemp) & tempMask)) {
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap + 1) & 128)) {
				if (!(*(_checkBitmapTemp + 1) & 128)) {
					_checkBitmap++;
					_checkBitmapTemp++;
					_checkMask = 128;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX++;
		return cpe();
	} else {
		return -1;
	}
}

int PrinceEngine::checkLeftUpDir() {
	if (_checkX && _checkY) {
		int tempMask = _checkMask;
		if (tempMask != 128) {
			tempMask <<= 1;
			if ((*(_checkBitmap - kPBW) & tempMask)) {
				if (!(*(_checkBitmapTemp - kPBW) & tempMask)) {
					_checkBitmap -= kPBW;
					_checkBitmapTemp -= kPBW;
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap - (kPBW + 1)) & 1)) {
				if (!(*(_checkBitmapTemp - (kPBW + 1)) & 1)) {
					_checkBitmap -= (kPBW + 1);
					_checkBitmapTemp -= (kPBW + 1);
					_checkMask = 1;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX--;
		_checkY--;
		return cpe();
	} else {
		return -1;
	}
}

int PrinceEngine::checkRightDownDir() {
	if (_checkX != (kMaxPicWidth / 2 - 1) && _checkY != (kMaxPicHeight / 2 - 1)) {
		int tempMask = _checkMask;
		if (tempMask != 1) {
			tempMask >>= 1;
			if ((*(_checkBitmap + kPBW) & tempMask)) {
				if (!(*(_checkBitmapTemp + kPBW) & tempMask)) {
					_checkBitmap += kPBW;
					_checkBitmapTemp += kPBW;
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap + kPBW + 1) & 128)) {
				if (!(*(_checkBitmapTemp + kPBW + 1) & 128)) {
					_checkBitmap += kPBW + 1;
					_checkBitmapTemp += kPBW + 1;
					_checkMask = 128;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX++;
		_checkY++;
		return cpe();
	} else {
		return -1;
	}
}

int PrinceEngine::checkRightUpDir() {
	if (_checkX != (kMaxPicWidth / 2 - 1) && _checkY) {
		int tempMask = _checkMask;
		if (tempMask != 1) {
			tempMask >>= 1;
			if ((*(_checkBitmap - kPBW) & tempMask)) {
				if (!(*(_checkBitmapTemp - kPBW) & tempMask)) {
					_checkBitmap -= kPBW;
					_checkBitmapTemp -= kPBW;
					_checkMask = tempMask;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		} else {
			if ((*(_checkBitmap - kPBW + 1) & 128)) {
				if (!(*(_checkBitmapTemp - kPBW + 1) & 128)) {
					_checkBitmap -= (kPBW - 1);
					_checkBitmapTemp -= (kPBW - 1);
					_checkMask = 128;
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
		_checkX++;
		_checkY--;
		return cpe();
	} else {
		return -1;
	}
}

bool PrinceEngine::tracePath(int x1, int y1, int x2, int y2) {
	for (uint i = 0; i < kPathBitmapLen; i++) {
		_roomPathBitmapTemp[i] = 0;
	}
	if (x1 != x2 || y1 != y2) {
		if (getPixelAddr(_roomPathBitmap, x1, y1)) {
			if (getPixelAddr(_roomPathBitmap, x2, y2)) {
				_coords = _coordsBuf;
				specialPlot(x1, y1);

				int x = x1;
				int y = y1;

				while (1) {
					int btx = x;
					int bty = y;
					byte *bcad = _coords;

					_traceLineLen = 0;
					_traceLineFirstPointFlag = true;
					int drawLineFlag = drawLine(x, y, x2, y2, &this->plotTraceLine, this);

					if (!drawLineFlag) {
						return true;
					} else if (drawLineFlag == -1 && _traceLineLen >= 2) {
						byte *tempCorrds = bcad;
						while (tempCorrds != _coords) {
							x = READ_LE_UINT16(tempCorrds);
							y = READ_LE_UINT16(tempCorrds + 2);
							tempCorrds += 4;
							specialPlot2(x, y);
						}
					} else {
						_coords = bcad;
						x = btx;
						y = bty;
					}

					Direction dir = makeDirection(x, y, x2, y2);

					_rembBitmapTemp = &_roomPathBitmapTemp[x / 8 + y * 80];
					_rembBitmap = &_roomPathBitmap[x / 8 + y * 80];
					_rembMask = 128 >> (x & 7);
					_rembX = x;
					_rembY = y;

					_checkBitmapTemp = _rembBitmapTemp;
					_checkBitmap = _rembBitmap;
					_checkMask = _rembMask;
					_checkX = _rembX;
					_checkY = _rembY;

					int result;
					switch (dir) {
					case kDirLD:
						result = leftDownDir();
						break;
					case kDirL:
						result = leftDir();
						break;
					case kDirLU:
						result = leftUpDir();
						break;
					case kDirRD:
						result = rightDownDir();
						break;
					case kDirR:
						result = rightDir();
						break;
					case kDirRU:
						result = rightUpDir();
						break;
					case kDirUL:
						result = upLeftDir();
						break;
					case kDirU:
						result = upDir();
						break;
					case kDirUR:
						result = upRightDir();
						break;
					case kDirDL:
						result = downLeftDir();
						break;
					case kDirD:
						result = downDir();
						break;
					case kDirDR:
						result = downRightDir();
						break;
					default:
						result = -1;
						error("tracePath: wrong direction %d", dir);
						break;
					}

					if (result) {
						byte *tempCoords = _coords;
						tempCoords -= 4;
						if (tempCoords > _coordsBuf) {
							int tempX = READ_LE_UINT16(tempCoords);
							int tempY = READ_LE_UINT16(tempCoords + 2);
							if (_checkX == tempX && _checkY == tempY) {
								_coords = tempCoords;
							}
							x = READ_LE_UINT16(tempCoords);
							y = READ_LE_UINT16(tempCoords + 2);
						} else {
							return false;
						}
					} else {
						x = _checkX;
						y = _checkY;
					}
				}
				return true;
			} else {
				error("tracePath: wrong destination point");
			}
		} else {
			error("tracePath: wrong start point");
		}
	} else {
		error("tracePath: same point");
	}
}

void PrinceEngine::specialPlotInside2(int x, int y) {
	WRITE_LE_UINT16(_coords2, x);
	_coords2 += 2;
	WRITE_LE_UINT16(_coords2, y);
	_coords2 += 2;
}

int PrinceEngine::plotTracePoint(int x, int y, void *data) {
	PrinceEngine *tracePoint = (PrinceEngine *)data;
	if (!tracePoint->_tracePointFirstPointFlag) {
		if (tracePoint->getPixelAddr(tracePoint->_roomPathBitmap, x, y)) {
			tracePoint->specialPlotInside2(x, y);
			return 0;
		} else {
			return -1;
		}
	} else {
		tracePoint->_tracePointFirstPointFlag = false;
		return 0;
	}
}

void PrinceEngine::approxPath() {
	byte *oldCoords;
	_coords2 = _coordsBuf2;
	byte *tempCoordsBuf = _coordsBuf; // first point on path
	byte *tempCoords = _coords;
	if (tempCoordsBuf != tempCoords) {
		tempCoords -= 4; // last point on path
		while (tempCoordsBuf != tempCoords) {
			int x1 = READ_LE_UINT16(tempCoords);
			int y1 = READ_LE_UINT16(tempCoords + 2);
			int x2 = READ_LE_UINT16(tempCoordsBuf);
			int y2 = READ_LE_UINT16(tempCoordsBuf + 2);
			tempCoordsBuf += 4;
			//TracePoint
			oldCoords = _coords2;
			if (_coords2 == _coordsBuf2) {
				WRITE_LE_UINT16(_coords2, x1);
				WRITE_LE_UINT16(_coords2 + 2, y1);
				_coords2 += 4;
			} else {
				int testX = READ_LE_UINT16(_coords2 - 4);
				int testY = READ_LE_UINT16(_coords2 - 2);
				if (testX != x1 || testY != y1) {
					WRITE_LE_UINT16(_coords2, x1);
					WRITE_LE_UINT16(_coords2 + 2, y1);
					_coords2 += 4;
				}
			}
			_tracePointFirstPointFlag = true;
			bool drawLineFlag = drawLine(x1, y1, x2, y2, &this->plotTracePoint, this);
			if (!drawLineFlag) {
				tempCoords = tempCoordsBuf - 4;
				tempCoordsBuf = _coordsBuf;
			} else {
				_coords2 = oldCoords;
			}
		}
	}
}

void PrinceEngine::freeDirectionTable() {
	if (_directionTable != nullptr) {
		free(_directionTable);
		_directionTable = nullptr;
	}
}

int PrinceEngine::scanDirectionsFindNext(byte *tempCoordsBuf, int xDiff, int yDiff) {

	int tempX, tempY, direction;

	tempX = Hero::kHeroDirLeft;
	if (xDiff < 0) {
		tempX = Hero::kHeroDirRight;
	}

	tempY = Hero::kHeroDirUp;
	if (yDiff < 0) {
		tempY = Hero::kHeroDirDown;
	}

	while (1) {
		int againPointX1 = READ_LE_UINT16(tempCoordsBuf);
		int againPointY1 = READ_LE_UINT16(tempCoordsBuf + 2);
		tempCoordsBuf += 4;

		if (tempCoordsBuf == _coords) {
			direction = tempX;
			break;
		}

		int dX = againPointX1 - READ_LE_UINT16(tempCoordsBuf);
		int dY = againPointY1 - READ_LE_UINT16(tempCoordsBuf + 2);

		if (dX != xDiff) {
			direction = tempY;
			break;
		}

		if (dY != yDiff) {
			direction = tempX;
			break;
		}
	}
	return direction;
}

void PrinceEngine::scanDirections() {
	freeDirectionTable();
	byte *tempCoordsBuf = _coordsBuf;
	if (tempCoordsBuf != _coords) {
		int size = (_coords - tempCoordsBuf) / 4 + 1; // number of coord points plus one for end marker
		_directionTable = (byte *)malloc(size);
		byte *tempDirTab = _directionTable;
		int direction = -1;
		int lastDirection = -1;

		while (1) {
			int x1 = READ_LE_UINT16(tempCoordsBuf);
			int y1 = READ_LE_UINT16(tempCoordsBuf + 2);
			tempCoordsBuf += 4;
			if (tempCoordsBuf == _coords) {
				break;
			}
			int x2 = READ_LE_UINT16(tempCoordsBuf);
			int y2 = READ_LE_UINT16(tempCoordsBuf + 2);

			int xDiff = x1 - x2;
			int yDiff = y1 - y2;

			if (xDiff) {
				if (yDiff) {
					if (lastDirection != -1) {
						direction = lastDirection;
						if (direction == Hero::kHeroDirLeft) {
							if (xDiff < 0) {
								direction = scanDirectionsFindNext(tempCoordsBuf, xDiff, yDiff);
							}
						} else if (direction == Hero::kHeroDirRight) {
							if (xDiff >= 0) {
								direction = scanDirectionsFindNext(tempCoordsBuf, xDiff, yDiff);
							}
						} else if (direction == Hero::kHeroDirUp) {
							if (yDiff < 0) {
								direction = scanDirectionsFindNext(tempCoordsBuf, xDiff, yDiff);
							}
						} else {
							if (yDiff >= 0) {
								direction = scanDirectionsFindNext(tempCoordsBuf, xDiff, yDiff);
							}
						}
					} else {
						direction = scanDirectionsFindNext(tempCoordsBuf, xDiff, yDiff);
					}
				} else {
					direction = Hero::kHeroDirLeft;
					if (xDiff < 0) {
						direction = Hero::kHeroDirRight;
					}
				}
			} else {
				if (yDiff) {
					direction = Hero::kHeroDirUp;
					if (yDiff < 0) {
						direction = Hero::kHeroDirDown;
					}
				} else {
					direction = lastDirection;
				}
			}
			lastDirection = direction;
			*tempDirTab = direction;
			tempDirTab++;
		}
		*tempDirTab = *(tempDirTab - 1);
		tempDirTab++;
		*tempDirTab = 0;
	}
}

void PrinceEngine::moveShandria() {
	int shanLen1 = _shanLen;
	if (_flags->getFlagValue(Flags::SHANDOG)) {
		_secondHero->freeHeroAnim();
		_secondHero->freeOldMove();
		byte *shanCoords = _mainHero->_currCoords + shanLen1 * 4 - 4;
		int shanX = READ_LE_UINT16(shanCoords - 4);
		int shanY = READ_LE_UINT16(shanCoords - 2);
		int xDiff = shanX - _secondHero->_middleX;
		if (xDiff < 0) {
			xDiff *= -1;
		}
		int yDiff = shanY - _secondHero->_middleY;
		if (yDiff < 0) {
			yDiff *= -1;
		}
		shanCoords -= 4;
		if (shanCoords != _mainHero->_currCoords) {
			yDiff *= 1.5;
			int shanDis =  xDiff * xDiff + yDiff * yDiff;
			if (shanDis >= kMinDistance) {
				while (1) {
					shanCoords -= 4;
					if (shanCoords == _mainHero->_currCoords) {
						break;
					}
					int x = READ_LE_UINT16(shanCoords);
					int y = READ_LE_UINT16(shanCoords + 2);
					int pointDiffX = x - shanX;
					if (pointDiffX < 0) {
						pointDiffX *= -1;
					}
					int pointDiffY = y - shanY;
					if (pointDiffY < 0) {
						pointDiffY *= -1;
					}
					pointDiffY *= 1.5;
					int distance = pointDiffX * pointDiffX + pointDiffY * pointDiffY;
					if (distance >= kMinDistance) {
						break;
					}
				}
				int pathSizeDiff = (shanCoords - _mainHero->_currCoords) / 4;
				int destDir = *(_mainHero->_currDirTab + pathSizeDiff);
				_secondHero->_destDirection = destDir;
				int destX = READ_LE_UINT16(shanCoords);
				int destY = READ_LE_UINT16(shanCoords + 2);
				_secondHero->_coords = makePath(kSecondHero, _secondHero->_middleX, _secondHero->_middleY, destX, destY);
				if (_secondHero->_coords != nullptr) {
					_secondHero->_currCoords = _secondHero->_coords;
					int delay = shanLen1 - _shanLen;
					if (delay < 6) {
						delay = 6;
					}
					_secondHero->_moveDelay = delay / 2;
					_secondHero->_state = Hero::kHeroStateDelayMove;
					_secondHero->_dirTab = _directionTable;
					_secondHero->_currDirTab = _directionTable;
					_directionTable = nullptr;
				}
			}
		}
	}
}

byte *PrinceEngine::makePath(int heroId, int currX, int currY, int destX, int destY) {
	int realDestX = destX;
	int realDestY = destY;
	_flags->setFlagValue(Flags::MOVEDESTX, destX);
	_flags->setFlagValue(Flags::MOVEDESTY, destY);

	int x1 = currX / 2;
	int y1 = currY / 2;
	int x2 = destX / 2;
	int y2 = destY / 2;

	if ((x1 != x2) || (y1 != y2)) {
		findPoint(x1, y1);
		if (!getPixelAddr(_roomPathBitmap, _fpX, _fpY)) {
			return nullptr;
		}
		if ((x1 != _fpX) || (y1 != _fpY)) {
			x1 = _fpX;
			y1 = _fpY;
		}
		findPoint(x2, y2);
		if (!getPixelAddr(_roomPathBitmap, _fpX, _fpY)) {
			return nullptr;
		}
		if ((x2 != _fpX) || (y2 != _fpY)) {
			x2 = _fpX;
			y2 = _fpY;
			if (!_flags->getFlagValue(Flags::EXACTMOVE)) {
				realDestX = x2 * 2;
				realDestY = y2 * 2;
				_flags->setFlagValue(Flags::MOVEDESTX, realDestX);
				_flags->setFlagValue(Flags::MOVEDESTY, realDestY);
			} else {
				return nullptr;
			}
		}

		if ((x1 == x2) && (y1 == y2)) {
			if (!heroId) {
				_mainHero->freeOldMove();
				_mainHero->_state = Hero::kHeroStateTurn;
			} else if (heroId == 1) {
				_secondHero->freeOldMove();
				_secondHero->_state = Hero::kHeroStateTurn;
			}
			return nullptr;
		}

		int pathLen1 = 0;
		int pathLen2 = 0;
		int stX = x1;
		int stY = y1;
		int sizeCoords2 = 0;

		if (tracePath(x1, y1, x2, y2)) {
			allocCoords2();
			approxPath();
			sizeCoords2 = _coords2 - _coordsBuf2;
			for (int i = 0; i < sizeCoords2; i++) {
				_coordsBuf[i] = _coordsBuf2[i];
			}
			_coords = _coordsBuf + sizeCoords2;
			approxPath();
			_coordsBuf3 = _coordsBuf2;
			_coordsBuf2 = nullptr;
			_coords3 = _coords2;
			_coords2 = nullptr;
			pathLen1 = _coords3 - _coordsBuf3;
		}
		if (tracePath(x2, y2, x1, y1)) {
			allocCoords2();
			approxPath();
			sizeCoords2 = _coords2 - _coordsBuf2;
			for (int i = 0; i < sizeCoords2; i++) {
				_coordsBuf[i] = _coordsBuf2[i];
			}
			_coords = _coordsBuf + sizeCoords2;
			approxPath();
			pathLen2 = _coords2 - _coordsBuf2;
		}

		byte *chosenCoordsBuf = _coordsBuf2;
		byte *choosenCoords = _coords2;
		int choosenLength = pathLen1;
		if (pathLen1 < pathLen2) {
			chosenCoordsBuf = _coordsBuf3;
			choosenCoords = _coords3;
			choosenLength = pathLen2;
		}

		if (choosenLength) {
			if (chosenCoordsBuf != nullptr) {
				int tempXBegin = READ_LE_UINT16(chosenCoordsBuf);
				int tempYBegin = READ_LE_UINT16(chosenCoordsBuf + 2);
				if (stX != tempXBegin || stY != tempYBegin) {
					SWAP(chosenCoordsBuf, choosenCoords);
					chosenCoordsBuf -= 4;
					byte *tempCoordsBuf = _coordsBuf;
					while (1) {
						int cord = READ_LE_UINT32(chosenCoordsBuf);
						WRITE_LE_UINT32(tempCoordsBuf, cord);
						tempCoordsBuf += 4;
						if (chosenCoordsBuf == choosenCoords) {
							break;
						}
						chosenCoordsBuf -= 4;
					}
					_coords = tempCoordsBuf;
				} else {
					int sizeChoosen = choosenCoords - chosenCoordsBuf;
					for (int i = 0; i < sizeChoosen; i++) {
						_coordsBuf[i] = chosenCoordsBuf[i];
					}
					_coords = _coordsBuf + sizeChoosen;
				}
				WRITE_LE_UINT32(_coords, 0xFFFFFFFF);
				freeCoords2();
				freeCoords3();
				scanDirections();

				byte *tempCoordsBuf = _coordsBuf;
				byte *tempCoords = _coords;
				byte *newCoords;
				if (tempCoordsBuf != tempCoords) {
					int normCoordsSize = _coords - _coordsBuf + 4;
					newCoords = (byte *)malloc(normCoordsSize);
					byte *newCoordsBegin = newCoords;
					while (tempCoordsBuf != tempCoords) {
						int newValueX = READ_LE_UINT16(tempCoordsBuf);
						WRITE_LE_UINT16(newCoords, newValueX * 2);
						newCoords += 2;
						int newValueY = READ_LE_UINT16(tempCoordsBuf + 2);
						WRITE_LE_UINT16(newCoords, newValueY * 2);
						newCoords += 2;
						tempCoordsBuf += 4;
					}
					WRITE_LE_UINT16(newCoords - 4, realDestX);
					WRITE_LE_UINT16(newCoords - 2, realDestY);
					WRITE_LE_UINT32(newCoords, 0xFFFFFFFF);
					newCoords += 4;
					_shanLen = (newCoords - newCoordsBegin);
					_shanLen /= 4;
					return newCoordsBegin;
				}
			}
		}
		_coords = _coordsBuf;
		freeCoords2();
		freeCoords3();
		return nullptr;
	} else {
		if (!heroId) {
			_mainHero->freeOldMove();
			_mainHero->_state = Hero::kHeroStateTurn;
		} else if (heroId == 1) {
			_secondHero->freeOldMove();
			_secondHero->_state = Hero::kHeroStateTurn;
		}
		return nullptr;
	}
}

void PrinceEngine::allocCoords2() {
	if (_coordsBuf2 == nullptr) {
		_coordsBuf2 = (byte *)malloc(kTracePts * 4);
		_coords2 = _coordsBuf2;
	}
}

void PrinceEngine::freeCoords2() {
	if (_coordsBuf2 != nullptr) {
		free(_coordsBuf2);
		_coordsBuf2 = nullptr;
		_coords2 = nullptr;
	}
}

void PrinceEngine::freeCoords3() {
	if (_coordsBuf3 != nullptr) {
		free(_coordsBuf3);
		_coordsBuf3 = nullptr;
		_coords3 = nullptr;
	}
}

void PrinceEngine::openInventoryCheck() {
	if (!_optionsFlag) {
		if (_mouseFlag == 1 || _mouseFlag == 2) {
			if (_mainHero->_visible) {
				if (!_flags->getFlagValue(Flags::INVALLOWED)) {
					// 29 - Basement, 50 - Map
					if (_locationNr != 29 && _locationNr != 50) {
						Common::Point mousePos = _system->getEventManager()->getMousePos();
						if (mousePos.y < 4 && !_showInventoryFlag) {
							_invCounter++;
						} else {
							_invCounter = 0;
						}
						if (_invCounter >= _invMaxCount) {
							inventoryFlagChange(true);
						}
					}
				}
			}
		}
	}
}

void PrinceEngine::mainLoop() {
	changeCursor(0);
	_currentTime = _system->getMillis();

	while (!shouldQuit()) {
		Common::Event event;
		Common::EventManager *eventMan = _system->getEventManager();
		while (eventMan->pollEvent(event)) {
			switch (event.type) {
			case Common::EVENT_KEYDOWN:
				keyHandler(event);
				break;
			case Common::EVENT_LBUTTONDOWN:
				leftMouseButton();
				break;
			case Common::EVENT_RBUTTONDOWN:
				rightMouseButton();
				break;
			default:
				break;
			}
		}

		if (shouldQuit()) {
			return;
		}

		// for "throw a rock" mini-game
		mouseWeirdo();

		_interpreter->stepBg();
		_interpreter->stepFg();

		drawScreen();

		_graph->update(_graph->_frontScreen);

		openInventoryCheck();

		pausePrinceEngine();
	}
}

} // End of namespace Prince
