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

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

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

#include "base/version.h"

#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"
#include "agi/opcodes.h"
#include "agi/menu.h"

#include "common/random.h"
#include "common/textconsole.h"

namespace Agi {

#define p0	(p[0])
#define p1	(p[1])
#define p2	(p[2])
#define p3	(p[3])
#define p4	(p[4])
#define p5	(p[5])
#define p6	(p[6])

#define code state->_curLogic->data
#define ip	state->_curLogic->cIP
#define vt	state->viewTable[p0]
#define vt_v state->viewTable[state->vars[p0]]

#define _v state->vars

#define getGameID() state->_vm->getGameID()
#define getFeatures() state->_vm->getFeatures()
#define getVersion() state->_vm->getVersion()
#define getLanguage() state->_vm->getLanguage()
#define setflag(a,b) state->_vm->setflag(a,b)
#define getflag(a) state->_vm->getflag(a)

void cmdIncrement(AgiGame *state, uint8 *p) {
	if (getVersion() < 0x2000) {
		if (_v[p0] < 0xf0)
			++_v[p0];
	} else {
		if (_v[p0] != 0xff)
			++_v[p0];
	}
}

void cmdDecrement(AgiGame *state, uint8 *p) {
	if (_v[p0] != 0)
		--_v[p0];
}

void cmdAssignN(AgiGame *state, uint8 *p) {
	_v[p0] = p1;

	// WORKAROUND for a bug in fan game "Get outta SQ"
	// Total number of points is stored in variable 7, which
	// is then incorrectly assigned to 0. Thus, when the game
	// is restarted, "Points 0 of 0" is shown. We set the
	// variable to the correct value here
	// Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score
	// is lost on restart"
	if (getGameID() == GID_GETOUTTASQ && p0 == 7)
		_v[p0] = 8;
}

void cmdAddN(AgiGame *state, uint8 *p) {
	_v[p0] += p1;
}

void cmdSubN(AgiGame *state, uint8 *p) {
	_v[p0] -= p1;
}

void cmdAssignV(AgiGame *state, uint8 *p) {
	_v[p0] = _v[p1];
}

void cmdAddV(AgiGame *state, uint8 *p) {
	_v[p0] += _v[p1];
}

void cmdSubV(AgiGame *state, uint8 *p) {
	_v[p0] -= _v[p1];
}

void cmdMulN(AgiGame *state, uint8 *p) {
	_v[p0] *= p1;
}

void cmdMulV(AgiGame *state, uint8 *p) {
	_v[p0] *= _v[p1];
}

void cmdDivN(AgiGame *state, uint8 *p) {
	_v[p0] /= p1;
}

void cmdDivV(AgiGame *state, uint8 *p) {
	_v[p0] /= _v[p1];
}

void cmdRandomV1(AgiGame *state, uint8 *p) {
	_v[p0] = state->_vm->_rnd->getRandomNumber(250);
}

void cmdRandom(AgiGame *state, uint8 *p) {
	_v[p2] = state->_vm->_rnd->getRandomNumber(p1 - p0) + p0;
}

void cmdLindirectN(AgiGame *state, uint8 *p) {
	_v[_v[p0]] = p1;
}

void cmdLindirectV(AgiGame *state, uint8 *p) {
	_v[_v[p0]] = _v[p1];
}

void cmdRindirect(AgiGame *state, uint8 *p) {
	_v[p0] = _v[_v[p1]];
}

void cmdSet(AgiGame *state, uint8 *p) {
	setflag(*p, true);
}

void cmdReset(AgiGame *state, uint8 *p) {
	setflag(*p, false);
}

void cmdToggle(AgiGame *state, uint8 *p) {
	setflag(*p, !getflag(*p));
}

void cmdSetV(AgiGame *state, uint8 *p) {
	if (getVersion() < 0x2000) {
		_v[p0] = 1;
	} else {
		setflag(_v[p0], true);
	}
}

void cmdResetV(AgiGame *state, uint8 *p) {
	if (getVersion() < 0x2000) {
		_v[p0] = 0;
	} else {
		setflag(_v[p0], false);
	}
}

void cmdToggleV(AgiGame *state, uint8 *p) {
	if (getVersion() < 0x2000) {
		_v[p0] ^= 1;
	} else {
		setflag(_v[p0], !getflag(_v[p0]));
	}
}

void cmdNewRoom(AgiGame *state, uint8 *p) {
	state->_vm->newRoom(p0);

	// WORKAROUND: Works around intro skipping bug (#1737343) in Gold Rush.
	// Intro was skipped because the enter-keypress finalizing the entering
	// of the copy protection string (Copy protection is in logic.128) was
	// left over to the intro scene (Starts with room 73 i.e. logic.073).
	// The intro scene checks for any keys pressed and if it finds any it
	// jumps to the game's start (Room 1 i.e. logic.001). We clear the
	// keyboard buffer when the intro sequence's first room (Room 73) is
	// loaded so that no keys from the copy protection scene can be left
	// over to cause the intro to skip to the game's start.
	if (getGameID() == GID_GOLDRUSH && p0 == 73)
		state->keypress = 0;
}

void cmdNewRoomF(AgiGame *state, uint8 *p) {
	state->_vm->newRoom(_v[p0]);
}

void cmdLoadView(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rVIEW, p0);
}

void cmdLoadLogic(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rLOGIC, p0);
}

void cmdLoadSound(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rSOUND, p0);
}

void cmdLoadViewF(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rVIEW, _v[p0]);
}

void cmdLoadLogicF(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rLOGIC, _v[p0]);
}

void cmdDiscardView(AgiGame *state, uint8 *p) {
	state->_vm->agiUnloadResource(rVIEW, p0);
}

void cmdObjectOnAnything(AgiGame *state, uint8 *p) {
	vt.flags &= ~(fOnWater | fOnLand);
}

void cmdObjectOnLand(AgiGame *state, uint8 *p) {
	vt.flags |= fOnLand;
}

void cmdObjectOnWater(AgiGame *state, uint8 *p) {
	vt.flags |= fOnWater;
}

void cmdObserveHorizon(AgiGame *state, uint8 *p) {
	vt.flags &= ~fIgnoreHorizon;
}

void cmdIgnoreHorizon(AgiGame *state, uint8 *p) {
	vt.flags |= fIgnoreHorizon;
}

void cmdObserveObjs(AgiGame *state, uint8 *p) {
	vt.flags &= ~fIgnoreObjects;
}

void cmdIgnoreObjs(AgiGame *state, uint8 *p) {
	vt.flags |= fIgnoreObjects;
}

void cmdObserveBlocks(AgiGame *state, uint8 *p) {
	vt.flags &= ~fIgnoreBlocks;
}

void cmdIgnoreBlocks(AgiGame *state, uint8 *p) {
	vt.flags |= fIgnoreBlocks;
}

void cmdSetHorizon(AgiGame *state, uint8 *p) {
	state->horizon = p0;
}

void cmdGetPriority(AgiGame *state, uint8 *p) {
	_v[p1] = vt.priority;
}

void cmdSetPriority(AgiGame *state, uint8 *p) {
	vt.flags |= fFixedPriority;
	vt.priority = p1;

	// WORKAROUND: this fixes bug #1712585 in KQ4 (dwarf sprite priority)
	// For this scene, ego (Rosella) hasn't got a fixed priority till script 54
	// explicitly sets priority 8 for her, so that she can walk back to the table
	// without being drawn over the other dwarfs
	// It seems that in this scene, ego's priority is set to 8, but the priority of
	// the last dwarf with the soup bowls (view 152) is also set to 8, which causes
	// the dwarf to be drawn behind ego
	// With this workaround, when the game scripts set the priority of view 152
	// (seventh dwarf with soup bowls), ego's priority is set to 7
	// The game script itself sets priotity 8 for ego before she starts walking,
	// and then releases the fixed priority set on ego after ego is seated
	// Therefore, this workaround only affects that specific part of this scene
	// Ego is set to object 19 by script 54
	if (getGameID() == GID_KQ4 && vt.currentView == 152) {
		state->viewTable[19].flags |= fFixedPriority;
		state->viewTable[19].priority = 7;
	}
}

void cmdSetPriorityF(AgiGame *state, uint8 *p) {
	vt.flags |= fFixedPriority;
	vt.priority = _v[p1];
}

void cmdReleasePriority(AgiGame *state, uint8 *p) {
	vt.flags &= ~fFixedPriority;
}

void cmdSetUpperLeft(AgiGame *state, uint8 *p) {				// do nothing (AGI 2.917)
}

void cmdStartUpdate(AgiGame *state, uint8 *p) {
	state->_vm->startUpdate(&vt);
}

void cmdStopUpdate(AgiGame *state, uint8 *p) {
	state->_vm->stopUpdate(&vt);
}

void cmdCurrentView(AgiGame *state, uint8 *p) {
	_v[p1] = vt.currentView;
}

void cmdCurrentCel(AgiGame *state, uint8 *p) {
	_v[p1] = vt.currentCel;
	debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]);
}

void cmdCurrentLoop(AgiGame *state, uint8 *p) {
	_v[p1] = vt.currentLoop;
}

void cmdLastCel(AgiGame *state, uint8 *p) {
	_v[p1] = vt.loopData->numCels - 1;
}

void cmdSetCel(AgiGame *state, uint8 *p) {
	state->_vm->setCel(&vt, p1);

	if (getVersion() >= 0x2000) {
		vt.flags &= ~fDontupdate;
	}
}

void cmdSetCelF(AgiGame *state, uint8 *p) {
	state->_vm->setCel(&vt, _v[p1]);
	vt.flags &= ~fDontupdate;
}

void cmdSetView(AgiGame *state, uint8 *p) {
	state->_vm->setView(&vt, p1);
}

void cmdSetViewF(AgiGame *state, uint8 *p) {
	state->_vm->setView(&vt, _v[p1]);
}

void cmdSetLoop(AgiGame *state, uint8 *p) {
	state->_vm->setLoop(&vt, p1);
}

void cmdSetLoopF(AgiGame *state, uint8 *p) {
	state->_vm->setLoop(&vt, _v[p1]);
}

void cmdNumberOfLoops(AgiGame *state, uint8 *p) {
	_v[p1] = vt.numLoops;
}

void cmdFixLoop(AgiGame *state, uint8 *p) {
	vt.flags |= fFixLoop;
}

void cmdReleaseLoop(AgiGame *state, uint8 *p) {
	vt.flags &= ~fFixLoop;
}

void cmdStepSize(AgiGame *state, uint8 *p) {
	vt.stepSize = _v[p1];
}

void cmdStepTime(AgiGame *state, uint8 *p) {
	vt.stepTime = vt.stepTimeCount = _v[p1];
}

void cmdCycleTime(AgiGame *state, uint8 *p) {
	vt.cycleTime = vt.cycleTimeCount = _v[p1];
}

void cmdStopCycling(AgiGame *state, uint8 *p) {
	vt.flags &= ~fCycling;
}

void cmdStartCycling(AgiGame *state, uint8 *p) {
	vt.flags |= fCycling;
}

void cmdNormalCycle(AgiGame *state, uint8 *p) {
	vt.cycle = kCycleNormal;
	vt.flags |= fCycling;
}

void cmdReverseCycle(AgiGame *state, uint8 *p) {
	vt.cycle = kCycleReverse;
	vt.flags |= fCycling;
}

void cmdSetDir(AgiGame *state, uint8 *p) {
	vt.direction = _v[p1];
}

void cmdGetDir(AgiGame *state, uint8 *p) {
	_v[p1] = vt.direction;
}

void cmdGetRoomF(AgiGame *state, uint8 *p) {
	_v[p1] = state->_vm->objectGetLocation(_v[p0]);
}

void cmdPut(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(p0, _v[p1]);
}

void cmdPutF(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(_v[p0], _v[p1]);
}

void cmdDrop(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(p0, 0);
}

void cmdGet(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(p0, EGO_OWNED);
}

void cmdGetV1(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(p0, EGO_OWNED_V1);
}

void cmdGetF(AgiGame *state, uint8 *p) {
	state->_vm->objectSetLocation(_v[p0], EGO_OWNED);
}

void cmdWordToString(AgiGame *state, uint8 *p) {
	strcpy(state->strings[p0], state->egoWords[p1].word);
}

void cmdOpenDialogue(AgiGame *state, uint8 *p) {
	state->hasWindow = true;
}

void cmdCloseDialogue(AgiGame *state, uint8 *p) {
	state->hasWindow = false;
}

void cmdCloseWindow(AgiGame *state, uint8 *p) {
	state->_vm->closeWindow();
}

void cmdStatusLineOn(AgiGame *state, uint8 *p) {
	state->statusLine = true;
	state->_vm->writeStatus();
}

void cmdStatusLineOff(AgiGame *state, uint8 *p) {
	state->statusLine = false;
	state->_vm->writeStatus();
}

void cmdShowObj(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->showObj(p0);
}

void cmdShowObjV(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->showObj(_v[p0]);
}

void cmdSound(AgiGame *state, uint8 *p) {
	state->_vm->_sound->startSound(p0, p1);
}

void cmdStopSound(AgiGame *state, uint8 *p) {
	state->_vm->_sound->stopSound();
}

void cmdMenuInput(AgiGame *state, uint8 *p) {
	state->_vm->newInputMode(INPUT_MENU);
}

void cmdEnableItem(AgiGame *state, uint8 *p) {
	state->_vm->_menu->setItem(p0, true);
}

void cmdDisableItem(AgiGame *state, uint8 *p) {
	state->_vm->_menu->setItem(p0, false);
}

void cmdSubmitMenu(AgiGame *state, uint8 *p) {
	state->_vm->_menu->submit();
}

void cmdSetScanStart(AgiGame *state, uint8 *p) {
	state->_curLogic->sIP = state->_curLogic->cIP;
}

void cmdResetScanStart(AgiGame *state, uint8 *p) {
	state->_curLogic->sIP = 2;
}

void cmdSaveGame(AgiGame *state, uint8 *p) {
	state->simpleSave ? state->_vm->saveGameSimple() : state->_vm->saveGameDialog();
}

void cmdLoadGame(AgiGame *state, uint8 *p) {
	assert(1);
	state->simpleSave ? state->_vm->loadGameSimple() : state->_vm->loadGameDialog();
}

void cmdInitDisk(AgiGame *state, uint8 *p) {				// do nothing
}

void cmdLog(AgiGame *state, uint8 *p) {				// do nothing
}

void cmdTraceOn(AgiGame *state, uint8 *p) {				// do nothing
}

void cmdTraceInfo(AgiGame *state, uint8 *p) {				// do nothing
}

void cmdShowMem(AgiGame *state, uint8 *p) {
	state->_vm->messageBox("Enough memory");
}

void cmdInitJoy(AgiGame *state, uint8 *p) { // do nothing
}

void cmdScriptSize(AgiGame *state, uint8 *p) {
	debug(0, "script.size(%d)", p0);
}

void cmdCancelLine(AgiGame *state, uint8 *p) {
	state->inputBuffer[0] = 0;
	state->cursorPos = 0;
	state->_vm->writePrompt();
}

// This implementation is based on observations of Amiga's Gold Rush.
// You can try this out (in the original and in ScummVM) by writing "bird man"
// to enter Gold Rush's debug mode and then writing "show position" or "sp".
// TODO: Make the cycle and motion status lines more like in Amiga's Gold Rush.
// TODO: Add control status line (After stepsize, before cycle status).
// Don't know what the control status means yet, possibly flags?
// Examples of the control-value (Taken in the first screen i.e. room 1):
// 4051 (When ego is stationary),
// 471 (When walking on the first screen's bridge),
// 71 (When walking around, using the mouse or the keyboard).
void cmdObjStatusF(AgiGame *state, uint8 *p) {
	const char *cycleDesc;  // Object's cycle description line
	const char *motionDesc; // Object's motion description line
	char msg[256];          // The whole object status message

	// Generate cycle description line
	switch (vt_v.cycle) {
	case kCycleNormal:
		cycleDesc = "normal cycle";
		break;
	case kCycleEndOfLoop:
		cycleDesc = "end of loop";
		break;
	case kCycleRevLoop:
		cycleDesc = "reverse loop";
		break;
	case kCycleReverse:
		cycleDesc = "reverse cycle";
		break;
	default:
		cycleDesc = "unknown cycle type";
		break;
	}

	// Generate motion description line
	switch (vt_v.motion) {
	case kMotionNormal:
		motionDesc = "normal motion";
		break;
	case kMotionWander:
		motionDesc = "wandering";
		break;
	case kMotionFollowEgo:
		motionDesc = "following ego";
		break;
	case kMotionMoveObj:
		// Amiga's Gold Rush! most probably uses "move to (x, y)"
		// here with real values for x and y. The same output
		// is used when moving the ego around using the mouse.
		motionDesc = "moving to a point";
		break;
	default:
		motionDesc = "unknown motion type";
		break;
	}

	sprintf(msg,
		"Object %d:\n" \
		"x: %d  xsize: %d\n" \
		"y: %d  ysize: %d\n" \
		"pri: %d\n" \
		"stepsize: %d\n" \
		"%s\n" \
		"%s",
		_v[p0],
		vt_v.xPos, vt_v.xSize,
		vt_v.yPos, vt_v.ySize,
		vt_v.priority,
		vt_v.stepSize,
		cycleDesc,
		motionDesc);
	state->_vm->messageBox(msg);
}

// unknown commands:
// unk_170: Force savegame name -- j5
// unk_171: script save -- j5
// unk_172: script restore -- j5
// unk_173: Activate keypressed control (ego only moves while key is pressed)
// unk_174: Change priority table (used in KQ4) -- j5
// unk_177: Disable menus completely -- j5
// unk_181: Deactivate keypressed control (default control of ego)
void cmdSetSimple(AgiGame *state, uint8 *p) {
	if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) {
		state->simpleSave = true;
	} else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures.
		// Load the picture. Similar to void cmdLoad_pic(AgiGame *state, uint8 *p).
		state->_vm->_sprites->eraseBoth();
		state->_vm->agiLoadResource(rPICTURE, _v[p0]);

		// Draw the picture. Similar to void cmdDraw_pic(AgiGame *state, uint8 *p).
		state->_vm->_picture->decodePicture(_v[p0], false, true);
		state->_vm->_sprites->blitBoth();
		state->pictureShown = 0;

		// Show the picture. Similar to void cmdShow_pic(AgiGame *state, uint8 *p).
		setflag(fOutputMode, false);
		state->_vm->closeWindow();
		state->_vm->_picture->showPic();
		state->pictureShown = 1;

		// Simulate slowww computer. Many effects rely on this
		state->_vm->pause(kPausePicture);
	}
}

void cmdPopScript(AgiGame *state, uint8 *p) {
	if (getVersion() >= 0x2915) {
		debug(0, "pop.script");
	}
}

void cmdHoldKey(AgiGame *state, uint8 *p) {
	if (getVersion() >= 0x3098) {
		state->_vm->_egoHoldKey = true;
	}
}

void cmdDiscardSound(AgiGame *state, uint8 *p) {
	if (getVersion() >= 0x2936) {
		debug(0, "discard.sound");
	}
}

void cmdHideMouse(AgiGame *state, uint8 *p) {
	// WORKAROUND: Turns off current movement that's being caused with the mouse.
	// This fixes problems with too many popup boxes appearing in the Amiga
	// Gold Rush's copy protection failure scene (i.e. the hanging scene, logic.192).
	// Previously multiple popup boxes appeared one after another if you tried
	// to walk somewhere else than to the right using the mouse.
	// FIXME: Write a proper implementation using disassembly and
	//        apply it to other games as well if applicable.
	state->viewTable[0].flags &= ~fAdjEgoXY;

	g_system->showMouse(false);
}

void cmdAllowMenu(AgiGame *state, uint8 *p) {
	if (getVersion() >= 0x3098) {
		setflag(fMenusWork, ((p0 != 0) ? true : false));
	}
}

void cmdShowMouse(AgiGame *state, uint8 *p) {
	g_system->showMouse(true);
}

void cmdFenceMouse(AgiGame *state, uint8 *p) {
	state->mouseFence.moveTo(p0, p1);
	state->mouseFence.setWidth(p2 - p0);
	state->mouseFence.setHeight(p3 - p1);
}

void cmdReleaseKey(AgiGame *state, uint8 *p) {
	if (getVersion() >= 0x3098) {
		state->_vm->_egoHoldKey = false;
	}
}

void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {
	int8 x, y;

	switch (logicNamesCmd[182].argumentsLength()) {
	// The 2 arguments version is used at least in Amiga Gold Rush!
	// (v2.05 1989-03-09, Amiga AGI 2.316) in logics 130 and 150
	// (Using arguments (0, 0), (0, 7), (0, 8), (9, 9) and (-9, 9)).
	case 2:
		// Both arguments are signed 8-bit (i.e. in range -128 to +127).
		x = (int8) p0;
		y = (int8) p1;

		// Turn off ego's current movement caused with the mouse if
		// adj.ego.move.to.x.y is called with other arguments than previously.
		// Fixes weird looping behavior when walking to a ladder in the mines
		// (Rooms 147-162) in Gold Rush using the mouse. Sometimes the ego didn't
		// stop when walking to a ladder using the mouse but kept moving on the
		// ladder in a horizontally looping manner i.e. from right to left, from
		// right to left etc. In the Amiga Gold Rush the ego stopped when getting
		// onto the ladder so this is more like it (Although that may be caused
		// by something else because this command doesn't do any flag manipulations
		// in the Amiga version - checked it with disassembly).
		if (x != state->adjMouseX || y != state->adjMouseY)
			state->viewTable[EGO_VIEW_TABLE].flags &= ~fAdjEgoXY;

		state->adjMouseX = x;
		state->adjMouseY = y;

		debugC(4, kDebugLevelScripts, "adj.ego.move.to.x.y(%d, %d)", x, y);
		break;
	// TODO: Check where (if anywhere) the 0 arguments version is used
	case 0:
	default:
		state->viewTable[0].flags |= fAdjEgoXY;
		break;
	}
}

void cmdParse(AgiGame *state, uint8 *p) {
	_v[vWordNotFound] = 0;
	setflag(fEnteredCli, false);
	setflag(fSaidAcceptedInput, false);

	state->_vm->dictionaryWords(state->_vm->agiSprintf(state->strings[p0]));
}

void cmdCall(AgiGame *state, uint8 *p) {
	int oldCIP;
	int oldLognum;

	// CM: we don't save sIP because set.scan.start can be
	//     used in a called script (fixes xmas demo)
	oldCIP = state->_curLogic->cIP;
	oldLognum = state->lognum;

	state->_vm->runLogic(p0);

	state->lognum = oldLognum;
	state->_curLogic = &state->logics[state->lognum];
	state->_curLogic->cIP = oldCIP;
}

void cmdCallF(AgiGame *state, uint8 *p) {
	cmdCall(state, &_v[p0]);
}

void cmdDrawPicV1(AgiGame *state, uint8 *p) {
	debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", _v[p0]);
	state->_vm->_picture->decodePicture(_v[p0], true);

	state->_vm->clearPrompt();

	// Simulate slowww computer. Many effects rely on this
	state->_vm->pause(kPausePicture);
}

void cmdDrawPic(AgiGame *state, uint8 *p) {
	debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]);
	state->_vm->_sprites->eraseBoth();
	state->_vm->_picture->decodePicture(_v[p0], true);
	state->_vm->_sprites->blitBoth();
	state->_vm->_sprites->commitBoth();
	state->pictureShown = 0;
	debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]);

	// WORKAROUND for a script bug which exists in SQ1, logic scripts
	// 20 and 110. Flag 103 is not reset correctly, which leads to erroneous
	// behavior from view 46 (the spider droid). View 46 is supposed to
	// follow ego and explode when it comes in contact with him. However, as
	// flag 103 is not reset correctly, when the player goes down the path
	// and back up, the spider is always at the base of the path (since it
	// can't go up) and kills the player when he goes down at ground level
	// (although the spider droid sprite itself seems to be correctly positioned).
	// With this workaround, when the player goes back to picture 20 (1 screen
	// above the ground), flag 103 is reset, thereby fixing this issue. Note
	// that this is a script bug and occurs in the original interpreter as well.
	// Fixes bug #1658514: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger
	if (getGameID() == GID_SQ1 && _v[p0] == 20)
		setflag(103, false);

	// Simulate slowww computer. Many effects rely on this
	state->_vm->pause(kPausePicture);
}

void cmdShowPic(AgiGame *state, uint8 *p) {
	debugC(6, kDebugLevelScripts, "=== show pic ===");

	setflag(fOutputMode, false);
	state->_vm->closeWindow();
	state->_vm->_picture->showPic();
	state->pictureShown = 1;

	debugC(6, kDebugLevelScripts, "--- end of show pic ---");
}

void cmdLoadPic(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->eraseBoth();
	state->_vm->agiLoadResource(rPICTURE, _v[p0]);
	state->_vm->_sprites->blitBoth();
	state->_vm->_sprites->commitBoth();
}

void cmdLoadPicV1(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rPICTURE, _v[p0]);
}

void cmdDiscardPic(AgiGame *state, uint8 *p) {
	debugC(6, kDebugLevelScripts, "--- discard pic ---");
	// do nothing
}

void cmdOverlayPic(AgiGame *state, uint8 *p) {
	debugC(6, kDebugLevelScripts, "--- overlay pic ---");

	state->_vm->_sprites->eraseBoth();
	state->_vm->_picture->decodePicture(_v[p0], false);
	state->_vm->_sprites->blitBoth();
	state->pictureShown = 0;
	state->_vm->_sprites->commitBoth();

	// Simulate slowww computer. Many effects rely on this
	state->_vm->pause(kPausePicture);
}

void cmdShowPriScreen(AgiGame *state, uint8 *p) {
	state->_vm->_debug.priority = 1;
	state->_vm->_sprites->eraseBoth();
	state->_vm->_picture->showPic();
	state->_vm->_sprites->blitBoth();

	state->_vm->waitKey();

	state->_vm->_debug.priority = 0;
	state->_vm->_sprites->eraseBoth();
	state->_vm->_picture->showPic();
	state->_vm->_sprites->blitBoth();
}

void cmdAnimateObj(AgiGame *state, uint8 *p) {
	if (getVersion() < 0x2000) {
		if (vt.flags & fDidntMove)
			return;
	} else {
		if (vt.flags & fAnimated)
			return;
	}

	debugC(4, kDebugLevelScripts, "animate vt entry #%d", p0);
	vt.flags = fAnimated | fUpdate | fCycling;

	if (getVersion() < 0x2000) {
		vt.flags |= fDidntMove;
	}

	vt.motion = kMotionNormal;
	vt.cycle = kCycleNormal;
	vt.direction = 0;
}

void cmdUnanimateAll(AgiGame *state, uint8 *p) {
	int i;

	for (i = 0; i < MAX_VIEWTABLE; i++)
		state->viewTable[i].flags &= ~(fAnimated | fDrawn);
}

void cmdDraw(AgiGame *state, uint8 *p) {
	if (vt.flags & fDrawn)
		return;

	if (vt.ySize <= 0 || vt.xSize <= 0)
		return;

	debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry);

	vt.flags |= fUpdate;
	if (getVersion() >= 0x3000) {
		state->_vm->setLoop(&vt, vt.currentLoop);
		state->_vm->setCel(&vt, vt.currentCel);
	}

	state->_vm->fixPosition(p0);
	vt.xPos2 = vt.xPos;
	vt.yPos2 = vt.yPos;
	vt.celData2 = vt.celData;
	state->_vm->_sprites->eraseUpdSprites();
	vt.flags |= fDrawn;

	// WORKAROUND: This fixes a bug with AGI Fanmade game Space Trek.
	// The original workaround checked if AGI version was <= 2.440, which could
	// cause regressions with some AGI games. The original workaround no longer
	// works for Space Trek in ScummVM, as all fanmade games are set to use
	// AGI version 2.917, but it applies to all other games where AGI version is
	// <= 2.440, which was not the original purpose of this workaround. It is
	// assumed that this bug is caused by AGI Studio, so this applies to all
	// fanmade games only.
	// TODO: Investigate this further and check if any other fanmade AGI
	// games are affected. If yes, then it'd be best to set this for Space
	// Trek only
	if (getFeatures() & GF_FANMADE)	// See Sarien bug #546562
		vt.flags |= fAnimated;

	state->_vm->_sprites->blitUpdSprites();
	vt.flags &= ~fDontupdate;

	state->_vm->_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos, true);

	debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags);
}

void cmdErase(AgiGame *state, uint8 *p) {
	if (~vt.flags & fDrawn)
		return;

	state->_vm->_sprites->eraseUpdSprites();

	if (vt.flags & fUpdate) {
		vt.flags &= ~fDrawn;
	} else {
		state->_vm->_sprites->eraseNonupdSprites();
		vt.flags &= ~fDrawn;
		state->_vm->_sprites->blitNonupdSprites();
	}
	state->_vm->_sprites->blitUpdSprites();

	int x1, y1, x2, y2;

	x1 = MIN((int)MIN(vt.xPos, vt.xPos2), MIN(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width));
	x2 = MAX((int)MAX(vt.xPos, vt.xPos2), MAX(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width));
	y1 = MIN((int)MIN(vt.yPos, vt.yPos2), MIN(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height));
	y2 = MAX((int)MAX(vt.yPos, vt.yPos2), MAX(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height));

	state->_vm->_sprites->commitBlock(x1, y1, x2, y2, true);
}

void cmdPosition(AgiGame *state, uint8 *p) {
	vt.xPos = vt.xPos2 = p1;
	vt.yPos = vt.yPos2 = p2;

	// WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication"
	// with an accompanying identical workaround in position.v-command (i.e. command 0x26).
	// These two workarounds together make up the whole fix. The bug was caused by
	// wrongly written script data in Space Trek v1.0's scripts (At least logics 4 and 11).
	// Position-command was called with horizontal values over 200 (Outside the screen!).
	// Clipping the coordinates so the views stay wholly on-screen seems to fix the problems.
	//   It is probable (Would have to check better with disassembly to be completely sure)
	// that AGI 2.440 clipped its coordinates in its position and position.v-commands
	// although AGI 2.917 certainly doesn't (Checked that with disassembly) and that's why
	// Space Trek may have worked better with AGI 2.440 than with some other AGI versions.
	//   I haven't checked but if Space Trek solely abuses the position-command we wouldn't
	// strictly need the identical workaround in the position.v-command but it does make
	// for a nice symmetry.
	if (getFeatures() & GF_CLIPCOORDS)
		state->_vm->clipViewCoordinates(&vt);
}

void cmdPositionV1(AgiGame *state, uint8 *p) {
	vt.xPos = p1;
	vt.yPos = p2;
}

void cmdPositionF(AgiGame *state, uint8 *p) {
	vt.xPos = vt.xPos2 = _v[p1];
	vt.yPos = vt.yPos2 = _v[p2];

	// WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication"
	// with an accompanying identical workaround in position-command (i.e. command 0x25).
	// See that workaround's comment for more in-depth information.
	if (getFeatures() & GF_CLIPCOORDS)
		state->_vm->clipViewCoordinates(&vt);
}

void cmdPositionFV1(AgiGame *state, uint8 *p) {
	vt.xPos = _v[p1];
	vt.yPos = _v[p2];
}

void cmdGetPosn(AgiGame *state, uint8 *p) {
	state->vars[p1] = (unsigned char)vt.xPos;
	state->vars[p2] = (unsigned char)vt.yPos;
}

void cmdReposition(AgiGame *state, uint8 *p) {
	int dx = (int8) _v[p1], dy = (int8) _v[p2];

	debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy);
	vt.flags |= fUpdatePos;

	if (dx < 0 && vt.xPos < -dx)
		vt.xPos = 0;
	else
		vt.xPos += dx;

	if (dy < 0 && vt.yPos < -dy)
		vt.yPos = 0;
	else
		vt.yPos += dy;

	state->_vm->fixPosition(p0);
}

void cmdRepositionV1(AgiGame *state, uint8 *p) {
	vt.xPos2 = vt.xPos;
	vt.yPos2 = vt.yPos;
	vt.flags |= fUpdatePos;

	vt.xPos = (vt.xPos + p1) & 0xff;
	vt.yPos = (vt.yPos + p2) & 0xff;
}

void cmdRepositionTo(AgiGame *state, uint8 *p) {
	vt.xPos = p1;
	vt.yPos = p2;
	vt.flags |= fUpdatePos;
	state->_vm->fixPosition(p0);
}

void cmdRepositionToF(AgiGame *state, uint8 *p) {
	vt.xPos = _v[p1];
	vt.yPos = _v[p2];
	vt.flags |= fUpdatePos;
	state->_vm->fixPosition(p0);
}

void cmdAddToPic(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, p6);
}

void cmdAddToPicV1(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, -1);
}

void cmdAddToPicF(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]);
}

void cmdForceUpdate(AgiGame *state, uint8 *p) {
	state->_vm->_sprites->eraseBoth();
	state->_vm->_sprites->blitBoth();
	state->_vm->_sprites->commitBoth();
}

void cmdReverseLoop(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
	vt.cycle = kCycleRevLoop;
	vt.flags |= (fDontupdate | fUpdate | fCycling);
	vt.parm1 = p1;
	setflag(p1, false);
}

void cmdReverseLoopV1(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
	vt.cycle = kCycleRevLoop;
	state->_vm->setCel(&vt, 0);
	vt.flags |= (fDontupdate | fUpdate | fCycling);
	vt.parm1 = p1;
	vt.parm3 = 0;
}

void cmdEndOfLoop(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
	vt.cycle = kCycleEndOfLoop;
	vt.flags |= (fDontupdate | fUpdate | fCycling);
	vt.parm1 = p1;
	setflag(p1, false);
}

void cmdEndOfLoopV1(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1);
	vt.cycle = kCycleEndOfLoop;
	state->_vm->setCel(&vt, 0);
	vt.flags |= (fDontupdate | fUpdate | fCycling);
	vt.parm1 = p1;
	vt.parm3 = 0;
}

void cmdBlock(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3);
	state->block.active = true;
	state->block.x1 = p0;
	state->block.y1 = p1;
	state->block.x2 = p2;
	state->block.y2 = p3;
}

void cmdUnblock(AgiGame *state, uint8 *p) {
	state->block.active = false;
}

void cmdNormalMotion(AgiGame *state, uint8 *p) {
	vt.motion = kMotionNormal;
}

void cmdStopMotion(AgiGame *state, uint8 *p) {
	vt.direction = 0;
	vt.motion = kMotionNormal;
	if (p0 == 0) {		// ego only
		_v[vEgoDir] = 0;
		state->playerControl = false;
	}
}

void cmdStopMotionV1(AgiGame *state, uint8 *p) {
	vt.flags &= ~fAnimated;
}

void cmdStartMotion(AgiGame *state, uint8 *p) {
	vt.motion = kMotionNormal;
	if (p0 == 0) {		// ego only
		_v[vEgoDir] = 0;
		state->playerControl = true;
	}
}

void cmdStartMotionV1(AgiGame *state, uint8 *p) {
	vt.flags |= fAnimated;
}

void cmdPlayerControl(AgiGame *state, uint8 *p) {
	state->playerControl = true;
	state->viewTable[0].motion = kMotionNormal;
}

void cmdProgramControl(AgiGame *state, uint8 *p) {
	state->playerControl = false;
}

void cmdFollowEgo(AgiGame *state, uint8 *p) {
	vt.motion = kMotionFollowEgo;
	vt.parm1 = p1 > vt.stepSize ? p1 : vt.stepSize;
	vt.parm2 = p2;
	vt.parm3 = 0xff;

	if (getVersion() < 0x2000) {
		_v[p2] = 0;
		vt.flags |= fUpdate | fAnimated;
	} else {
		setflag(p2, false);
		vt.flags |= fUpdate;
	}
}

void cmdMoveObj(AgiGame *state, uint8 *p) {
	// _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4);

	vt.motion = kMotionMoveObj;
	vt.parm1 = p1;
	vt.parm2 = p2;
	vt.parm3 = vt.stepSize;
	vt.parm4 = p4;

	if (p3 != 0)
		vt.stepSize = p3;

	if (getVersion() < 0x2000) {
		_v[p4] = 0;
		vt.flags |= fUpdate | fAnimated;
	} else {
		setflag(p4, false);
		vt.flags |= fUpdate;
	}

	if (p0 == 0)
		state->playerControl = false;

	// AGI 2.272 (ddp, xmas) doesn't call move_obj!
	if (getVersion() > 0x2272)
		state->_vm->moveObj(&vt);
}

void cmdMoveObjF(AgiGame *state, uint8 *p) {
	vt.motion = kMotionMoveObj;
	vt.parm1 = _v[p1];
	vt.parm2 = _v[p2];
	vt.parm3 = vt.stepSize;
	vt.parm4 = p4;

	if (_v[p3] != 0)
		vt.stepSize = _v[p3];

	setflag(p4, false);
	vt.flags |= fUpdate;

	if (p0 == 0)
		state->playerControl = false;

	// AGI 2.272 (ddp, xmas) doesn't call move_obj!
	if (getVersion() > 0x2272)
		state->_vm->moveObj(&vt);
}

void cmdWander(AgiGame *state, uint8 *p) {
	if (p0 == 0)
		state->playerControl = false;

	vt.motion = kMotionWander;
	if (getVersion() < 0x2000) {
		vt.flags |= fUpdate | fAnimated;
	} else {
		vt.flags |= fUpdate;
	}
}

void cmdSetGameID(AgiGame *state, uint8 *p) {
	if (state->_curLogic->texts && (p0 - 1) <= state->_curLogic->numTexts)
		Common::strlcpy(state->id, state->_curLogic->texts[p0 - 1], 8);
	else
		state->id[0] = 0;

	debug(0, "Game ID: \"%s\"", state->id);
}

void cmdPause(AgiGame *state, uint8 *p) {
	int tmp = state->clockEnabled;
	const char *b[] = { "Continue", NULL };
	const char *b_ru[] = { "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec", NULL };

	state->clockEnabled = false;

	switch (getLanguage()) {
	case Common::RU_RUS:
		state->_vm->selectionBox("  \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0.  \n\n\n", b_ru);
		break;
	default:
		state->_vm->selectionBox("  Game is paused.  \n\n\n", b);
		break;
	}
	state->clockEnabled = tmp;
}

void cmdSetMenu(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts);

	if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts)
		state->_vm->_menu->add(state->_curLogic->texts[p0 - 1]);
}

void cmdSetMenuItem(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts);

	if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts)
		state->_vm->_menu->addItem(state->_curLogic->texts[p0 - 1], p1);
}

void cmdVersion(AgiGame *state, uint8 *p) {
	char ver2Msg[] =
	    "\n"
	    "                               \n\n"
	    "  Emulating Sierra AGI v%x.%03x\n";
	char ver3Msg[] =
	    "\n"
	    "                             \n\n"
	    "  Emulating AGI v%x.002.%03x\n";
	// no Sierra as it wraps textbox

	Common::String verMsg = TITLE " v%s";

	int ver = getVersion();
	int maj = (ver >> 12) & 0xf;
	int min = ver & 0xfff;

	verMsg += (maj == 2 ? ver2Msg : ver3Msg);
	verMsg = Common::String::format(verMsg.c_str(), gScummVMVersion, maj, min);

	state->_vm->messageBox(verMsg.c_str());
}

void cmdConfigureScreen(AgiGame *state, uint8 *p) {
	state->lineMinPrint = p0;
	state->lineUserInput = p1;
	state->lineStatus = p2;
}

void cmdTextScreen(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "switching to text mode");
	state->gfxMode = false;

	// Simulates the "bright background bit" of the PC video
	// controller.
	if (state->colorBg)
		state->colorBg |= 0x08;

	state->_vm->_gfx->clearScreen(state->colorBg);
}

void cmdGraphics(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "switching to graphics mode");

	if (!state->gfxMode) {
		state->gfxMode = true;
		state->_vm->_gfx->clearScreen(0);
		state->_vm->_picture->showPic();
		state->_vm->writeStatus();
		state->_vm->writePrompt();
	}
}

void cmdSetTextAttribute(AgiGame *state, uint8 *p) {
	state->colorFg = p0;
	state->colorBg = p1;

	if (state->gfxMode) {
		if (state->colorBg != 0) {
			state->colorFg = 0;
			state->colorBg = 15;
		}
	}
}

void cmdStatus(AgiGame *state, uint8 *p) {
	state->_vm->inventory();
}

void cmdQuit(AgiGame *state, uint8 *p) {
	const char *buttons[] = { "Quit", "Continue", NULL };

	state->_vm->_sound->stopSound();
	if (p0) {
		state->_vm->quitGame();
	} else {
		if (state->_vm->selectionBox(" Quit the game, or continue? \n\n\n", buttons) == 0) {
			state->_vm->quitGame();
		}
	}
}

void cmdQuitV1(AgiGame *state, uint8 *p) {
	state->_vm->_sound->stopSound();
	state->_vm->quitGame();
}

void cmdRestartGame(AgiGame *state, uint8 *p) {
	const char *buttons[] = { "Restart", "Continue", NULL };
	int sel;

	state->_vm->_sound->stopSound();
	sel = getflag(fAutoRestart) ? 0 :
		state->_vm->selectionBox(" Restart game, or continue? \n\n\n", buttons);

	if (sel == 0) {
		state->_vm->_restartGame = true;
		setflag(fRestartGame, true);
		state->_vm->_menu->enableAll();
	}
}

void cmdDistance(AgiGame *state, uint8 *p) {
	int16 x1, y1, x2, y2, d;
	VtEntry *v0 = &state->viewTable[p0];
	VtEntry *v1 = &state->viewTable[p1];

	if (v0->flags & fDrawn && v1->flags & fDrawn) {
		x1 = v0->xPos + v0->xSize / 2;
		y1 = v0->yPos;
		x2 = v1->xPos + v1->xSize / 2;
		y2 = v1->yPos;
		d = ABS(x1 - x2) + ABS(y1 - y2);
		if (d > 0xfe)
			d = 0xfe;
	} else {
		d = 0xff;
	}

	// WORKAROUND: Fixes King's Quest IV's script bug #1660424 (KQ4: Zombie bug).
	// In the graveyard (Rooms 16 and 18) at night if you had the Obsidian Scarab (Item 4)
	// and you were very close to a spot where a zombie was going to rise up from the
	// ground you could reproduce the bug. Just standing there and letting the zombie
	// try to rise up the Obsidian Scarab would repel the zombie immediately and that
	// would make the script bug so that the zombie would still come up but it just
	// wouldn't chase Rosella around anymore. If it had worked correctly the zombie
	// wouldn't have come up at all or it would have come up and gone back down
	// immediately. The latter approach is the one implemented here.
	if (getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) {
		// Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18.
		// Variables 221-223 are used to save the distance between each zombie and Rosella.
		// Variables 155, 156 and 162 are used to save the state of each zombie in room 16.
		// Variables 155, 156 and 158 are used to save the state of each zombie in room 18.
		// Rosella gets turned to a zombie only if any of the zombies is under 10 units away
		// from her and she doesn't have the Obsidian Scarab (Item 4). Likewise Rosella makes
		// a zombie go back into the ground if the zombie comes under 15 units away from her
		// and she has the Obsidian Scarab. To ensure a zombie always first rises up before
		// checking for either of the aforementioned conditions (Rosella getting turned to
		// a zombie or the zombie getting turned away by the scarab) we make it appear the
		// zombie is far away from Rosella if the zombie is not already up and chasing her.
		enum zombieStates {ZOMBIE_SET_TO_RISE_UP, ZOMBIE_RISING_UP, ZOMBIE_CHASING_EGO};
		uint8 zombieStateVarNumList[] = {155, 156, (uint8)((_v[vCurRoom] == 16) ? 162 : 158)};
		uint8 zombieNum         = p2 - 221;                         // Zombie's number (In range 0-2)
		uint8 zombieStateVarNum = zombieStateVarNumList[zombieNum]; // Number of the variable containing zombie's state
		uint8 zombieState       = _v[zombieStateVarNum];            // Zombie's state
		// If zombie is not chasing Rosella then set its distance from Rosella to the maximum
		if (zombieState != ZOMBIE_CHASING_EGO)
			d = 0xff;
	}

	_v[p2] = (unsigned char)d;
}

void cmdAcceptInput(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal");

	state->_vm->newInputMode(INPUT_NORMAL);
	state->inputEnabled = true;
	state->_vm->writePrompt();
}

void cmdPreventInput(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input");

	state->_vm->newInputMode(INPUT_NONE);
	state->inputEnabled = false;

	// Always clear with black background. Fixes bug #3080041.
	state->_vm->clearPrompt(true);
}

void cmdGetString(AgiGame *state, uint8 *p) {
	int tex, row, col;

	debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4);

	tex = p1 - 1;
	row = p2;
	col = p3;

	// Workaround for SQLC bug.
	// See Sarien bug #792125 for details
	if (row > 24)
		row = 24;
	if (col > 39)
		col = 39;

	state->_vm->newInputMode(INPUT_GETSTRING);

	if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= tex) {
		int len = strlen(state->_curLogic->texts[tex]);

		state->_vm->printText(state->_curLogic->texts[tex], 0, col, row, len, state->colorFg, state->colorBg);
		state->_vm->getString(col + len - 1, row, p4, p0);

		// SGEO: display input char
		state->_vm->_gfx->printCharacter((col + len), row, state->cursorChar, state->colorFg, state->colorBg);
	}

	do {
		state->_vm->mainCycle();
	} while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame));
}

void cmdGetNum(AgiGame *state, uint8 *p) {
	debugC(4, kDebugLevelScripts, "%d %d", p0, p1);

	state->_vm->newInputMode(INPUT_GETSTRING);

	if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= (p0 - 1)) {
		int len = strlen(state->_curLogic->texts[p0 - 1]);

		state->_vm->printText(state->_curLogic->texts[p0 - 1], 0, 0, 22, len, state->colorFg, state->colorBg);
		state->_vm->getString(len - 1, 22, 3, MAX_STRINGS);

		// CM: display input char
		state->_vm->_gfx->printCharacter((p3 + len), 22, state->cursorChar, state->colorFg, state->colorBg);
	}

	do {
		state->_vm->mainCycle();
	} while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame));

	_v[p1] = atoi(state->strings[MAX_STRINGS]);

	debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], _v[p1]);

	state->_vm->clearLines(22, 22, state->colorBg);
	state->_vm->flushLines(22, 22);
}

void cmdSetCursorChar(AgiGame *state, uint8 *p) {
	if (state->_curLogic->texts != NULL && (p0 - 1) <= state->_curLogic->numTexts) {
		state->cursorChar = *state->_curLogic->texts[p0 - 1];
	} else {
		// default
		state->cursorChar = '_';
	}
}

void cmdSetKey(AgiGame *state, uint8 *p) {
	int key = 256 * p1 + p0;
	int slot = -1;

	for (int i = 0; i < MAX_CONTROLLERS; i++) {
		if (slot == -1 && !state->controllers[i].keycode)
			slot = i;

		if (state->controllers[i].keycode == key && state->controllers[i].controller == p2)
			return;
	}

	if (slot == -1) {
		warning("Number of set.keys exceeded %d", MAX_CONTROLLERS);
		return;
	}

	debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", p0, p1, p2);
	state->controllers[slot].keycode = key;
	state->controllers[slot].controller = p2;

	state->controllerOccured[p2] = false;
}

void cmdSetString(AgiGame *state, uint8 *p) {
	// CM: to avoid crash in Groza (str = 150)
	if (p0 > MAX_STRINGS)
		return;
	strcpy(state->strings[p0], state->_curLogic->texts[p1 - 1]);
}

void cmdDisplay(AgiGame *state, uint8 *p) {
	// V1 has 4 args
	int t = (getVersion() >= 0x2000 ? p2 : p3);
	int len = 40;

	char *s = state->_vm->wordWrapString(state->_curLogic->texts[t - 1], &len);

	state->_vm->printText(s, p1, 0, p0, 40, state->colorFg, state->colorBg);

	free(s);
}

void cmdDisplayF(AgiGame *state, uint8 *p) {
	state->_vm->printText(state->_curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, state->colorFg, state->colorBg);
}

void cmdClearTextRect(AgiGame *state, uint8 *p) {
	int c, x1, y1, x2, y2;

	if ((c = p4) != 0)
		c = 15;

	x1 = p1 * CHAR_COLS;
	y1 = p0 * CHAR_LINES;
	x2 = (p3 + 1) * CHAR_COLS - 1;
	y2 = (p2 + 1) * CHAR_LINES - 1;

	// Added to prevent crash with x2 = 40 in the iigs demo
	if (x1 > GFX_WIDTH)
		x1 = GFX_WIDTH - 1;
	if (x2 > GFX_WIDTH)
		x2 = GFX_WIDTH - 1;
	if (y1 > GFX_HEIGHT)
		y1 = GFX_HEIGHT - 1;
	if (y2 > GFX_HEIGHT)
		y2 = GFX_HEIGHT - 1;

	state->_vm->_gfx->drawRectangle(x1, y1, x2, y2, c);
	state->_vm->_gfx->flushBlock(x1, y1, x2, y2);
}

void cmdToggleMonitor(AgiGame *state, uint8 *p) {
	debug(0, "toggle.monitor");
}

void cmdEchoLine(AgiGame *state, uint8 *p) {
	strcpy((char *)state->inputBuffer, (const char *)state->echoBuffer);
	state->cursorPos = strlen((char *)state->inputBuffer);
	state->hasPrompt = 0;
}

void cmdClearLines(AgiGame *state, uint8 *p) {
	uint8 l;

	// Residence 44 calls clear.lines(24,0,0), see Sarien bug #558423
	l = p1 ? p1 : p0;

	// Agent06 incorrectly calls clear.lines(1,150,0), see ScummVM bugs
	// #1935838 and #1935842
	l = (l <= 24) ? l : 24;

	state->_vm->clearLines(p0, l, p2);
	state->_vm->flushLines(p0, l);
}

void cmdPrint(AgiGame *state, uint8 *p) {
	int n = p0 < 1 ? 1 : p0;

	state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0);
}

void cmdPrintF(AgiGame *state, uint8 *p) {
	int n = _v[p0] < 1 ? 1 : _v[p0];

	state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0);
}

void cmdPrintAt(AgiGame *state, uint8 *p) {
	int n = p0 < 1 ? 1 : p0;

	debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3);

	state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3);
}

void cmdPrintAtV(AgiGame *state, uint8 *p) {
	int n = _v[p0] < 1 ? 1 : _v[p0];

	state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3);
}

void cmdPushScript(AgiGame *state, uint8 *p) {
	// We run AGIMOUSE always as a side effect
	//if (getFeatures() & GF_AGIMOUSE || true) {
		state->vars[27] = state->_vm->_mouse.button;
		state->vars[28] = state->_vm->_mouse.x / 2;
		state->vars[29] = state->_vm->_mouse.y;
	/*} else {
		if (getVersion() >= 0x2915) {
			debug(0, "push.script");
		}
	}*/
}

void cmdSetPriBase(AgiGame *state, uint8 *p) {
	int i, x, pri;

	debug(0, "Priority base set to %d", p0);

	// state->alt_pri = true;
	x = (_HEIGHT - p0) * _HEIGHT / 10;

	for (i = 0; i < _HEIGHT; i++) {
		pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5;
		if (pri > 15)
			pri = 15;
		state->priTable[i] = pri;
	}
}

void cmdMousePosn(AgiGame *state, uint8 *p) {
	_v[p0] = WIN_TO_PIC_X(state->_vm->_mouse.x);
	_v[p1] = WIN_TO_PIC_Y(state->_vm->_mouse.y);
}

void cmdShakeScreen(AgiGame *state, uint8 *p) {
	int i;

	// AGIPAL uses shake.screen values between 100 and 109 to set the palette
	// (Checked the original AGIPAL-hack's shake.screen-routine's disassembly).
	if (p0 >= 100 && p0 < 110) {
		if (getFeatures() & GF_AGIPAL) {
			state->_vm->_gfx->setAGIPal(p0);
			return;
		} else {
			warning("It looks like GF_AGIPAL flag is missing");
		}
	}

	// Disables input while shaking to prevent bug
	// #1678230: AGI: Entering text while screen is shaking
	bool originalValue = state->inputEnabled;
	state->inputEnabled = false;

	state->_vm->_gfx->shakeStart();

	state->_vm->_sprites->commitBoth();		// Fixes SQ1 demo
	for (i = 4 * p0; i; i--) {
		state->_vm->_gfx->shakeScreen(i & 1);
		state->_vm->_gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
		state->_vm->mainCycle();
	}
	state->_vm->_gfx->shakeEnd();

	// Sets input back to what it was
	state->inputEnabled = originalValue;
}

void cmdSetSpeed(AgiGame *state, uint8 *p) {
	// V1 command
	(void)state;
	(void)p;
	// speed = _v[p0];
}

void cmdSetItemView(AgiGame *state, uint8 *p) {
	// V1 command
	(void)state;
	(void)p;
}

void cmdCallV1(AgiGame *state, uint8 *p) {
	state->_vm->agiLoadResource(rLOGIC, p0);
	// FIXME: The following instruction looks incomplete.
	// Maybe something is meant to be assigned to, or read from,
	// the logic_list entry?
//	state->logic_list[++state->max_logics];
	// For now, just do the increment, to silence a clang warning
	++state->max_logics;
	_v[13] = 1;
}

void cmdNewRoomV1(AgiGame *state, uint8 *p) {
	warning("cmdNewRoomV1()");
	state->_vm->agiLoadResource(rLOGIC, p0);
	state->max_logics = 1;
	state->logic_list[1] = p0;
	_v[13] = 1;
}

void cmdNewRoomVV1(AgiGame *state, uint8 *p) {
	warning("cmdNewRoomVV1()");
	state->_vm->agiLoadResource(rLOGIC, _v[p0]);
	state->max_logics = 1;
	state->logic_list[1] = _v[p0];
	_v[13] = 1;
}

void cmdUnknown(AgiGame *state, uint8 *p) {
	warning("Skipping unknown opcode %2X", *(code + ip - 1));
}

/**
 * Execute a logic script
 * @param n  Number of the logic resource to execute
 */
int AgiEngine::runLogic(int n) {
	AgiGame *state = &_game;
	uint8 op = 0;
	uint8 p[CMD_BSIZE] = { 0 };
	int num = 0;
	ScriptPos sp;
	//int logic_index = 0;

	state->logic_list[0] = 0;
	state->max_logics = 0;

	debugC(2, kDebugLevelScripts, "=================");
	debugC(2, kDebugLevelScripts, "runLogic(%d)", n);

	sp.script = n;
	sp.curIP = 0;
	_game.execStack.push_back(sp);

	// If logic not loaded, load it
	if (~_game.dirLogic[n].flags & RES_LOADED) {
		debugC(4, kDebugLevelScripts, "logic %d not loaded!", n);
		agiLoadResource(rLOGIC, n);
	}

	_game.lognum = n;
	_game._curLogic = &_game.logics[_game.lognum];

	_game._curLogic->cIP = _game._curLogic->sIP;

	_timerHack = 0;
	while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) {
		if (_debug.enabled) {
			if (_debug.steps > 0) {
				if (_debug.logic0 || n) {
					debugConsole(n, lCOMMAND_MODE, NULL);
					_debug.steps--;
				}
			} else {
				_sprites->blitBoth();
				_sprites->commitBoth();
				do {
					mainCycle();
				} while (!_debug.steps && _debug.enabled);
				_sprites->eraseBoth();
			}
		}

		_game.execStack.back().curIP = ip;

		char st[101];
		int sz = MIN(_game.execStack.size(), 100u);
		memset(st, '.', sz);
		st[sz] = 0;

		switch (op = *(code + ip++)) {
		case 0xff:	// if (open/close)
			testIfCode(n);
			break;
		case 0xfe:	// goto
			// +2 covers goto size
			ip += 2 + ((int16)READ_LE_UINT16(code + ip));

			// timer must keep running even in goto loops,
			// but AGI engine can't do that :(
			if (_timerHack > 20) {
				pollTimer();
				updateTimer();
				_timerHack = 0;
			}
			break;
		case 0x00:	// return
			debugC(2, kDebugLevelScripts, "%sreturn() // Logic %d", st, n);
			debugC(2, kDebugLevelScripts, "=================");

//			if (getVersion() < 0x2000) {
//				if (logic_index < state->max_logics) {
//					n = state->logic_list[++logic_index];
//					state->_curLogic = &state->logics[n];
//					state->lognum = n;
//					ip = 2;
//					warning("running logic %d\n", n);
//					break;
//				}
//				_v[13]=0;
//			}

			_game.execStack.pop_back();
			return 1;
		default:
			num = logicNamesCmd[op].argumentsLength();
			memmove(p, code + ip, num);
			memset(p + num, 0, CMD_BSIZE - num);

			debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]);

			_agiCommands[op](&_game, p);
			ip += num;
		}

//		if ((op == 0x0B || op == 0x3F || op == 0x40) && logic_index < state->max_logics) {
//			n = state->logic_list[++logic_index];
//			state->_curLogic = &state->logics[n];
//			state->lognum = n;
//			ip = 2;
//			warning("running logic %d\n", n);
//		}

		if (_game.exitAllLogics)
			break;
	}

	_game.execStack.pop_back();

	return 0;		// after executing new.room()
}

void AgiEngine::executeAgiCommand(uint8 op, uint8 *p) {
	debugC(2, kDebugLevelScripts, "%s(%d %d %d)", logicNamesCmd[op].name, p[0], p[1], p[2]);

	_agiCommands[op](&_game, p);
}

} // End of namespace Agi
