/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include "gamos/detection.h"
#include "gamos/gamos.h"

#include "graphics/cursorman.h"
#include "graphics/framelimiter.h"
#include "graphics/paletteman.h"

#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/endian.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/util.h"

#include "engines/util.h"

#include "audio/mididrv.h"
#include "audio/midiplayer.h"

namespace Gamos {

GamosEngine::GamosEngine(OSystem *syst, const GamosGameDescription *gameDesc) : Engine(syst),
	_gameDescription(gameDesc),
	_vm(this, callbackVMCallDispatcher),
	_messageProc(this),
	_txtInputVMAccess(_vm),
	_randomSource("gamos") {
	for(uint i = 0; i < 256; i++)
		_txtInputObjects[i] = nullptr;
	memset(_txtInputBuffer, 0, sizeof(_txtInputBuffer));
}

GamosEngine::~GamosEngine() {
	freeImages();
	freeSequences();
	delete _screen;
}

void GamosEngine::freeImages() {
	for (Image *img : _images)
		delete img;

	_images.clear();
}

void GamosEngine::freeSequences() {
	for (ImageSeq *seq : _imgSeq)
		delete seq;

	_imgSeq.clear();
}

Common::Error GamosEngine::run() {

	CursorMan.setDefaultArrowCursor();
	CursorMan.showMouse(true);

	if (getGameLanguage() == Common::RU_RUS)
		setCP1251();
	else
		setCP1252();

	_engineVersion = getEngineVersion() & 0xFF;

	_gameFile.setVersion(_engineVersion);

	init(getRunFile());

	Common::Event e;

	while (!shouldQuit()) {
		Common::Point prevMousePos = _messageProc._mouseReportedPos;

		while (_system->getEventManager()->pollEvent(e)) {
			_messageProc.processMessage(e);
		}

		uint32 curTime = _system->getMillis();
		if (curTime >= _lastTimeStamp + _delayTime) {
			_lastTimeStamp = curTime;

			if (_messageProc._inputFlags & 2) {

			}

			uint8 result = 2;
			while (result == 2) {
				result = update({}, _messageProc._mouseReportedPos, _messageProc._mouseActPos, _messageProc._act2, _messageProc._act1, _messageProc._rawKeyCode, true);
			}

			if (!result)
				break;

			_messageProc._act2 = ACT_NONE;
			_messageProc._act1 = ACT_NONE;
			_messageProc._rawKeyCode = ACT_NONE;
		} else {
			if (prevMousePos != _messageProc._mouseReportedPos)
				_system->updateScreen();
			_system->delayMillis(1);
		}

		//if (_delayTime)
	}

	stopSounds();
	stopMidi();
	stopCDAudio();

	_enableMovie = true;
	_enableMidi = true;
	_enableSounds = true;
	_enableInput = true;
	_isResLoadingProcess = true;
	writeStateFile();

	return Common::kNoError;
}


bool GamosEngine::loader2() {
	int32 skipsz = _gameFile.readSint32LE();
	_gameFile.skip(skipsz);

	if (_gameFile.readByte() != 7)
		return false;

	RawData data;
	if (!_gameFile.readCompressedData(&data))
		return false;

	int32 p1 = 0;
	int32 p2 = 0;
	int32 pid = 0;
	byte resType = 0;
	int32 resSize = 0;

	Common::MemoryReadStream dataStream(data.data(), data.size());
	while (!dataStream.eos()) {
		byte curByte = dataStream.readByte();

		if (curByte == 0) {
			break;
		} else if (curByte == 0x80) {
			p1 = 0;
			p2 = 0;
			pid = dataStream.readSint32LE();
		} else if (curByte == 1) {
			p1 = dataStream.readSint32LE();
		} else if (curByte == 2) {
			p2 = dataStream.readSint32LE();
		} else if (curByte == 7) {
			/*int32 needsz = dataStream.readSint32LE(); // check free mem ? */
			dataStream.skip(4);
		} else if (curByte == 0x40) {
			resSize = 4;
			resType = 0x40;
			if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
				return false;

			dataStream.skip(resSize);
		} else if (curByte == 0x41 || curByte == 0x42) {
			resSize = dataStream.readSint32LE();
			resType = curByte;
			if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
				return false;

			_loadedDataSize += (resSize + 3) & (~3);

			dataStream.skip(resSize);
		} else if (curByte == 0x43) {
			resSize = 0x10;
			resType = 0x43;
			if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
				return false;

			_loadedDataSize += (resSize + 3) & (~3);

			dataStream.skip(resSize);
		} else if (curByte == 0xff) {
			//warning("0xFF  %d %d %d", pid, p1, p2);
			if (!reuseLastResource(resType, pid, p1, p2, 0))
				return false;
		} else {
			warning("loader2 want %x", curByte);
			return false;
		}
	}

	return true;
}

bool GamosEngine::loadModule(uint id) {
	_keySeq.clear();

	if ((!_isResLoadingProcess && !writeStateFile()) ||
	        !_gameFile.seekDir(1))
		return false;

	_currentModuleID = id;
	const byte targetDir = 2 + id;

	//DAT_004126e4 = 1;
	_currentGameScreen = -1;
	_readingBkgMainId = -1;
	_countReadedBkg = 0;
	//DAT_004126ec = 0;
	//INT_004126e8 = 0;

	_xorSeq[0].clear();
	_xorSeq[1].clear();
	_xorSeq[2].clear();

	stopMidi();
	stopCDAudio();
	stopSounds();

	/* Complete me */

	bool prefixLoaded = false;
	byte prevByte = 0;
	bool doLoad = true;

	uint32 p1 = 0;
	uint32 p2 = 0;
	uint32 p3 = 0;
	uint32 pid = 0;

	while (doLoad) {
		byte curByte = _gameFile.readByte();

		switch (curByte) {
		case 0:
			if (prefixLoaded) {
				doLoad = false;
				break;
			}

			prefixLoaded = true;

			if (!_gameFile.seekDir(targetDir))
				return false;

			break;
		case CONFTP_P1:
			p1 = (uint32)_gameFile.readPackedInt();
			break;
		case CONFTP_P2:
			p2 = (uint32)_gameFile.readPackedInt();
			break;
		case CONFTP_P3:
			p3 = (uint32)_gameFile.readPackedInt();
			break;
		case 4: {
			_resReadOffset = _gameFile.pos();
			bool isResource = true;
			if (prevByte == RESTP_GAMECONF) {
				RawData data;
				if (!_gameFile.readCompressedData(&data))
					return false;
				if (_isResLoadingProcess && !_isSaveLoadingProcess)
					readData2(data);
				if (!_isSaveLoadingProcess) {
					_scrollY = 0;
					_scrollX = 0;
					_scrollTrackObj = -1;
					_scrollSpeed = 16;
					_scrollCutoff = 80;
					_scrollSpeedReduce = -1;
					_scrollBorderB = 0;
					_scrollBorderU = 0;
					_scrollBorderR = 0;
					_scrollBorderL = 0;
				}
				isResource = false; /* do not loadResHandler */
			} else if (prevByte == RESTP_GAMECONF2) {
				if (!initMainDatas())
					return false;
				isResource = false; /* do not loadResHandler */
			} else if (prevByte == RESTP_DATACONF) {
				RawData data;
				if (!_gameFile.readCompressedData(&data))
					return false;
				if (pid == id)
					readElementsConfig(data);
				isResource = false; /* do not loadResHandler */
			} else if (prevByte == RESTP_BKG) {
				/* free elements ? */
				_readingBkgOffset = _gameFile.pos();
				_countReadedBkg++;
			}

			RawData data;
			if (isResource) {
				if (!_gameFile.readCompressedData(&data))
					return false;

				if (!loadResHandler(prevByte, pid, p1, p2, p3, data))
					return false;

			}

			uint32 datasz = (data.size() + 3) & (~3);

			switch (prevByte) {
			case RESTP_DATACONF:
			case RESTP_BKG:
			case RESTP_INITACT:
			case RESTP_ACT_INFO:
			case RESTP_SPR_INFO:
			case RESTP_UNKNOWN_50:
				break;

			case RESTP_SPR_SEQIMGDATA:
				//warning("t %x sz %x sum %x", prevByte, data.size(), _loadedDataSize);
				if (_onlyScanImage)
					_loadedDataSize += 0x10;
				else
					_loadedDataSize += datasz;
				break;

			default:
				//warning("t %x sz %x sum %x", prevByte, data.size(), _loadedDataSize);
				_loadedDataSize += datasz;
				break;
			}

			break;
		}
		case 5: {
			byte t = _gameFile.readByte();
			if (t == 0 || (t & 0xec) != 0xec)
				return false;

			byte sz = (t & 3) + 1;
			int32 movieSize = 0;
			for (uint i = 0; i < sz; ++i)
				movieSize |= _gameFile.readByte() << (i * 8);

			if (prevByte == 0x14)
				_movieOffsets[pid] = _gameFile.pos();

			_gameFile.skip(movieSize);
			break;
		}
		case 6:
			if (!loader2())
				return false;
			break;
		case 0xFF:
			if (!reuseLastResource(prevByte, pid, p1, p2, 0))
				return false;
			break;
		default:
			p1 = 0;
			p2 = 0;
			p3 = 0;
			pid = 0;
			prevByte = curByte & CONFTP_RESMASK;

			if ((curByte & CONFTP_IDFLG) == 0)
				pid = (uint32)_gameFile.readPackedInt();

			break;
		}
	}

	//FUN_00404a28();
	if (_isSaveLoadingProcess)
		return true;

	// Reverse Here
	setCursor(0, false);

	if (!loadStateFile())
		return false;

	int bkg = _readingBkgMainId;
	if (bkg == -1)
		bkg = 0;

	if (!switchToGameScreen(bkg, false))
		return false;

	return true;
}

bool GamosEngine::loadResHandler(uint tp, uint pid, uint p1, uint p2, uint p3, const byte *data, uint32 dataSize) {
	if (tp == RESTP_VMSTATE) {
		Common::MemoryReadStream dataStream(data, dataSize, DisposeAfterUse::NO);

		_addrBlk12 = _loadedDataSize;
		_addrFPS = _loadedDataSize + 1;
		_addrKeyDown = _loadedDataSize + 2;
		_addrKeyCode = _loadedDataSize + 3;
		_addrCurrentFrame = _loadedDataSize + 4;

		_vm.memory().setU8(_addrBlk12, dataStream.readByte());
		dataStream.skip(1);
		_vm.memory().setU8(_addrFPS, _fps);
		_vm.memory().setU8(_addrKeyDown, dataStream.readByte());
		_vm.memory().setU8(_addrKeyCode, dataStream.readByte());
		_vm.memory().setU32(_addrCurrentFrame, dataStream.readUint32LE());

		setFPS(_fps);
	} else if (tp == RESTP_VMDATA) {
		_vm.writeMemory(_loadedDataSize, data, dataSize);
	} else if (tp == RESTP_BKG) {
		loadBackground(pid, data, dataSize);
	} else if (tp == RESTP_INITACT) {
		if (!_isSaveLoadingProcess) {
			for (uint i = 0; i < _states.size(); i++)
				_states.at(i) = ObjState(0xfe, 0, 0xf);

			_ignoreSoundActions = true;

			Actions acts;
			acts.parse(data, dataSize);
			doActions(acts, true);

			if (_needReload)
				warning("needs reload from loadResHandler, CANT HAPPEN!");

			_ignoreSoundActions = false;

			storeToGameScreen(pid);
		}
	} else if (tp == RESTP_ACT_INFO) {
		if (dataSize != 4)
			return false;
		_objectActions[pid].actType = data[0];
		_objectActions[pid].mask = data[1];
		_objectActions[pid].priority = data[2];
		_objectActions[pid].storageSize = data[3] + 1;
	} else if (tp == RESTP_ACT_ONCREATE) {
		_vm.writeMemory(_loadedDataSize, data, dataSize);
		_objectActions[pid].onCreateAddress = _loadedDataSize + p3;
		//warning("RESTP_ACT_ONCREATE %x pid %d sz %x", _loadedDataSize, pid, dataSize);
	} else if (tp == RESTP_ACT_ONDELETE) {
		_vm.writeMemory(_loadedDataSize, data, dataSize);
		_objectActions[pid].onDeleteAddress = _loadedDataSize + p3;
		//warning("RESTP_ACT_ONDELETE %x pid %d sz %x", _loadedDataSize, pid, dataSize);
	} else if (tp == RESTP_ACT_COUNT) {
		if (dataSize % 4 != 0 || dataSize < 4)
			return false;
		_objectActions[pid].actions.resize(dataSize / 4);
	} else if (tp == RESTP_ACT_DATA) {
		Actions &scr = _objectActions[pid].actions[p1];
		scr.parse(data, dataSize);
	} else if (tp == RESTP_ACT_COND) {
		_vm.writeMemory(_loadedDataSize, data, dataSize);
		_objectActions[pid].actions[p1].conditionAddress = _loadedDataSize + p3;
		//warning("RESTP_ACT_COND %x pid %d p1 %d sz %x", _loadedDataSize, pid, p1, dataSize);
	} else if (tp == RESTP_ACT_FUNC) {
		_vm.writeMemory(_loadedDataSize, data, dataSize);
		_objectActions[pid].actions[p1].functionAddress = _loadedDataSize + p3;
		//warning("RESTP_ACT_FUNC %x pid %d p1 %d sz %x", _loadedDataSize, pid, p1, dataSize);
	} else if (tp == RESTP_UNK_MASKS) {
		//warning("Data 38 size %zu", dataSize);
		_thing2[pid].masks.assign(data, data + dataSize);
	} else if (tp == RESTP_UNK_OIDS) {
		if (data[0] == 0)
			_thing2[pid].oids.clear();
		else
			_thing2[pid].oids.assign(data + 1, data + 1 + data[0]);
	} else if (tp == RESTP_UNK_ACTST) {
		_thing2[pid].actsT.assign(data, data + dataSize);
	} else if (tp == RESTP_SPR_INFO) {
		return loadSpriteInfo(pid, data, dataSize);
	} else if (tp == RESTP_SPR_SEQLEN) {
		return loadSpriteSeqLength(pid, data, dataSize);
	} else if (tp == RESTP_SPR_SEQIMGINFO) {
		return loadSpriteSeqImageInfo(pid, p1, data, dataSize);
	} else if (tp == RESTP_SPR_SEQIMGDATA) {
		return loadSpriteSeqImageData(pid, p1, p2, data, dataSize);
	} else if (tp == RESTP_UNKNOWN_50) {
		/* just ignore it? */
	} else if (tp == RESTP_SFX_SAMPLE) {
		uint32 datSz = getU32(data) & (~3);
		_soundSamples[pid].assign(data + 4, data + 4 + datSz);
		//warning("sound  size %d", dataSize);
	} else if (tp == RESTP_MIDI_TRACK) {
		return loadMidiTrack(pid, data, dataSize);
		//warning("midi  size %d", dataSize);
	} else if (tp == RESTP_SUB_ACT) {
		_subtitleActions[pid].parse(data, dataSize);
	} else if (tp == RESTP_SUB_PLACE) {
		Common::MemoryReadStream dataStream(data, dataSize, DisposeAfterUse::NO);
		const int count = dataSize / 8;
		_subtitlePoints[pid].resize(count);

		for (int i = 0; i < count; i++) {
			SubtitlePoint &d = _subtitlePoints[pid][i];

			d.x = dataStream.readSint16LE();
			d.y = dataStream.readSint16LE();
			d.sprId = dataStream.readUint16LE();

			dataStream.skip(2);
		}
	} else if (tp == RESTP_XORSEQ0) {
		loadXorSeq(data, dataSize, 0);
	} else if (tp == RESTP_XORSEQ1) {
		loadXorSeq(data, dataSize, 1);
	} else if (tp == RESTP_XORSEQ2) {
		loadXorSeq(data, dataSize, 2);
	} else {
		warning("Unk Res %x at %x sz %x", tp, _loadedDataSize, dataSize);
	}
	return true;
}

bool GamosEngine::loadResHandler(uint tp, uint pid, uint p1, uint p2, uint p3, const RawData &data) {
	return loadResHandler(tp, pid, p1, p2, p3, data.data(), data.size());
}

bool GamosEngine::reuseLastResource(uint tp, uint pid, uint p1, uint p2, uint p3) {
	if (tp == RESTP_SPR_SEQIMGDATA) {
		_sprites[pid].sequences[p1]->operator[](p2).image = _images.back();
	} else if (tp == RESTP_SPR_SEQIMGINFO) {
		_sprites[pid].sequences[p1] = _imgSeq.back();
	} else {
		error("Reuse of resource not implemented: resource type %x, id %d %d %d %d", tp, pid, p1, p2, p3);
	}
	return true;
}


bool GamosEngine::initMainDatas() {
	RawData rawdata;

	if (!_gameFile.readCompressedData(&rawdata))
		return false;

	Common::MemoryReadStream dataStream(rawdata.data(), rawdata.size(), DisposeAfterUse::NO);

	_magic = dataStream.readUint32LE();

	if (_magic != getEngineVersion()) {
		error("InitMainData: Invalid engine version! get %x expecting %x", _magic, getEngineVersion());
		return false;
	}

	/* skip count of pages 1kb size */
	dataStream.skip(4); // 4
	if (_engineVersion >= 0x12) {
		/* skip read buffer size */
		dataStream.skip(4); // 8
	}
	_width = dataStream.readUint32LE(); // c
	_height = dataStream.readUint32LE(); // 10
	_gridCellW = dataStream.readSint32LE(); // 14
	_gridCellH = dataStream.readSint32LE(); // 18
	_movieCount = dataStream.readUint32LE(); // 1c
	dataStream.skip(3); // skip unknown unused 20
	_fps = dataStream.readByte(); // 23
	dataStream.skip(1); // skip unknown unused  24
	_drawCursor = dataStream.readByte(); // 25

	if (_engineVersion >= 0x12) {
		_fadeEffectID = dataStream.readByte(); // 26
		_playIntro = dataStream.readByte(); // 27
	} else {
		_playIntro = dataStream.readByte();
		_fadeEffectID = dataStream.readByte();
	}

	if (_engineVersion >= 0x12) {
		_introPos.x = dataStream.readSint32LE(); // 28
		_introPos.y = dataStream.readSint32LE(); // 2c
		_introSize.x = dataStream.readSint32LE(); // 30
		_introSize.y = dataStream.readSint32LE(); // 34

		int64 pos = dataStream.pos();
		_string1 = dataStream.readString(0, 64); // 38
		dataStream.seek(pos + 64);
	} else {
		_string1 = "";
	}

	_winCaption = dataStream.readString(0, 32); // 78

	if (!_screen) {
		initGraphics(_width, _height);
		_screen = new Graphics::Screen();
	}

	_movieOffsets.clear();
	_movieOffsets.resize(_movieCount, 0);

	_objects.clear();

	return true;
}

bool GamosEngine::init(const Common::String &moduleName) {
	_isSaveLoadingProcess = false;

	if (!_gameFile.open(Common::Path(moduleName)))
		return false;

	if (!loadInitModule())
		return false;

	_scummMidiVolume = !ConfMan.hasKey("music_volume") ? 256 : ConfMan.getInt("music_volume");
	_scummSndVolume = !ConfMan.hasKey("sfx_volume") ? 256 : ConfMan.getInt("sfx_volume");
	_savedSndVolume = 255;
	_savedMidiVolume = 255;
	_sndVolumeTarget = _savedSndVolume;
	_midiVolumeTarget = _savedMidiVolume;

	_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, (_sndVolume * _scummSndVolume) >> 8);
	_musicPlayer.setVolume((_midiVolume * _scummMidiVolume) >> 8);

	playVideo("intro", _introPos, _introSize);

	if (!playIntro())
		return false;

	return true;
}

bool GamosEngine::loadInitModule() {
	rndSeed(_randomSource.getRandomNumber(UINT_MAX));
	_curObjIndex = -1;
	_curObject = nullptr;
	_curAction = nullptr;
	_xorSeq[2].clear();
	_xorSeq[1].clear();
	_xorSeq[0].clear();
	_isMoviePlay = 0;
	_txtInputActive = false;
	//_updateMouse = false;
	_isResLoadingProcess = true;
	_savedSndVolume = 0;
	_savedMidiVolume = 0;
	_sndVolumeTarget = 0;
	_midiVolumeTarget = 0;
	//_mouseInWindow = false;

	return loadModule(0);
}

void GamosEngine::setFPS(uint fps) {
	_delayTime = 0;
	if (fps)
		_delayTime = 1000 / fps;
}

void GamosEngine::readElementsConfig(const RawData &data) {
	Common::MemoryReadStream dataStream(data.data(), data.size(), DisposeAfterUse::NO);

	freeImages();
	freeSequences();

	uint32 bkgnum1 = dataStream.readUint32LE(); // 0
	uint32 bkgnum2 = dataStream.readUint32LE(); // 4
	_statesWidth = dataStream.readUint32LE(); // 8
	_statesHeight = dataStream.readUint32LE(); // c
	_bkgSize.x = dataStream.readUint32LE(); // 10
	_bkgSize.y = dataStream.readUint32LE(); // 14
	/* bkgbufferSize */ dataStream.readUint32LE(); // 18
	uint32 actsCount = dataStream.readUint32LE(); // 1c
	uint32 unk1Count = dataStream.readUint32LE(); // 20
	uint32 imageCount = dataStream.readUint32LE(); // 24
	uint32 soundCount = dataStream.readUint32LE(); // 28
	uint32 midiCount = dataStream.readUint32LE(); // 2c
	uint32 dat6xCount = dataStream.readUint32LE(); // 30

	_statesShift = 2;
	for (uint i = 2; i < 9; i++) {
		if (_statesWidth <= (uint32)(1 << i)) {
			_statesShift = i;
			break;
		}
	}

	_states.clear();
	_states.resize(_statesWidth, _statesHeight);

	_statesCount = _statesHeight * _statesWidth;
	_pathRight = _statesWidth - 1;
	_pathBottom = _statesHeight - 1;
	_pathMap.clear();
	_pathMap.resize(_statesWidth, _statesHeight);

	_gameScreens.clear();
	_gameScreens.resize(bkgnum1 * bkgnum2);

	_sprites.clear();
	_sprites.resize(imageCount);

	for (uint i = 0; i < imageCount; i++)
		_sprites[i].index = i;

	_midiTracks.clear();
	_midiTracks.resize(midiCount);

	_soundSamples.clear();
	_soundSamples.resize(soundCount);

	_thing2.clear();
	_thing2.resize(unk1Count);

	_objectActions.clear();
	_objectActions.resize(actsCount);

	_subtitleActions.clear();
	_subtitlePoints.clear();

	_subtitleActions.resize(dat6xCount);
	_subtitlePoints.resize(dat6xCount);

	_loadedDataSize = 0;
	_vm.clearMemory();
}

void GamosEngine::loadXorSeq(const byte *data, uint32 dataSize, int id) {
	Common::MemoryReadStream dataStream(data, dataSize);

	Common::Array<XorArg> &seq = _xorSeq[id];

	uint32 num = dataStream.readUint32LE();
	seq.resize(num);

	for (uint i = 0; i < num; ++i) {
		seq[i].pos = dataStream.readUint32LE();
		seq[i].len = dataStream.readUint32LE();
	}
}

bool GamosEngine::loadSpriteInfo(int32 id, const byte *data, uint32 dataSize) {
	if (dataSize < 4)
		return false;

	if (dataSize % 4)
		warning("dataSize > 4");

	_sprites[id].field_0 = data[0];
	_sprites[id].flags = data[1];
	_sprites[id].lastChar = data[2];
	_sprites[id].frameCount = data[3];

	_onlyScanImage = data[1] & 0x80;

	return true;
}

bool GamosEngine::loadSpriteSeqLength(int32 id, const byte *data, uint32 dataSize) {
	if (*(const uint32 *)data != 0)
		error("41 not null!!!");

	if (dataSize % 4)
		warning("loadRes41 datasize > 4");
	_sprites[id].sequences.resize(dataSize / 4);
	return true;
}

bool GamosEngine::loadSpriteSeqImageInfo(int32 id, int32 p1, const byte *data, uint32 dataSize) {
	//warning("loadRes42 pid %d p %d sz %x",id, p1, dataSize);

	if (_sprites[id].sequences.size() == 0)
		_sprites[id].sequences.resize(1);

	int32 count = dataSize / 8;
	_imgSeq.push_back(new ImageSeq(count));
	_sprites[id].sequences[p1] = _imgSeq.back();

	Common::MemoryReadStream strm(data, dataSize);
	for (int i = 0; i < count; ++i) {
		int32 dataz = strm.readSint32LE();
		if (dataz != 0) {
			error("42    nut null");
		}

		ImagePos &imgpos = _sprites[id].sequences[p1]->operator[](i);
		imgpos.xoffset = strm.readSint16LE();
		imgpos.yoffset = strm.readSint16LE();
	}
	return true;
}

bool GamosEngine::loadSpriteSeqImageData(int32 id, int32 p1, int32 p2, const byte *data, uint32 dataSize) {
	_images.push_back(new Image());
	_sprites[id].sequences[p1]->operator[](p2).image = _images.back();

	Image *img = _sprites[id].sequences[p1]->operator[](p2).image;

	Common::MemoryReadStream s(data, dataSize);
	img->surface.pitch = img->surface.w = s.readSint16LE();
	img->surface.h = s.readSint16LE();
	img->loaded = false;
	img->offset = -1;

	uint32 token = s.readUint32LE();

	/* token 'Disk' */
	if (token == 0x4469736b) {
		img->offset = s.readSint32LE();
		img->cSize = s.readSint32LE();
	} else {
		if (_sprites[id].flags & 0x80) {
			if (_gameFile._lastReadDecompressedSize) {
				img->offset = _gameFile._lastReadDataOffset;
				img->cSize = _gameFile._lastReadSize;
			} else {
				img->offset = _gameFile._lastReadDataOffset;
				img->cSize = 0;
			}
		} else {
			img->loaded = true;
			img->rawData.assign(data + 4, data + dataSize);
			img->surface.setPixels(img->rawData.data());
			img->surface.format = Graphics::PixelFormat::createFormatCLUT8();
		}
	}

	return true;
}

bool GamosEngine::loadMidiTrack(int32 id, const byte *data, uint32 dataSize) {
	_midiTracks[id].assign(data, data + dataSize);
	return true;
}

bool GamosEngine::loadBackground(int32 id, const byte *data, uint32 dataSize) {
	GameScreen &bimg = _gameScreens[id];
	bimg.loaded = true;
	bimg.offset = _readingBkgOffset;
	bimg._savedStates.clear();
	bimg._savedObjects.clear();
	bimg.palette = nullptr;

	bimg._bkgImageData.assign(data, data + dataSize);

	Common::MemoryReadStream strm(data, dataSize);

	if (_readingBkgMainId == -1 && (strm.readUint32LE() & 0x80000000))
		_readingBkgMainId = id;

	//warning("res 18 id %d 4: %x", id, strm.readUint32LE());

	strm.seek(8);

	bimg._bkgImage.pitch = bimg._bkgImage.w = strm.readUint32LE();
	bimg._bkgImage.h = strm.readUint32LE();

	uint32 imgsize = strm.readUint32LE();

	//warning("res 18 id %d 14: %x", id, strm.readUint32LE());

	bimg._bkgImage.setPixels(bimg._bkgImageData.data() + 0x18);
	bimg._bkgImage.format = Graphics::PixelFormat::createFormatCLUT8();

	bimg.palette = bimg._bkgImageData.data() + 0x18 + imgsize;

	return true;
}


bool GamosEngine::playIntro() {
	if (_movieCount != 0 && _playIntro == 1)
		return playMovie(0);
	return true;
}

bool GamosEngine::moviePlayerPlay(int id) {
	bool res = _moviePlayer.playMovie(&_gameFile, _movieOffsets[id], this);
	return res;
}


bool GamosEngine::playMovie(uint32 id) {
	if (_enableMovie) {
		_isMoviePlay++;
		bool res = moviePlayerPlay(id);
		_isMoviePlay--;
		return res;
	}

	return true;
}

void GamosEngine::stopMidi() {
	_musicPlayer.stopMusic();
	_midiStarted = false;
}

void GamosEngine::stopCDAudio() {
	//warning("Not implemented stopMCI");
	_cdAudioTrack = -1;
}

void GamosEngine::stopSounds() {
	_mixer->stopAll();
}



void GamosEngine::setErrMessage(const Common::String &msg) {
	if (_errSet)
		return;

	_errMessage = msg;
	_errSet = true;
}

void GamosEngine::updateScreen(bool checkers, const Common::Rect &rect) {
	if (_width == 0 || _height == 0)
		return;

	if (!checkers || shouldQuit()) {
		_screen->addDirtyRect(rect);
		return;
	}

	/* checkers update */
	static const Common::Point checkerCoords[16] = {
		{0, 0}, {16, 32}, {48, 16}, {16, 48},
		{0, 32}, {32, 48}, {16, 16}, {48, 0},
		{32, 32}, {0, 48}, {32, 16}, {16, 0},
		{48, 32}, {32, 0}, {0, 16}, {48, 48}
	};

	/* 0.4sec */
	const int16 maxDelay = (400 / 16) - 1;

	_screen->clearDirtyRects();

	for (int16 p = 0; p < 16; p++) {
		uint32 val = _system->getMillis();
		const Common::Point point = checkerCoords[p];
		for (uint32 x = point.x; x < _width; x += 64) {
			for (uint32 y = point.y; y < _height; y += 64) {
				_screen->addDirtyRect(Common::Rect(x, y, x + 16, y + 16));
			}
		}
		_screen->update();

		while (_system->getMillis() - val < maxDelay) {
			_system->delayMillis(1);

			if (eventsSkip()) {
				_screen->addDirtyRect(rect);
				_screen->update();
				return;
			} else {
				_system->updateScreen();
			}
		}
	}
}

void GamosEngine::flushDirtyRects(bool apply) {
	if (apply) {
		for (const Common::Rect &r : _dirtyRects) {
			updateScreen(false, r);
		}
	}
	_dirtyRects.clear();

	_screen->update();

	_inputMouseActId = 0xff;
	_inputMouseActType = ACT_NONE;
	PTR_00417388 = nullptr;

	rndSeed(_randomSource.getRandomNumber(UINT_MAX));
	_inputActObj = nullptr;
	cycleNextInputObj(nullptr);
}

bool GamosEngine::usePalette(const byte *pal, int num, int fade, bool winColors) {

	static const byte winColorMap[20][3] = {
		/* r     g     b */
		{ 0x00, 0x00, 0x00 },
		{ 0x80, 0x00, 0x00 },
		{ 0x00, 0x80, 0x00 },
		{ 0x80, 0x80, 0x00 },
		{ 0x00, 0x00, 0x80 },
		{ 0x80, 0x00, 0x80 },
		{ 0x00, 0x80, 0x80 },
		{ 0xc0, 0xc0, 0xc0 },
		{ 0xc0, 0xdc, 0xc0 },
		{ 0xa6, 0xca, 0xf0 },

		{ 0xff, 0xfb, 0xf0 },
		{ 0xa0, 0xa0, 0xa4 },
		{ 0x80, 0x80, 0x80 },
		{ 0xff, 0x00, 0x00 },
		{ 0x00, 0xff, 0x00 },
		{ 0xff, 0xff, 0x00 },
		{ 0x00, 0x00, 0xff },
		{ 0xff, 0x00, 0xff },
		{ 0x00, 0xff, 0xff },
		{ 0xff, 0xff, 0xff }
	};

	if (!pal)
		return false;

	if (_width != 0 && _height != 0) {
		if (fade == 0 || shouldQuit()) {
			uint16 color = _screen->getPalette().findBestColor(0, 0, 0);
			_screen->fillRect(_screen->getBounds(), color);
			_screen->update();
		} else {
			uint16 color = 0;
			if (fade == 2)
				color = _screen->getPalette().findBestColor(0x80, 0x80, 0x80);
			else if (fade == 3)
				color = _screen->getPalette().findBestColor(0xc0, 0xc0, 0xc0);
			else if (fade == 4)
				color = _screen->getPalette().findBestColor(0xff, 0xff, 0xff);
			else
				color = _screen->getPalette().findBestColor(0, 0, 0);

			/* 0.4sec */
			const int16 maxDelay = (400 / 8) - 1;

			for (int j = 0 ; j < 8; j++) {
				uint32 val = _system->getMillis();

				for (int i = j; i < _screen->w; i += 8)
					_screen->drawLine(i, 0, i, _screen->h - 1, color);
				for (int i = j; i < _screen->h; i += 8)
					_screen->drawLine(0, i, _screen->w - 1, i, color);

				_screen->update();

				while (_system->getMillis() - val < maxDelay) {
					_system->delayMillis(1);

					if (eventsSkip()) {
						j = 8;
						color = _screen->getPalette().findBestColor(0, 0, 0);
						_screen->fillRect(_screen->getBounds(), color);
						_screen->update();
						break;
					} else {
						_system->updateScreen();
					}
				}
			}
		}
	}

	Graphics::Palette newPal(256);
	newPal.set(pal, 0, num);

	if (winColors) {
		newPal.set(winColorMap[0], 0, 10);
		newPal.set(winColorMap[10], 246, 10);
	}

	newPal.resize(num, true);

	_screen->setPalette(newPal);
	return true;
}

bool GamosEngine::setPaletteCurrentGS() {
	_currentFade = _fadeEffectID;

	int curGS = _currentGameScreen;
	if (curGS == -1)
		curGS = 0;

	if (!usePalette(_gameScreens[curGS].palette, 256, _currentFade, true))
		return false;

	addDirtyRect(Common::Rect(_bkgSize.x, _bkgSize.y));

	return true;
}


void GamosEngine::readData2(const RawData &data) {
	Common::MemoryReadStream dataStream(data.data(), data.size());

	//warning("Game data size %d", data.size());

	if (_engineVersion == 0x18) {
		_stateExt = dataStream.readString(0, 4); // FIX ME
		dataStream.seek(4);
		_messageProc._inputFlags = dataStream.readByte(); //4
		dataStream.seek(8);
		_svModuleId = dataStream.readSint32LE(); // 8
		_svGameScreen = dataStream.readSint32LE(); // c
		_d2_fld10 = dataStream.readUint32LE(); // x10
		_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
		_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
		_enableInput = dataStream.readByte() != 0 ? true : false; // x16
		_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
		_enableCDAudio = dataStream.readByte() != 0 ? true : false; // x18
		_cdAudioTrack = dataStream.readSByte(); // x19
		dataStream.seek(0x1c);
		_scrollX = dataStream.readSint32LE(); // x1c
		_scrollY = dataStream.readSint32LE(); // x20
		_scrollTrackObj = dataStream.readSint16LE(); // x24
		_scrollSpeed = dataStream.readSint16LE(); // x26
		_scrollCutoff = dataStream.readSint16LE(); // x28
		_scrollSpeedReduce = dataStream.readSint16LE(); // x2a
		_scrollBorderL = dataStream.readByte(); // x2c
		_scrollBorderR = dataStream.readByte(); // x2d
		_scrollBorderU = dataStream.readByte(); // x2e
		_scrollBorderB = dataStream.readByte(); // x2f
		_sndChannels = dataStream.readByte(); // x30
		_sndVolume = dataStream.readByte(); // x34
		_midiVolume = dataStream.readByte(); // x1a
		_svFps = dataStream.readByte(); // x1b
		_svFrame = dataStream.readSint32LE(); // x1c
		_midiTrack = dataStream.readUint32LE(); //0x38
		_mouseCursorImgId = dataStream.readSint32LE(); //0x3c
		//0x40
		for (int i = 0; i < 12; i++) {
			_messageProc._keyCodes[i] = dataStream.readByte();
		}
	} else if (_engineVersion >= 0x12 && _engineVersion <= 0x16) {
		_stateExt = dataStream.readString(0, 4); // FIX ME
		dataStream.seek(4);
		_messageProc._inputFlags = dataStream.readByte(); //4
		dataStream.seek(8);
		_svModuleId = dataStream.readSint32LE();
		_svGameScreen = dataStream.readSint32LE();
		_d2_fld10 = dataStream.readUint32LE();
		_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
		_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
		_enableInput = dataStream.readByte() != 0 ? true : false; // x16
		_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
		_enableCDAudio = false;
		_cdAudioTrack = -1;
		_scrollX = 0;
		_scrollY = 0;
		_scrollTrackObj = -1;
		_scrollSpeed = 16;
		_scrollCutoff = 80;
		_scrollSpeedReduce = -1;
		_scrollBorderL = 0;
		_scrollBorderR = 0;
		_scrollBorderU = 0;
		_scrollBorderB = 0;
		_sndChannels = dataStream.readByte(); // x18
		_sndVolume = dataStream.readByte(); // x19
		_midiVolume = dataStream.readByte(); // x1a
		_svFps = dataStream.readByte(); // x1b
		_svFrame = dataStream.readSint32LE(); // x1c
		_midiTrack = dataStream.readUint32LE(); // x20
		_mouseCursorImgId = dataStream.readSint32LE(); // x24
		//0x28
		for (int i = 0; i < 12; i++) {
			_messageProc._keyCodes[i] = dataStream.readByte();
		}
	} else if (_engineVersion == 0xb) {
		_stateExt = dataStream.readString(0, 4); // FIX ME
		dataStream.seek(4);
		_messageProc._inputFlags = dataStream.readByte(); //4
		dataStream.seek(8);
		_svModuleId = dataStream.readSint32LE(); // 8
		_svGameScreen = dataStream.readSint32LE(); // c
		_d2_fld10 = dataStream.readUint32LE(); // 10
		_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
		_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
		_enableInput = dataStream.readByte() != 0 ? true : false; // x16
		_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
		_enableCDAudio = false;
		_cdAudioTrack = -1;
		_scrollX = 0;
		_scrollY = 0;
		_scrollTrackObj = -1;
		_scrollSpeed = 16;
		_scrollCutoff = 80;
		_scrollSpeedReduce = -1;
		_scrollBorderL = 0;
		_scrollBorderR = 0;
		_scrollBorderU = 0;
		_scrollBorderB = 0;
		_sndChannels = dataStream.readByte(); // x18
		_sndVolume = dataStream.readByte(); // x19
		_midiVolume = dataStream.readByte(); // x1a
		_svFps = dataStream.readByte(); // x1b
		_svFrame = dataStream.readSint32LE(); // x1c
		_midiTrack = dataStream.readUint32LE(); // x20
		_mouseCursorImgId = dataStream.readSint32LE(); // x24
		//0x28

		_messageProc._keyCodes[0] = KeyCodes::WIN_UP;
		_messageProc._keyCodes[1] = KeyCodes::WIN_PRIOR;
		_messageProc._keyCodes[2] = KeyCodes::WIN_RIGHT;
		_messageProc._keyCodes[3] = KeyCodes::WIN_NEXT;
		_messageProc._keyCodes[4] = KeyCodes::WIN_DOWN;
		_messageProc._keyCodes[5] = KeyCodes::WIN_END;
		_messageProc._keyCodes[6] = KeyCodes::WIN_LEFT;
		_messageProc._keyCodes[7] = KeyCodes::WIN_HOME;
		_messageProc._keyCodes[8] = KeyCodes::WIN_SPACE;
		_messageProc._keyCodes[9] = KeyCodes::WIN_RETURN;
		_messageProc._keyCodes[10] = KeyCodes::WIN_TAB;
		_messageProc._keyCodes[11] = KeyCodes::WIN_INVALID;
	}
}


bool GamosEngine::playMidi(Common::Array<byte> *buffer) {
	_musicPlayer.stopMusic();
	_midiStarted = _musicPlayer.playMusic(buffer);
	return _midiStarted;
}

bool GamosEngine::playSound(uint id) {
	Audio::SeekableAudioStream *stream = Audio::makeRawStream(_soundSamples[id].data(), _soundSamples[id].size(), 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
	_mixer->playStream(Audio::Mixer::kSFXSoundType, nullptr, stream, -1, ((uint16)_sndVolume * _scummSndVolume) >> 8);
	return true;
}

int GamosEngine::stepVolume(int volume, int target) {
	int d = target - volume;
	if (d == 0)
		return 0;

	int step = 255 / _fps;
	if (d < 0) {
		step = -step;
		if (step < d)
			step = d;
	} else {
		if (step > d)
			step = d;
	}
	return step;
}

void GamosEngine::changeVolume() {
	const int sndStep = stepVolume(_sndVolume, _sndVolumeTarget);
	if (sndStep) {
		_sndVolume += sndStep;
		_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ((uint16)_sndVolume * _scummSndVolume) >> 8);
	}
	const int midiStep = stepVolume(_midiVolume, _midiVolumeTarget);
	if (midiStep) {
		_midiVolume += midiStep;
		_musicPlayer.setVolume(((uint16)_midiVolume * _scummMidiVolume) >> 8);
	}
}

void GamosEngine::syncSoundSettings() {
	Engine::syncSoundSettings();

	const uint16 newSndVolume = !ConfMan.hasKey("sfx_volume") ? 256 : ConfMan.getInt("sfx_volume");
	const uint16 newMusicVolume = !ConfMan.hasKey("music_volume") ? 256 : ConfMan.getInt("music_volume");

	if (newSndVolume != _scummSndVolume) {
		_scummSndVolume = newSndVolume;
		_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ((uint16)_sndVolume * _scummSndVolume) >> 8);
	}

	if (newMusicVolume != _scummMidiVolume) {
		_scummMidiVolume = newMusicVolume;
		_musicPlayer.setVolume(((uint16)_midiVolume * _scummMidiVolume) >> 8);
	}

}


uint8 GamosEngine::update(Common::Point screenSize, Common::Point mouseMove, Common::Point actPos, uint8 act2, uint8 act1, uint16 keyCode, bool mouseInWindow) {
	_needReload = false;
	_vm._interrupt = false;

	if (_enableInput == 0) {
		act1 = ACT_NONE;
		act2 = ACT_NONE;
		keyCode = ACT_NONE;
	}

	_pressedKeyCode = keyCode;

	if (_pressedKeyCode != 0 && _pressedKeyCode != ACT_NONE) {
		if (_keySeq.size() >= 32)
			_keySeq = _keySeq.substr(_keySeq.size() - 31);

		_keySeq += _pressedKeyCode;
	}

	processInput(mouseMove, actPos, act2, act1);
	changeVolume();

	if (!updateVMInputFrameStates())
		return 0;

	bool loop = false;
	if (!_txtInputActive)
		loop = updateObjects();
	else
		loop = onTxtInputUpdate(act2);

	if (_needReload)
		return 2;  // rerun update after loadModule

	while (loop) {
		if (!PTR_00417388) {
			if (updateMouseCursor(mouseMove) && scrollAndDraw())
				return 1;
			else
				return 0;
		}

		_pressedKeyCode = ACT_NONE;

		if (!updateVMInputFrameStates())
			return 0;

		if (!_txtInputActive)
			loop = updateObjects();
		else
			loop = onTxtInputUpdate(act2);

		if (_needReload)
			return 2; // rerun update after loadModule
	}

	return 0;
}

int32 GamosEngine::doActions(const Actions &a, bool absolute) {
	Common::Array<Common::Point> ARR_00412208(512);

	if (!absolute) {
		_curObjectStartCell = _curObject->cell;
	} else {
		_curObject = nullptr;
		_curObjIndex = -1;
		_curAction = nullptr;
		_curObjectStartCell = Common::Point();
		_curObjectT = 1;
		_preprocDataId = 0;
		_curObjStorage = nullptr;
	}

	_curObjectCurrentCell = _curObjectStartCell;

	int32 spos = -1;
	int32 sbuf[6];


	if (a.flags & Actions::HAS_CONDITION) {
		if (a.conditionAddress != -1) {
			if (!doScript(a.conditionAddress))
				return 0;
			if (_needReload)
				return 0;
		}
	}

	if (a.flags & Actions::HAS_ACT2) {
		bool fastSkipAll = false;
		for (const ActTypeEntry &ate : a.act_2) {

			if (ate.t == 4) {
				spos++;
				if (spos == 0) {
					sbuf[0] = 0;
					sbuf[1] = 0;
				} else {
					int32 p = sbuf[spos * 2 - 1];
					sbuf[spos * 2 + 1] = p;
					sbuf[spos * 2] = p;
				}
			} else {
				spos = -1;
			}

			int32 ps = spos * 2 + 1;

			for (uint i = 0; i < ate.entries.size(); i++) {
				/* use copy of entrie because it will be modified */
				ActEntry e = ate.entries[i];

				preprocessData(_preprocDataId, &e);

				ObjState fb;
				if (!absolute) {
					Common::Point xy;
					xy.x = (e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth;
					xy.y = (e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight;
					fb = _states.at(xy);
				} else {
					fb = _states.at(e.x, e.y);
				}

				int cval = 0;
				int fnc = e.t;
				if ((e.flags & 1) == 0) {
					if (e.actid == fb.actid && (fb.t & e.t)) {
						cval = 2;
					}
				} else if (fb.actid != 0xfe &&
				           (_thing2[e.actid].masks[(fb.actid) >> 3] & (1 << (fb.actid & 7))) != 0) {

					if (!_thing2[e.actid].actsT.empty()) {
						e.t = _thing2[e.actid].actsT[fb.actid] >> 4;
						preprocessData(fnc + 8, &e);
					}

					if (fb.t & e.t) {
						cval = 2;
					}
				}

				if ((e.flags & 2) == cval) {
					if ((e.flags & 0xc) == 0) {
						break;
					}
					if ((e.flags & 0xc) == 4)
						return 0;
					if ((e.flags & 0xc) == 8) {
						fastSkipAll = true;
						break;
					}
					ARR_00412208[ sbuf[ps] ] = Common::Point(e.x, e.y);
					sbuf[ps]++;
				} else if ((ate.entries.size() - i) == 1 && spos > -1 && sbuf[spos * 2] == sbuf[ps]) {
					return 0;
				}
			}

			if (fastSkipAll)
				break;
		}
	}

	_curObjectActProcessed = false;
	if (a.flags & Actions::HAS_ACT4) {
		ActEntry e = a.act_4;
		preprocessData(_preprocDataId, &e);
		preprocessDataB1(e.t, &e);
		rnd();
		e.flags = a.act_4.flags;
		processActionCurObject(e);
		if (_needReload)
			return 0;
	}

	_gfxObjectCreated = false;
	if (a.flags & Actions::HAS_FUNCTION) {
		uint32 fldsv = 0;
		if (_curObject)
			fldsv = _curObject->priority;
		if (a.functionAddress != -1)
			doScript(a.functionAddress);
		if (_needReload)
			return 0;
		if (!_gfxObjectCreated && !_curObjectActProcessed && _curObject && _curObject->priority != fldsv && _curObject->curObjectId != -1)
			addDirtRectOnObject(&_objects[_curObject->curObjectId]);
	}

	if (!_gfxObjectCreated && _curObjectActProcessed)
		updateLinkedGfxObject(_curObject);

	int32 retval = 0;

	if (a.flags & Actions::HAS_ACT10) {
		int ivar5 = -1;
		for (const ActTypeEntry &ate : a.act_10) {
			switch (ate.t) {
			case 0: {
				uint16 rndval = rndRange16(a.num_act_10e);
				for (ActEntry e : a.act_10end[rndval]) {
					retval += processData(e, absolute);
					if (_needReload)
						return 0;
				}
			}
			break;

			case 1: {
				int32 num = rndRange16(ate.entries.size());
				for (uint i = 0; i < ate.entries.size(); i++) {
					if (num != 0) {
						ActEntry e = ate.entries[i];
						retval += processData(e, absolute);
						if (_needReload)
							return 0;
					}
					num--;
				}

			}
			break;

			case 2: {
				int32 num = rndRange16(ate.entries.size());
				ActEntry e = ate.entries[num];
				retval += processData(e, absolute);
				if (_needReload)
					return 0;
			}
			break;

			case 3: {
				for (uint i = 0; i < ate.entries.size(); i++) {
					uint16 doproc = rndRange16(2);

					if (doproc != 0) {
						ActEntry e = ate.entries[i];
						retval += processData(e, absolute);
						if (_needReload)
							return 0;
					}
				}
			}
			break;

			default: {
				ivar5++;
				/* Seems it's has a error in original
				   think it's must be:
				   min + rnd(max-min) */

				uint32 lb = rnd() >> 0x10;
				uint32 idx = ((sbuf[ivar5 * 2 + 1] - sbuf[ivar5 * 2]) * lb + sbuf[ivar5 * 2]) >> 0x10;
				Common::Point point = ARR_00412208[ idx ];

				for (ActEntry e : ate.entries) {
					if (Common::Point(e.x, e.y) == point) {
						retval += processData(e, absolute);
						if (_needReload)
							return 0;
						break;
					}
				}
			}
			break;

			}
		}
	}

	return retval + 1;
}

uint32 GamosEngine::getU32(const void *ptr) {
	const uint8 *p = (const uint8 *)ptr;
	return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}


void GamosEngine::preprocessData(int id, ActEntry *e) {
	switch (id) {

	default:
	case 0:
		break;

	case 1:
	case 10: {
		static const uint8 lookup[16] = {0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15};
		int8 tmp = e->y;
		e->y = e->x;
		e->x = -tmp;
		e->t = lookup[ e->t ];
	}
	break;

	case 2:
	case 12: {
		static const uint8 lookup[16] = {0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15};
		e->y = -e->y;
		e->x = -e->x;
		e->t = lookup[ e->t ];
	}
	break;

	case 3:
	case 16: {
		static const uint8 lookup[16] = {0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15};
		int8 tmp = e->x;
		e->x = e->y;
		e->y = -tmp;
		e->t = lookup[ e->t ];
	}
	break;

	case 4: {
		static const uint8 lookup[16] = {0, 1, 8, 9, 4, 5, 12, 13, 2, 3, 10, 11, 6, 7, 14, 15};
		e->x = -e->x;
		e->t = lookup[ e->t ];
	}
	break;

	case 5: {
		static const uint8 lookup[16] = {0, 2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15};
		int8 tmp = e->x;
		e->x = -e->y;
		e->y = -tmp;
		e->t = lookup[ e->t ];
	}
	break;

	case 6: {
		static const uint8 lookup[16] = {0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15};
		e->y = -e->y;
		e->t = lookup[ e->t ];
	}
	break;

	case 7: {
		static const uint8 lookup[16] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
		uint8 tmp = e->x;
		e->x = e->y;
		e->y = tmp;
		e->t = lookup[ e->t ];
	}
	break;
	}
}

void GamosEngine::preprocessDataB1(int id, ActEntry *e) {
	switch (id) {

	default:
	case 0:
		break;

	case 1:
	case 2:
	case 4:
	case 8:
		//e->t = e->t;
		break;

	case 3: {
		static const uint8 lookup[2] = {1, 2};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 5: {
		static const uint8 lookup[2] = {1, 4};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 6: {
		static const uint8 lookup[2] = {2, 4};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 7: {
		static const uint8 lookup[3] = {1, 2, 4};
		e->t = lookup[rndRange16(3)];
	}
	break;

	case 9: {
		static const uint8 lookup[2] = {1, 8};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 0xa: {
		static const uint8 lookup[2] = {2, 8};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 0xb: {
		static const uint8 lookup[3] = {1, 2, 8};
		e->t = lookup[rndRange16(3)];
	}
	break;

	case 0xc: {
		static const uint8 lookup[2] = {4, 8};
		e->t = lookup[rndRange16(2)];
	}
	break;

	case 0xd: {
		static const uint8 lookup[3] = {1, 4, 8};
		e->t = lookup[rndRange16(3)];
	}
	break;

	case 0xe: {
		static const uint8 lookup[3] = {2, 4, 8};
		e->t = lookup[rndRange16(3)];
	}
	break;

	case 0xf: {
		static const uint8 lookup[4] = {1, 2, 4, 8};
		e->t = lookup[rndRange16(4)];
	}
	break;
	}
}

int GamosEngine::processData(ActEntry e, bool absolute) {
	preprocessData(_preprocDataId, &e);
	if (!absolute) {
		createActiveObject(e, Common::Point(
		             (e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth,
		             (e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight) );
		if (_needReload)
			return 0;
		return e.x == 0 && e.y == 0;
	} else {
		createActiveObject(e, Common::Point(e.x, e.y) );
		return 0;
	}
}

void GamosEngine::processActionCurObject(ActEntry e) {
	if (e.x != 0 || e.y != 0) {
		_curObjectCurrentCell.x = (e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth;
		_curObjectCurrentCell.y = (e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight;

		ObjState st = _curObject->state;
		_states.at(_curObjectStartCell) = ObjState(st.actid, 0, st.t);

		removeObjectAtCoords(_curObjectCurrentCell, false);

		_curObject->cell = _curObjectCurrentCell;

		ObjState rthing = _states.at(_curObjectCurrentCell);
		_curObject->state = ObjState(rthing.actid, st.flags, rthing.t);

		_states.at(_curObjectCurrentCell) = ObjState(_curObject->actID, 0, _curObject->t);

		_curObjectActProcessed = true;
	}

	if (e.t != _curObjectT) {
		_curObjectT = e.t;
		_curObject->t = e.t;

		ObjState &stref = _states.at(_curObjectCurrentCell);
		stref.flags = 0;
		stref.t = _curObjectT;

		_curObjectActProcessed = true;
	}
}

void GamosEngine::createActiveObject(ActEntry e, Common::Point cell) {
	ObjState &stref = _states.at(cell);

	uint8 oid = e.actid;

	if ((e.flags & 1) == 0) {
		if (oid == 0xfe) {
			removeObjectAtCoords(cell, true);
			if (_needReload)
				return;

			stref = e;
			return;
		}
	} else {
		Unknown1 &unk1 = _thing2[ oid ];
		uint8 index = rndRange16(unk1.oids.size());
		oid = unk1.oids[ index ];
		if (!unk1.actsT.empty()) {
			byte id1 = e.t;
			e.t = unk1.actsT[ oid ] >> 4;
			preprocessData(8 + id1, &e);
		}
	}

	preprocessDataB1(e.t, &e);

	e.flags = 0;

	rnd(); // needed?

	Object *obj = nullptr;
	int index = 0;
	byte *odat = nullptr;

	ObjectAction &act = _objectActions[oid];
	if (act.actType == 0) {
		removeObjectAtCoords(cell, true);
		if (_needReload)
			return;
		obj = nullptr;
		index = -1;
		odat = nullptr;
	} else {
		removeObjectAtCoords(cell, false);
		if (_needReload)
			return;
		obj = getFreeObject();
		obj->flags = Object::FLAG_VALID | Object::FLAG_HASACTION;
		obj->t = e.t;
		obj->actID = oid;
		obj->inputFlag = 0;
		obj->priority = act.priority;
		obj->cell = cell;
		obj->tgtObjectId = -1;
		obj->curObjectId = -1;
		obj->state = stref;
		if (_curObject && obj->index > _curObject->index)
			obj->state.flags |= 1;

		// if (storageSize < 5) {
		//  obj->pImg = nullptr;
		//  odat = &obj->pImg;
		// } else {
		//  odat = malloc(storageSize);
		//  obj->pImg = (Sprite *)odat;
		//  obj->flags |= 8;
		// }
		obj->storage.clear();
		obj->storage.resize(act.storageSize, 0);
		odat = obj->storage.data();
		index = obj->index;
		if (act.actType == 3 && _inputActObj == nullptr)
			_inputActObj = obj;
	}

	stref = ObjState(oid, e.flags, e.t);
	executeScript(act.onCreateAddress, &act, obj, index, odat, cell, e.t);
}

void GamosEngine::removeObjectByIDMarkDirty(int32 id) {
	if (id != -1)
		removeObjectMarkDirty(&_objects[id]);
}


void GamosEngine::removeObjectAtCoords(Common::Point cell, bool deleteGfxObj) {
	ObjState &stref = _states.at(cell);

	uint8 actid = stref.actid;

	if (actid == 0xfe)
		return;

	ObjectAction &act = _objectActions[actid];
	Object *povar4 = nullptr;
	bool multidel = false;

	for (uint i = 0; i < _objects.size(); i++) {
		Object &obj = _objects[i];
		if (obj.flags & Object::FLAG_VALID) {
			if (obj.flags & Object::FLAG_HASACTION) {
				if (obj.cell == cell) {
					removeObjectByIDMarkDirty(obj.curObjectId);
					if (obj.curObjectId != obj.tgtObjectId)
						removeObjectByIDMarkDirty(obj.tgtObjectId);
					/* if (obj.flags & Object::FLAG_STORAGE)
					    obj.storage.clear(); */
					removeSubtitles(&obj);
					removeObject(&obj);
					cycleNextInputObj(&obj);
					povar4 = &obj;
					if (!deleteGfxObj || multidel)
						break;

					multidel = true;
				}
			} else {
				if (deleteGfxObj && obj.cell == cell &&
				        obj.actObjIndex == -1 && (obj.flags & Object::FLAG_FREECOORDS) == 0) {

					removeObjectMarkDirty(&obj);
					if (multidel)
						break;

					multidel = true;
				}
			}
		}
	}

	if (povar4)
		stref = ObjState(povar4->state.actid, 0, povar4->state.t);

	executeScript(act.onDeleteAddress, &act, nullptr, -1, nullptr, cell, stref.t);
}

Object *GamosEngine::getFreeObject() {
	Object *obj = nullptr;
	for (uint i = 0; i < _objects.size(); i++) {
		Object &rObj = _objects[i];
		if ((rObj.flags & Object::FLAG_VALID) == 0) {
			obj = &rObj;
			break;
		}
	}

	if (!obj) {
		_objects.emplace_back();
		obj = &_objects.back();
		obj->index = _objects.size() - 1;
	}

	obj->flags = Object::FLAG_VALID;
	obj->priority = 0;
	obj->cell.x = 0;
	obj->cell.y = 0;

	obj->sprId = -1;
	obj->seqId = -1;
	obj->frame = -1;
	obj->frameMax = -1;
	obj->position.x = 0;
	obj->position.y = 0;
	obj->actObjIndex = -1;

	obj->actID = 0;
	obj->t = 0;
	obj->state = ObjState();
	obj->inputFlag = 0;
	obj->tgtObjectId = -1;
	obj->curObjectId = -1;

	obj->pImg = nullptr;
	obj->storage.clear(); // clear it so gfx object will not have data
	return obj;
}

void GamosEngine::removeObject(Object *obj) {
	obj->flags = 0;
	/*
		In original engine here is free for continoues unused objects in the end
	*/
}

void GamosEngine::removeObjectMarkDirty(Object *obj) {
	if (obj->flags & Object::FLAG_GRAPHIC)
		addDirtRectOnObject(obj);
	removeObject(obj);
}

void GamosEngine::executeScript(int32 scriptAddr, ObjectAction *act, Object *pobj, int32 index, byte *storage, Common::Point cell, uint8 t) {
	if (scriptAddr == -1)
		return;

	const uint8 sv1 = _curObjectT;
	byte * const sv2 = _curObjStorage;
	const Common::Point sv4 = _curObjectStartCell;
	const Common::Point sv6 = _curObjectCurrentCell;
	const int32 sv7 = _curObjIndex;
	Object * const sv8 = _curObject;
	ObjectAction * const sv9 = _curAction;

	_curObjectT = t;
	_curObjStorage = storage;
	_curObjectStartCell = cell;
	_curObjectCurrentCell = cell;
	_curObjIndex = index;
	_curObject = pobj;
	_curAction = act;

	doScript(scriptAddr);

	_curObjectT = sv1;
	_curObjStorage = sv2;
	_curObjectStartCell = sv4;
	_curObjectCurrentCell = sv6;
	_curObjIndex = sv7;
	_curObject = sv8;
	_curAction = sv9;
}

bool GamosEngine::updateObjects() {
	if (_objects.empty())
		return true;

	Object *pobj = _firstUpdateObject;
	if (!pobj)
		pobj = &(_objects.front());

	for (uint32 objIdx = pobj->index; objIdx < _objects.size(); objIdx++) {
		pobj = &_objects[objIdx];

		if (pobj->isActionObject()) {
			if (!PTR_00417388 || (PTR_00417388[ pobj->actID >> 3 ] & (1 << (pobj->actID & 7)))) {
				if (pobj->state.flags & 1) {
					pobj->state.flags &= ~1;
				} else {
					if ((pobj->flags & Object::FLAG_TRANSITION) == 0) {
						if (pobj->curObjectId != -1 && updateGfxFrames(&_objects[pobj->curObjectId], false, true)) {
							pobj->curObjectId = pobj->tgtObjectId;
							if (pobj->tgtObjectId != -1) {
								Object &o = _objects[pobj->tgtObjectId];
								o.flags |= Object::FLAG_GRAPHIC;
								o.cell = pobj->cell;
								updateGfxObjectPosition(&o);
								addDirtRectOnObject(&o);
							}
						}
					} else {
						if (updateGfxFrames(&_objects[pobj->curObjectId], true, pobj->curObjectId != pobj->tgtObjectId)) {
							pobj->curObjectId = pobj->tgtObjectId;
							if (pobj->tgtObjectId != -1) {
								Object &o = _objects[pobj->tgtObjectId];
								o.flags |= Object::FLAG_GRAPHIC;
								o.cell = pobj->cell;
								updateGfxObjectPosition(&o);
								addDirtRectOnObject(&o);
							}
							pobj->flags &= ~Object::FLAG_TRANSITION;
						} else {
							if (pobj == _firstUpdateObject) {
								goto loopExit;
							}
							goto continue_to_next_object;
						}
					}

					_curObject = pobj;
					_curObjIndex = pobj->index;
					_curAction = &_objectActions[pobj->actID];
					_curObjStorage = pobj->storage.data();

					_pathInMove = false;
					for (Actions &scr : _curAction->actions) {
						_curObjectT = _curObject->t;

						int ivr8 = 0;
						if (_curObjectT == 2)
							ivr8 = 1;
						else if (_curObjectT == 4)
							ivr8 = 2;
						else if (_curObjectT == 8)
							ivr8 = 3;

						bool tmp = false;
						for (int i = 0; i < 8; i++) {
							if (_curAction->mask & (1 << i)) {
								int fncid = ((i & 3) + ivr8) & 3;
								if (i > 3)
									fncid += 4;
								DAT_004173ec = fncid;

								_restartUpdateObject = false;
								_preprocDataId = fncid;
								int32 res = doActions(scr, false);

								if (_needReload)
									return false;

								if (res == 1) {
									if (_restartUpdateObject) {
										tmp = true;
										break;
									}

									if (_firstUpdateObject) {
										_firstUpdateObject = nullptr;
										goto loopExit;
									}
									goto continue_to_next_object;
								} else if (res != 0) {
									if (_firstUpdateObject) {
										_firstUpdateObject = nullptr;
										goto loopExit;
									}
									cycleNextInputObj(pobj);
									goto continue_to_next_object;
								}
							}
						}

						if (scr.flags & 0x80) {
							if (tmp) {
								_firstUpdateObject = pobj;
								goto loopExit;
							}

							if (_firstUpdateObject) {
								_firstUpdateObject = nullptr;
								goto loopExit;
							}

							break;
						}
					}
				}
			}
		} else {
			if (!PTR_00417388 && pobj->isGraphicObject() && pobj->actObjIndex == -1)
				updateGfxFrames(pobj, false, true);
		}
continue_to_next_object:
		;
	}

loopExit:
	_curObject = nullptr;
	_curObjIndex = -1;
	return true;
}

bool GamosEngine::updateGfxFrames(Object *obj, bool p2, bool p1) {
	if (obj->frameMax < 2) {
		if (p2 || (obj->flags & Object::FLAG_DIRTRECT)) {
			addDirtRectOnObject(obj);
			if (p1)
				removeObject(obj);
			return true;
		}
	} else {
		addDirtRectOnObject(obj);
		obj->frame++;

		if (obj->frame == obj->frameMax) {
			obj->frame = 0;
			obj->pImg = &_sprites[obj->sprId].sequences[obj->seqId]->operator[](obj->frame);
			if (p2 || (obj->flags & Object::FLAG_DIRTRECT)) {
				addDirtRectOnObject(obj);
				if (p1)
					removeObject(obj);
				return true;
			}
		} else {
			obj->pImg = &_sprites[obj->sprId].sequences[obj->seqId]->operator[](obj->frame);
		}

		if ((obj->flags & Object::FLAG_FREECOORDS) == 0)
			updateGfxObjectPosition(obj);

		addDirtRectOnObject(obj);
	}
	return false;
}

void GamosEngine::updateGfxObjectPosition(Object *gfxObj) {
	ImagePos *imgPos = gfxObj->pImg;
	Image *img = imgPos->image;

	int32 x = gfxObj->cell.x * _gridCellW;
	int32 y = gfxObj->cell.y * _gridCellH;

	if (gfxObj->actObjIndex != -1) {
		Object *o = &_objects[ gfxObj->actObjIndex ];
		if (o->flags & Object::FLAG_TRANSITION) {
			int t = gfxObj->frame + 1;
			x += (o->cell.x - gfxObj->cell.x) * _gridCellW * t / gfxObj->frameMax;
			y += (o->cell.y - gfxObj->cell.y) * _gridCellH * t / gfxObj->frameMax;
		}
	}

	if (gfxObj->flags & Object::FLAG_FLIPH)
		gfxObj->position.x = x - (img->surface.w - _gridCellW - imgPos->xoffset);
	else
		gfxObj->position.x = x - imgPos->xoffset;

	if (gfxObj->flags & Object::FLAG_FLIPV)
		gfxObj->position.y = y - (img->surface.h - _gridCellH - imgPos->yoffset);
	else
		gfxObj->position.y = y - imgPos->yoffset;
}

void GamosEngine::addDirtRectOnObject(Object *obj) {
	ImagePos *imgPos = obj->pImg;
	Common::Rect rect;
	rect.left = obj->position.x;
	rect.top = obj->position.y;
	if (obj->flags & Object::FLAG_FREECOORDS) {
		rect.left -= imgPos->xoffset;
		rect.top -= imgPos->yoffset;
	}
	rect.setWidth(imgPos->image->surface.w);
	rect.setHeight(imgPos->image->surface.h);

	addDirtyRect(rect);
}

void GamosEngine::addDirtyRect(const Common::Rect &rect) {
	if (_dirtyRects.empty()) {
		_dirtyRects.push_back(rect);
		return;
	}

	bool intersects = 0;
	for (uint i = 0; i < _dirtyRects.size(); i++) {
		Common::Rect &r = _dirtyRects[i];

		if (!rect.intersects(r))
			continue;

		intersects = true;

		r.extend(rect);
		break;
	}

	if (!intersects) {
		_dirtyRects.push_back(rect);
		return;
	}

rerunCheck:
	for (int i = _dirtyRects.size() - 2; i > 0; i--) {
		for (int j = _dirtyRects.size() - 1; j > i; j--) {
			Common::Rect &r1 = _dirtyRects[i];
			Common::Rect &r2 = _dirtyRects[j];
			if (!r1.intersects(r2))
				continue;

			r1.extend(r2);
			_dirtyRects.remove_at(j);
			goto rerunCheck;
		}
	}
}

void GamosEngine::doDraw() {
	if (_dirtyRects.empty())
		return;

	int32 bkg = _currentGameScreen;
	if (bkg == -1)
		bkg = 0;

	Common::Array<Object *> drawList(1024);  //_drawElements.size(), 1024) );

	int cnt = 0;
	for (uint i = 0; i < _objects.size(); i++) {
		Object &obj = _objects[i];
		if ( obj.isGraphicObject() ) {
			drawList[cnt] = &obj;
			cnt++;
		}
	}

	drawList.resize(cnt);

	if (cnt) {
		for (uint i = 0; i < drawList.size() - 1; i++) {
			for (uint j = i + 1; j < drawList.size(); j++) {
				Object *o1 = drawList[i];
				Object *o2 = drawList[j];
				if (o1->priority < o2->priority) {
					drawList[i] = o2;
					drawList[j] = o1;
				}
			}
		}
	}

	for (uint i = 0; i < _dirtyRects.size(); i++) {
		Common::Rect r = _dirtyRects[i];

		r.translate(-_scrollX, -_scrollY);
		r.clip(_screen->getBounds());

		if (r.isEmpty())
			continue;

		Common::Rect srcRect = r;
		srcRect.translate(_scrollX, _scrollY);

		/* update bkg at this rect */
		if (_gameScreens[bkg].loaded) {
			_screen->blitFrom(_gameScreens[bkg]._bkgImage, srcRect, r.origin());
		}

		for (Object *o : drawList) {
			if (o->pImg && loadImage(o->pImg->image)) {

				Common::Rect s;
				s.left = o->position.x - _scrollX;
				s.top = o->position.y - _scrollY;

				if (o->flags & Object::FLAG_FREECOORDS) {
					s.left -= o->pImg->xoffset;
					s.top -= o->pImg->yoffset;
				}

				s.setWidth(o->pImg->image->surface.w);
				s.setHeight(o->pImg->image->surface.h);

				if (!s.intersects(r))
					continue;

				Common::Rect sdirt = s;
				sdirt.clip(r);

				Common::Rect ssrc(sdirt.origin() - s.origin(), sdirt.width(), sdirt.height());

				uint flip = 0;
				if (o->flags & Object::FLAG_FLIPH)
					flip |= Graphics::FLIP_H;
				if (o->flags & Object::FLAG_FLIPV)
					flip |= Graphics::FLIP_V;

				Blitter::blit(&o->pImg->image->surface, ssrc, _screen->surfacePtr(), sdirt, flip);
			}
		}

		if (!_currentFade)
			_screen->addDirtyRect(r);
	}


	if (_currentFade)
		updateScreen(true, Common::Rect(_bkgSize.x, _bkgSize.y));

	_currentFade = 0;

	_dirtyRects.clear();

	_screen->update();
}

bool GamosEngine::loadImage(Image *img) {
	if (img->loaded)
		return true;

	if (img->offset < 0)
		return false;

	_gameFile.seek(img->offset, 0);

	if (img->cSize == 0) {
		img->rawData.resize((img->surface.w * img->surface.h + 16) & ~0xf);

		_gameFile.read(img->rawData.data(), img->surface.w * img->surface.h);
		img->surface.setPixels(img->rawData.data());
	} else {
		img->rawData.resize((img->surface.w * img->surface.h + 4 + 16) & ~0xf);

		RawData tmp(img->cSize);
		_gameFile.read(tmp.data(), tmp.size());
		_gameFile.decompress(&tmp, &img->rawData);
		img->surface.setPixels(img->rawData.data() + 4);
	}

	img->surface.format = Graphics::PixelFormat::createFormatCLUT8();
	img->loaded = true;
	return true;
}

uint32 GamosEngine::doScript(uint32 scriptAddress) {
	uint32 res = _vm.doScript(scriptAddress, _curObjStorage);
	return res;
}


void GamosEngine::vmCallDispatcher(VM::Context *ctx, uint32 funcID) {
	uint32 arg1 = 0, arg2 = 0;

	if (_engineVersion <= 0x12 && funcID >= 47) // skip 47 func for old versions
		funcID++;

	switch (funcID) {
	case 0:
		_restartUpdateObject = true;
		ctx->EAX.setVal(1);
		break;
	case 1:
		ctx->EAX.setVal( _curObject->curObjectId == -1 ? 1 : 0 );
		break;

	case 2:
		arg1 = ctx->pop32();
		if (_curObject->tgtObjectId == -1)
			ctx->EAX.setVal(0);
		else
			ctx->EAX.setVal( _objects[ _curObject->tgtObjectId ].sprId == (int32)arg1 ? 1 : 0 );
		break;
	case 3:
		ctx->EAX.setVal( (_curObject->inputFlag & 0x90) == 0x10 ? 1 : 0 );
		break;
	case 4:
		ctx->EAX.setVal( (_curObject->inputFlag & 0xa0) == 0x20 ? 1 : 0 );
		break;
	case 5:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( (uint32)(_curObject->inputFlag & 0xb0) == arg1 ? 1 : 0 );
		break;
	case 6:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( (uint32)(_curObject->inputFlag & 0x4f) == arg1 ? 1 : 0 );
		break;
	case 7:
		arg1 = ctx->pop32();
		if ((_curObject->inputFlag & 0x40) == 0 || (uint32)(_curObject->inputFlag & 8) != (uint32)(arg1 & 8))
			ctx->EAX.setVal(0);
		else
			ctx->EAX.setVal( FUN_0040705c(arg1 & 7, _curObject->inputFlag & 7) ? 1 : 0 );
		break;
	case 8:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( _curObject->priority == arg1 ? 1 : 0 );
		break;
	case 9:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( savedDoActions(_subtitleActions[arg1]) );
		break;
	case 10:
		ctx->EAX.setVal( _curObject->state.actid == 0xfe ? 1 : 0 );
		break;
	case 11:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( _curObject->state.actid == arg1 ? 1 : 0 );
		break;
	case 12:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( _thing2[arg1].masks[ _curObject->state.actid >> 3 ] & (1 << (_curObject->state.actid & 7)) );
		break;
	case 13: {
		VM::ValAddr regRef = ctx->popReg();
		Common::String str = ctx->getString(regRef);

		ctx->EAX.setVal(0);

		for(uint i = 0; i < str.size(); i++) {
			if (str[i] == _pressedKeyCode) {
				ctx->EAX.setVal(1);
				break;
			}
		}
		break;
	}

	case 14:
		arg1 = ctx->pop32();
		loadModule(arg1);
		setNeedReload();
		ctx->EAX.setVal(1);
		break;

	case 15:
		arg1 = ctx->pop32();
		switchToGameScreen(arg1, false);
		setNeedReload();
		break;

	case 16:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( playMidiTrack(arg1) );
		break;

	case 17:
		arg1 = ctx->pop32();
		if (!_ignoreSoundActions && _enableSounds)
			playSound(arg1);
		ctx->EAX.setVal(1);
		break;

	case 18:
		arg1 = ctx->pop32();
		ctx->EAX.setVal( playMovie(arg1) ? 1 : 0 );
		break;

	case 19:
		arg1 = ctx->pop32();
		_gfxObjectCreated = true;
		createGfxObject(arg1, Common::Point(_curObjectCurrentCell.x * _gridCellW, _curObjectCurrentCell.y * _gridCellH), false);
		ctx->EAX.setVal( 1 );
		break;

	case 20: {
		arg1 = ctx->pop32();
		for (const SubtitlePoint &d : _subtitlePoints[arg1]) {
			createGfxObject(d.sprId, d, true);
		}
		ctx->EAX.setVal( savedDoActions(_subtitleActions[arg1]) );
	} break;

	case 21: {
		VM::ValAddr regRef = ctx->popReg();
		arg2 = ctx->pop32();
		ctx->EAX.setVal( txtInputBegin(ctx, regRef.getMemType(), regRef.getOffset(), arg2, _curObjectCurrentCell.x * _gridCellW, _curObjectCurrentCell.y * _gridCellH) );
	} break;

	case 22: {
		VM::ValAddr regRef = ctx->popReg();
		arg2 = ctx->pop32();
		const SubtitlePoint &d = _subtitlePoints[arg2][0];
		ctx->EAX.setVal( txtInputBegin(ctx, regRef.getMemType(), regRef.getOffset(), d.sprId, d.x, d.y) );
	} break;

	case 23: {
		VM::ValAddr regRef = ctx->popReg();
		arg2 = ctx->pop32();
		addSubtitles(ctx, regRef.getMemType(), regRef.getOffset(), arg2, _curObjectCurrentCell.x * _gridCellW, _curObjectCurrentCell.y * _gridCellH);
		ctx->EAX.setVal(1);
	} break;

	case 24: {
		VM::ValAddr regRef = ctx->popReg();
		arg2 = ctx->pop32();
		const SubtitlePoint &d = _subtitlePoints[arg2][0];
		addSubtitles(ctx, regRef.getMemType(), regRef.getOffset(), d.sprId, d.x, d.y);

		ctx->EAX.setVal(1);
	}
	break;

	case 25: {
		arg1 = ctx->pop32();
		if (_curObject->priority != arg1) {
			_curObject->priority = arg1;
			if (_curObject->tgtObjectId != -1) {
				Object &obj = _objects[_curObject->tgtObjectId];
				obj.priority = arg1;
			}
			if (_curObject->curObjectId != -1) {
				Object &obj = _objects[_curObject->curObjectId];
				obj.priority = arg1;
				addDirtRectOnObject(&obj);
			}
		}
		ctx->EAX.setVal(1);
	}
	break;

	case 26:
		removeSubtitles(_curObject);
		ctx->EAX.setVal(1);
		break;

	case 27:
		removeStaticGfxCurObj();
		ctx->EAX.setVal(1);
		break;

	case 28:
		arg1 = ctx->pop32();
		runRenewStaticGfxCurObj(arg1, false);
		ctx->EAX.setVal(1);
		break;

	case 29:
		arg1 = ctx->pop32();
		runRenewStaticGfxCurObj(arg1, true);
		ctx->EAX.setVal(1);
		break;

	case 30: {
		if (_curObject->curObjectId != -1) {
			Object *obj = &_objects[_curObject->curObjectId];
			_curObject->tgtObjectId = -1;
			_curObject->curObjectId = -1;
			removeObjectMarkDirty(obj);
		}
	} break;

	case 31:
		arg1 = ctx->pop32();
		setCursor(arg1, true);
		ctx->EAX.setVal(1);
		break;

	case 32:
		setCursor(0, false);
		ctx->EAX.setVal(1);
		break;

	case 33:
		_curObject->priority = _statesHeight - _curObject->cell.y;
		ctx->EAX.setVal(1);
		break;

	case 34: {
		VM::ValAddr regRef = ctx->popReg();
		ctx->setMem8(regRef, _curObject->priority);
		ctx->EAX.setVal(1);
	} break;

	case 35: {
		arg1 = ctx->pop32();
		uint ret = 0;
		switch (arg1) {
		case 3:
			ret = FUN_00408648(0xe, 0xff, 0xff);
			break;

		case 4:
			ret = FUN_00408648(0xe, 0xfe, 0xff);
			break;

		case 5:
			ret = FUN_00408648(0xe, 0xfe, 0xfe);
			break;

		case 6:
			ret = FUN_00408648(ACT2_MOUSEUP_L, 0xff, 0xff);
			break;

		case 7:
			ret = FUN_00408648(ACT2_MOUSEUP_L, 0xfe, 0xff);
			break;

		case 8:
			ret = FUN_00408648(ACT2_MOUSEUP_L, 0xfe, 0xfe);
			break;

		case 9:
			ret = FUN_00408648(ACT2_MOUSEUP_R, 0xff, 0xff);
			break;

		case 10:
			ret = FUN_00408648(ACT2_MOUSEUP_R, 0xfe, 0xff);
			break;

		case 11:
			ret = FUN_00408648(ACT2_MOUSEUP_R, 0xfe, 0xfe);
			break;

		default:
			break;
		}
		ctx->EAX.setVal(ret);
	} break;

	case 36: {
		arg1 = ctx->pop32();
		arg2 = ctx->pop32();

		uint ret = 0;
		switch (arg1) {
		case 1:
			ret = FUN_00408648(0, arg2, 0xff);
			break;

		case 2:
			ret = FUN_00408648(0, arg2, 0xfe);
			break;

		case 3:
			ret = FUN_00408648(0xe, arg2, 0xff);
			break;

		case 4:
			ret = FUN_00408648(0xe, arg2, 0xfe);
			break;

		case 5:
			ret = FUN_00408648(0xe, arg2, arg2);
			break;

		case 6:
			ret = FUN_00408648(ACT2_MOUSEUP_L, arg2, 0xff);
			break;

		case 7:
			ret = FUN_00408648(ACT2_MOUSEUP_L, arg2, 0xfe);
			break;

		case 8:
			ret = FUN_00408648(ACT2_MOUSEUP_L, arg2, arg2);
			break;

		case 9:
			ret = FUN_00408648(ACT2_MOUSEUP_R, arg2, 0xff);
			break;

		case 10:
			ret = FUN_00408648(ACT2_MOUSEUP_R, arg2, 0xfe);
			break;

		case 11:
			ret = FUN_00408648(ACT2_MOUSEUP_R, arg2, arg2);
			break;

		default:
			break;
		}
		ctx->EAX.setVal(ret);
	} break;

	case 37: {
		arg1 = ctx->pop32();
		arg2 = ctx->pop32();

		uint ret = 0;
		switch (arg1) {
		case 1:
			ret = FUN_004088cc(0, arg2, 0xff);
			break;

		case 2:
			ret = FUN_004088cc(0, arg2, 0xfe);
			break;

		case 3:
			ret = FUN_004088cc(0xe, arg2, 0xff);
			break;

		case 4:
			ret = FUN_004088cc(0xe, arg2, 0xfe);
			break;

		case 5:
			ret = FUN_004088cc(0xe, arg2, arg2);
			break;

		case 6:
			ret = FUN_004088cc(ACT2_MOUSEUP_L, arg2, 0xff);
			break;

		case 7:
			ret = FUN_004088cc(ACT2_MOUSEUP_L, arg2, 0xfe);
			break;

		case 8:
			ret = FUN_004088cc(ACT2_MOUSEUP_L, arg2, arg2);
			break;

		case 9:
			ret = FUN_004088cc(ACT2_MOUSEUP_R, arg2, 0xff);
			break;

		case 10:
			ret = FUN_004088cc(ACT2_MOUSEUP_R, arg2, 0xfe);
			break;

		case 11:
			ret = FUN_004088cc(ACT2_MOUSEUP_R, arg2, arg2);
			break;

		default:
			break;
		}
		ctx->EAX.setVal(ret);
	} break;

	case 38:
		arg1 = ctx->pop32();
		if (_pathInMove && (int32)arg1 == _pathDir4)
			ctx->EAX.setVal(1);
		else
			ctx->EAX.setVal(0);
		break;

	case 39:
		arg1 = ctx->pop32();
		if (_pathInMove && (int32)arg1 == _pathDir8)
			ctx->EAX.setVal(1);
		else
			ctx->EAX.setVal(0);
		break;

	case 40:
		arg1 = ctx->pop32();
		if (_pathInMove && FUN_0040705c(arg1, _pathDir4) != 0)
			ctx->EAX.setVal(1);
		else
			ctx->EAX.setVal(0);
		break;

	case 41:
		arg1 = ctx->pop32();
		if (_pathInMove && FUN_0040705c(arg1, _pathDir8) != 0)
			ctx->EAX.setVal(1);
		else
			ctx->EAX.setVal(0);
		break;

	case 42: {
		arg1 = ctx->pop32();
		if (_pathInMove != false) {
			if (arg1 == 0) {
				_pathInMove = false;
				_inputMouseActType = 0xff;
				_inputMouseActId = 0xff;
			} else if (arg1 == 1) {
				ActEntry tmp;
				tmp.actid = 0xfe;
				tmp.t = _curObjectT;
				tmp.flags = 0;
				createActiveObject(tmp, _pathTargetCell);
			} else if (arg1 == 2) {
				ActEntry tmp;
				tmp.actid = 0;
				tmp.t = _curObjectT;
				tmp.flags = 0;
				tmp.x = _pathTargetCell.x - _pathStartCell.x;
				tmp.y = _pathTargetCell.y - _pathStartCell.y;
				processActionCurObject(tmp);
			}
		}
		ctx->EAX.setVal(1);
	} break;

	case 43: {
		arg1 = ctx->pop32();
		if (_pathInMove) {
			ActEntry tmp;
			tmp.actid = arg1;
			tmp.t = _curObjectT;
			tmp.flags = 0;
			createActiveObject(tmp, _pathTargetCell);
		}
		ctx->EAX.setVal(1);
	} break;

	case 44: {
		arg1 = ctx->pop32();
		if (_pathInMove) {
			ActEntry tmp;
			tmp.actid = arg1;
			tmp.t = _curObjectT;
			tmp.flags = 1;
			createActiveObject(tmp, _pathTargetCell);
		}
		ctx->EAX.setVal(1);
	} break;

	case 45:
		arg1 = ctx->pop32();
		// Seems here needed only ->t ?
		// In AiTi arg1 0x20, 0x40, 0x80
		ctx->EAX.setVal( ((_curObject->flags | (_curObject->t << 4)) & arg1) ? 1 : 0 );
		break;

	case 46: {
		VM::ValAddr a1 = ctx->popReg();
		VM::ValAddr a2 = ctx->popReg();
		Common::String s = ctx->getString(a1);
		for(uint i = 0; i <= s.size(); i++) {
			ctx->setMem8(a2.getMemType(), a2.getOffset() + i, s.c_str()[i]);
		}
	} break;

	case 47: {
		arg1 = ctx->pop32();

		switch (arg1) {
		case 0:
			ctx->EAX.setVal(_enableInput ? 1 : 0);
			break;

		case 1:
			ctx->EAX.setVal(_enableSounds ? 1 : 0);
			break;

		case 2:
			ctx->EAX.setVal(_midiVolumeTarget != 0 ? 1 : 0);
			break;

		case 3:
			ctx->EAX.setVal(_enableMovie ? 1 : 0);
			break;

		case 4:
			ctx->EAX.setVal(_enableCDAudio ? 1 : 0);
			break;

		default:
			break;
		}
	}
	break;

	case 48: {
		arg1 = ctx->pop32();

		switch (arg1) {
		case 0:
			_enableInput = false;
			break;
		case 1:
			_enableInput = true;
			break;
		case 2:
			_enableSounds = false;
			_sndVolumeTarget = 0;
			break;
		case 3:
			_enableSounds = true;
			_sndVolumeTarget = _savedSndVolume;
			break;
		case 4:
			_midiVolumeTarget = 0;
			break;
		case 5:
			_midiVolumeTarget = _savedMidiVolume;
			break;
		case 6:
			_enableMovie = false;
			break;
		case 7:
			_enableMovie = true;
			break;
		case 8:
			//FUN_0040a9c0(0);
			_enableCDAudio = false;
			break;
		case 9:
			if (_cdAudioTrack != -1) {
				//FUN_0040a958(_d2_fld19);
			}
			_enableCDAudio = true;
			break;
		default:
			break;
		}
		ctx->EAX.setVal(1);
	} break;

	case 49: {
		arg1 = ctx->pop32();
		arg2 = ctx->pop32();

		if (arg1 == 0) {
			ctx->EAX.setVal(deleteSaveFile(arg2) ? 1 : 0);
		} else if (arg1 == 1) {
			ctx->EAX.setVal(writeSaveFile(arg2) ? 1 : 0);
		} else if (arg1 == 2) {
			ctx->EAX.setVal(loadSaveFile(arg2) ? 1 : 0);
		}
	}
	break;

	case 50:
		arg1 = ctx->pop32();
		PTR_00417388 = _thing2[arg1].masks.data();
		ctx->EAX.setVal(1);
		break;

	case 51:
		PTR_00417388 = nullptr;
		ctx->EAX.setVal(1);
		break;

	case 52:
		arg1 = ctx->pop32();
		/* HELP */
		//FUN_0040c614(arg1);
		ctx->EAX.setVal(1);
		break;

	case 53: {
		arg1 = ctx->pop32();
		VM::ValAddr adr = ctx->popReg();
		uint kode = ctx->getMem8(adr);
		_messageProc._keyCodes[arg1] = kode;
		ctx->EAX.setVal(kode);
	} break;

	case 54:
		arg1 = ctx->pop32();
		ctx->EAX.setVal(rndRange16(arg1));
		break;

	case 55: {
		VM::ValAddr regRef = ctx->popReg(); //implement
		Common::String str = ctx->getString(regRef);

		char buffer[256];
		int a = 0, b = 0, c = 0, d = 0;
		if (sscanf(str.c_str(), "%255s %d %d %d %d", buffer, &a, &b, &c, &d) > 0) {
			stopMidi();
			stopSounds();

			playVideo(Common::String(buffer), Common::Point(a, b), Common::Point(c, d));

			if (_cdAudioTrack != -1) {
				/* vm func 58 */
			}

			if (_midiTrack != -1) {
				playMidiTrack(_midiTrack);
			}
		}
		ctx->EAX.setVal(1);
	}
	break;

	case 56: {
		VM::ValAddr regRef = ctx->popReg(); //implement
		Common::String str = ctx->getString(regRef);
		warning("Create process: %s", str.c_str());
		ctx->EAX.setVal(1);
	} break;

	case 57: {
		VM::ValAddr regRef = ctx->popReg(); //implement
		Common::String str = ctx->getString(regRef);
		if (_keySeq.find(str) != Common::String::npos) {
			_keySeq.clear();
			ctx->EAX.setVal(1);
		} else
			ctx->EAX.setVal(0);
	} break;

	case 58: {
		arg1 = ctx->pop32();
		/* CD AUDIO */
		ctx->EAX.setVal(1);
	} break;

	case 59: {
		arg1 = ctx->pop32();
		/* CD AUDIO */
		ctx->EAX.setVal(1);
	} break;

	case 60:
		arg1 = ctx->pop32();
		if (arg1 == 0)
			_scrollTrackObj = -1;
		else
			_scrollTrackObj = _curObjIndex;
		ctx->EAX.setVal(1);
		break;

	case 61: {
		arg1 = ctx->pop32();
		VM::ValAddr adr = ctx->popReg();
		Common::String tmp = ctx->getString(adr);

		int val1 = 0, val2 = 0, val3 = 0, val4 = 0;
		sscanf(tmp.c_str(), "%d %d %d %d", &val1, &val2, &val3, &val4);

		if (arg1 == 0) {
			_scrollBorderL = val1;
			_scrollBorderR = val2;
			_scrollBorderU = val3;
			_scrollBorderB = val4;
		} else {
			_scrollSpeed = val1;
			_scrollCutoff = val2;
			_scrollSpeedReduce = val3;
		}
		ctx->EAX.setVal(1);
	} break;

	default:
		warning("Call Dispatcher %d", funcID);
		ctx->EAX.setVal(0);
		break;
	}
}

void GamosEngine::callbackVMCallDispatcher(void *engine, VM::Context *ctx, uint32 funcID) {
	GamosEngine *gamos = (GamosEngine *)engine;
	gamos->vmCallDispatcher(ctx, funcID);
}

uint32 GamosEngine::playMidiTrack(uint32 id) {
	if (!_ignoreSoundActions) {
		stopMidi();
		if (_enableMidi) {
			_midiTrack = id;
			if (id >= _midiTracks.size())
				return 0;

			return playMidi(&_midiTracks[id]) ? 1 : 0;
		}
	}
	return 1;
}


bool GamosEngine::createGfxObject(uint32 id, Common::Point position, bool staticObject) {
	Sprite &spr = _sprites[id];
	Object *gfxObj = getFreeObject();

	gfxObj->flags |= Object::FLAG_GRAPHIC;

	if (spr.flags & 1)
		gfxObj->flags |= Object::FLAG_DIRTRECT;

	gfxObj->frameMax = spr.frameCount;
	int16 idx = -1;
	if (!staticObject)
		idx = _curObjIndex;

	gfxObj->actObjIndex = idx;
	gfxObj->position = position;

	if (!staticObject) {
		if (!_curObject) {
			gfxObj->priority = _curAction->priority;
		} else {
			int32 index = gfxObj->index;
			if (_curObject->curObjectId != -1) {
				Object *pgObj = &_objects[_curObject->curObjectId];
				addDirtRectOnObject(pgObj);
				pgObj->flags &= ~Object::FLAG_GRAPHIC;
				if (_curObject->tgtObjectId != _curObject->curObjectId)
					removeObject(pgObj);
			}

			_curObject->curObjectId = index;
			if (!(gfxObj->flags & Object::FLAG_DIRTRECT)) {
				if (_curObject->tgtObjectId != -1)
					removeObject(&_objects[_curObject->tgtObjectId]);
				_curObject->tgtObjectId = index;
			}

			gfxObj->priority = _curObject->priority;
			if (_curObjectCurrentCell != _curObjectStartCell) {
				_curObject->flags |= Object::FLAG_TRANSITION;
			}
		}
	} else {
		gfxObj->priority = _curObject->priority;
		gfxObj->cell.x = -1;
		gfxObj->cell.y = -1;
	}

	gfxObjectCalculateFlip(id, gfxObj, staticObject);
	return true;
}

void GamosEngine::gfxObjectCalculateFlip(int32 sprId, Object *obj, bool p) {
	obj->flags &= ~(Object::FLAG_FLIPH | Object::FLAG_FLIPV);
	obj->actID = 0;
	obj->frame = 0;
	obj->sprId = sprId;
	obj->seqId = 0;

	Sprite &spr = _sprites[sprId];


	if (spr.lastChar == 1) {
		obj->pImg = &spr.sequences[0]->operator[](0);
		if (_curObjectT == 8) {
			if (spr.flags & 2)
				obj->flags |= Object::FLAG_FLIPH;
		} else if (_curObjectT == 4 && (spr.flags & 4)) {
			obj->flags |= Object::FLAG_FLIPV;
		}
	} else {
		if (_curObjectT == 1) {
			obj->seqId = 1;
			if (_curObjectCurrentCell.y == _curObjectStartCell.y && (spr.flags & 8))
				obj->seqId = 0;
		} else if (_curObjectT == 2) {
			obj->seqId = 3;
			if (_curObjectStartCell.y < _curObjectCurrentCell.y)
				obj->seqId = 2;
			else if (_curObjectStartCell.y > _curObjectCurrentCell.y) {
				obj->seqId = 4;
				if (spr.flags & 4) {
					obj->seqId = 2;
					obj->flags |= Object::FLAG_FLIPV;
				}
			} else if (_curObjectCurrentCell.x == _curObjectStartCell.x && (spr.flags & 8))
				obj->seqId = 0;
		} else if (_curObjectT == 4) {
			obj->seqId = 5;
			if (_curObjectCurrentCell.y == _curObjectStartCell.y && (spr.flags & 8))
				obj->seqId = 0;
			else if (spr.flags & 4) {
				obj->seqId = 1;
				obj->flags |= Object::FLAG_FLIPV;
			}
		} else {
			obj->seqId = 7;
			if (_curObjectCurrentCell.y == _curObjectStartCell.y) {
				if ((spr.flags & 8) && _curObjectCurrentCell.x == _curObjectStartCell.x)
					obj->seqId = 0;
				else if (spr.flags & 2) {
					obj->seqId = 3;
					obj->flags |= Object::FLAG_FLIPH;
				}
			} else {
				if (_curObjectStartCell.y < _curObjectCurrentCell.y) {
					obj->seqId = 8;
					if (spr.flags & 2) {
						obj->seqId = 2;
						obj->flags |= Object::FLAG_FLIPH;
					}
				} else {
					obj->seqId = 6;
					if (spr.flags & 4) {
						obj->seqId = 8;
						obj->flags |= Object::FLAG_FLIPV;

						if (spr.flags & 2) {
							obj->seqId = 2;
							obj->flags |= Object::FLAG_FLIPH;
						}
					} else if (spr.flags & 2) {
						obj->seqId = 4;
						obj->flags |= Object::FLAG_FLIPH;
					}
				}
			}
		}
		obj->pImg = &spr.sequences[obj->seqId]->operator[](0);
	}
	if (!p) {
		obj->cell = _curObjectStartCell;
		updateGfxObjectPosition(obj);
	} else {
		obj->flags |= Object::FLAG_FREECOORDS;
	}

	addDirtRectOnObject(obj);
}

void GamosEngine::updateLinkedGfxObject(Object *obj) {
	if (obj->curObjectId != -1) {
		Object &yobj = _objects[obj->curObjectId];
		addDirtRectOnObject(&yobj);
		if (_curObjectStartCell != _curObjectCurrentCell)
			obj->flags |= Object::FLAG_TRANSITION;
		gfxObjectCalculateFlip(yobj.sprId, &yobj, false);
	}
}

void GamosEngine::removeSubtitles(Object *obj) {
	if (obj->state.flags & 2) {
		obj->state.flags &= ~2;
		//for (int index = obj->index; index < _objects.size(); index++) {
		for (uint index = 0; index < _objects.size(); index++) {
			Object *pobj = &_objects[index];
			if (pobj->isOverlayObject() && pobj->actObjIndex == obj->index)
				removeObjectMarkDirty(pobj);
		}
	}
}

void GamosEngine::cycleNextInputObj(Object *obj) {
	if (obj == _inputActObj) {
		_inputActObj = nullptr;
		int32 n = 0;

		int16 objIndex = -1;
		if (obj)
			objIndex = obj->index;

		for (uint32 i = 0; i < _objects.size(); i++) {
			Object &robj = _objects[i];

			if (robj.index > objIndex)
				n++;

			if (robj.isActionObject() && _objectActions[robj.actID].actType == 3) {
				if (n) {
					_inputActObj = &robj;
					break;
				}
				if (!_inputActObj)
					_inputActObj = &robj;
			}
		}
	}
}

void GamosEngine::setCursor(int id, bool dirtRect) {
	if (_drawCursor == 0)
		_mouseCursorImgId = id;
	else
		_mouseCursorImgId = -1;

	_cursorFrame = 0;
}


bool GamosEngine::checkPointOnLinkedGfx(Object *obj, Common::Point pos) {
	if (obj->curObjectId == -1)
		return false;

	Object &gfxobj = _objects[obj->curObjectId];
	if (Common::Rect(gfxobj.position.x, gfxobj.position.y, gfxobj.position.x + gfxobj.pImg->image->surface.w, gfxobj.position.y + gfxobj.pImg->image->surface.h).contains(pos))
		return true;
	return false;
}

void GamosEngine::processInput(Common::Point move, Common::Point actPos, uint8 act2, uint8 act1) {
	uint8 tmpb = 0;
	if (act2 == ACT2_TAB)
		cycleNextInputObj(_inputActObj);
	else if (act2 == ACT2_MOUSEUP_L)
		tmpb = 0x90;
	else if (act2 == ACT2_MOUSEUP_R)
		tmpb = 0xa0;
	else if (act2 == ACT_NONE)
		actPos = move;

	if (act1 != ACT_NONE)
		tmpb |= act1 | 0x40;

	actPos += Common::Point(_scrollX, _scrollY);

	Object *pobj = nullptr;
	uint8 actT = 0;
	uint8 pobjF5 = 255;

	for (uint i = 0; i < _objects.size(); i++) {
		Object &obj = _objects[i];
		if (obj.isActionObject()) {
			ObjectAction &action = _objectActions[obj.actID];
			if (action.actType == 1)
				obj.inputFlag = tmpb;
			else if (action.actType == 2)
				obj.inputFlag = tmpb & 0x4f;
			else if (action.actType == 3) {
				if (&obj == _inputActObj)
					obj.inputFlag = tmpb & 0x4f;
				else
					obj.inputFlag = 0;
			}

			if ((!pobj || obj.priority <= pobjF5) && checkPointOnLinkedGfx(&obj, actPos)) {
				actT = action.actType;
				pobjF5 = obj.priority;
				pobj = &obj;
			}
		}
	}

	if (!pobj) {
		_inputActCell.x = actPos.x / _gridCellW;
		_inputActCell.y = actPos.y / _gridCellH;
		_inputActId = _states.at(_inputActCell).actid;
	} else {
		_inputActId = actT;
		if (actT == 2) {
			if (act2 == ACT_NONE)
				tmpb |= 0x10;
			else if (act2 == ACT2_MOUSEDOWN)
				tmpb |= 0x20;

			pobj->inputFlag = tmpb;
		} else if (actT == 3 && (tmpb == 0x90 || tmpb == 0xa0)) {
			_inputActObj = pobj;
			pobj->inputFlag = tmpb;
		}

		_inputActCell = pobj->cell;
	}

	if (act2 == ACT2_MOUSEUP_L || act2 == ACT2_MOUSEUP_R) {
		_inputMouseActType = act2;
		_inputMouseActId = _inputActId;
		_inputMouseActCell = _inputActCell;
	} else {
		if (act2 == ACT2_MOUSEDOWN)
			_inputMouseActType = ACT_NONE;
	}

}

uint32 GamosEngine::savedDoActions(const Actions &a) {
	const bool sv1 = _gfxObjectCreated;
	const uint8 sv2 = _curObjectT;
	byte * const sv3 = _curObjStorage;
	const uint8 sv6 = _preprocDataId;
	const Common::Point sv8 = _curObjectStartCell;
	const Common::Point sv10 = _curObjectCurrentCell;
	const int sv11 = _curObjIndex;
	Object * const sv12 = _curObject;
	ObjectAction * const sv13 = _curAction;

	const uint32 res = doActions(a, true);

	_gfxObjectCreated = sv1;
	_curObjectT = sv2;
	_curObjStorage = sv3;
	_preprocDataId = sv6;
	_curObjectStartCell = sv8;
	_curObjectCurrentCell = sv10;
	_curObjIndex = sv11;
	_curObject = sv12;
	_curAction = sv13;

	return res;
}

void GamosEngine::addSubtitles(VM::Context *ctx, byte memtype, int32 offset, int32 sprId, int32 x, int32 y) {
	removeSubtitles(_curObject);
	_curObject->state.flags |= 2;

	while (true) {
		uint8 ib = ctx->getMem8(memtype, offset);
		offset++;

		if (ib == 0)
			break;

		if (ib == 0xf) {
			byte flg = ctx->getMem8(memtype, offset);
			offset++;
			byte b2 = ctx->getMem8(memtype, offset);
			offset++;

			if ((flg & 0x70) == 0x20) {
				byte funcid = ctx->getMem8(memtype, offset);
				offset++;
				warning("CHECKIT and write funcid %d", funcid);
			} else {
				if ((flg & 0x70) == 0 || (flg & 0x70) == 0x10) {
					int32 boff = 0;
					byte btp = VM::REF_EDI;

					if ((flg & 0x70) == 0x10)
						btp = VM::REF_EBX;

					if ((flg & 0x80) == 0) {
						boff = ctx->getMem8(memtype, offset);
						offset++;
					} else {
						boff = ctx->getMem32(memtype, offset);
						offset += 4;
					}

					Common::String tmp;
					switch (flg & 7) {
					case 0:
						tmp = gamos_itoa((int32)(int8)ctx->getMem8(btp, boff), 10);
						break;

					case 1: {
						VM::ValAddr addr;
						addr.setVal( ctx->getMem32(btp, boff) );
						tmp = ctx->getString(addr, b2);
					} break;

					case 2:
						tmp = ctx->getString(btp, boff, b2);
						break;

					case 3:
						tmp = gamos_itoa(ctx->getMem32(btp, boff), 10);
						break;

					case 4: {
						VM::ValAddr addr;
						addr.setVal( ctx->getMem32(btp, boff) );
						tmp = gamos_itoa(ctx->getMem32(addr), 10);
					} break;

					case 5:
						break;
					}

					for (uint i = 0; i < tmp.size(); i++) {
						addSubtitleImage((uint8)tmp[i], sprId, &x, y);
					}
				}
			}
		} else {
			addSubtitleImage(ib, sprId, &x, y);
		}
	}

}

Object *GamosEngine::addSubtitleImage(uint32 frame, int32 spr, int32 *pX, int32 y) {
	Object *gfxObj = getFreeObject();
	gfxObj->flags |= Object::FLAG_GRAPHIC | Object::FLAG_OVERLAY | Object::FLAG_FREECOORDS;
	//gfxObj->frame = 0;
	gfxObj->frameMax = 1;
	gfxObj->priority = _curObject->priority;
	gfxObj->cell.x = -1;
	gfxObj->cell.y = -1;
	gfxObj->actObjIndex = _curObjIndex;
	gfxObj->position.x = *pX;
	gfxObj->position.y = y;
	gfxObj->sprId = spr;
	gfxObj->seqId = 0;
	gfxObj->frame = frame - _sprites[spr].startChar;
	gfxObj->pImg = &_sprites[spr].sequences[gfxObj->seqId]->operator[](gfxObj->frame);

	*pX += gfxObj->pImg->image->surface.w - gfxObj->pImg->xoffset;

	addDirtRectOnObject(gfxObj);
	return gfxObj;
}

bool GamosEngine::updateVMInputFrameStates() {
	if (_pressedKeyCode == ACT_NONE) {
		_vm.memory().setU8(_addrKeyCode, 0);
		_vm.memory().setU8(_addrKeyDown, 0);
	} else {
		_vm.memory().setU8(_addrKeyCode, _pressedKeyCode);
		_vm.memory().setU8(_addrKeyDown, 1);
	}

	if (_vm.memory().getU8(_addrBlk12) != 0)
		return false;

	uint32 frameval = _vm.memory().getU32(_addrCurrentFrame);
	_vm.memory().setU32(_addrCurrentFrame, frameval + 1);

	uint8 fpsval = _vm.memory().getU8(_addrFPS);

	if (fpsval == 0) {
		fpsval = 1;
		_vm.memory().setU8(_addrFPS, 1);
	} else if (fpsval > 50) {
		fpsval = 50;
		_vm.memory().setU8(_addrFPS, 50);
	}

	if (fpsval != _fps) {
		_fps = fpsval;
		setFPS(_fps);
	}

	return true;
}

void GamosEngine::FUN_00407db8(uint8 p) {
	if ((p == ACT2_MOUSEUP_L) || (p == ACT2_MOUSEUP_R))
		_pathTargetCell = _inputMouseActCell;
	else
		_pathTargetCell = _inputActCell;

	_pathStartCell = _curObject->cell;
	_pathDir4 = -1;
	_pathDir8 = -1;
	_pathInMove = false;
}

byte GamosEngine::FUN_00408648(uint8 p1, uint8 p2, uint8 p3) {
	FUN_00407db8(p1);

	if (p1 == ACT2_MOUSEUP_L || p1 == ACT2_MOUSEUP_R) {
		if (p1 != _inputMouseActType)
			return 0;
		if (p2 != 0xff && p2 != _inputMouseActId)
			return 0;
	} else {
		if (p1 != 0xe) {
			if (p3 == 0xff)
				return FUN_004084bc(p2);
			else
				return FUN_00408510(p2);
		}
		if (p2 != 0xff && p2 != _inputActId)
			return 0;
	}

	if (p3 == 0xff)
		return pathFindMoveToTarget();
	else if (p3 == 0xfe)
		return FUN_0040856c();
	else
		return FUN_004085d8(p2);
}

byte GamosEngine::FUN_004084bc(uint8 p) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			const uint8 id = _states.at(i, j).actid;
			if (id == p)
				_pathMap.at(i, j) = PATH_TARGET;
			else
				_pathMap.at(i, j) = PATH_FREE;
		}
	}
	return pathFindCalcMove(true);
}

byte GamosEngine::FUN_00408510(uint8 p) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			const uint8 id = _states.at(i, j).actid;

			if (id == 0xfe)
				_pathMap.at(i, j) = PATH_FREE;
			else if (id == p)
				_pathMap.at(i, j) = PATH_TARGET;
			else
				_pathMap.at(i, j) = PATH_OBSTACLE;
		}
	}
	return pathFindCalcMove(false);
}

byte GamosEngine::FUN_0040856c() {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			uint8 id = _states.at(i, j).actid;

			if (id == 0xfe)
				_pathMap.at(i, j) = PATH_FREE;
			else
				_pathMap.at(i, j) = PATH_OBSTACLE;
		}
	}
	_pathMap.at(_pathTargetCell) = PATH_TARGET;
	return pathFindCalcMove(false);
}

byte GamosEngine::FUN_004085d8(uint8 p) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			uint8 id = _states.at(i, j).actid;

			if (id == p)
				_pathMap.at(i, j) = PATH_FREE;
			else
				_pathMap.at(i, j) = PATH_OBSTACLE;
		}
	}
	_pathMap.at(_pathTargetCell) = PATH_TARGET;
	return pathFindCalcMove(false);
}

byte GamosEngine::pathFindCalcMove(bool faceTarget) {
	_pathMap.at(_pathStartCell.x, _pathStartCell.y) = PATH_STEP1;

	while (true) {
		byte res = pathFindSetNeighbor(PATH_STEP1, PATH_STEP3);
		if (res == 0) // no set
			return 0;
		else if (res == 1) { // target achieve
			if (faceTarget)
				return pathFindMoveToTarget();
			else
				return pathFindTraceMove(PATH_STEP1);
		}

		res = pathFindSetNeighbor(PATH_STEP3, PATH_STEP2);
		if (res == 0) // no set
			return 0;
		else if (res == 1) { // target achieve
			if (faceTarget)
				return pathFindMoveToTarget();
			else
				return pathFindTraceMove(PATH_STEP3);
		}

		res = pathFindSetNeighbor(PATH_STEP2, PATH_STEP1);
		if (res == 0) // no set
			return 0;
		else if (res == 1) { // target achieve
			if (faceTarget)
				return pathFindMoveToTarget();
			else
				return pathFindTraceMove(PATH_STEP2);
		}
	}
}

byte GamosEngine::pathFindMoveToTarget() {
	int32 xdist = _pathStartCell.x - _pathTargetCell.x;
	if (xdist < 1)
		xdist = -xdist;

	int32 ydist = _pathStartCell.y - _pathTargetCell.y;
	if (ydist < 1)
		ydist = -ydist;

	if ((xdist == 0) && (ydist == 0))
		return 0;

	if ((xdist == 0) || (ydist / xdist) > 3) {
		if (ydist > 1) {
			_pathDir8 = PATH_DIR_D;
			if (_pathTargetCell.y <= _pathStartCell.y)
				_pathDir8 = PATH_DIR_U;
		}
		_pathDir4 = PATH_DIR_D;
		if (_pathTargetCell.y <= _pathStartCell.y)
			_pathDir4 = PATH_DIR_U;
	} else if ((ydist == 0) || (xdist / ydist) > 3) {
		if (xdist > 1) {
			_pathDir8 = PATH_DIR_R;
			if (_pathTargetCell.x <= _pathStartCell.x)
				_pathDir8 = PATH_DIR_L;
		}
		_pathDir4 = PATH_DIR_R;
		if (_pathTargetCell.x <= _pathStartCell.x)
			_pathDir4 = PATH_DIR_L;
	} else {
		if (_pathStartCell.x < _pathTargetCell.x) {
			_pathDir8 = PATH_DIR_DR;
			if (_pathTargetCell.y <= _pathStartCell.y)
				_pathDir8 = PATH_DIR_UR;
		} else {
			_pathDir8 = PATH_DIR_DL;
			if (_pathTargetCell.y <= _pathStartCell.y)
				_pathDir8 = PATH_DIR_UL;
		}

		if (ydist < xdist) {
			_pathDir4 = PATH_DIR_R;
			if (_pathTargetCell.x <= _pathStartCell.x)
				_pathDir4 = PATH_DIR_L;
		} else {
			_pathDir4 = PATH_DIR_D;
			if (_pathTargetCell.y <= _pathStartCell.y)
				_pathDir4 = PATH_DIR_U;
		}
	}

	_pathInMove = true;
	return 1;
}

byte GamosEngine::pathFindTraceMove(uint8 p) {
	int32 x = _pathTargetCell.x;
	int32 y = _pathTargetCell.y;
	int32 px = -1;
	int32 py = -1;

	while (true) {
		int32 xdist = _pathStartCell.x - x;
		if (xdist < 1)
			xdist = -xdist;
		int32 ydist = _pathStartCell.y - y;
		if (ydist < 1)
			ydist = -ydist;

		int32 xx = x;
		int32 yy = y;

		if (ydist < xdist) {
			if (x >= 1 && _pathMap.at(x - 1, y) == p) {
				xx = x - 1;
			} else if (x <= (int32)(_pathRight - 1) && _pathMap.at(x + 1, y) == p) {
				xx = x + 1;
			} else if (y >= 1 && _pathMap.at(x, y - 1) == p) {
				yy = y - 1;
			} else if (y <= (int32)(_pathBottom - 1) && _pathMap.at(x, y + 1) == p) {
				yy = y + 1;
			} else {
				return ydist;
			}
		} else {
			if (y >= 1 && _pathMap.at(x, y - 1) == p) {
				yy = y - 1;
			} else if (y <= (int32)(_pathBottom - 1) && _pathMap.at(x, y + 1) == p) {
				yy = y + 1;
			} else if (x >= 1 && _pathMap.at(x - 1, y) == p) {
				xx = x - 1;
			} else if (x <= (int32)(_pathRight - 1) && _pathMap.at(x + 1, y) == p) {
				xx = x + 1;
			} else {
				return ydist;
			}
		}

		if (xx == _pathStartCell.x && yy == _pathStartCell.y) {
			_pathDir4 = PATH_DIR_R;
			if (x <= xx) {
				_pathDir4 = PATH_DIR_L;
				if (x >= xx) {
					_pathDir4 = PATH_DIR_D;
					if (y <= yy)
						_pathDir4 = PATH_DIR_U;
				}
			}
			if (px != -1) {
				if (py > yy) {
					_pathDir8 = PATH_DIR_DR;
					if (px <= xx) {
						_pathDir8 = PATH_DIR_DL;
						if (px >= xx)
							_pathDir8 = PATH_DIR_D;
					}
				} else if (py < yy) {
					_pathDir8 = PATH_DIR_UR;
					if (px <= xx) {
						_pathDir8 = PATH_DIR_UL;
						if (px >= xx)
							_pathDir8 = PATH_DIR_U;
					}
				} else {
					_pathDir8 = PATH_DIR_R;
					if (px <= xx)
						_pathDir8 = PATH_DIR_L;
				}
			}
			_pathInMove = true;
			return 1;
		}

		py = y;
		px = x;

		y = yy;
		x = xx;

		if (p == PATH_STEP3)
			p = PATH_STEP1;
		else if (p == PATH_STEP2)
			p = PATH_STEP3;
		else if (p == PATH_STEP1)
			p = PATH_STEP2;
	}
}

byte GamosEngine::pathFindSetNeighbor(uint8 checkVal, uint8 setVal) {
	uint8 ret = 0;

	for (uint32 y = 0; y < _statesHeight; y++) {
		for (uint32 x = 0; x < _statesWidth; x++) {
			uint8 &rval = _pathMap.at(x, y);
			if (rval == PATH_FREE) {
				if ((x > 0 && _pathMap.at(x - 1, y) == checkVal) ||
				        (x < _pathRight && _pathMap.at(x + 1, y) == checkVal) ||
				        (y > 0 && _pathMap.at(x, y - 1) == checkVal) ||
				        (y < _pathBottom && _pathMap.at(x, y + 1) == checkVal)) {
					ret = setVal;
					rval = setVal;
				}
			} else if (rval == PATH_TARGET) {
				if ((x > 0 && _pathMap.at(x - 1, y) == checkVal) ||
				        (x < _pathRight && _pathMap.at(x + 1, y) == checkVal) ||
				        (y > 0 && _pathMap.at(x, y - 1) == checkVal) ||
				        (y < _pathBottom && _pathMap.at(x, y + 1) == checkVal)) {
					_pathTargetCell.x = x;
					_pathTargetCell.y = y;
					return 1;
				}
			}
		}
	}
	return ret;
}

byte GamosEngine::FUN_004088cc(uint8 p1, uint8 p2, uint8 p3) {
	FUN_00407db8(p1);

	if (p1 == ACT2_MOUSEUP_L || p1 == ACT2_MOUSEUP_R) {
		if (p1 != _inputMouseActType)
			return 0;

		if ( (_thing2[p2].masks[ _inputMouseActId >> 3 ] & (1 << (_inputMouseActId & 7))) == 0 )
			return 0;
	} else {
		if (p1 != 0xe) {
			if (p3 == 0xff)
				return FUN_004086e4(_thing2[p2].masks);
			else
				return FUN_00408778(_thing2[p2].masks);
		}

		if ( (_thing2[p2].masks[ _inputActId >> 3 ] & (1 << (_inputActId & 7))) == 0 )
			return 0;
	}

	if (p3 == 0xff)
		return pathFindMoveToTarget();
	else if (p3 == 0xfe)
		return FUN_0040881c(_thing2[p2].masks);
	else
		return FUN_0040856c();
}

byte GamosEngine::FUN_004086e4(const Common::Array<byte> &arr) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			const uint8 id = _states.at(i, j).actid;

			if ( ((arr[id >> 3]) & (1 << (id & 7))) == 0 )
				_pathMap.at(i, j) = PATH_FREE;
			else
				_pathMap.at(i, j) = PATH_TARGET;
		}
	}
	return pathFindCalcMove(true);
}

byte GamosEngine::FUN_00408778(const Common::Array<byte> &arr) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			const uint8 id = _states.at(i, j).actid;

			if ( ((arr[id >> 3]) & (1 << (id & 7))) == 0 )
				_pathMap.at(i, j) = PATH_OBSTACLE;
			else
				_pathMap.at(i, j) = PATH_TARGET;
		}
	}
	return pathFindCalcMove(false);
}

byte GamosEngine::FUN_0040881c(const Common::Array<byte> &arr) {
	for (uint j = 0; j < _statesHeight; j++) {
		for (uint i = 0; i < _statesWidth; i++) {
			const uint8 id = _states.at(i, j).actid;

			if ( ((arr[id >> 3]) & (1 << (id & 7))) == 0 )
				_pathMap.at(i, j) = PATH_OBSTACLE;
			else
				_pathMap.at(i, j) = PATH_FREE;
		}
	}
	_pathMap.at(_pathTargetCell) = PATH_TARGET;
	return pathFindCalcMove(false);
}




void Actions::parse(const byte *data, uint32 dataSize) {
	Common::MemoryReadStream rstream(data, dataSize);

	/* clean first */
	act_2.clear();
	act_10.clear();
	act_10end[0].clear();
	act_10end[1].clear();
	act_10end[2].clear();

	/* start parsing */
	flags = rstream.readByte();

	uint8 tmp = rstream.readByte();
	act_4.actid = 0;
	act_4.flags = 0;
	act_4.t = tmp >> 4;
	act_4.x = rstream.readSByte();
	act_4.y = rstream.readSByte();

	num_act_10e = tmp & 0x3;

	if (flags & HAS_CONDITION)
		rstream.skip(4);

	if (flags & HAS_ACT2) {
		act_2.reserve(4);

		while (true) {
			act_2.emplace_back();
			ActTypeEntry &entrie = act_2.back();

			uint16 num = rstream.readUint16LE();
			uint8 bits = rstream.readByte();

			entrie.t = rstream.readByte();
			entrie.entries.resize(num);

			for (uint16 i = 0; i < num; i++) {
				ActEntry &a = entrie.entries[i];
				a.actid = rstream.readByte();
				tmp = rstream.readByte();
				a.flags = tmp & 0xf;
				a.t = tmp >> 4;
				a.x = rstream.readSByte();
				a.y = rstream.readSByte();
			}

			if (bits & 1)
				break;
		}
	}

	if (flags & HAS_FUNCTION)
		rstream.skip(4);

	if (flags & HAS_ACT10) {
		act_10.reserve(4);

		while (true) {
			act_10.emplace_back();
			ActTypeEntry &entrie = act_10.back();

			uint16 num = rstream.readUint16LE();
			uint8 f = rstream.readByte();

			entrie.t = rstream.readByte();

			if (entrie.t == 0) {
				for (int j = 0; j < num_act_10e; j++) {
					act_10end[j].resize(num);
					for (uint16 i = 0; i < num; i++) {
						ActEntry &a = act_10end[j][i];
						a.actid = rstream.readByte();
						tmp = rstream.readByte();
						a.flags = tmp & 0xf;
						a.t = tmp >> 4;
						a.x = rstream.readSByte();
						a.y = rstream.readSByte();
					}

					if (num_act_10e - j > 1) {
						num = rstream.readUint16LE();
						rstream.skip(2);
					}
				}
				break;
			}

			entrie.entries.resize(num);

			for (uint16 i = 0; i < num; i++) {
				ActEntry &a = entrie.entries[i];
				a.actid = rstream.readByte();
				tmp = rstream.readByte();
				a.flags = tmp & 0xf;
				a.t = tmp >> 4;
				a.x = rstream.readSByte();
				a.y = rstream.readSByte();
			}

			if (f & 1)
				break;
		}
	}
}

void GamosEngine::runRenewStaticGfxCurObj(uint8 val, bool rnd) {
	removeStaticGfxCurObj();

	if (rnd)
		val = _thing2[val].oids[ rndRange16(_thing2[val].oids.size()) ];

	_curObject->state = ObjState(val, 0, 1);

	ObjectAction &act = _objectActions[val];
	executeScript(act.onCreateAddress, &act, nullptr, -1, nullptr, _curObject->cell, 1);
}

void GamosEngine::removeStaticGfxCurObj() {
	if (_curObject->state.actid != 0xfe) {
		ObjectAction &act = _objectActions[_curObject->state.actid];

		for (uint i = 0; i < _objects.size(); i++) {
			Object &obj = _objects[i];
			if (obj.isStaticObject() &&
			        obj.actObjIndex == -1 &&
			        obj.cell.x == _curObject->cell.x &&
			        obj.cell.y == _curObject->cell.y) {

				removeObjectMarkDirty(&obj);
				break;
			}
		}

		executeScript(act.onDeleteAddress, &act, nullptr, -1, nullptr, _curObject->cell, _curObject->state.t);
		_curObject->state = ObjState(0xfe, 0, 0xf);
	}
}


bool GamosEngine::updateMouseCursor(Common::Point mouseMove) {
	if (_mouseCursorImgId >= 0 && _drawCursor == 0 && _mouseCursorImgId < (int32)_sprites.size()) {
		Sprite &cursorSpr = _sprites[_mouseCursorImgId];

		if (cursorSpr.frameCount > 1) {

			_cursorFrame++;
			if (_cursorFrame >= cursorSpr.frameCount)
				_cursorFrame = 0;

			ImagePos &impos = cursorSpr.sequences[0]->operator[](_cursorFrame);
			Graphics::Surface &surf = impos.image->surface;
			CursorMan.replaceCursor(surf, -impos.xoffset, -impos.yoffset, 0);
			CursorMan.disableCursorPalette(true);
		} else {
			if (_currentCursor != _mouseCursorImgId) {
				ImagePos &impos = cursorSpr.sequences[0]->operator[](0);
				Graphics::Surface &surf = impos.image->surface;
				CursorMan.replaceCursor(surf, -impos.xoffset, -impos.yoffset, 0);
				CursorMan.disableCursorPalette(true);
			}
		}
	} else {
		if (_currentCursor != _mouseCursorImgId)
			CursorMan.setDefaultArrowCursor();
	}

	_currentCursor = _mouseCursorImgId;
	return true;
}

bool GamosEngine::scrollAndDraw() {
	if (_scrollTrackObj != -1) {
		Object &obj = _objects[_scrollTrackObj];
		Common::Point objPos(obj.cell.x * _gridCellW, obj.cell.y * _gridCellH);

		Common::Rect objArea;
		objArea.right = _scrollX + _width - (_scrollBorderR + 1) * _gridCellW;
		objArea.bottom = _scrollY + _height - (_scrollBorderB + 1) * _gridCellH;
		objArea.left = _scrollX + _scrollBorderL * _gridCellW;
		objArea.top = _scrollY + _scrollBorderU * _gridCellH;

		int32 lDistance = 0;
		int32 rDistance = 0;
		int32 uDistance = 0;
		int32 dDistance = 0;

		if (objPos.x < objArea.left) {
            lDistance = objArea.left - objPos.x;
            if (lDistance > _scrollX)
                lDistance = _scrollX;
        } else if (objPos.x > objArea.right) {
            int32 maxR = _bkgSize.x - _width - _scrollX;
            rDistance = objPos.x - objArea.right;
            if (rDistance > maxR)
                rDistance = maxR;
        }

        if (objPos.y < objArea.top) {
            uDistance = objArea.top - objPos.y;
            if (uDistance > _scrollY)
                uDistance = _scrollY;
        } else if (objPos.y > objArea.bottom) {
            int32 maxD = _bkgSize.y - _height - _scrollY;
            dDistance = objPos.y - objArea.bottom;
            if (dDistance > maxD)
                dDistance = maxD;
        }

		if (lDistance != 0 || rDistance != 0 || uDistance != 0 || dDistance != 0) {
			int32 lSpeed = _scrollSpeed;
			int32 rSpeed = _scrollSpeed;
			int32 uSpeed = _scrollSpeed;
			int32 dSpeed = _scrollSpeed;
			while (lDistance != 0 || rDistance != 0 || uDistance != 0 || dDistance != 0) {
				int32 lDelta = (lDistance < lSpeed) ? lDistance : lSpeed;
				int32 rDelta = (rDistance < rSpeed) ? rDistance : rSpeed;
				int32 uDelta = (uDistance < uSpeed) ? uDistance : uSpeed;
				int32 dDelta = (dDistance < dSpeed) ? dDistance : dSpeed;

				_scrollX += rDelta - lDelta;
				_scrollY += dDelta - uDelta;

				eventsSkip();
				doDraw();

				lDistance -= lDelta;
				if (lDistance != 0 && lDistance <= _scrollCutoff) {
					lSpeed += _scrollSpeedReduce;
					if (lSpeed < 2)
						lSpeed = 1;
				}

				rDistance -= rDelta;
				if (rDistance != 0 && rDistance <= _scrollCutoff) {
					rSpeed += _scrollSpeedReduce;
					if (rSpeed < 2)
						rSpeed = 1;
				}

				uDistance -= uDelta;
				if (uDistance != 0 && uDistance <= _scrollCutoff) {
					uSpeed += _scrollSpeedReduce;
					if (uSpeed < 2)
						uSpeed = 1;
				}

				dDistance -= dDelta;
				if (dDistance != 0 && dDistance <= _scrollCutoff) {
					dSpeed += _scrollSpeedReduce;
					if (dSpeed < 2)
						dSpeed = 1;
				}

				_system->delayMillis(1000 / 15); // 15fps
			}
		}
	}

	doDraw();

	return true;
}

Common::String GamosEngine::gamos_itoa(int n, uint radix) {
	Common::String tmp;
	bool minus = false;
	uint un = n;
	if (radix == 10 && n < 0) {
		un = -n;
		minus = true;
	}

	if (un == 0) {
		tmp += '0';
	} else {
		while (un != 0) {
			uint r = un % radix;
			un /= radix;
			if (r > 9)
				tmp += 'A' + r - 10;
			else
				tmp += '0' + r;
		}
	}
	if (minus)
		tmp += '-';

	for (int i = 0, j = tmp.size() - 1; i < j; i++, j--) {
		char c = tmp[i];
		tmp.setChar(tmp[j], i);
		tmp.setChar(c, j);
	}
	return tmp;
}


bool GamosEngine::FUN_0040705c(int a, int b) {
	static const int arr[8] = {PATH_DIR_U, PATH_DIR_UL, PATH_DIR_L, PATH_DIR_DL, PATH_DIR_D, PATH_DIR_DR, PATH_DIR_R, PATH_DIR_UR};
	int v = DAT_004173ec;
	if (v > 3) {
		v -= 4;
		a = arr[a];
	}

	return ((a + v * 2) & 7) == b;
}

int GamosEngine::txtInputBegin(VM::Context *ctx, byte memtype, int32 offset, int sprId, int32 x, int32 y) {
	if (memtype != VM::REF_EDI) {
		error("Unsupported memtype");
		return 0;
	}

	if (_txtInputActive == false) {
		removeSubtitles(_curObject);
		_curObject->state.flags |= 2;
		_txtInputVmOffset = offset;
		_txtInputSpriteID = sprId;
		_txtInputX = x;
		_txtInputY = y;
		_txtInputObject = _curObject;
		_txtInputAction = _curAction;
		_txtInputObjectIndex = _curObjIndex;

		txtInputProcess(0);
		return 1;
	}
	return 0;
}

void GamosEngine::txtInputProcess(uint8 c) {
	_curObject = _txtInputObject;
	_curAction = _txtInputAction;
	_curObjIndex = _txtInputObjectIndex;

	Sprite &spr = _sprites[_txtInputSpriteID];

	uint8 ib = c;

	while (true) {
		if (ib == 0) {
			if (_txtInputActive) {
				_txtInputActive = false;
				removeSubtitles(_curObject);
				return;
			}
			_txtInputActive = true;
			_txtInputTyped = false;
			ib = _vm.memory().getU8(_txtInputVmOffset);
			_txtInputVmOffset++;
			continue;
		} else if (ib == KeyCodes::WIN_BACK) {
			if (_txtInputTyped) {
				if (_txtInputLength)
					txtInputEraseBack(1);
				return;
			}
		} else if (ib == KeyCodes::WIN_RETURN) {
			if (_txtInputTyped) {
				_txtInputBuffer[_txtInputLength] = 0;
				switch (_txtInputFlags & 7) {
					case 0:
						_txtInputVMAccess.setU8( atoi((char *)_txtInputBuffer) );
						break;

					case 1: {
						VmTxtFmtAccess adr(_vm);
						adr.setVal( _txtInputVMAccess.getU32() );
						adr.write(_txtInputBuffer, _txtInputLength + 1);
					} break;

					case 2:
						_txtInputVMAccess.write(_txtInputBuffer, _txtInputLength + 1);
						break;

					case 3:
						_txtInputVMAccess.setU32( atoi((char *)_txtInputBuffer) );
						break;

					case 4: {
						VmTxtFmtAccess adr(_vm);
						adr.setVal( _txtInputVMAccess.getU32() );
						adr.setU32( atoi((char *)_txtInputBuffer) );
					} break;
				}

				_txtInputTyped = false;
				ib = _vm.memory().getU8(_txtInputVmOffset);
				_txtInputVmOffset++;
				continue;
			}
		} else if (ib == 0xf) {
			_txtInputFlags = _vm.memory().getU8(_txtInputVmOffset);
			_txtInputVmOffset++;
			_txtInputMaxLength = _vm.memory().getU8(_txtInputVmOffset);
			_txtInputVmOffset++;

			if ((_txtInputFlags & 0x70) == 0 || (_txtInputFlags & 0x70) == 0x10) {
				_txtInputVMAccess.setMemType(VM::REF_EDI);
				if ((_txtInputFlags & 0x70) == 0x10) {
					_txtInputVMAccess.setMemType(VM::REF_EBX);
					_txtInputVMAccess.objMem = _curObjStorage;
				}
				if ( (_txtInputFlags & 0x80) == 0 ) {
					_txtInputVMAccess.setOffset( _vm.memory().getU8(_txtInputVmOffset) );
					_txtInputVmOffset++;
				} else {
					_txtInputVMAccess.setOffset( _vm.memory().getU32(_txtInputVmOffset) );
					_txtInputVmOffset += 4;
				}
				switch (_txtInputFlags & 7) {
					case 0:
					case 3:
					case 4:
						_txtInputIsNumber = true;
						break;

					case 1:
					case 2:
						_txtInputIsNumber = false;
						break;
				}

				_txtInputLength = 0;
				_txtInputTyped = true;
				return;
			}
		} else if (ib == KeyCodes::WIN_ESCAPE) {
			if (_txtInputTyped) {
				if (_txtInputLength != 0) {
                    txtInputEraseBack(_txtInputLength);
                    return;
                }

				if (_txtInputActive) {
					_txtInputActive = false;
					removeSubtitles(_curObject);
					return;
				}
				_txtInputActive = true;
				_txtInputTyped = false;
				ib = _vm.memory().getU8(_txtInputVmOffset);
				_txtInputVmOffset++;
				continue;
			}
		}

		if (_txtInputTyped) {
			if (_txtInputLength < _txtInputMaxLength) {
				if (ib < spr.startChar)
					ib = tolower(ib);
				if (ib > spr.lastChar)
					ib = toupper(ib);
				if (ib >= spr.startChar && ib <= spr.lastChar &&
				   (_txtInputIsNumber == false || Common::isDigit(ib))) {
					_txtInputBuffer[_txtInputLength] = ib;
					_txtInputObjects[_txtInputLength] = addSubtitleImage(ib, _txtInputSpriteID, &_txtInputX, _txtInputY);
					_txtInputLength++;
				}
			}
			return;
		} else {
			addSubtitleImage(ib, _txtInputSpriteID, &_txtInputX, _txtInputY);
			ib = _vm.memory().getU8(_txtInputVmOffset);
			_txtInputVmOffset++;
		}
	}
}

void GamosEngine::txtInputEraseBack(int n) {
	for (int32 i = _txtInputLength - 1; i >= 0 && n > 0; i--, n--) {
		ImagePos *ips = _txtInputObjects[i]->pImg;
		_txtInputX -= ips->image->surface.w - ips->xoffset;
		removeObjectMarkDirty(_txtInputObjects[i]);

		_txtInputLength--;
	}
}

bool GamosEngine::onTxtInputUpdate(uint8 c) {
	for(uint i = 0; i < _objects.size(); i++) {
		Object &obj = _objects[i];
		if ((obj.flags & (Object::FLAG_GRAPHIC | Object::FLAG_VALID | Object::FLAG_HASACTION | Object::FLAG_TRANSITION)) == (Object::FLAG_GRAPHIC | Object::FLAG_VALID)) {
			if ((obj.frame + 1 == obj.frameMax) && obj.actObjIndex != -1) {
				obj.cell = _objects[ obj.actObjIndex ].cell;
			}
			updateGfxFrames(&obj, false, false);
		}
	}

	if (_pressedKeyCode != KeyCodes::WIN_SPACE && _pressedKeyCode != KeyCodes::WIN_RETURN &&
		(_pressedKeyCode == ACT_NONE || c != ACT_NONE) )
		return true;

	txtInputProcess(_pressedKeyCode);
	return true;
}

bool GamosEngine::eventsSkip(bool breakOnInput) {
	bool brk = false;
	Common::Event e;
	while (_system->getEventManager()->pollEvent(e)) {
		if (breakOnInput) {
			if (e.type == Common::EVENT_LBUTTONUP ||
				e.type == Common::EVENT_RBUTTONUP ||
				e.type == Common::EVENT_KEYUP ||
				e.type == Common::EVENT_QUIT)
				brk = true;
		}
	}

	return shouldQuit() || brk;
}


void GamosEngine::dumpActions() {
	Common::String t = Common::String::format("./dumps/actions_%d.txt", _currentModuleID);

	Common::DumpFile f;

	if (!f.open(t.c_str(), true))
		error("Cannot create actions dump file");

	int i = 0;
	for (ObjectAction &act : _objectActions) {
		f.writeString(Common::String::format("Act %d : actType %x mask %x priority %x storage size %x\n", i, act.actType, act.mask, act.priority, act.storageSize));
		if (act.onCreateAddress != -1) {
			t = _vm.disassembly(act.onCreateAddress);
			f.writeString(Common::String::format("Script1 : \n%s\n", t.c_str()));
		}

		if (act.onDeleteAddress != -1) {
			t = _vm.disassembly(act.onDeleteAddress);
			f.writeString(Common::String::format("Script2 : \n%s\n", t.c_str()));
		}

		int j = 0;
		for (Actions &sc : act.actions) {
			f.writeString(Common::String::format("subscript %d : \n", j));

			if (sc.conditionAddress != -1) {
				t = _vm.disassembly(sc.conditionAddress);
				f.writeString(Common::String::format("condition : \n%s\n", t.c_str()));
			}

			if (sc.functionAddress != -1) {
				t = _vm.disassembly(sc.functionAddress);
				f.writeString(Common::String::format("action : \n%s\n", t.c_str()));
			}

			j++;
		}


		f.writeString("\n\n#############################################\n\n");

		i++;
	}

	i = 0;
	for (const Actions &act : _subtitleActions) {
		if (act.flags & Actions::HAS_CONDITION) {
			t = _vm.disassembly(act.conditionAddress);
			f.writeString(Common::String::format("SubAct %d condition : \n%s\n", i, t.c_str()));
		}

		if (act.flags & Actions::HAS_FUNCTION) {
			t = _vm.disassembly(act.functionAddress);
			f.writeString(Common::String::format("SubAct %d action : \n%s\n", i, t.c_str()));
		}

		i++;
	}

	f.flush();
	f.close();

	warning("Actions saved into actions_%d.txt", _currentModuleID);
}

} // End of namespace Gamos
