/* 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 "common/system.h"
#include "common/debug.h"

#include "chamber/chamber.h"
#include "chamber/common.h"
#include "chamber/script.h"
#include "chamber/enums.h"
#include "chamber/resdata.h"
#include "chamber/cga.h"
#include "chamber/cursor.h"
#include "chamber/portrait.h"
#include "chamber/input.h"
#include "chamber/menu.h"
#include "chamber/room.h"
#include "chamber/dialog.h"
#include "chamber/print.h"
#include "chamber/anim.h"
#include "chamber/invent.h"
#include "chamber/sound.h"
#include "chamber/savegame.h"
#include "chamber/ifgm.h"

#if 0
#define DEBUG_SCRIPT
char DEBUG_SCRIPT_LOG[] = "!script.log";
#endif

#include "chamber/scrvars.h"

namespace Chamber {

byte rand_seed;
uint16 the_command;
uint16 script_res;
byte *script_ptr, *script_end_ptr;
byte *script_stack[5 * 2];
byte **script_stack_ptr = script_stack;

void *script_vars[kScrPools_MAX] = {
	&script_word_vars,
	&script_word_vars,
	&script_byte_vars,
	inventory_items,
	zones_data,
	pers_list,
	inventory_items,
	inventory_items + kItemZapstik1 - 1,
	pers_list
};

extern void askDisk2(void);

/*
Get next random byte value
*/
byte getRand(void) {
	script_byte_vars.rand_value = aleat_data[++rand_seed];
	return script_byte_vars.rand_value;
}

/*
Get next random word value
*/
uint16 getRandW(void) {
	uint16 r = getRand() << 8;
	return r | getRand();
}

uint16 Swap16(uint16 x) {
	return (x << 8) | (x >> 8);
}

/*
Script handlers exit codes
*/
enum CommandStatus {
	ScriptContinue = 0, /*run next script command normally*/
	ScriptRerun = 1, /*abort current script, execute new command*/
	/*TODO: maybe define ScriptRestartGame to support game restart?*/
};

uint16 CMD_TRAP(void) {
	warning("CMD TRAP");
	promptWait();
	for (;;) ;
	return 0;
}

uint16 SCR_TRAP(void) {
	warning("SCR TRAP 0x%02X @ 0x%lX", *script_ptr, script_ptr - templ_data);
	promptWait();
	for (;;) ;
	return 0;
}

/*
Remove "not interested in that" tag from owned items
*/
void reclaimRefusedItems(void) {
	int16 i;
	for (i = 0; i < MAX_INV_ITEMS; i++) {
		if (inventory_items[i].flags == (ITEMFLG_OWNED | ITEMFLG_DONTWANT))
			inventory_items[i].flags = ITEMFLG_OWNED;
	}
}

/*
Trade with a fellow Aspirant (the one that offers to swap an item)
*/
uint16 SCR_1_AspirantItemTrade(void) {
	byte *old_script, *old_script_end = script_end_ptr;

	item_t *item1, *item2;

	script_ptr++;
	old_script = script_ptr;

	for (;;) {
		inv_bgcolor = 0xFF;
		openInventory(0xFE, ITEMFLG_OWNED);

		if (inv_count == 0) {
			the_command = 0xC1BC;
			runCommand();
			break;
		}

		if (the_command == 0) {
			the_command = 0xC1C0;
			runCommand();
			break;
		}

		the_command = 0x9140;

		if (aspirant_ptr->item == 0)
			break;

		item1 = &inventory_items[aspirant_ptr->item - 1];	/*aspirant's item*/
		item2 = (item_t *)(script_vars[kScrPool3_CurrentItem]);	/*our offer*/

		if (item2->flags == (ITEMFLG_OWNED | ITEMFLG_DONTWANT) || item1->name == item2->name) {
			the_command = 0xC1C0;
			runCommand();
			break;
		}

		if (item2->name == 109	/*SKULL*/
		        || item2->name == 132	/*ZAPSTIK*/
		        || item2->name == 108  /*DAGGER*/
		        || script_byte_vars.rand_value < 154) {
			/*accept*/
			item2->flags = ITEMFLG_ASPIR;
			item1->flags = ITEMFLG_OWNED;
			aspirant_ptr->item = script_byte_vars.inv_item_index;
			switch (item2->name) {
			case 132:	/*ZAPSTIK*/
				script_byte_vars.zapstiks_owned--;
				the_command = 0xC04B;
				break;
			case 104:	/*ROPE*/
				the_command = 0xC1BA;
				break;
			case 107:	/*GOBLET*/
				the_command = 0xC1BB;
				break;
			default:	/*STONE FLY*/
				the_command = 0xC1B9;
			}
			runCommand();
			break;
		} else {
			/*not interested*/
			item2->flags = ITEMFLG_OWNED | ITEMFLG_DONTWANT;
			the_command = 0xC1BD;
			runCommand();
			continue;
		}
	}

	reclaimRefusedItems();

	script_ptr = old_script;
	script_end_ptr = old_script_end;

	return 0;
}

/*
Trade with a rude/passive Aspirant (the one that says nasty words about you)
*/
uint16 SCR_2_RudeAspirantTrade(void) {
	byte *old_script, *old_script_end = script_end_ptr;

	item_t *item1, *item2;

	script_ptr++;
	old_script = script_ptr;

	the_command = 0x9099;   /*WHAT DO YOU WANT TO EXCHANGE?*/
	runCommand();

	for (;;) {
		inv_bgcolor = 0xFF;
		openInventory(0xFE, ITEMFLG_OWNED);

		if (inv_count == 0) {
			the_command = 0xC1C5;
			runCommand();
			break;
		}

		if (the_command == 0)
			break;

		the_command = 0x9140;   /*NOTHING ON HIM*/

		if (aspirant_ptr->item == 0) {
			runCommand();
			break;
		}

		item1 = &inventory_items[aspirant_ptr->item - 1];	/*aspirant's item*/
		item2 = (item_t *)(script_vars[kScrPool3_CurrentItem]);	/*our offer*/

		if (item2->flags == (ITEMFLG_OWNED | ITEMFLG_DONTWANT)) {
			the_command = 0xC1C0;
			runCommand();
			break;
		}

		/*only trade for ROPE, LANTERN, STONE FLY, GOBLET*/
		if (item1->name < 104 || item1->name >= 108) {
			runCommand();
			break;
		}

		if ((item1->name != item2->name)
		        && (item2->name == 109	/*SKULL*/
		            || item2->name == 132	/*ZAPSTIK*/
		            || item2->name == 108	/*DAGGER*/
		            || script_byte_vars.rand_value < 154)) {

			/*show confirmation menu*/
			script_byte_vars.trade_accepted = 0;
			the_command = 0xC1C6;
			runCommand();
			if (script_byte_vars.trade_accepted == 0)
				break;

			item2->flags = ITEMFLG_ASPIR;
			item1->flags = ITEMFLG_OWNED;
			aspirant_ptr->item = script_byte_vars.inv_item_index;
			switch (item2->name) {
			case 132:	/*ZAPSTIK*/
				script_byte_vars.zapstiks_owned--;
				the_command = 0xC04B;
				break;
			case 104:	/*ROPE*/
				the_command = 0xC1BA;
				break;
			case 107:	/*GOBLET*/
				the_command = 0xC1BB;
				break;
			default:	/*STONE FLY*/
				the_command = 0xC1B9;
			}
			runCommand();
			break;
		} else {
			/*not interested*/
			item2->flags = ITEMFLG_OWNED | ITEMFLG_DONTWANT;
			the_command = 0xC1BD;
			runCommand();
			continue;
		}
	}

	reclaimRefusedItems();

	script_ptr = old_script;
	script_end_ptr = old_script_end;

	return 0;
}

/*
Steal a Zapstik form Protozorq
*/
uint16 SCR_4_StealZapstik(void) {
	byte *old_script;

	pers_t *pers = (pers_t *)(script_vars[kScrPool8_CurrentPers]);

	script_ptr++;
	old_script = script_ptr;

	if ((pers->index & ~7) != 0x30) {
		the_command = 0x9148;   /*YOU`VE ALREADY GOT IT*/
		runCommand();
	} else {
		pers->index &= ~0x18;

		script_vars[kScrPool3_CurrentItem] = &inventory_items[kItemZapstik1 - 1 + (script_byte_vars.cur_pers - 1) - kPersProtozorq1];
		script_byte_vars.steals_count++;

		bounceCurrentItem(ITEMFLG_OWNED, 85);  /*bounce to inventory*/

		the_command = 0x9147;   /*YOU GET HIS ZAPSTIK*/
		if (script_byte_vars.zapstik_stolen == 0) {
			runCommand();
			script_byte_vars.zapstik_stolen = 1;
			the_command = 0x9032;   /*THIS SHOULD GIVE YOU THE EDGE IN MOST COMBATS!*/
		}
		runCommand();
	}

	script_ptr = old_script;

	return 0;
}


byte wait_delta = 0;

/*
Wait for a specified number of seconds (real time) or a keypress
*/
void wait(byte seconds) {
	warning("STUB: Wait(%d)", seconds);

#if 0
	struct time t;
	uint16 endtime;

	seconds += wait_delta;
	if (seconds > 127)  /*TODO: is this a check for a negative value?*/
		seconds = 0;

	gettime(&t);
	endtime = t.ti_sec * 100 + t.ti_hund + seconds * 100;

	while (buttons == 0) {
		uint16 current;
		gettime(&t);
		current = t.ti_sec * 100 + t.ti_hund;
		if (endtime >= 6000 && current < 2048)  /*TODO: some kind of overflow check???*/
			current += 6000;
		if (current >= endtime)
			break;
	}
#endif
}

/*
Wait for a 4 seconds or a keypress
*/
uint16 SCR_2C_Wait4(void) {
	script_ptr++;
	wait(4);
	return 0;
}

/*
Wait for a specified number of seconds or a keypress
TODO: Always waits for a 4 seconds due to a bug?
*/
uint16 SCR_2D_Wait(void) {
	byte seconds;
	script_ptr++;
	seconds = *script_ptr++;
	(void)seconds;
	wait(4);    /*TODO: looks like a bug?*/
	return 0;
}

/*
Show blinking prompt indicator and wait for a keypress
*/
uint16 SCR_2E_promptWait(void) {
	script_ptr++;
	promptWait();
	return 0;
}


#define VARTYPE_VAR 0x80
#define VARTYPE_BLOCK 0x40
#define VARTYPE_WORD 0x20
#define VARTYPE_KIND 0x1F

#define VARSIZE_BYTE 0
#define VARSIZE_WORD 1

byte var_size;

/*
Fetch variable's value and address
*/
uint16 loadVar(byte **ptr, byte **varptr) {
	byte vartype;
	byte *varbase;
	uint16 value = 0;
	var_size = VARSIZE_BYTE;
	vartype = *((*ptr)++);
	if (vartype & VARTYPE_VAR) {
		/*variable*/
		byte varoffs;
		varbase = (byte *)script_vars[vartype & VARTYPE_KIND];
		if (vartype & VARTYPE_BLOCK) {
			byte *end;
			byte index = *((*ptr)++);
			varbase = seekToEntryW(varbase, index, &end);
		}
		varoffs = *((*ptr)++);
#if 1
		{
			int16 maxoffs = 0;
			switch (vartype & VARTYPE_KIND) {
			case kScrPool0_WordVars0:
			case kScrPool1_WordVars1:
				maxoffs = sizeof(script_word_vars);
				break;
			case kScrPool2_ByteVars:
				maxoffs = sizeof(script_byte_vars);
				break;
			case kScrPool3_CurrentItem:
				maxoffs = sizeof(item_t);
				break;
			case kScrPool4_ZoneSpots:
				maxoffs = RES_ZONES_MAX;
				break;
			case kScrPool5_Persons:
				maxoffs = sizeof(pers_list);
				break;
			case kScrPool6_Inventory:
				maxoffs = sizeof(inventory_items);
				break;
			case kScrPool7_Zapstiks:
				maxoffs = sizeof(inventory_items) - sizeof(item_t) * (kItemZapstik1 - 1);
				break;
			case kScrPool8_CurrentPers:
				maxoffs = sizeof(pers_t);
				break;
			}
			if (varoffs >= maxoffs) {
				warning("Scr var out of bounds @ %X (pool %d, ofs 0x%X, max 0x%X)", (uint16)(script_ptr - templ_data), vartype & VARTYPE_KIND, varoffs, maxoffs);
				promptWait();
			}
		}
#endif
		value = varbase[varoffs];
		if (vartype & VARTYPE_WORD) {
			value = (value << 8) | varbase[varoffs + 1];
			var_size = VARSIZE_WORD;
		}
		*varptr = &varbase[varoffs];

#if 0
		/*TODO: debug stuff, remove me*/
		if (varoffs == 0x48)
			warning("Var 2.%X = %X", varoffs, value);
#endif
	} else {
		/*immediate value*/
		value = *((*ptr)++);
		if (vartype & VARTYPE_WORD) {
			value = (value << 8) | *((*ptr)++);
			var_size = VARSIZE_WORD;
		}
		*varptr = 0;
	}
	return value;
}

#define MATHOP_END 0x80
#define MATHOP_CMP 0x40

#define MATHOP_ADD 0x20
#define MATHOP_SUB 0x10
#define MATHOP_AND 0x08
#define MATHOP_OR  0x04
#define MATHOP_XOR 0x02

#define MATHOP_EQ  0x20
#define MATHOP_B   0x10
#define MATHOP_A   0x08
#define MATHOP_NEQ 0x04
#define MATHOP_LE  0x02
#define MATHOP_GE  0x01

/*
Perform math/logic operation on two operands
*/
uint16 mathOp(byte op, uint16 op1, uint16 op2) {
	if (op & MATHOP_CMP) {
		if (op & MATHOP_EQ)
			if (op1 == op2) return 0xffff;
		if (op & MATHOP_B)
			if (op1 < op2) return 0xffff;
		if (op & MATHOP_A)
			if (op1 > op2) return 0xffff;
		if (op & MATHOP_NEQ)
			if (op1 != op2) return 0xffff;
		if (op & MATHOP_LE)
			if ((int16)op1 <= (int16)op2) return 0xffff;
		if (op & MATHOP_GE)
			if ((int16)op1 >= (int16)op2) return 0xffff;
		return 0;
	} else {
		if (op & MATHOP_ADD)
			op1 += op2;
		if (op & MATHOP_SUB)
			op1 -= op2;
		if (op & MATHOP_AND)
			op1 &= op2;
		if (op & MATHOP_OR)
			op1 |= op2;
		if (op & MATHOP_XOR)
			op1 ^= op2;
		return op1;
	}
}

/*
Evaluate an expression
*/
uint16 mathExpr(byte **ptr) {
	byte op;
	uint16 op1, op2;
	byte *opptr;
	op1 = loadVar(ptr, &opptr);
	while (((op = *((*ptr)++)) & MATHOP_END) == 0) {
		op2 = loadVar(ptr, &opptr);
		op1 = mathOp(op, op1, op2);
	}
	return op1;
}

/*
Evaluate an expression and assign result to a variable
*/
uint16 SCR_3B_MathExpr(void) {
	uint16 op1, op2;
	byte *opptr;

	script_ptr++;

	/*get result variable pointer*/
	op1 = loadVar(&script_ptr, &opptr);

	/*evaluate*/
	op2 = mathExpr(&script_ptr);

	/*store result*/
	/*TODO: original bug? MathExpr may overwrite global var_size, so mixed-size expressions will produce errorneous results*/
	if (var_size == VARSIZE_BYTE)
		*opptr = op2 & 255;
	else {
		opptr[0] = op2 >> 8;    /*store in big-endian*/
		opptr[1] = op2 & 255;
	}

	(void)op1;
	/*return op1;*/ /*previous value, never used?*/
	return 0;
}

/*
Discard current callchain (the real one) and execute command
*/
uint16 SCR_4D_PriorityCommand(void) {
	script_ptr++;
	the_command = *script_ptr++;          /*little-endian*/
	the_command |= (*script_ptr++) << 8;
	the_command |= 0xF000;

	g_vm->_prioritycommand_1 = true;

	return ScriptRerun;
}

/*
Jump to routine
*/
uint16 SCR_12_Chain(void) {
	script_ptr++;
	the_command = *script_ptr++;          /*little-endian*/
	the_command |= (*script_ptr++) << 8;
	script_ptr = getScriptSubroutine(the_command - 1);
	return 0;
}

/*
Absolute jump
Jumping past current routine ends the script
*/
uint16 SCR_33_Jump(void) {
	uint16 offs;
	script_ptr++;
	offs = *script_ptr++;          /*little-endian*/
	offs |= (*script_ptr++) << 8;
	script_ptr = templ_data + offs;
	return 0;
}

/*
Conditional jump (IF/ELSE block)
*/
uint16 SCR_3C_CondExpr(void) {
	script_ptr++;

	if (mathExpr(&script_ptr)) {
		/*fall to IF block*/
		script_ptr += 2;
	} else {
		/*branch to ELSE block*/
		script_ptr -= 1;    /*simulate opcode byte for Jump handler*/
		return SCR_33_Jump();
	}
	return 0;
}

/*
Absolute subroutine call
*/
uint16 SCR_34_Call(void) {
	uint16 offs;
	script_ptr++;
	offs = *script_ptr++;          /*little-endian*/
	offs |= (*script_ptr++) << 8;
	*script_stack_ptr++ = script_ptr;
	*script_stack_ptr++ = script_end_ptr;
	script_ptr = templ_data + offs;
	return 0;
}

/*
Return from script subroutine
*/
uint16 SCR_35_Ret(void) {
	script_end_ptr = *(--script_stack_ptr);
	script_ptr = *(--script_stack_ptr);
	return 0;
}

/*
Draw portrait, pushing it from left to right
*/
uint16 SCR_5_DrawPortraitLiftRight(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	/*TODO: use local args instead of globals*/
	cga_AnimLiftToRight(width, cur_image_pixels + width - 1, width, 1, height, CGA_SCREENBUFFER, CalcXY_p(x, y));
	return 0;
}

/*
Draw portrait, pushing it from right to left
*/
uint16 SCR_6_DrawPortraitLiftLeft(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	/*TODO: use local args instead of globals*/
	cga_AnimLiftToLeft(width, cur_image_pixels, width, 1, height, CGA_SCREENBUFFER, CalcXY_p(x + width - 1, y));
	return 0;
}

/*
Draw portrait, pushing it from top to bottom
*/
uint16 SCR_7_DrawPortraitLiftDown(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	/*TODO: use local args instead of globals*/
	cga_AnimLiftToDown(cur_image_pixels, cur_image_size_w, cur_image_size_w, cur_image_size_h, CGA_SCREENBUFFER, cur_image_offs);
	return 0;
}

/*
Draw portrait, pushing it from bottom to top
*/
uint16 SCR_8_DrawPortraitLiftUp(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	/*TODO: use local args instead of globals*/
	cga_AnimLiftToUp(cur_image_pixels, cur_image_size_w, cur_image_size_w, cur_image_size_h, CGA_SCREENBUFFER, x, y + height - 1);
	return 0;
}

/*
Draw portrait, no special effects
*/
uint16 SCR_9_DrawPortrait(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	cga_BlitAndWait(cur_image_pixels, cur_image_size_w, cur_image_size_w, cur_image_size_h, CGA_SCREENBUFFER, cur_image_offs);
	return 0;
}

/*
Draw portrait, no special effects
*/
uint16 SCR_A_DrawPortrait(void) {
	return SCR_9_DrawPortrait();
}

/*
Draw screen pixels using 2-phase clockwise twist
*/
void twistDraw(byte x, byte y, byte width, byte height, byte *source, byte *target) {
	int16 i;
	uint16 sx, ex, sy, ey, t;
	sx = x * 4;
	ex = x * 4 + width * 4 - 1;
	sy = y;
	ey = y + height - 1;

	for (i = 0; i < width * 4; i++) {
		cga_TraceLine(sx, ex, sy, ey, source, target);
		waitVBlank();
		sx += 1;
		ex -= 1;
	}

	t = sx;
	sx = ex + 1;
	ex = t - 1;

	t = sy;
	sy = ey;
	ey = t;

	for (i = 0; i < height; i++) {
		cga_TraceLine(sx, ex, sy, ey, source, target);
		waitVBlank();
		sy -= 1;
		ey += 1;
	}
}

/*
Draw image with twist-effect
*/
uint16 SCR_B_DrawPortraitTwistEffect(void) {
	byte x, y, width, height;
	uint16 offs;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	offs = CalcXY_p(x, y);

	cga_SwapScreenRect(cur_image_pixels, width, height, backbuffer, offs);
	twistDraw(x, y, width, height, backbuffer, frontbuffer);
	cga_BlitAndWait(scratch_mem2, width, width, height, backbuffer, offs);

	return 0;
}

/*
Draw screen pixels using arc-like sweep
*/
void arcDraw(byte x, byte y, byte width, byte height, byte *source, byte *target) {
	int16 i;
	uint16 sx, ex, sy, ey;
	sx = x * 4;
	ex = x * 4 + width * 2 - 1;
	sy = y + height - 1;
	ey = y + height - 1;

	for (i = 0; i < height; i++) {
		cga_TraceLine(sx, ex, sy, ey, source, target);
		waitVBlank();
		sy -= 1;
	}

	for (i = 0; i < width * 4; i++) {
		cga_TraceLine(sx, ex, sy, ey, source, target);
		waitVBlank();
		sx += 1;
	}

	for (i = 0; i < height + 1; i++) {
		cga_TraceLine(sx, ex, sy, ey, source, target);
		waitVBlank();
		sy += 1;
	}
}

/*
Draw image with arc-effect
*/
uint16 SCR_C_DrawPortraitArcEffect(void) {
	byte x, y, width, height;
	uint16 offs;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	offs = CalcXY_p(x, y);

	cga_SwapScreenRect(cur_image_pixels, width, height, backbuffer, offs);
	arcDraw(x, y, width, height, backbuffer, frontbuffer);
	cga_BlitAndWait(scratch_mem2, width, width, height, backbuffer, offs);

	return 0;
}

/*
Draw image with slow top-to-down reveal effect by repeatedly draw its every 17th pixel
*/
uint16 SCR_D_DrawPortraitDotEffect(void) {
	//int16 i;
	byte x, y, width, height;
	uint16 offs, step = 17;
	byte *target = CGA_SCREENBUFFER;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	cur_image_end = width * height;
	int16 count = 0;

	for (offs = 0; offs != cur_image_end;) {
		target[CalcXY_p(x + offs % cur_image_size_w, y + offs / cur_image_size_w)] = cur_image_pixels[offs]; // TODO check this

		if (count % 5 == 0)
			cga_blitToScreen(offs, 4, 1);

		offs += step;
		if (offs > cur_image_end)
			offs -= cur_image_end;

		count++;
	}
	return 0;
}

/*
Draw image with slow zoom-in reveal effect
*/
uint16 SCR_E_DrawPortraitZoomIn(void) {
	byte x, y, width, height;

	script_ptr++;

	if (!drawPortrait(&script_ptr, &x, &y, &width, &height))
		return 0;

	cga_AnimZoomIn(cur_image_pixels, cur_image_size_w, cur_image_size_h, frontbuffer, cur_image_offs);
	return 0;
}


uint16 drawPortraitZoomed(byte **params) {
	byte x, y, width, height;
	byte zwidth, zheight;

	right_button = 0;   /*prevent cancel or zoom parameters won't be consumed*/
	if (!drawPortrait(params, &x, &y, &width, &height))
		return 0;   /*TODO: maybe just remove the if/return instead?*/

	zwidth = *((*params)++);
	zheight = *((*params)++);

	/*adjust the rect for new size*/
	last_dirty_rect->width = zwidth + 2;
	last_dirty_rect->height = zheight;

	cga_ZoomImage(cur_image_pixels, cur_image_size_w, cur_image_size_h, zwidth, zheight, frontbuffer, cur_image_offs);
	return 0;
}

/*
Draw image with specified w/h zoom
*/
uint16 SCR_10_DrawPortraitZoomed(void) {
	script_ptr++;
	drawPortraitZoomed(&script_ptr);

#if 0
	/*TODO: debug wait*/
	promptWait();
#endif

	return 0;
}

/*
Hide portrait, pushing it from right to left
*/
uint16 SCR_19_HidePortraitLiftLeft(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	/*TODO: This originally was done by reusing door sliding routine*/

	/*offs = CalcXY_p(x + 1, y);*/
	offs++;

	while (--width) {
		cga_HideScreenBlockLiftToLeft(1, CGA_SCREENBUFFER, backbuffer, width, height, CGA_SCREENBUFFER, offs);
	}

	offs--;

	/*hide leftmost line*/
	/*TODO: move this to CGA?*/
	uint16 ooffs = offs;
	byte oh = height;
	while (height--) {
		memcpy(frontbuffer + offs, backbuffer + offs, 1);

		offs ^= g_vm->_line_offset;
		if ((offs & g_vm->_line_offset) == 0)
			offs += g_vm->_screenBPL;
	}
	cga_blitToScreen(ooffs, 1, oh);

	return 0;
}

/*
Hide portrait, pushing it from left to right
*/
uint16 SCR_1A_HidePortraitLiftRight(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	/*TODO: This originally was done by reusing door sliding routine*/

	offs = CalcXY_p(x + width - 2, y);

	while (--width) {
		cga_HideScreenBlockLiftToRight(1, CGA_SCREENBUFFER, backbuffer, width, height, CGA_SCREENBUFFER, offs);
	}

	offs++;

	/*hide leftmost line*/
	/*TODO: move this to CGA?*/
	uint16 ooffs = offs;
	byte oh = height;
	while (height--) {
		memcpy(frontbuffer + offs, backbuffer + offs, 1);

		offs ^= g_vm->_line_offset;
		if ((offs & g_vm->_line_offset) == 0)
			offs += g_vm->_screenBPL;
	}
	cga_blitToScreen(ooffs, 1, oh);

	return 0;
}

/*
Hide portrait, pushing it from bottom to top
*/
uint16 SCR_1B_HidePortraitLiftUp(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	offs = CalcXY_p(x, y + 1);

	while (--height) {
		cga_HideScreenBlockLiftToUp(1, CGA_SCREENBUFFER, backbuffer, width, height, CGA_SCREENBUFFER, offs);
	}

	/*hide topmost line*/
	/*TODO: move this to CGA?*/
	offs ^= g_vm->_line_offset;
	if ((offs & g_vm->_line_offset) != 0)
		offs -= g_vm->_screenBPL;
	memcpy(CGA_SCREENBUFFER + offs, backbuffer + offs, width);
	cga_blitToScreen(offs, width, 1);
	return 0;
}


/*
Hide portrait, pushing it from top to bottom
*/
uint16 SCR_1C_HidePortraitLiftDown(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	offs = CalcXY_p(x, y + height - 2);

	while (--height) {
		cga_HideScreenBlockLiftToDown(1, CGA_SCREENBUFFER, backbuffer, width, height, CGA_SCREENBUFFER, offs);
	}

	/*hide bottommost line*/
	/*TODO: move this to CGA?*/
	offs ^= g_vm->_line_offset;
	if ((offs & g_vm->_line_offset) == 0)
		offs += g_vm->_screenBPL;
	memcpy(CGA_SCREENBUFFER + offs, backbuffer + offs, width);
	cga_blitToScreen(offs, width, 1);
	return 0;
}


/*
Hide portrait with twist effect
*/
uint16 SCR_1E_HidePortraitTwist(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	twistDraw(x, y, width, height, backbuffer, frontbuffer);

	return 0;
}

/*
Hide portrait with arc effect
*/
uint16 SCR_1F_HidePortraitArc(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	arcDraw(x, y, width, height, backbuffer, frontbuffer);

	return 0;
}

/*
Hide portrait with dots effect
*/
uint16 SCR_20_HidePortraitDots(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	dot_effect_step = 17;
	dot_effect_delay = 100;
	copyScreenBlockWithDotEffect(backbuffer, x, y, width, height, frontbuffer);

	return 0;
}

/*
Play room's door open animation
*/
uint16 SCR_39_AnimRoomDoorOpen(void) {
	byte door;

	script_ptr++;
	door = *script_ptr++;
	animRoomDoorOpen(door);
	return 0;
}

/*
Play room's door close animation
*/
uint16 SCR_3A_AnimRoomDoorClose(void) {
	byte door;

	script_ptr++;
	door = *script_ptr++;
	animRoomDoorClose(door);
	return 0;
}

uint16 SCR_25_ChangeZoneOnly(void) {
	byte index;
	byte old = script_byte_vars.zone_room;

	script_ptr++;
	index = *script_ptr++;

	changeZone(index);
	script_byte_vars.zone_room = old;
	return 0;
}

#define JCOUNT 16

typedef struct jpoint_t {
	signed short x;
	signed short y;
} jpoint_t;

static jpoint_t jdeltas[JCOUNT] = {
	{0, -2},
	{1, -2},
	{2, -2},
	{2, -1},
	{2, 0},
	{2, 1},
	{2, 2},
	{1, 2},
	{0, 2},
	{ -1, 2},
	{ -2, 2},
	{ -2, 1},
	{ -2, 0},
	{ -2, -1},
	{ -2, -2},
	{ -1, -2}
};

/*
Play exploding zoom animation
*/
void jaggedZoom(byte *source, byte *target) {
	int16 i;
	jpoint_t points[JCOUNT + 1];
	uint16 outside = 0;
	uint16 cycle = 0;
	uint16 choices = 0;

	for (i = 0; i < JCOUNT; i++) {
		points[i].x = 320;
		points[i].y = 200;
	}
	points[i].x = 0;
	points[i].y = 0;

	for (;;) {
		cycle++;
		if (cycle % 8 == 0)
			choices = getRandW();

		for (i = 0; i < JCOUNT; i++) {
			signed short t;
			if (choices & (1 << i)) {
				t = points[i].x + jdeltas[i].x;
				if (t < 0 || t >= 600) { /*TODO: 640?*/
					outside |= 0x8000;  /*TODO: should this mask be rotated?*/
					t = points[i].x;
				}
				points[i].x = t;

				t = points[i].y + jdeltas[i].y;
				if (t < 0 || t >= 400) {
					outside |= 0x8000;
					t = points[i].y;
				}
				points[i].y = t;
			}
		}

		if (outside)
			break;

		for (i = 0; i < JCOUNT; i++) {
			uint16 sx = points[i].x;
			uint16 sy = points[i].y;
			uint16 ex = points[i + 1].x;
			uint16 ey = points[i + 1].y;
			if (ex == 0 && ey == 0) {
				ex = points[0].x;
				ey = points[0].y;
			}
			cga_TraceLine(sx / 2, ex / 2, sy / 2, ey / 2, source, target);
			/*TODO: waitVBlank(); maybe?*/
		}
	}
}

typedef struct star_t {
	uint16 ofs;
	byte pixel;
	byte mask;
	signed short x;
	signed short y;
	uint16 z;
} star_t;

/*
Generate random star
*/
void randomStar(star_t *star) {
	star->x = getRandW();
	star->y = getRandW();
	star->z = getRandW() & 0xFFF;
}

/*
Generate a bunch of random stars
*/
star_t *initStarfield(void) {
	int16 i;
	star_t *stars = (star_t *)scratch_mem2;
	for (i = 0; i < 300; i++) {
		stars[i].ofs = 0;
		stars[i].pixel = 0;
		stars[i].mask = 0;
		randomStar(&stars[i]);
	}
	return stars;
}

/*
Draw a frame of starfield animation and update stars
*/
void drawStars(star_t *stars, int16 iter, byte *target) {
	int16 i;
	/*TODO: bug? initialized 300 stars, but animated only 256?*/
	for (i = 0; i < 256; i++, stars++) {
		short z, x, y;
		byte pixel, mask;

		target[stars->ofs] &= stars->mask;
		if (stars->z < 328) {
			if (iter >= 30) {
				randomStar(stars);
				stars->z |= 0x1800;
			}
			continue;
		}

		stars->z -= 328;
		z = 0xCFFFFul / (stars->z + 16);
		x = ((long)z * stars->x) >> 16;
		y = ((long)z * stars->y) >> 16;

		x += 320 / 2;
		y += 200 / 2;
		if (x < 0 || x >= 320 || y < 0 || y >= 200) {
			stars->z = 0;
			continue;
		}

		stars->ofs = cga_CalcXY(x, y);

		pixel = (stars->z < 0xE00) ? 0xC0 : 0x40;
		pixel >>= (x % 4) * 2;
		mask = 0xC0;
		mask = ~(mask >> (x % 4) * 2);
		stars->pixel = pixel;
		stars->mask = mask;

		target[stars->ofs] &= mask;
		target[stars->ofs] |= pixel;
	}
}

/*
Play starfield animation
*/
void animStarfield(star_t *stars, byte *target) {
	int16 i;
	for (i = 100; i; i--)
		drawStars(stars, i, target);
}

/*
Play Game Over sequence and restart the game
*/
uint16 SCR_26_GameOver(void) {
	IFGM_PlaySample(160);
	in_de_profundis = 0;
	script_byte_vars.game_paused = 1;
	memset(backbuffer, 0, sizeof(backbuffer) - 2);  /*TODO: original bug?*/
	jaggedZoom(backbuffer, frontbuffer);
	cga_BackBufferToRealFull();
	cga_ColorSelect(0x30);
	animStarfield(initStarfield(), frontbuffer);
	playAnim(44, 156 / 4, 95);
	script_byte_vars.zone_index = 135;

	/*reload background*/
	while (!loadFond())
		askDisk2();

	jaggedZoom(backbuffer, frontbuffer);

	cga_BackBufferToRealFull();
	restartGame();
	return 0;
}

/*
Draw all active room's persons
*/
uint16 SCR_4C_DrawPersons(void) {
	script_ptr++;
	drawPersons();
	return 0;
}

/*
Redraw all room's static objects
*/
uint16 SCR_13_RedrawRoomStatics(void) {
	byte index;
	script_ptr++;
	index = *script_ptr++;
	redrawRoomStatics(index, 0);
	return 0;
}

/*
Go to a new zone
If go through a door, play door's opening animation
*/
uint16 SCR_42_LoadZone(void) {
	byte index;

	script_ptr++;
	index = *script_ptr++;

	skip_zone_transition = 0;
	if (right_button)
		script_byte_vars.last_door = 0;
	else {
		if ((script_byte_vars.cur_spot_flags & (SPOTFLG_20 | SPOTFLG_10 | SPOTFLG_8)) == 0)
			script_byte_vars.last_door = script_byte_vars.cur_spot_flags & 7;
		else if ((script_byte_vars.cur_spot_flags & ((SPOTFLG_20 | SPOTFLG_10 | SPOTFLG_8))) == SPOTFLG_8) {
			skip_zone_transition = 1;
			animRoomDoorOpen(script_byte_vars.cur_spot_idx);
			script_byte_vars.last_door = script_byte_vars.cur_spot_flags & 7;
		} else
			script_byte_vars.last_door = 0;
	}
	beforeChangeZone(index);
	changeZone(index);
	script_byte_vars.zone_area_copy = script_byte_vars.zone_area;
	script_byte_vars.cur_spot_idx = findInitialSpot();
	skip_zone_transition |= script_byte_vars.cur_spot_idx;

	drawRoomStatics();

	if (script_byte_vars.bvar_5F != 0) {
		redrawRoomStatics(script_byte_vars.bvar_5F, 0);
		script_byte_vars.bvar_5F = 0;
	}

	backupSpotsImages();
	prepareVorts();
	prepareTurkey();
	prepareAspirant();
	drawPersons();
	script_byte_vars.cur_spot_flags = 0;
	return 0;
}

/*
Draw current sprites
*/
uint16 SCR_59_blitSpritesToBackBuffer(void) {
	script_ptr++;
	blitSpritesToBackBuffer();
	return 0;
}

/*
Apply current palette
*/
uint16 SCR_5A_SelectPalette(void) {
	script_ptr++;
	selectPalette();
	return 0;
}

/*
Apply specific palette
*/
uint16 SCR_5E_SelectTempPalette(void) {
	byte index;
	script_ptr++;
	index = *script_ptr++;
	selectSpecificPalette(index);
	return 0;
}

/*
Draw new zone
*/
uint16 SCR_43_RefreshZone(void) {
	script_ptr++;
	refreshZone();
	return 0;
}

/*
Go to new zone and draw it
*/
uint16 SCR_36_ChangeZone(void) {
	SCR_42_LoadZone();
	refreshZone();
	return 0;
}

/*
Draw a static sprite in the room
*/
void SCR_DrawRoomObjectBack(byte *x, byte *y, byte *w, byte *h) {
	byte obj[3];

	script_ptr++;
	obj[0] = *script_ptr++; /*spr*/
	obj[1] = *script_ptr++; /*x*/
	obj[2] = *script_ptr++; /*y*/

	drawRoomStaticObject(obj, x, y, w, h);
}

/*
Draw a static sprite in the room (to backbuffer)
*/
uint16 SCR_5F_DrawRoomObjectBack(void) {
	byte x, y, w, h;
	SCR_DrawRoomObjectBack(&x, &y, &w, &h);
	return 0;
}

/*
Display a static sprite in the room (to screen)
*/
uint16 SCR_11_DrawRoomObject(void) {
	byte x, y, w, h;
	SCR_DrawRoomObjectBack(&x, &y, &w, &h);
	cga_CopyScreenBlock(backbuffer, w, h, frontbuffer, CalcXY_p(x, y));
	return 0;
}

/*
Draw box with item sprite and its name
*/
uint16 SCR_3_DrawItemBox(void) {
	byte current;

	item_t *item;
	byte x, y;
	byte *msg;

	script_ptr++;
	current = *script_ptr++;

	if (current)
		item = (item_t *)script_vars[kScrPool3_CurrentItem];
	else
		item = &inventory_items[aspirant_ptr->item - 1];

	x = dirty_rects[0].x;
	y = dirty_rects[0].y + 70;
	msg = seekToString(desci_data, 274 + item->name);

	desciTextBox(x, y, 18, msg);
	drawSpriteN(item->sprite, x, y + 1, frontbuffer);

	return 0;
}

/*
Draw simple bubble with text
*/
uint16 SCR_37_desciTextBox(void) {
	byte x, y, w;
	byte *msg;
	script_ptr++;
	msg = seekToStringScr(desci_data, *script_ptr, &script_ptr);
	script_ptr++;
	x = *script_ptr++;
	y = *script_ptr++;
	w = *script_ptr++;
	desciTextBox(x, y, w, msg);
	return 0;
}


/*
Play portrait animation
*/
uint16 SCR_18_AnimPortrait(void) {
	byte layer, index, delay;
	script_ptr++;

	layer = *script_ptr++;
	index = *script_ptr++;
	delay = *script_ptr++;

	animPortrait(layer, index, delay);

	return 0;
}

/*
Play animation
*/
uint16 SCR_38_PlayAnim(void) {
	byte index, x, y;
	script_ptr++;
	index = *script_ptr++;
	x = *script_ptr++;
	y = *script_ptr++;
	playAnim(index, x, y);
	return 0;
}

/*
Pop up the actions menu and handle its commands
*/
uint16 SCR_3D_ActionsMenu(void) {
	uint16 cmd;

	byte *old_script = script_ptr;
	byte *old_script_end = script_end_ptr;

	act_menu_x = 0xFF;

	for (;;) {
		script_ptr++;
		actionsMenu(&script_ptr);
		if (the_command == 0xFFFF)
			break;

		cmd = the_command & 0xF000;
		if (cmd == 0xC000 || cmd == 0xA000) {
			return ScriptRerun;
		}

		runCommand();
		// For properly returning to RunScript() during the 2nd Call from SCR_4D_PriorityCommand() to runCommand()
		if (g_vm->_prioritycommand_1) {
			g_vm->_prioritycommand_2 = true;
			break;
		}

		script_byte_vars.used_commands++;
		if (script_byte_vars.bvar_43 == 0 && script_byte_vars.used_commands > script_byte_vars.check_used_commands) {
			the_command = Swap16(script_word_vars.next_aspirant_cmd);
			if (the_command)
				return ScriptRerun;
		}

		script_ptr = old_script;
		if (--script_byte_vars.tries_left == 0)
			resetAllPersons();
	}

	script_end_ptr = old_script_end;
	return ScriptContinue;
}

/*
The Wall room puzzle
*/
uint16 SCR_3E_TheWallAdvance(void) {
	script_ptr++;

	IFGM_PlaySample(29);
	script_byte_vars.the_wall_phase = (script_byte_vars.the_wall_phase + 1) % 4;
	switch (script_byte_vars.the_wall_phase) {
	default:
		theWallPhase3_DoorOpen1();
		break;
	case 0:
		theWallPhase0_DoorOpen2();
		break;
	case 1:
		theWallPhase1_DoorClose1();
		break;
	case 2:
		theWallPhase2_DoorClose2();
		break;
	}

	return 0;
}

/*
When playing cups with proto
*/
uint16 SCR_28_MenuLoop(void) {
	byte cursor;
	byte mask, value;

	script_ptr++;
	cursor = *script_ptr++;
	mask = *script_ptr++;
	value = *script_ptr++;

	selectCursor(cursor);

	menuLoop(mask, value);

	return 0;
}


/*
Restore screen data from back buffer as specified by dirty rects of specified index
*/
uint16 SCR_2A_PopDialogRect(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs); /*TODO: implicit target*/
	cga_CopyScreenBlock(backbuffer, 2, 21, CGA_SCREENBUFFER, offs = (x << 8) | y);  /*TODO: implicit target*/

	cur_dlg_index = 0;

	return 0;
}

/*
Restore screen data from back buffer as specified by dirty rect of kind dialog bubble
*/
uint16 SCR_2B_PopAllBubbles(void) {
	script_ptr++;
	popDirtyRects(DirtyRectBubble);
	return 0;
}

/*
Hide a portrait, with shatter effect
*/
uint16 SCR_22_HidePortraitShatter(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	cga_HideShatterFall(CGA_SCREENBUFFER, backbuffer, width, height, CGA_SCREENBUFFER, offs);

	return 0;
}

/*
Hide a portrait, no special effects
*/
uint16 SCR_23_HidePortrait(void) {
	byte index;
	byte kind;
	byte x, y;
	byte width, height;
	uint16 offs;

	script_ptr++;
	index = *script_ptr++;

	getDirtyRectAndFree(index, &kind, &x, &y, &width, &height, &offs);
	if (right_button) {
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
		return 0;
	}

	cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);

	return 0;
}

/*
Hide a portrait, no special effects
*/
uint16 SCR_1D_HidePortrait(void) {
	return SCR_23_HidePortrait();
}

/*
Hide a portrait, no special effects
*/
uint16 SCR_21_HidePortrait(void) {
	return SCR_23_HidePortrait();
}

/*
Hide a portrait, no special effects
*/
uint16 SCR_3F_HidePortrait(void) {
	return SCR_23_HidePortrait();
}

/*
Restore screen data from back buffer for all portraits
*/
uint16 SCR_24_PopAllPortraits(void) {
	script_ptr++;
	popDirtyRects(DirtyRectSprite);
	return 0;
}

/*
Restore screen data from back buffer for all text bubbles
*/
uint16 SCR_40_PopAllTextBoxes() {
	script_ptr++;
	popDirtyRects(DirtyRectText);
	return 0;
}

/*
Draw updated Hands in Who Will Be Saved
*/
uint16 SCR_41_LiftHand(void) {
	script_ptr++;
	redrawRoomStatics(92, script_byte_vars.hands);
	cga_BackBufferToRealFull();
	playSound(31);
	return 0;
}

byte fight_mode = 0;

uint16 SCR_30_Fight(void) {
	static byte player_image[] = {26, 0, 0};
	byte *image = player_image;

	byte x, y, width, height, kind;
	uint16 offs;
	byte *old_script, *old_script_end = script_end_ptr;
	pers_t *pers = (pers_t *)(script_vars[kScrPool8_CurrentPers]);

	byte strength, win, rnd;

	script_ptr++;
	old_script = script_ptr;

	x = 140 / 4;
	y = 20;

	fight_mode = 1;

	if (pers->name != 44) {	/*VORT*/
		if (next_vorts_cmd == 0xA015) {
			the_command = 0xA015;
			runCommand();
			selectPerson(PersonOffset(pers - pers_list));
		}
		if (Swap16(script_word_vars.next_aspirant_cmd) == 0xC357) {
			the_command = 0xC357;
			runCommand();
		}

		pers = (pers_t *)(script_vars[kScrPool8_CurrentPers]);
		if (pers->name != 56 && pers->name != 51) {	/*MONKEY, TURKEY*/
			x = dirty_rects[0].x + 64 / 4;
			y = dirty_rects[0].y;
			fight_mode = 0;
		}
	}

	/*draw player portrait*/
	player_image[1] = x;
	player_image[2] = y;
	if (drawPortrait(&image, &x, &y, &width, &height))
		cga_AnimLiftToLeft(width, cur_image_pixels, width, 1, height, CGA_SCREENBUFFER, CalcXY_p(x + width - 1, y));

	blinkToWhite();

	if (pers->name != 44 && pers->name != 56 && pers->name != 51) {	/*VORT, MONKEY, TURKEY*/
		getDirtyRectAndFree(1, &kind, &x, &y, &width, &height, &offs);
		cga_CopyScreenBlock(backbuffer, width, height, CGA_SCREENBUFFER, offs);
	}

	/*check fight outcome*/

	strength = 0;

	script_byte_vars.fight_status = 0;

	if (script_byte_vars.extreme_violence == 0) {
		static byte character_strength[] = {
			1,	/*THE MASTER OF ORDEALS*/
			3,	/*PROTOZORQ*/
			1,	/*VORT*/
			1,	/*THE POORMOUTH*/
			1,	/*KHELE*/
			1,  /*THE MISTRESS*/
			5,	/*DEILOS*/
			3,	/*ASPIRANT*/
			2,	/*DIVO*/
			1,	/*TURKEY*/
			1,	/*PRIESTESS*/
			1,	/*SCI FI*/
			1,	/*NORMAJEEN*/
			1,	/*ASH*/
			1,	/*MONKEY*/
			1,	/*HARSSK*/
			1	/*ZORQ*/
		};

		strength = character_strength[pers->name - 42];

		/*check if can decrease*/
		if (strength != 1 && (pers->flags & PERSFLG_80))
			strength--;

		if (script_byte_vars.zapstiks_owned != 0 || script_byte_vars.bvar_66 != 0)
			strength--;
	}

	/*check if can increase*/
	if (strength != 5) {
		if ((pers->item >= kItemDagger1 && pers->item <= kItemDagger4)
		        || (pers->item >= kItemZapstik1 && pers->item <= kItemZapstik13)	/*TODO: ignore kItemZapstik14?*/
		        || pers->item == kItemBlade || pers->item == kItemChopper
		        || ((pers->index >> 3) == 6))
			strength++;
	}

	/*
	win flags:
	   1 - player win
	   2 - player lose
	   4 - "YOU RUN..."
	   8 - "THE ASPIRANT STEALS EVERYTHING YOU HAVE!"
	0x20 - "TU NUH RAY VUN IN FAY VRABLE SIT YOU AISHUN."
	0x40 - "OUT KUM UNSER TUN."
	0x80 - "SIT YOU ASHUN KRITI KAL FOR TOONUH RAY VUN."
	*/

	win = 1;
	rnd = script_byte_vars.rand_value;

#ifdef CHEAT
	strength = 1;
#endif

	if (strength >= 2) {
		if (strength == 2) {
			if (rnd >= 205)
				win = getRand() < 128 ? (0x40 | 0x10 | 1) : (0x40 | 0x10 | 2);
		} else if (strength == 4 && rnd < 100) {
			win = getRand() < 128 ? (0x40 | 0x10 | 1) : (0x40 | 0x10 | 2);
		} else {
			win = 2;
			if (strength == 3) {
				if (rnd < 128)  /*TODO: check me, maybe original bug (checks against wrong reg?)*/
					win = getRand() < 51 ? (0x80 | 0x10 | 1) : (0x80 | 0x10 | 2);
				else
					win = getRand() < 205 ? (0x20 | 0x10 | 1) : (0x20 | 0x10 | 2);
			}
		}
	}

	script_byte_vars.fight_status = win;

	script_ptr = old_script;
	script_end_ptr = old_script_end;
	return 0;
}

byte prev_fight_mode = 0;
uint16 fight_pers_ofs = 0;

typedef struct fightentry_t {
	byte   room;
	animdesc_t      anim;
} fightentry_t;

fightentry_t fightlist1[] = {
	{50, {47, {{36, 153}}}},
	{51, {47, {{36, 153}}}},
	{53, {47, {{37, 160}}}},
	{54, {47, {{36, 153}}}},
	{56, {47, {{31, 128}}}},
	{57, {47, {{27, 161}}}},
	{58, {47, {{28, 152}}}},
	{59, {47, {{25, 153}}}},
	{60, {47, {{22, 155}}}},
	{61, {47, {{27, 160}}}}
};

fightentry_t fightlist2[] = {
	{ 1, {24, {{42, 128}}}},
	{ 2, {24, {{44, 126}}}},
	{ 3, {24, {{47, 126}}}},
	{ 4, {24, {{44, 126}}}},
	{ 5, {24, {{47, 126}}}},
	{ 6, {24, {{28, 126}}}},
	{ 7, {24, {{55, 126}}}},
	{ 8, {24, {{49, 126}}}},
	{10, {24, {{41, 147}}}},
	{11, {24, {{41, 147}}}},
	{18, {24, {{41, 147}}}},
	{19, {24, {{41, 147}}}},
	{90, {24, {{44, 121}}}},
	{91, {28, {{24, 123}}}},
	{12, {24, {{41, 147}}}},
	{13, {24, {{41, 147}}}},
	{35, {24, {{39, 147}}}},
	{42, {24, {{39, 147}}}},
	{50, {55, {{46, 130}}}},
	{52, {24, {{42, 121}}}},
	{54, {55, {{46, 130}}}},
	{61, {67, {{37, 125}}}},
	{62, {55, {{32, 133}}}},
	{63, {55, {{32, 133}}}},
	{64, {55, {{32, 133}}}},
	{65, {55, {{32, 133}}}}
};

fightentry_t fightlist3[] = {
	{ 2, {25, {{35, 144}}}},
	{ 3, {25, {{38, 144}}}},
	{ 4, {25, {{35, 144}}}},
	{ 5, {25, {{38, 144}}}},
	{ 6, {25, {{19, 144}}}},
	{ 7, {25, {{46, 144}}}},
	{ 8, {26, {{64, 132}}}},
	{10, {25, {{32, 165}}}},
	{11, {25, {{32, 165}}}},
	{12, {25, {{32, 165}}}},
	{13, {25, {{32, 165}}}},
	{18, {25, {{32, 165}}}},
	{19, {25, {{32, 165}}}},
	{90, {36, {{27, 127}}}},
	{91, {27, {{44, 123}}}},
	{35, {25, {{30, 165}}}},
	{42, {25, {{30, 165}}}},
	{50, {56, {{36, 153}}}},
	{54, {56, {{36, 153}}}},
	{62, {56, {{22, 156}}}},
	{63, {56, {{22, 156}}}},
	{64, {56, {{22, 156}}}},
	{65, {56, {{22, 156}}}}
};

/*Draw defeated enemy*/
uint16 SCR_31_Fight2(void) {
	script_ptr++;

	if (script_byte_vars.bvar_43 != 18) {
		pers_t *pers = (pers_t *)(script_vars[kScrPool8_CurrentPers]);
		fight_pers_ofs = (byte *)pers - (byte *)pers_list; /*TODO check size*/
		pers->flags |= PERSFLG_40;
		pers->area = 0;
		found_spot->flags &= ~SPOTFLG_80;
		if (pers->index == 16) {	/*Vort trio*/
			pers_list[kPersVort2].area = script_byte_vars.zone_area;
			pers_list[kPersVort2].flags = pers->flags;
			if (script_byte_vars.zapstiks_owned == 0) {
				static const animdesc_t anim19 = {ANIMFLG_USESPOT | 19, { { 0, 0 } }};
				animateSpot(&anim19);
			}
			the_command = next_vorts_cmd;
			runCommand();
		} else if (pers->index == 8) {	/*Vort duo*/
			pers_list[kPersVort3].area = script_byte_vars.zone_area;
			pers_list[kPersVort3].flags = pers->flags;
			if (script_byte_vars.zapstiks_owned == 0) {
				static const animdesc_t anim20 = {ANIMFLG_USESPOT | 20, { { 0, 0 } }};
				animateSpot(&anim20);
			}
			the_command = next_vorts_cmd;
			runCommand();
		} else {
			if (prev_fight_mode == 0
			        && script_byte_vars.zapstiks_owned != 0
			        && fight_mode == 0) {
				script_byte_vars.fight_status &= ~1;
			} else {
				uint16 i;
				fightentry_t *fightlist;
				uint16 fightlistsize;
				byte animidx;

				prev_fight_mode = 0;
				switch (pers->name) {
				case 56:	/*MONKEY*/
					animidx = 47;
					fightlist = fightlist1;
					fightlistsize = 10;
					break;
				case 51:	/*TURKEY*/
					next_turkey_cmd = 0;
					animidx = 66;
					fightlist = fightlist1;
					fightlistsize = 10;
					break;
				default:
					animidx = 0;
					fightlist = fightlist2;
					fightlistsize = 26;
				}

				for (i = 0; i < fightlistsize; i++) {
					if (fightlist[i].room == script_byte_vars.zone_room) {
						if (animidx != 0) {
							fightlist[i].anim.index = animidx;
							IFGM_PlaySample(150);
						}
						if (fightlist[i].anim.index == 55)
							playSound(151);
						playAnim(fightlist[i].anim.index, fightlist[i].anim.params.coords.x, fightlist[i].anim.params.coords.y);
						break;
					}
				}
			}
		}
	}

	return 0;
}

void FightWin(void) {
	script_byte_vars.bvar_67 = 0;

	if (script_byte_vars.bvar_43 != 18 && *spot_sprite != 0) {
		cga_RestoreImage(*spot_sprite, frontbuffer);
		cga_RestoreImage(*spot_sprite, backbuffer);

		if (script_byte_vars.extreme_violence == 0
		        && script_byte_vars.bvar_60 == 0
		        && script_byte_vars.zapstiks_owned != 0
		        && fight_mode == 0) {
			script_byte_vars.bvar_67 = 1;
			playSound(149);
			playAnim(40, found_spot->sx, found_spot->sy);
		}
	}

	prev_fight_mode = script_byte_vars.extreme_violence;
	script_byte_vars.extreme_violence = 0;
}

uint16 SCR_32_FightWin(void) {

	script_ptr++;

	FightWin();

	return 0;
}

void DrawDeathAnim(void) {
	int16 i;

	/*remove existing cadaver if any*/
	if (selectPerson(PersonOffset(kPersCadaver))) {
		found_spot->flags &= ~SPOTFLG_80;
		cga_RestoreImage(*spot_sprite, backbuffer);
	}

	for (i = 0; i < 23; i++) {
		if (fightlist3[i].room == script_byte_vars.zone_room) {
			playAnim(fightlist3[i].anim.index, fightlist3[i].anim.params.coords.x, fightlist3[i].anim.params.coords.y);
			break;
		}
	}
}

uint16 SCR_16_DrawDeathAnim(void) {
	script_ptr++;

	DrawDeathAnim();

	return 0;
}

uint16 SCR_60_ReviveCadaver(void) {
	pers_t *pers;
	script_ptr++;

	blitSpritesToBackBuffer();

	selectPerson(PersonOffset(kPersCadaver));

	script_byte_vars.bvar_60 = 1;
	FightWin();
	script_byte_vars.bvar_60 = 0;
	pers_list[kPersCadaver].area = 0;

	selectPerson(fight_pers_ofs);
	zone_spots[5].flags = SPOTFLG_40 | SPOTFLG_10 | SPOTFLG_2 | SPOTFLG_1;
	found_spot->flags |= SPOTFLG_80;

	pers = (pers_t *)script_vars[kScrPool8_CurrentPers];
	pers->flags &= ~PERSFLG_40;
	pers->area = script_byte_vars.zone_area;

	drawPersons();
	cga_BackBufferToRealFull();

	return 0;
}


uint16 SCR_57_ShowCharacterSprite(void) {
	byte index, x, y;

	script_ptr++;
	index = *script_ptr++;
	x = *script_ptr++;
	y = *script_ptr++;

	drawCharacterSprite(index, x, y, frontbuffer);

	return 0;
}

uint16 SCR_58_DrawCharacterSprite(void) {
	byte index, x, y;

	script_ptr++;
	index = *script_ptr++;
	x = *script_ptr++;
	y = *script_ptr++;

	drawCharacterSprite(index, x, y, backbuffer);

	return 0;
}

extern void exitGame(void);

uint16 SCR_15_SelectSpot(void) {
	byte mask, index;

	script_ptr++;
	mask = *script_ptr++;
	index = *script_ptr++;
	if (mask != 0) {
		index = findSpotByFlags(mask, index);   /*TODO: return 0 if not found?*/
		if (index == 0xFF) {
			TODO("ERROR: SelectSpot: spot not found");
			exitGame(); /*hard abort*/
		}
	}
	found_spot = &zone_spots[index - 1];
	script_byte_vars.cur_spot_idx = index;
	spot_sprite = &sprites_list[index - 1];

	findPerson();

	if (script_byte_vars.cur_pers == 0)
		script_vars[kScrPool8_CurrentPers] = &pers_list[kPersProtozorq12];

	return 0;
}

uint16 SCR_44_BackBufferToScreen(void) {
	script_ptr++;
	cga_BackBufferToRealFull();
	return 0;
}

/*
Animate De Profundis room on entry
*/
uint16 SCR_45_DeProfundisRoomEntry(void) {
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Platform*/
	sprofs = getPuzzlSprite(3, 140 / 4, 174, &w, &h, &ofs);
	cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);

	/*draw Granite Monster*/
	sprofs = getPuzzlSprite(119, 128 / 4, 94, &w, &h, &ofs);
	cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);

	promptWait();

	for (; h; h--) {
		waitVBlank();
		waitVBlank();
		cga_BlitFromBackBuffer(w, 1, CGA_SCREENBUFFER, ofs);

		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) == 0)
			ofs += g_vm->_screenBPL;

		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	cga_BlitFromBackBuffer(w, 1, CGA_SCREENBUFFER, ofs);

	return 0;
}

/*
Animate De Profundis hook (lower)
*/
uint16 SCR_46_DeProfundisLowerHook(void) {
	byte y;
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Hook*/
	sprofs = getPuzzlSprite(96, 140 / 4, 18, &w, &h, &ofs);

	h = 1;
	y = 15;
	sprofs = y * 20 / 4 * 2;    /*TODO: 20 is the sprite width. replace with w?*/

	for (; y; y--) {
		waitVBlank();
		cga_BlitFromBackBuffer(w, h, CGA_SCREENBUFFER, ofs);

		h++;
		sprofs -= 20 / 4 * 2;

		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	return 0;
}

/*
Animate De Profundis monster (rise)
*/
uint16 SCR_47_DeProfundisRiseMonster(void) {
	byte y;
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Granite Monster head*/
	sprofs = getPuzzlSprite(118, 112 / 4, 174, &w, &h, &ofs);

	h = 1;
	y = 68;

	for (; y; y--) {
		waitVBlank();

		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) != 0)
			ofs -= g_vm->_screenBPL;

		h++;

		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	return 0;
}

/*
Animate De Profundis monster (lower)
*/
uint16 SCR_48_DeProfundisLowerMonster(void) {
	byte y;
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Hook*/
	sprofs = getPuzzlSprite(118, 112 / 4, 106, &w, &h, &ofs);

	y = 34;

	for (; y; y--) {
		waitVBlank();
		cga_BlitFromBackBuffer(w, 1, CGA_SCREENBUFFER, ofs);

		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) == 0)
			ofs += g_vm->_screenBPL;

		h--;
		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	return 0;
}

/*
Animate De Profundis hook (rise)
*/
uint16 SCR_49_DeProfundisRiseHook(void) {
	byte y;
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Hook*/
	sprofs = getPuzzlSprite(96, 140 / 4, 18, &w, &h, &ofs);

	h = 16;
	y = 15;

	for (; y; y--) {
		waitVBlank();
		cga_BlitFromBackBuffer(w, h, CGA_SCREENBUFFER, ofs);

		h--;
		sprofs += 20 / 4 * 2;

		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	cga_BlitFromBackBuffer(w, 1, CGA_SCREENBUFFER, ofs);

	return 0;
}

/*
Animate De Profundis platform
*/
uint16 SCR_65_DeProfundisMovePlatform(void) {
	byte state;
	byte x, y;
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;
	state = *script_ptr++;

	x = 140 / 4;
	y = 174;
	if (state != 0)
		y += 4;

	/*draw Platform*/
	sprofs = getPuzzlSprite(3, x, y, &w, &h, &ofs);

	y = 4;
	if (state) {
		h -= 4;
		y--;
	}

	for (; y; y--) {
		waitVBlank();
		cga_BlitFromBackBuffer(w, h, CGA_SCREENBUFFER, ofs);

		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) == 0)
			ofs += g_vm->_screenBPL;

		h--;

		cga_BlitScratchBackSprite(sprofs, w, h, CGA_SCREENBUFFER, ofs);
	}

	if (state)
		cga_BlitFromBackBuffer(w, h, CGA_SCREENBUFFER, ofs);

	return 0;
}

/*
Animate De Profundis monster ride to exit door
*/
uint16 SCR_66_DeProfundisRideToExit(void) {
	uint16 w, h;
	uint16 sprofs, ofs;

	script_ptr++;

	/*draw Granite Monster*/
	sprofs = getPuzzlSprite(119, 128 / 4, 139, &w, &h, &ofs);

	cga_BlitScratchBackSprite(sprofs, w, 20, backbuffer, ofs);

	dot_effect_delay = 1;
	dot_effect_step = 17;
	copyScreenBlockWithDotEffect(backbuffer, 112 / 4, 139, 72 / 4, 40, frontbuffer);

	return 0;
}

/*
Draw item bounce to room objects animation
*/
uint16 SCR_4E_BounceCurrentItemToRoom(void) {
	script_ptr++;
	bounceCurrentItem(ITEMFLG_ROOM, 43);
	return 0;
}

/*
Draw item bounce to inventory animation
*/
uint16 SCR_4F_BounceCurrentItemToInventory(void) {
	script_ptr++;
	bounceCurrentItem(ITEMFLG_OWNED, 85);
	return 0;
}

/*
Draw item bounce to inventory animation
*/
uint16 SCR_50_BounceItemToInventory(void) {
	byte itemidx;

	script_ptr++;
	itemidx = *script_ptr++;
	script_vars[kScrPool3_CurrentItem] = &inventory_items[itemidx - 1];

	bounceCurrentItem(ITEMFLG_OWNED, 85);
	return 0;
}

/*
Take away Protozorq's zapstik and bounce it to room
*/
uint16 SCR_4B_ProtoDropZapstik(void) {
	pers_t *pers = (pers_t *)(script_vars[kScrPool8_CurrentPers]);

	script_ptr++;

	if ((pers->index & 0x38) != 0x30)
		return 0;

	pers->index &= ~0x18;

	script_vars[kScrPool3_CurrentItem] = &inventory_items[kItemZapstik1 - 1 + (script_byte_vars.cur_pers - 1) - kPersProtozorq1];

	bounceCurrentItem(ITEMFLG_ROOM, 43);

	return 0;
}

/*
Take away Aspirant's item and bounce it to the inventory
*/
void LootAspirantsItem(void) {
	if (aspirant_ptr->item != 0) {
		item_t *item = &inventory_items[aspirant_ptr->item - 1];
		aspirant_ptr->item = 0;

		script_vars[kScrPool3_CurrentItem] = item;
		script_byte_vars.steals_count++;
		script_byte_vars.bvar_6D[aspirant_ptr->index >> 6] = item->name; /*TODO: check these index bits*/
		bounceCurrentItem(ITEMFLG_OWNED, 85);
		the_command = 0x90AA;   /*OK*/
	} else
		the_command = 0x9140;   /*NOTHING ON HIM*/
}

/*
Take away Aspirant's item and bounce it to the inventory
*/
uint16 SCR_2F_LootAspirantsItem() {
	script_ptr++;
	LootAspirantsItem();
	return ScriptRerun;
}

/*
Trade with Skull Trader
*/
uint16 SCR_51_SkullTraderItemTrade(void) {
	byte *old_script, *old_script_end = script_end_ptr;
	byte status;

	if (script_byte_vars.bvar_26 >= 63)  /*TODO: hang?*/
		return 0;

	script_ptr++;
	old_script = script_ptr;

	inv_bgcolor = 0xFF;
	openInventory(0xFF, ITEMFLG_OWNED);

	status = 1;
	if (inv_count != 0) {
		status = 2;
		if (the_command != 0) {
			status = 3;
			if (script_byte_vars.inv_item_index >= kItemRope1 && script_byte_vars.inv_item_index <= kItemLantern4) {
				the_command = 0xC204;	/*WHICH ONE DO YOU WANT?*/
				runCommand();

				((item_t *)(script_vars[kScrPool3_CurrentItem]))->flags = 0;

				openInventory(0xFF, ITEMFLG_TRADER);

				status = 4;
				if (the_command != 0) {
					/*50% chance to win the item*/
					status = 5;	/*lose*/
#ifdef CHEAT_TRADER
					{	/*always win at the Skull Trader*/
#else
					if (script_byte_vars.rand_value < 128) {
#endif
						status = 6;	/*win*/
						((item_t *)(script_vars[kScrPool3_CurrentItem]))[-1].flags = ITEMFLG_TRADER; /*offer previous item copy for next trade*/
						((item_t *)(script_vars[kScrPool3_CurrentItem]))->flags = 0;	/*consume selected item*/
					}
				}
			}
		}
	}

	script_byte_vars.skull_trader_status = status;

	script_ptr = old_script;
	script_end_ptr = old_script_end;

	return 0;
}

uint16 SCR_52_RefreshSpritesData(void) {
	script_ptr++;
	refreshSpritesData();
	return 0;
}

uint16 SCR_53_FindInvItem(void) {
	byte first, count, flags, i;
	item_t *item;
	script_ptr++;
	first = *script_ptr++;
	count = *script_ptr++;
	flags = *script_ptr++;
	item = &inventory_items[first - 1];
	for (i = 0; i < count; i++) {
		if (item[i].flags == flags) {
			script_vars[kScrPool3_CurrentItem] = &item[i];
			return 0;
		}
	}
	script_vars[kScrPool3_CurrentItem] = &item[count - 1];
	return 0;
}

/*
Restore whole room from backbuffer, with dot effect
*/
uint16 SCR_54_DotFadeRoom(void) {
	script_ptr++;

	dot_effect_delay = 1;
	dot_effect_step = 17;
	copyScreenBlockWithDotEffect(backbuffer, room_bounds_rect.sx, room_bounds_rect.sy, room_bounds_rect.ex - room_bounds_rect.sx, room_bounds_rect.ey - room_bounds_rect.sy, frontbuffer);

	return 0;
}

uint16 SCR_55_DrawRoomItemsIndicator(void) {
	script_ptr++;
	drawRoomItemsIndicator();
	return 0;
}

/*
TODO: check and rename me
*/
uint16 SCR_56_MorphRoom98(void) {
	int16 h;
	uint16 ofs;
	script_ptr++;

	IFGM_PlaySample(242);

	redrawRoomStatics(98, 0);

	ofs = cga_CalcXY(0, 136);
	for (h = 60; h; h--) {
		memcpy(frontbuffer + ofs, backbuffer + ofs, g_vm->_screenBPL);
		waitVBlank();
		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) != 0)
			ofs -= g_vm->_screenBPL;
	}

	backupSpotImage(&zone_spots[3], &sprites_list[3], sprites_list[3]);

	IFGM_StopSample();

	return 0;
}

/*
Copy backbuffer to screen, with added vertical mirror
*/
void ShowMirrored(uint16 h, uint16 ofs) {
	uint16 x, ofs2 = ofs;

	/*move 1 line up*/
	ofs2 ^= g_vm->_line_offset;
	if ((ofs2 & g_vm->_line_offset) != 0)
		ofs2 -= g_vm->_screenBPL;

	while (h--) {

		for (x = 0; x < g_vm->_screenBPL; x++) {
			frontbuffer[ofs2 + x] = frontbuffer[ofs + x] = backbuffer[ofs + x];
			backbuffer[ofs + x] = 0;
		}

		/*move 1 line down*/
		ofs += g_vm->_screenBPL;
		ofs ^= g_vm->_line_offset;
		if ((ofs & g_vm->_line_offset) != 0)
			ofs -= g_vm->_screenBPL;

		/*move 1 line up*/
		ofs2 ^= g_vm->_line_offset;
		if ((ofs2 & g_vm->_line_offset) != 0)
			ofs2 -= g_vm->_screenBPL;
	}
}

void LiftLines(int16 n, byte *source, uint16 sofs, byte *target, uint16 tofs) {
	while (n--) {
		memcpy(target + tofs, source + sofs, g_vm->_screenBPL);

		sofs += g_vm->_screenBPL;
		sofs ^= g_vm->_line_offset;
		if ((sofs & g_vm->_line_offset) != 0)
			sofs -= g_vm->_screenBPL;

		tofs += g_vm->_screenBPL;
		tofs ^= g_vm->_line_offset;
		if ((tofs & g_vm->_line_offset) != 0)
			tofs -= g_vm->_screenBPL;
	}
}

#define kSaucerAnimFrames 53

static void AnimSaucer(void) {
	static byte image1[] = {167, 0, 146};
	byte *pimage1 = image1;
	byte *sequence = souco_data;
	byte x, y, width, height;
	uint16 xx, yy, ww, hh;
	byte height_new, height_prev;
	uint16 delay;
	byte scroll_done = 0;

	memset(backbuffer, 0, sizeof(backbuffer) - 2);  /*TODO: original bug?*/
	cga_BackBufferToRealFull();
	cga_ColorSelect(0x30);

	right_button = 0;
	if (!drawPortrait(&pimage1, &x, &y, &width, &height))
		return;

	height_prev = 200 - 1;
	delay = 10000;

	xx = x; /*TODO: is it ok? maybe need *4*/
	yy = y;
	ww = 254;
	hh = 107;

	for (; sequence < souco_data + kSaucerAnimFrames * 8; sequence += 8) {
		uint16 i, ofs, ofs2, baseofs;

		if (sequence != souco_data) {
			/*reuse portrait's params for initial state*/
			xx = (sequence[0] << 8) | sequence[1];
			yy = (sequence[2] << 8) | sequence[3];
			ww = (sequence[4] << 8) | sequence[5];
			hh = (sequence[6] << 8) | sequence[7];
		}

		hh >>= 1;

		height_new = yy + hh;
		height_prev -= (yy - 1);

		/*scale the saucer*/
		cga_ZoomInplaceXY(cur_image_pixels, width, height, ww, hh, xx, yy, backbuffer);

		baseofs = cga_CalcXY(0, yy);

		if (!scroll_done) {
			/*scroll the saucer*/
			scroll_done = 1;

			ofs2 = ofs = baseofs;

			/*previous line*/
			ofs ^= g_vm->_line_offset;
			if ((ofs & g_vm->_line_offset) != 0)
				ofs -= g_vm->_screenBPL;

			for (i = 0; i < 55; i++) {
				memcpy(backbuffer + ofs, backbuffer + ofs2, g_vm->_screenBPL);

				/*next line*/
				ofs2 += g_vm->_screenBPL;
				ofs2 ^= g_vm->_line_offset;
				if ((ofs2 & g_vm->_line_offset) != 0)
					ofs2 -= g_vm->_screenBPL;

				/*previous line line*/
				ofs ^= g_vm->_line_offset;
				if ((ofs & g_vm->_line_offset) != 0)
					ofs -= g_vm->_screenBPL;
			}

			ofs2 = cga_CalcXY(0, 200 - 1);

			for (i = 0; i < 108; i++) {
				LiftLines(i + 1, backbuffer, ofs, frontbuffer, ofs2);

				ofs2 ^= g_vm->_line_offset;
				if ((ofs2 & g_vm->_line_offset) != 0)
					ofs2 -= g_vm->_screenBPL;

				waitVBlank();
				waitVBlank();
			}

			/*wipe 56 lines*/
			memset(backbuffer + ofs2, 0, 56 / 2 * g_vm->_screenBPL);
			ofs2 ^= g_vm->_line_offset;
			if ((ofs2 & g_vm->_line_offset) == 0)
				ofs2 += g_vm->_screenBPL;
			memset(backbuffer + ofs2, 0, 54 / 2 * g_vm->_screenBPL);

			for (i = 0xFFFF; i--;) ; /*TODO: weak delay*/

			IFGM_PlaySample(240);
		}

		/*draw the full saucer on screen*/
		ShowMirrored(height_prev + 1, baseofs);
		height_prev = height_new;

		waitVBlank();
		for (i = delay; i--;) ; /*TODO: weak delay*/
		delay += 500;
	}
}

extern int16 loadSplash(const char *filename);

/*
TODO: check me
*/
void theEnd(void) {
	static byte image2[] = {168, 28, 85, 22, 15};
	byte *pimage2 = image2;

	AnimSaucer();

	if (g_vm->getLanguage() == Common::EN_USA) {
		drawPortraitZoomed(&pimage2);

		script_byte_vars.zone_index = 135;

		do {
			pollInputButtonsOnly();
		}
		while(buttons == 0);

		while (!loadFond())
			askDisk2();
		jaggedZoom(backbuffer, frontbuffer);
		cga_BackBufferToRealFull();
	} else {
		while (!loadSplash("PRES.BIN"))
			askDisk2();
		cga_BackBufferToRealFull();
	}
}

uint16 SCR_5B_TheEnd(void) {
	script_ptr++;   /*Useless since this handler never returns*/
	script_byte_vars.game_paused = 5;

	theEnd();

	if (g_vm->getLanguage() == Common::EN_USA)
		restartGame();
	else
		for (;;) ;  /*HANG*/

	return 0;
}


/*
Discard all inventory items
*/
uint16 SCR_5C_ClearInventory(void) {
	int16 i;
	script_ptr++;

	for (i = 0; i < MAX_INV_ITEMS; i++) {
		if (inventory_items[i].flags == ITEMFLG_OWNED)
			inventory_items[i].flags = 0;
	}

	script_byte_vars.zapstiks_owned = 0;

	return 0;
}

/*
Drop group of items from inventory to room
*/
void DropItems(int16 first, int16 count) {
	int16 i;

	for (i = 0; i < count; i++) {
		if (inventory_items[first + i].flags == ITEMFLG_OWNED) {
			inventory_items[first + i].flags = ITEMFLG_ROOM;
			inventory_items[first + i].area = script_byte_vars.zone_area;
		}
	}
}

/*
Drop weapon-like items from inventory to room
*/
uint16 SCR_5D_DropWeapons(void) {
	script_ptr++;

	DropItems(kItemDagger1 - 1, 4);   /*DAGGER*/
	DropItems(kItemZapstik1 - 1, 14);  /*ZAPSTIK*/
	DropItems(kItemBlade - 1, 2);   /*SACRIFICIAL BLADE , CHOPPER*/

	script_byte_vars.zapstiks_owned = 0;

	return 0;
}

/*
React to Psi power
*/
uint16 SCR_62_PsiReaction(void) {
	byte power;
	uint16 cmd;

	script_ptr++;
	power = *script_ptr++;

	cmd = script_word_vars.zone_obj_cmds[(script_byte_vars.cur_spot_idx - 1) * 5 + power];
	if (cmd == 0)
		cmd = script_word_vars.psi_cmds[power];	/*TODO: is this consistent with zone_obj_cmds?*/

	the_command = Swap16(cmd);

	return 0;
}

/*TODO: rename me*/
uint16 SCR_63_LiftSpot6(void) {
	script_ptr++;

	blitSpritesToBackBuffer();
	zone_spots[6].sy -= 5;
	zone_spots[6].ey -= 5;
	backupSpotsImages();
	drawPersons();
	cga_BackBufferToRealFull();

	return 0;
}

uint16 SCR_64_DrawBoxAroundSpot(void) {
	script_ptr++;
	drawBoxAroundSpot();
	return 0;
}

/*
Draw text box
*/
uint16 SCR_14_DrawDesc(void) {
	byte *msg;
	script_ptr++;
	msg = seekToStringScr(desci_data, *script_ptr, &script_ptr);
	script_ptr++;

	drawMessage(msg, CGA_SCREENBUFFER);

	return 0;
}


/*
Draw dialog bubble with text for a person, wait for a key, then hide. Auto find bubble location
Use "thought" spike
*/
uint16 SCR_17_DrawPersonThoughtBubbleDialog(void) {
	byte x, y;
	byte *msg;
	script_ptr++;
	msg = seekToStringScr(diali_data, *script_ptr, &script_ptr);
	script_ptr++;

	x = found_spot->sx;
	y = found_spot->sy;

	if (x < 140 / 4)
		drawPersonBubble(found_spot->ex, y - 40, SPIKE_BUBLEFT | 20, msg);
	else
		drawPersonBubble(x - 80 / 4, y - 40, SPIKE_BUBRIGHT | 20, msg);

	promptWait();
	popDirtyRects(DirtyRectBubble);
	return 0;
}

/*
Draw dialog bubble with text for a person, wait for a key, then hide. Auto find bubble location
Use normal spike
*/
uint16 SCR_61_drawPersonBubbleDialog(void) {
	byte x, y;
	byte *msg;
	script_ptr++;
	msg = seekToStringScr(diali_data, *script_ptr, &script_ptr);
	script_ptr++;

	x = found_spot->sx;
	y = found_spot->sy;

	if (x < 140 / 4)
		drawPersonBubble(found_spot->ex, y - 40, SPIKE_DNLEFT | 20, msg);
	else
		drawPersonBubble(x - 80 / 4, y - 40, SPIKE_DNRIGHT | 20, msg);

	promptWait();
	popDirtyRects(DirtyRectBubble);
	return 0;
}

#if 1
byte *DebugString(char *msg, ...) {
	int16 i;
	byte c;
	static byte m[256];
	va_list ap;

	va_start(ap, msg);
	vsnprintf((char *)m, 256, msg, ap);
	va_end(ap);

	for (i = 0; m[i]; i++) {
		c = m[i];
		if (c >= 'A' && c <= 'Z')
			c = 0x21 + (c - 'A');
		else if (c >= 'a' && c <= 'z')
			c = 0x21 + (c - 'a');
		else if (c >= '0' && c <= '9')
			c = 0x10 + (c - '0');
		else if (c == ' ')
			c = 0x20;
		else if (c == '!')
			c = 0x01;
		else if (c == ',')
			c = 0x0C;
		else if (c == '.')
			c = 0x0E;
		else if (c == '\n')
			c = 0x00;
		else
			c = 0x1F;
		m[i] = c;
	}

	cur_str_end = m + i;

	return m;
}
#endif

/*
Draw dialog bubble with text for gauss
*/
uint16 SCR_27_DrawGaussBubble(void) {
	byte *msg;

	script_ptr++;
	msg = seekToStringScr(diali_data, *script_ptr, &script_ptr);
	script_ptr++;

	drawPersonBubble(32 / 4, 20, 15, msg);
	return 0;
}

/*
Draw dialog bubble with text
*/
uint16 SCR_29_DialiTextBox(void) {
	byte x, y, f;
	byte *msg;
	script_ptr++;
	msg = seekToStringScr(diali_data, *script_ptr, &script_ptr);
	cur_dlg_index = cur_str_index;  /*TODO: useless?*/

	script_ptr++;
	x = *script_ptr++;
	y = *script_ptr++;
	f = *script_ptr++;

	drawPersonBubble(x, y, f, msg);
	return 0;
}

/*
Do nothing in PC/CGA version
*/
uint16 SCR_F_Unused(void) {
	script_ptr++;
	script_ptr++;
	script_ptr++;
	script_ptr++;
	return 0;
}

/*
Do nothing in PC/CGA version
*/
uint16 SCR_4A_Unused(void) {
	script_ptr++;
	return 0;
}

/*
Do nothing in PC/CGA version
*/
uint16 SCR_67_Unused(void) {
	script_ptr++;
	script_ptr++;
	return 0;
}

/*
Play Sfx
NB! Do nothing in EU PC/CGA version
*/
uint16 SCR_68_PlaySfx(void) {
	byte index;
	script_ptr++;
	index = *script_ptr++;
	script_ptr++;
	IFGM_PlaySfx(index);
	return 0;
}

/*
Play sound
*/
uint16 SCR_69_playSound(void) {
	byte index;
	script_ptr++;
	index = *script_ptr++;
	script_ptr++;

	playSound(index);
	return 0;
}

/*
Do nothing in PC/CGA version
*/
uint16 SCR_6A_Unused(void) {
	script_ptr++;
	return 0;
}

/*
Open room's items inventory
*/
uint16 CMD_1_RoomObjects(void) {
	updateUndrawCursor(CGA_SCREENBUFFER);
	inv_bgcolor = 0xAA;
	openInventory((0xFF << 8) | ITEMFLG_ROOM, (script_byte_vars.zone_area << 8) | ITEMFLG_ROOM);
	return ScriptRerun;
}

/*
Open Psi Powers menu
*/
uint16 CMD_2_PsiPowers(void) {
	/*Psi powers bar*/
	backupAndShowSprite(3, 280 / 4, 40);
	processInput();
	do {
		pollInput();
		selectCursor(CURSOR_FINGER);
		checkPsiCommandHover();
		if (command_hint != 100)
			command_hint += 109;
		if (command_hint != last_command_hint)
			drawCommandHint();
		drawHintsAndCursor(CGA_SCREENBUFFER);
	} while (buttons == 0);
	undrawCursor(CGA_SCREENBUFFER);
	cga_RestoreBackupImage(CGA_SCREENBUFFER);
	return ScriptRerun;
}

/*
Open normal inventory box
*/
uint16 CMD_3_Possessions(void) {
	updateUndrawCursor(CGA_SCREENBUFFER);
	inv_bgcolor = 0x55;
	openInventory(ITEMFLG_OWNED, ITEMFLG_OWNED);
	return ScriptRerun;
}

/*
Show energy level
*/
uint16 CMD_4_EnergyLevel(void) {
	static byte energy_image[] = {130, 236 / 4, 71};
	byte x, y, width, height;
	byte *image = energy_image;
	byte anim = 40;

	popDirtyRects(DirtyRectSprite);
	popDirtyRects(DirtyRectBubble);

	cur_dlg_index = 0;
	ifgm_flag2 = 0xff;

	if (script_byte_vars.psy_energy != 0)
		anim = 41 + (script_byte_vars.psy_energy / 16);

	if (drawPortrait(&image, &x, &y, &width, &height)) {
		cga_BlitAndWait(cur_image_pixels, cur_image_size_w, cur_image_size_w, cur_image_size_h, CGA_SCREENBUFFER, cur_image_offs);
	}

	do {
		IFGM_PlaySample(28);
		animPortrait(1, anim, 10);
		animPortrait(1, anim + 14, 10);
		pollInputButtonsOnly();
	} while (buttons == 0);

	popDirtyRects(DirtyRectSprite);

	ifgm_flag2 = 0;
	IFGM_StopSample();

	return 0;
}

/*
Advance time
*/
uint16 CMD_5_Wait(void) {

	script_byte_vars.bvar_25++;
	script_word_vars.timer_ticks2 = Swap16(Swap16(script_word_vars.timer_ticks2) + 300);

	the_command = next_vorts_cmd;
	runCommand();

	the_command = next_turkey_cmd;
	runCommand();

	script_byte_vars.used_commands = script_byte_vars.check_used_commands;

	the_command = Swap16(script_word_vars.wvar_0E);

	if (the_command == 0) {
		if (script_word_vars.next_aspirant_cmd == 0) {
			the_command = 0x9005;
			runCommand();
		}
	} else {
		if (script_byte_vars.bvar_26 >= 63 && script_byte_vars.zone_area < 22 && script_byte_vars.zone_area != 1)
			the_command = 0x9005;
		return ScriptRerun;
	}

	return 0;
}

/*
Load game (menu)
*/
uint16 CMD_6_Load(void) {
	the_command = 0xC35C;
	return ScriptRerun;
}

/*
Save game (menu)
*/
uint16 CMD_7_Save(void) {
	the_command = 0xC35D;
	return ScriptRerun;
}

/*
Show timer
*/
uint16 CMD_8_Timer(void) {
	static byte timer_image[] = {163, 244 / 4, 104};
	byte x, y, width, height;
	byte *image = timer_image;

	if (drawPortrait(&image, &x, &y, &width, &height)) {
		cga_BlitAndWait(cur_image_pixels, cur_image_size_w, cur_image_size_w, cur_image_size_h, CGA_SCREENBUFFER, cur_image_offs);
	}

	do {
		uint16 timer = Swap16(script_word_vars.timer_ticks2);
		uint16 minutes = timer % (60 * 60);

		char_draw_coords_x = 260 / 4;
		char_draw_coords_y = 120;

		waitVBlank();
		cga_PrintChar(timer / (60 * 60) + 16, CGA_SCREENBUFFER);
		cga_PrintChar((minutes & 1) ? 26 : 0, CGA_SCREENBUFFER);    /*colon*/
		cga_PrintChar(minutes / (60 * 10) + 16, CGA_SCREENBUFFER);
		cga_PrintChar(minutes / 60 + 16, CGA_SCREENBUFFER);
		pollInputButtonsOnly();
	} while (buttons == 0);

	popDirtyRects(DirtyRectSprite);

	return 0;
}

int16 ConsumePsiEnergy(byte amount) {
	byte current = script_byte_vars.psy_energy;

	if (current < amount) {
		/*no energy left*/
		playAnim(68, 296 / 4, 71);
		return 0;
	}

	script_byte_vars.psy_energy = current - amount;

	/*significantly changed?*/
	if ((current & 0xF0) != (script_byte_vars.psy_energy & 0xF0))
		playAnim(68, 296 / 4, 71);

	return 1;
}

uint16 CMD_A_PsiSolarEyes(void) {
	if (!ConsumePsiEnergy(2))
		return 0;

	if (zone_palette == 14) {
		redrawRoomStatics(script_byte_vars.zone_room, zone_palette);
		zone_palette = 0;
		cga_BackBufferToRealFull();
	}

	the_command = Swap16(script_word_vars.wvar_AA);
	runCommand();
	script_byte_vars.cur_spot_flags = 0xFF;

	return 0;
}


uint16 GetZoneObjCommand(uint16 offs) {
	/*TODO: fix me: change offs/2 to index*/
	the_command = Swap16(script_word_vars.zone_obj_cmds[(script_byte_vars.cur_spot_idx - 1) * 5 + offs / 2]);
	return the_command;
}

void DrawStickyNet(void) {
	byte x, y, w, h;

	uint16 ofs;
	byte *sprite = loadPuzzlToScratch(80);

	x = room_bounds_rect.sx;
	y = room_bounds_rect.sy;
	w = room_bounds_rect.ex - x;
	h = room_bounds_rect.ey - y;

	ofs = CalcXY_p(x, y);

	/*16x30 is the net sprite size*/

	for (; h; h -= 30) {
		int16 i;
		for (i = 0; i < w; i += 16 / 4)
			drawSprite(sprite, frontbuffer, ofs + i);
		ofs += g_vm->_screenBPL * 30 / 2;
	}
}

uint16 CMD_B_PsiStickyFingers(void) {
	if (!ConsumePsiEnergy(3))
		return 0;

	if (script_byte_vars.bvar_43 != 0) {
		the_command = Swap16(script_word_vars.wvar_AC);
		return ScriptRerun;
	}

	backupScreenOfSpecialRoom();
	DrawStickyNet();
	selectCursor(CURSOR_FLY);
	menuLoop(0, 0);
	playSound(224);
	cga_BackBufferToRealFull();
	restoreScreenOfSpecialRoom();

	if (script_byte_vars.cur_spot_idx == 0 || GetZoneObjCommand(0 * 2) == 0)
		the_command = Swap16(script_word_vars.psi_cmds[0]);

	if (script_byte_vars.bvar_26 >= 63
	        && script_byte_vars.zone_area < 22
	        && script_byte_vars.zone_area != 1)
		the_command = 0x9005;

	return ScriptRerun;
}

uint16 CMD_C_PsiKnowMind(void) {
	if (!ConsumePsiEnergy(1))
		return 0;

	if (script_byte_vars.bvar_43 != 0) {
		the_command = Swap16(script_word_vars.wvar_AE);
		return ScriptRerun;
	}

	processMenu();

	if (script_byte_vars.cur_spot_idx == 0 || GetZoneObjCommand(2 * 2) == 0)
		the_command = Swap16(script_word_vars.psi_cmds[1]);

	return ScriptRerun;
}

uint16 CMD_D_PsiBrainwarp(void) {
	if (!ConsumePsiEnergy(2))
		return 0;

	if (script_byte_vars.bvar_43 == 0) {
		backupScreenOfSpecialRoom();
		processMenu();

		if (script_byte_vars.cur_spot_idx == 0) {
			the_command = Swap16(script_word_vars.wvar_0C);
			script_byte_vars.dead_flag = 0;
			return ScriptRerun;
		}

		if (GetZoneObjCommand(1 * 2) != 0) {
			playAnim(39, found_spot->sx + 8 / 4, found_spot->sy - 10);
			restoreScreenOfSpecialRoom();
			return ScriptRerun;
		}
	}

	if (script_byte_vars.bvar_43 == 18) {
		script_byte_vars.dead_flag = 1;
		script_byte_vars.tries_left = 2;
		return 0;
	}

	((pers_t *)script_vars[kScrPool8_CurrentPers])->flags |= PERSFLG_80;
	script_byte_vars.dead_flag = script_byte_vars.cur_spot_idx;
	script_byte_vars.tries_left = 2;
	the_command = 0;
	if (script_byte_vars.bvar_43 == 0) {
		playAnim(39, found_spot->sx + 8 / 4, found_spot->sy - 10);
		restoreScreenOfSpecialRoom();
		return ScriptRerun;
	}

	the_command = 0x90AA;
	return ScriptRerun;
}


uint16 CMD_E_PsiZoneScan(void) {
	byte x, y, w, h;
	uint16 offs;

	if (!ConsumePsiEnergy(1))
		return 0;

	if (script_byte_vars.bvar_43 != 0) {
		the_command = Swap16(script_word_vars.wvar_B8);
		return ScriptRerun;
	}

	backupScreenOfSpecialRoom();

	IFGM_PlaySample(26);

	offs = CalcXY_p(room_bounds_rect.sx, room_bounds_rect.sy);
	w = room_bounds_rect.ex - room_bounds_rect.sx;
	h = room_bounds_rect.ey - room_bounds_rect.sy;

	for (y = room_bounds_rect.sy; h; y++, h--) {
		spot_t *spot;
		for (x = 0; x < w; x++) frontbuffer[offs + x] = ~frontbuffer[offs + x];
		cga_blitToScreen(offs, w, 1);
		waitVBlank();
		for (x = 0; x < w; x++) frontbuffer[offs + x] = ~frontbuffer[offs + x];
		cga_blitToScreen(offs, w, 1);

		for (spot = zone_spots; spot != zone_spots_end; spot++) {
			if ((spot->flags & ~(SPOTFLG_40 | 7)) == (SPOTFLG_20 | SPOTFLG_8) && spot->sy == y) {
				playSound(27);
				spot->flags |= SPOTFLG_80;
				playAnim(38, spot->sx, spot->sy);
				break;
			}
		}

		offs ^= g_vm->_line_offset;
		if ((offs & g_vm->_line_offset) == 0)
			offs += g_vm->_screenBPL;
	}

	restoreScreenOfSpecialRoom();

	IFGM_StopSample();

	the_command = Swap16(script_word_vars.psi_cmds[2]);

	return ScriptRerun;

}

uint16 CMD_F_PsiPsiShift(void) {
	if (!ConsumePsiEnergy(3))
		return 0;

	if (script_byte_vars.bvar_43 != 0) {
		the_command = Swap16(script_word_vars.wvar_B0);
		return ScriptRerun;
	}

	selectCursor(CURSOR_GRAB);
	menuLoop(0, 0);
	backupScreenOfSpecialRoom();
	playSound(25);
	playAnim(39, cursor_x / 4, cursor_y);
	restoreScreenOfSpecialRoom();

	if (script_byte_vars.cur_spot_idx == 0 || GetZoneObjCommand(3 * 2) == 0)
		the_command = Swap16(script_word_vars.psi_cmds[5]);

	return ScriptRerun;
}

uint16 CMD_10_PsiExtremeViolence(void) {
	uint16 command;

	if (!ConsumePsiEnergy(8))
		return 0;

	script_byte_vars.extreme_violence = 1;

	if (script_byte_vars.bvar_43 != 0) {
		the_command = Swap16(script_word_vars.wvar_B2);
		return ScriptRerun;
	}

	processMenu();

	if (script_byte_vars.cur_spot_idx == 0) {
		the_command = Swap16(script_word_vars.psi_cmds[4]);
		script_byte_vars.extreme_violence = 0;
		return ScriptRerun;
	}

	command = GetZoneObjCommand(4 * 2);

	if ((command & 0xF000) == 0x9000)
		script_byte_vars.extreme_violence = 0;
	else if (command == 0) {
		the_command = Swap16(script_word_vars.psi_cmds[4]);
		script_byte_vars.extreme_violence = 0;
	}

	return ScriptRerun;
}

uint16 CMD_11_PsiTuneIn(void) {
	uint16 command;
	byte *msg;

	if (!ConsumePsiEnergy(4))
		return 0;

	if (script_byte_vars.bvar_43 != 0)
		command = Swap16(script_word_vars.wvar_B4);
	else {
		if (script_byte_vars.bvar_26 < 63 || script_byte_vars.zone_area >= 22)
			command = Swap16(script_word_vars.psi_cmds[3]);
		else
			command = 275;
	}

	/*TODO: is this really necessary? Maybe it's always set when loaded from script vars?*/
	if (command & 0x8000) {
		the_command = command;
		return ScriptRerun;
	}

	msg = seekToString(diali_data, command);
	cur_dlg_index = cur_str_index;  /*TODO: useless?*/

	drawPersonBubble(32 / 4, 20, 15, msg);

	promptWait();
	popDirtyRects(DirtyRectBubble);

	return 0;
}

void ActionForPersonChoice(uint16 *actions) {
	processMenu();
	the_command = 0x9183;   /*THERE`S NOBODY.*/
	if (script_byte_vars.cur_spot_idx != 0 && script_byte_vars.cur_pers != 0) {
		pers_t *pers = (pers_t *)script_vars[kScrPool8_CurrentPers];
		byte index = pers->name;
		if (index == 93)    /*CADAVER*/
			index = 19 + 42;
		else if (index == 133)  /*SCI FI*/
			index = 18 + 42;

		index -= 42;        /*Person names: THE MASTER OF ORDEALS, etc*/

		the_command = actions[index];

		playSound(22);
	}
}

/*TODO: ensure these are never accessed/modified from the scripts*/
uint16 menu_commands_12[] = {
	0xC0F0,
	0xC0D7,
	0x9019,
	0xC0DA,
	0x9019,
	0xC0F1,
	0x9007,
	0xC0D8,
	0x9007,
	0x9019,
	0x9007,
	0x9007,
	0xC34E,
	0xC34F,
	0x9019,
	0x9019,
	0x9019,
	0x9019,
	0xC319,
	0x9007
};

uint16 menu_commands_22[] = {
	0xC325,
	0xC326,
	0xC31B,
	0xC31D,
	0xC31F,
	0xC31E,
	0xC320,
	0xC327,
	0x90EC,
	0xC31C,
	0xC328,
	0,
	0xC077,
	0xC077,
	0xC321,
	0xC322,
	0x9007,
	0xC323,
	0xC2A7,
	0xC324
};

uint16 menu_commands_24[] = {
	0xC344,
	0xC34A,
	0xC343,
	0xC34D,
	0x9019,
	0xC0F1,
	0x9007,
	0xC354,
	0x90EC,
	0xC343,
	0xC345,
	0,
	0xC343,
	0xC343,
	0xC343,
	0x9019,
	0x9019,
	0x9019,
	0xC343,
	0xC343
};

uint16 menu_commands_23[] = {
	0xC002,
	0xC32A,
	0x9019,
	0xC325,
	0xC114,
	0xC32B,
	0xC32C,
	0xC355,
	0x90EC,
	0x9019,
	0xC328,
	0,
	0xC077,
	0xC077,
	0x9019,
	0x9113,
	0,
	0xC323,
	0xC2A7,
	0xC32D
};

uint16 CMD_12_(void) {
	warning("cmd 12");
	ActionForPersonChoice(menu_commands_12);
	return ScriptRerun;
}

uint16 CMD_13_ActivateFountain(void) {
	static byte water1[] = {125, 156 / 4, 58};
	static byte water2[] = {126, 156 / 4, 58};
	static byte headl[] = {88, 152 / 4, 52};
	static byte headr[] = {88, (160 / 4) | 0x80, 52};

	byte x, y, w, h;
	uint16 i, j;

	script_byte_vars.bvar_6A = 1;
	for (i = 0; i < 10; i++) {
		drawRoomStaticObject(water1, &x, &y, &w, &h);
		waitVBlank();
		cga_BackBufferToRealFull();
		for (j = 0; j < 0x1FFF; j++) ; /*TODO: weak delay*/

		drawRoomStaticObject(water2, &x, &y, &w, &h);
		waitVBlank();
		cga_BackBufferToRealFull();
		for (j = 0; j < 0x1FFF; j++) ; /*TODO: weak delay*/
	}

	drawRoomStaticObject(headl, &x, &y, &w, &h);
	drawRoomStaticObject(headr, &x, &y, &w, &h);
	cga_BackBufferToRealFull();

	return 0;
}

/*
Vorts walking into the room
*/
uint16 CMD_14_VortAppear(void) {
	/*TODO: check me*/
	pers_list[kPersVort].area = script_byte_vars.zone_area;
	selectPerson(0);
	animateSpot(&vortanims_ptr->field_1);
	IFGM_StopSample();
	next_vorts_cmd = 0xA015;
	blitSpritesToBackBuffer();
	drawPersons();
	cga_BackBufferToRealFull();
	next_vorts_ticks = Swap16(script_word_vars.timer_ticks2) + 5;
	return 0;
}

pers_t *vort_ptr;

#define ADJACENT_AREAS_MAX 19

struct {
	byte zone; /* current zone */
	byte area; /* area accessible from this zone */
} adjacent_areas[ADJACENT_AREAS_MAX] = {
	{  2,  5},
	{  3,  8},
	{  4,  8},
	{  5,  2},
	{120,  3},
	{121,  3},
	{  8, 10},
	{ 10,  8},
	{ 60, 62},
	{ 62, 66},
	{ 68, 66},
	{ 69, 66},
	{ 67, 65},
	{ 65, 66},
	{ 70, 71},
	{ 71, 70},
	{ 59, 60},
	{ 60, 62},
	{ 63, 65}
};

/*
Vorts walking out of the room
*/
uint16 CMD_15_VortLeave(void) {
	/*TODO: check me*/

	uint16 i;
	animdesc_t *anim;
	pers_t *pers;

	if (pers_list[kPersVort].area != 0) {
		pers = &pers_list[kPersVort];
		anim = &vortanims_ptr->field_4;
	} else if (pers_list[kPersVort2].area != 0) {
		pers = &pers_list[kPersVort2];
		anim = &vortanims_ptr->field_7;
	} else {
		script_byte_vars.bvar_36 |= 0x80;

		pers_list[kPersVort3].area = 0;
		pers_list[kPersVort].flags = pers_list[kPersVort3].flags;

		pers = &pers_list[kPersVort];
		anim = &vortanims_ptr->field_A;
	}

	pers->area = 0;
	next_vorts_cmd = 0;
	for (i = 0; i < ADJACENT_AREAS_MAX; i++) {
		if (adjacent_areas[i].zone == script_byte_vars.zone_index) {
			next_vorts_cmd = 0xA016;
			next_vorts_ticks = Swap16(script_word_vars.timer_ticks2) + 5;
			pers->area = adjacent_areas[i].area;
		}
	}
	vort_ptr = pers;

	zone_spots[(pers->flags & 15) - 1].flags &= ~SPOTFLG_80;

	selectPerson(0);
	animateSpot(anim);
	IFGM_StopSample();
	script_byte_vars.bvar_36 &= 0x80;
	return 0;
}

/*
Vorts left the room
*/
uint16 CMD_16_VortGone(void) {
	vort_ptr->area = 0;
	next_vorts_cmd = 0;
	return 0;
}

/*
Take away Aspirant's item and bounce it to the inventory
*/
uint16 CMD_17_LootAspirantsItem() {
	LootAspirantsItem();
	return ScriptRerun;
}

/*
Aspirant walking out of the room
*/
uint16 CMD_18_AspirantLeave(void) {
	/*TODO: check me*/
	static const animdesc_t anim33 = {ANIMFLG_USESPOT | 33, { { 0, 0 } }};

	popDirtyRects(DirtyRectSprite);
	popDirtyRects(DirtyRectText);

	aspirant_ptr->area = 0;
	script_word_vars.next_aspirant_cmd = BE(0);

	if ((aspirant_ptr->flags & PERSFLG_40) == 0) {
		aspirant_spot->flags &= ~SPOTFLG_80;
		selectPerson(script_byte_vars.aspirant_pers_ofs);
		script_byte_vars.aspirant_flags = 0;
		animateSpot(&anim33);
	}

	return 0;
}

/*
Aspirant walking into the room
*/
uint16 CMD_19_AspirantAppear(void) {
	/*TODO: check me*/
	static const animdesc_t anim23 = {ANIMFLG_USESPOT | 23, { { 0, 0 } }};

	popDirtyRects(DirtyRectSprite);
	aspirant_ptr->area = script_byte_vars.zone_area;
	script_word_vars.next_aspirant_cmd = BE(0xA018);	/*leave*/
	script_byte_vars.check_used_commands = 3;
	script_byte_vars.used_commands = 0;
	selectPerson(script_byte_vars.aspirant_pers_ofs);
	animateSpot(&anim23);
	blitSpritesToBackBuffer();
	drawPersons();
	cga_BackBufferToRealFull();
	if (script_byte_vars.aspirant_flags == 5) {
		the_command = 0xC029;
		script_byte_vars.aspirant_flags = 0;
		return ScriptRerun;
	}
	if (script_byte_vars.aspirant_flags == 6) {
		the_command = 0xC165;
		script_byte_vars.aspirant_flags = 0;
		return ScriptRerun;
	}
	return 0;
}

/*
Aspirant is dead
*/
uint16 CMD_1A_AspirantDie(void) {
	/*TODO: check me, unused in game?*/
	script_byte_vars.bvar_45 = 0;
	zone_spots[5].flags = SPOTFLG_40 | SPOTFLG_10 | SPOTFLG_2 | SPOTFLG_1;
	script_word_vars.next_aspirant_cmd = 0;
	DrawDeathAnim();
	return 0;
}

/*
Show Holo screen anim and speech
*/
uint16 CMD_1B_Holo(void) {
	byte x, y;
	uint16 num;
	byte *msg;

	IFGM_PlaySample(225);

	x = found_spot->sx;
	y = found_spot->sy;

	playAnim(42, x + 4 / 4, y + 6);

	num = 321 + ((Swap16(script_word_vars.timer_ticks2) < 60 * 60) ? 0 : 4) + (script_byte_vars.rand_value % 4);
	msg = seekToString(diali_data, num);
	cur_dlg_index = cur_str_index;  /*TODO: useless?*/

	if (x < 140 / 4)
		drawPersonBubble(x + 32 / 4, y - 40, SPIKE_DNLEFT | 20, msg);
	else
		drawPersonBubble(x - 92 / 4, y - 40, SPIKE_DNRIGHT | 20, msg);

	IFGM_PlaySfx(0);

	playAnim(43, x, y);

	promptWait();
	popDirtyRects(DirtyRectBubble);

	IFGM_PlaySample(225);

	playAnim(45, x, y);

	return 0;
}

/*
Turkey walking into the room
*/
uint16 CMD_1E_TurkeyAppear(void) {
	/*TODO: check me*/
	pers_list[kPersTurkey].area = script_byte_vars.zone_area;
	selectPerson(PersonOffset(kPersTurkey));
	animateSpot(&turkeyanims_ptr->field_1);
	next_turkey_cmd = 0xA01F;
	blitSpritesToBackBuffer();
	drawPersons();
	cga_BackBufferToRealFull();
	next_turkey_ticks = Swap16(script_word_vars.timer_ticks2) + 5;
	return 0;
}

/*
Turkey leaving the room
*/
uint16 CMD_1F_TurkeyLeave(void) {
	uint16 i;
	animdesc_t *anim;
	pers_t *pers;

	pers = &pers_list[kPersTurkey];
	anim = &turkeyanims_ptr->field_4;

	pers->area = 0;
	next_turkey_cmd = 0;
	for (i = 0; i < ADJACENT_AREAS_MAX; i++) {
		if (adjacent_areas[i].zone == script_byte_vars.zone_index) {
			next_turkey_cmd = 0xA020;
			next_turkey_ticks = Swap16(script_word_vars.timer_ticks2) + 5;
			pers->area = adjacent_areas[i].area;
		}
	}

	zone_spots[(pers->flags & 15) - 1].flags &= ~SPOTFLG_80;

	selectPerson(PersonOffset(kPersTurkey));
	animateSpot(anim);
	return 0;
}

/*
Turkey left the room
*/
uint16 CMD_20_TurkeyGone(void) {
	pers_list[kPersTurkey].area = 0;
	next_turkey_cmd = 0;
	return 0;
}

/*
Talk to Vorts
*/
uint16 CMD_21_VortTalk(void) {
	byte x, y;
	uint16 num;
	byte *msg;

	if (script_byte_vars.rand_value >= 85)
		num = 6;
	else if (script_byte_vars.rand_value >= 170)
		num = 7;
	else
		num = 35;

	msg = seekToString(diali_data, num);
	cur_dlg_index = cur_str_index;  /*TODO: useless?*/

	x = found_spot->sx;
	y = found_spot->sy;

	if (x < 140 / 4)
		drawPersonBubble(found_spot->ex, y - 40, SPIKE_DNLEFT | 20, msg);
	else
		drawPersonBubble(x - 80 / 4, y - 40, SPIKE_DNRIGHT | 20, msg);

	IFGM_PlaySfx(0);

	promptWait();
	popDirtyRects(DirtyRectBubble);

	return 0;
}

uint16 CMD_22_(void) {
	ActionForPersonChoice(menu_commands_22);
	return ScriptRerun;
}

uint16 CMD_23_(void) {
	ActionForPersonChoice(menu_commands_23);
	return ScriptRerun;
}

uint16 CMD_24_(void) {
	ActionForPersonChoice(menu_commands_24);
	return ScriptRerun;
}

/*
Load save file
*/
uint16 CMD_25_LoadGame(void) {
	IFGM_StopSample();
	if (loadScena())
		the_command = 0x918F;   /*error loading*/
	else
		the_command = 0x90AA;
	return ScriptRerun;
}

/*
Write save file
*/
uint16 CMD_26_SaveGame(void) {
	if (saveScena())
		the_command = 0x9190;   /*error saving*/
	else
		the_command = 0x90AA;
	return ScriptRerun;
}



typedef uint16 (*cmdhandler_t)(void);

cmdhandler_t command_handlers[] = {
	0,
	CMD_1_RoomObjects,
	CMD_2_PsiPowers,
	CMD_3_Possessions,
	CMD_4_EnergyLevel,
	CMD_5_Wait,
	CMD_6_Load,
	CMD_7_Save,
	CMD_8_Timer,
	CMD_TRAP,
	CMD_A_PsiSolarEyes,
	CMD_B_PsiStickyFingers,
	CMD_C_PsiKnowMind,
	CMD_D_PsiBrainwarp,
	CMD_E_PsiZoneScan,
	CMD_F_PsiPsiShift,
	CMD_10_PsiExtremeViolence,  /*10*/
	CMD_11_PsiTuneIn,
	CMD_12_,
	CMD_13_ActivateFountain,
	CMD_14_VortAppear,
	CMD_15_VortLeave,
	CMD_16_VortGone,
	CMD_17_LootAspirantsItem,
	CMD_18_AspirantLeave,
	CMD_19_AspirantAppear,
	CMD_1A_AspirantDie,
	CMD_1B_Holo,
	CMD_TRAP,
	CMD_TRAP,
	CMD_1E_TurkeyAppear,
	CMD_1F_TurkeyLeave,
	CMD_20_TurkeyGone,  /*20*/
	CMD_21_VortTalk,
	CMD_22_,
	CMD_23_,
	CMD_24_,
	CMD_25_LoadGame,
	CMD_26_SaveGame
};
#define MAX_CMD_HANDLERS (sizeof(command_handlers) / sizeof(command_handlers[0]))

cmdhandler_t script_handlers[] = {
	0,
	SCR_1_AspirantItemTrade,
	SCR_2_RudeAspirantTrade,
	SCR_3_DrawItemBox,
	SCR_4_StealZapstik,
	SCR_5_DrawPortraitLiftRight,
	SCR_6_DrawPortraitLiftLeft,
	SCR_7_DrawPortraitLiftDown,
	SCR_8_DrawPortraitLiftUp,
	SCR_9_DrawPortrait,
	SCR_A_DrawPortrait,     /*TODO: same as SCR_9_DrawPortrait , unused*/
	SCR_B_DrawPortraitTwistEffect,
	SCR_C_DrawPortraitArcEffect,
	SCR_D_DrawPortraitDotEffect,
	SCR_E_DrawPortraitZoomIn,
	SCR_F_Unused,
	SCR_10_DrawPortraitZoomed,   /*10*/
	SCR_11_DrawRoomObject,
	SCR_12_Chain,
	SCR_13_RedrawRoomStatics,
	SCR_14_DrawDesc,
	SCR_15_SelectSpot,
	SCR_16_DrawDeathAnim,
	SCR_17_DrawPersonThoughtBubbleDialog,
	SCR_18_AnimPortrait,
	SCR_19_HidePortraitLiftLeft,
	SCR_1A_HidePortraitLiftRight,
	SCR_1B_HidePortraitLiftUp,
	SCR_1C_HidePortraitLiftDown,
	SCR_1D_HidePortrait,        /*TODO: same as SCR_23_HidePortrait , unused*/
	SCR_1E_HidePortraitTwist,
	SCR_1F_HidePortraitArc,
	SCR_20_HidePortraitDots,    /*20*/
	SCR_21_HidePortrait,        /*TODO: same as SCR_23_HidePortrait , unused*/
	SCR_22_HidePortraitShatter,
	SCR_23_HidePortrait,
	SCR_24_PopAllPortraits,
	SCR_25_ChangeZoneOnly,
	SCR_26_GameOver,
	SCR_27_DrawGaussBubble,
	SCR_28_MenuLoop,
	SCR_29_DialiTextBox,
	SCR_2A_PopDialogRect,
	SCR_2B_PopAllBubbles,
	SCR_2C_Wait4,
	SCR_2D_Wait,
	SCR_2E_promptWait,
	SCR_2F_LootAspirantsItem,
	SCR_30_Fight,   /*30*/
	SCR_31_Fight2,
	SCR_32_FightWin,
	SCR_33_Jump,
	SCR_34_Call,
	SCR_35_Ret,
	SCR_36_ChangeZone,
	SCR_37_desciTextBox,
	SCR_38_PlayAnim,
	SCR_39_AnimRoomDoorOpen,
	SCR_3A_AnimRoomDoorClose,
	SCR_3B_MathExpr,
	SCR_3C_CondExpr,
	SCR_3D_ActionsMenu,
	SCR_3E_TheWallAdvance,
	SCR_3F_HidePortrait,        /*TODO: same as SCR_23_HidePortrait , unused*/
	SCR_40_PopAllTextBoxes, /*40*/
	SCR_41_LiftHand,
	SCR_42_LoadZone,
	SCR_43_RefreshZone,
	SCR_44_BackBufferToScreen,
	SCR_45_DeProfundisRoomEntry,
	SCR_46_DeProfundisLowerHook,
	SCR_47_DeProfundisRiseMonster,
	SCR_48_DeProfundisLowerMonster,
	SCR_49_DeProfundisRiseHook,
	SCR_4A_Unused,
	SCR_4B_ProtoDropZapstik,
	SCR_4C_DrawPersons,
	SCR_4D_PriorityCommand,
	SCR_4E_BounceCurrentItemToRoom,
	SCR_4F_BounceCurrentItemToInventory,
	SCR_50_BounceItemToInventory,   /*50*/
	SCR_51_SkullTraderItemTrade,
	SCR_52_RefreshSpritesData,
	SCR_53_FindInvItem,
	SCR_54_DotFadeRoom,
	SCR_55_DrawRoomItemsIndicator,
	SCR_56_MorphRoom98,
	SCR_57_ShowCharacterSprite,
	SCR_58_DrawCharacterSprite,
	SCR_59_blitSpritesToBackBuffer,
	SCR_5A_SelectPalette,
	SCR_5B_TheEnd,
	SCR_5C_ClearInventory,
	SCR_5D_DropWeapons,
	SCR_5E_SelectTempPalette,
	SCR_5F_DrawRoomObjectBack,
	SCR_60_ReviveCadaver,   /*60*/
	SCR_61_drawPersonBubbleDialog,
	SCR_62_PsiReaction,
	SCR_63_LiftSpot6,
	SCR_64_DrawBoxAroundSpot,
	SCR_65_DeProfundisMovePlatform,
	SCR_66_DeProfundisRideToExit,
	SCR_67_Unused,
	SCR_68_PlaySfx,
	SCR_69_playSound,
	SCR_6A_Unused,
};
#define MAX_SCR_HANDLERS (sizeof(script_handlers) / sizeof(script_handlers[0]))

#ifdef DEBUG_SCRIPT
int16 runscr_reentr = 0;
int16 runcmd_reentr = 0;
#endif

/*
Run script routine
*/
uint16 RunScript(byte *code) {
	uint16 status = ScriptContinue;

#ifdef DEBUG_SCRIPT
	runscr_reentr += 1;
#endif

	script_ptr = code;
	while (script_ptr != script_end_ptr) {
		byte opcode = *script_ptr;

#ifdef DEBUG_SCRIPT
		{
			FILE *f = fopen(DEBUG_SCRIPT_LOG, "at");
			if (f) {
				uint16 offs = (script_ptr - templ_data) & 0xFFFF;
				fprintf(f, "%04X: %02X\n", offs, opcode);
				fclose(f);
			}
		}
#endif

#ifdef DEBUG_QUEST
		if (script_ptr - templ_data == 0x4F) {
			/*manipulate rand_value to get a quest item we need*/
			script_byte_vars.rand_value = DEBUG_QUEST;
		}
#endif


		if (opcode == 0 || opcode >= 107)
			break;

		status = script_handlers[opcode]();

		if (g_vm->_shouldRestart)
			return status;

		if (g_vm->_prioritycommand_1)
			return status;

		if (status != ScriptContinue || g_vm->_shouldQuit)
			break;
	}

#ifdef DEBUG_SCRIPT
	runscr_reentr -= 1;
#endif

	return status;
}

/*
Get script routine
*/
byte *getScriptSubroutine(uint16 index) {
	return seekToEntry(templ_data, index, &script_end_ptr);
}

/*
Run script command
*/
uint16 runCommand(void) {
	uint16 res;
	uint16 cmd;

again:;
	res = 0;

	if (the_command == 0)
		return 0;

#ifdef DEBUG_SCRIPT
	{
		FILE *f = fopen(DEBUG_SCRIPT_LOG, "at");
		if (f) {
			fprintf(f, "\nrunCommand 0x%04X rc: %d rs: %d\n", the_command, runcmd_reentr, runscr_reentr);
			fclose(f);
		}
	}

	runcmd_reentr += 1;

#endif

	cmd = the_command & 0x3FF;

	switch (the_command & 0xF000) {
	case 0: /*TODO what kind of call is this?*/
		res = RunScript(templ_data + the_command);
		break;
	case 0x9000:
		drawMessage(seekToString(desci_data, cmd), CGA_SCREENBUFFER);
		break;
	case 0xA000:
	case 0xB000:
		debug("Command: $%X 0x%X", the_command, cmd);
		res = command_handlers[cmd]();
		break;
	case 0xF000:
		/*restore sp from keep_sp then run script*/
		/*currently only supposed to work correctly from the SCR_4D_PriorityCommand handler*/
		debug("Restore: $%X 0x%X", the_command, cmd);
	/*TODO("SCR_RESTORE\n");*/
	/*fall through*/
	default:
		res = RunScript(getScriptSubroutine(cmd - 1));
	}

#ifdef DEBUG_SCRIPT
	runcmd_reentr -= 1;
#endif

#ifdef DEBUG_SCRIPT
	{
		FILE *f = fopen(DEBUG_SCRIPT_LOG, "at");
		if (f) {
			fprintf(f, "\n");
			fclose(f);
		}
	}
#endif
	if (g_vm->_shouldRestart)
		return runCommandKeepSp();

	if (g_vm->_prioritycommand_1 && !(g_vm->_prioritycommand_2))
		return res;

	if (g_vm->_prioritycommand_1 && g_vm->_prioritycommand_2)
		return runCommandKeepSp();

	/*TODO: this is pretty hacky, original code manipulates the stack to discard old script invocation*/
	if (res == ScriptRerun)
		goto again;

	return res;
}

uint16 runCommandKeepSp(void) {
	/*keep_sp = sp;*/
	g_vm->_prioritycommand_1 = false;
	g_vm->_prioritycommand_2 = false;
	if (g_vm->_shouldRestart)
		return RUNCOMMAND_RESTART;
	return runCommand();
}

} // End of namespace Chamber
