/*
 *  ucinternal.cc - Interpreter for usecode.
 *
 *  Copyright (C) 1999  Jeffrey S. Freedman
 *  Copyright (C) 2000-2022  The Exult Team
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <cstdio>         /* Debugging.           */
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <iomanip>

#ifdef XWIN
#include <csignal>
#endif
#include <algorithm>       // STL function things
#include <map>

#include "Gump.h"
#include "Gump_manager.h"
#include "Text_gump.h"
#include "Notebook_gump.h"
#include "Audio.h"
#include "animate.h"
#include "barge.h"
#include "chunks.h"
#include "conversation.h"
#include "exult.h"
#include "game.h"
#include "gamewin.h"
#include "gamemap.h"
#include "keyring.h"
#include "miscinf.h"
#include "monsters.h"
#include "mouse.h"
#include "schedule.h"
#include "tqueue.h"
#include "ucinternal.h"
#include "ucsched.h"
#include "useval.h"
#include "utils.h"
#include "actors.h"
#include "egg.h"
#include "actions.h"
#include "stackframe.h"
#include "ucfunction.h"
#include "effects.h"
#include "party.h"
#include "ucsymtbl.h"
#include "databuf.h"
#include "usefuns.h"
#include "opcodes.h"
#include "ios_state.hpp"
#include "touchui.h"

#if (defined(USE_EXULTSTUDIO) && defined(USECODE_DEBUGGER))
#include "server.h"
#include "servemsg.h"
#include "debugmsg.h"
#include "debugserver.h"
#endif

using std::cerr;
using std::cout;
using std::endl;
using std::istream;
using std::ifstream;
using std::ofstream;
using std::istream;
using std::ostream;
using std::exit;
using std::ios;
using std::dec;
using std::hex;
using std::memset;
using std::setfill;
using std::setw;
using std::size_t;
using std::string;
using std::strcat;
using std::strchr;
using std::strcmp;
using std::strcpy;
using std::strlen;
using std::vector;
using std::ostream;

// External globals..

extern bool intrinsic_trace;
extern int usecode_trace;

#ifdef USECODE_DEBUGGER

extern bool usecode_debugging;
std::vector<int> intrinsic_breakpoints;

void    initialise_usecode_debugger() {
	// Summon up the configuration file

	// List all the keys.

	// Render intrinsic names to numbers (unless already given as
	// a number (which might be hex. Convert from that.

	// push them all onto the list
}

#endif

void Usecode_internal::stack_trace(ostream &out) {
	if (call_stack.empty())
		return;

	auto iter = call_stack.begin();

	boost::io::ios_flags_saver flags(out);
	boost::io::ios_fill_saver fill(out);
	out << std::hex << std::setfill('0');
	do {
		out << *(*iter);
		auto it = except_stack.find(*iter);
		if (it != except_stack.end()) {
			out << ", active catch at 0x" << std::setw(8) << it->second;
		}
		out << endl;
		if ((*iter)->call_depth == 0)
			break;
		++iter;
	} while (true);
}

Usecode_function *Usecode_internal::find_function(int funcid) {
	Usecode_function *fun;
	// locate function
	unsigned int slotnum = funcid / 0x100;
	if (slotnum >= funs.size())
		fun = nullptr;
	else {
		Funs256 &slot = funs[slotnum];
		size_t index = funcid % 0x100;
		fun = index < slot.size() ? slot[index] : nullptr;
	}
	if (!fun) {
#ifdef DEBUG
		cout << "Usecode " << funcid << " not found." << endl;
#endif
	}
	return fun;
}

inline Usecode_class_symbol *Usecode_internal::get_class(int n) {
	return symtbl->get_class(n);
}

inline Usecode_class_symbol *Usecode_internal::get_class(const char *nm) {
	return symtbl->get_class(nm);
}

inline int Usecode_internal::get_shape_fun(int n) {
	return n < 0x400 ? n :
	       (symtbl ? symtbl->get_high_shape_fun(n)
	        // Default to 'old-style' high shape functions.
	        : 0x1000 + (n - 0x400));
}

inline bool Usecode_internal::is_object_fun(int n) {
	if (!symtbl)
		return n < 0x800;
	return symtbl->is_object_fun(n);
}

bool Usecode_internal::call_function(int funcid,
                                     int eventid,
                                     Game_object *caller,
                                     bool entrypoint, bool orig,
                                     int givenargs) {
	Usecode_function *fun = find_function(funcid);
	if (!fun)
		return false;
	if (orig)
		if (!(fun = fun->orig)) {
#ifdef DEBUG
			cout << "Original usecode " << funcid << " not found."
			     << endl;
#endif
			return false;
		}

	int depth;
	int oldstack;
	int chain;

	if (entrypoint) {
		depth = 0;
		oldstack = 0;
		chain = Stack_frame::getCallChainID();

	} else {
		Stack_frame *parent = call_stack.front();

		// find new depth
		depth = parent->call_depth + 1;

		// find number of elements available to pop from stack (as arguments)
		oldstack = sp - parent->save_sp;

		chain = parent->call_chain;

		if (caller == nullptr)
			caller = parent->caller_item.get(); // use parent's
	}

	auto *frame = new Stack_frame(fun, eventid, caller, chain, depth);

	int num_args = std::max(frame->num_args, givenargs);
	// Many functions have 'itemref' as a 'phantom' arg.
	// In the originals, this was probably so that the games
	// could know how much memory the function would need.
	// In any case, do this only if this was not an indirect call.
	if (givenargs == 0 && is_object_fun(funcid)) {
		if (--num_args < 0) {
			// Backwards compatibility with older mods.
			cerr << "Called usecode function " << hex << setfill('0')
			     << funcid << dec << setfill(' ');
			cerr << " with negative number of arguments." << endl
			     << "The mod/game was likely compiled with an outdated version of UCC"
			     << endl;
			num_args = 0;
		}
	}
#ifdef DEBUG
	int added_args = num_args - oldstack;
#endif
	while (num_args > oldstack) { // Not enough args pushed?
		pushi(0); // add zeroes
		oldstack++;
	}

	// Store args in first num_args locals
	int i;
	for (i = 0; i < num_args; i++) {
		Usecode_value val = pop();
		frame->locals[num_args - i - 1] = val;
	}

	// save stack pointer
	frame->save_sp = sp;

	// add new stack frame to top of stack
	call_stack.push_front(frame);

#ifdef DEBUG
	Usecode_symbol *fsym = symtbl ? (*symtbl)[funcid] : nullptr;
	cout << "Running usecode ";
	if (fsym)
		cout << fsym->get_name() << " [";
	cout << setw(4) << hex << setfill('0')
	     << funcid << dec << setfill(' ');
	if (fsym)
		cout << "]";
	cout << " (";
	for (i = 0; i < num_args; i++) {
		if (i)
			cout << ", ";
		frame->locals[i].print(cout);
	}
	cout << ") with event " << eventid
	     << ", depth " << frame->call_depth << endl;
	if (added_args > 0)
		cout << added_args << (added_args > 1 ? " args" : " arg")
		     << " had to be added to the stack for this call" << endl;
#endif

	return true;
}

void Usecode_internal::previous_stack_frame() {
	// remove current frame from stack
	Stack_frame *frame = call_stack.front();
	call_stack.pop_front();

	// restore stack pointer
	sp = frame->save_sp;

	// Get rid of exception handler for the current function (say, due to return
	// in a try block).
	auto it = except_stack.find(frame);
	if (it != except_stack.end()) {
		except_stack.erase(it);
	}

	if (frame->call_depth == 0) {
		// this was the function called from 'the outside'
		// push a marker (nullptr) for the interpreter onto the call stack,
		// so it knows it has to return instead of continuing
		// further up the call stack
		call_stack.push_front(nullptr);
	}

	delete frame;
}

void Usecode_internal::return_from_function(Usecode_value &retval) {
#ifdef DEBUG
	// store old function ID for debugging output
	int oldfunction = call_stack.front()->function->id;
#endif

	// back up a stack frame
	previous_stack_frame();

	// push the return value
	push(retval);

#ifdef DEBUG
	Stack_frame *parent_frame = call_stack.front();

	cout << "Returning (";
	retval.print(cout);
	cout << ") from usecode ";
	Usecode_symbol *fsym = symtbl ? (*symtbl)[oldfunction] : nullptr;
	if (fsym)
		cout << fsym->get_name();
	else
		cout << hex << setw(4) <<
		     setfill('0') << oldfunction << dec << setfill(' ');
	cout << endl;

	if (parent_frame) {
		int newfunction = call_stack.front()->function->id;
		Usecode_symbol *fsym = symtbl ? (*symtbl)[newfunction] : nullptr;

		cout << "...back into usecode ";
		if (fsym)
			cout << fsym->get_name();
		else
			cout << hex << setw(4) <<
			     setfill('0') << newfunction << dec <<
			     setfill(' ');
		cout << endl;
	}
#endif
}

void Usecode_internal::return_from_procedure() {
#ifdef DEBUG
	// store old function ID for debugging output
	int oldfunction = call_stack.front()->function->id;
#endif

	// back up a stack frame
	previous_stack_frame();

#ifdef DEBUG
	Stack_frame *parent_frame = call_stack.front();
	Usecode_symbol *fsym = symtbl ? (*symtbl)[oldfunction] : nullptr;

	cout << "Returning from usecode ";
	if (fsym)
		cout << fsym->get_name();
	else
		cout << hex << setw(4) << setfill('0') <<
		     oldfunction << dec << setfill(' ');
	cout << endl;

	if (parent_frame) {
		int newfunction = call_stack.front()->function->id;
		Usecode_symbol *fsym = symtbl ? (*symtbl)[newfunction] : nullptr;

		cout << "...back into usecode ";
		if (fsym)
			cout << fsym->get_name();
		else
			cout << hex << setw(4) << setfill('0') <<
			     newfunction << dec << setfill(' ');
		cout << endl;
	}
#endif
}

void Usecode_internal::abort_function(Usecode_value &retval) {
#ifdef DEBUG
	int functionid = call_stack.front()->function->id;

	cout << "Aborting from usecode " << hex << setw(4)
	     << setfill('0') << functionid << dec << setfill(' ')
	     << endl;
#endif

	// clear the entire call stack up to either a catch or the entry point
	while (call_stack.front() != nullptr) {
		previous_stack_frame();
		Stack_frame *frame = call_stack.front();
		auto it = except_stack.find(frame);
		if (it != except_stack.end()) {
			const uint8 *target = it->second;
#ifdef DEBUG
			int functionid = frame->function->id;

			cout << "Abort caught in usecode " << hex << setw(4)
				 << setfill('0') << functionid << " at location "
				 << setw(8) << setfill('0') << target << dec << setfill(' ')
				 << endl;
#endif
			except_stack.erase(it);
			frame->ip = target;
			// push the return value
			push(retval);
			break;
		}
	}
}

/*
 *  Append a string.
 */

void Usecode_internal::append_string(
    const char *str
) {
	if (!str)
		return;
	// Figure new length.
	int len = String ? strlen(String) : 0;
	len += strlen(str);
	char *newstr = new char[len + 1];
	if (String) {
		strcpy(newstr, String);
		delete [] String;
		String = strcat(newstr, str);
	} else
		String = strcpy(newstr, str);
}

// Push/pop stack.
inline void Usecode_internal::push(Usecode_value const &val) {
	*sp++ = val;
}

inline Usecode_value Usecode_internal::pop() {
	if (sp <= stack) {
		// Happens in SI #0x939
		cerr << "Stack underflow" << endl;
		return Usecode_value(0);
	}
	// +++++SHARED:  Shouldn't we reset *sp.
	return *--sp;
}

inline Usecode_value Usecode_internal::peek() {
	return sp[-1];
}

inline void Usecode_internal::pushref(Game_object *obj) {
	Usecode_value v(obj);
	push(v);
}

inline void Usecode_internal::pushref(Game_object_shared obj) {
	Usecode_value v(std::move(obj));
	push(v);
}

inline void Usecode_internal::pushi(long val) {     // Push/pop integers.
	Usecode_value v(val);
	push(v);
}

inline int Usecode_internal::popi() {
	Usecode_value val = pop();
	return val.need_int_value();
}

// Push/pop strings.
inline void Usecode_internal::pushs(const char *s) {
	Usecode_value val(s);
	push(val);
}

/*
 *  Get a game object from an "itemref", which might be the actual
 *  pointer, or might be -(npc number).
 *
 *  Output: ->game object.
 */

Game_object *Usecode_internal::get_item(
    Usecode_value &itemref
) {
	// If array, take 1st element.
	Usecode_value &elemval = itemref.get_elem0();

	if (elemval.is_ptr())
		return elemval.get_ptr_value();

	long val = elemval.get_int_value();
	if (!val)
		return nullptr;
	Game_object *obj = nullptr;
	if (val == -356)        // Avatar.
		return gwin->get_main_actor();
	else if (val < -356 && val > -360)  // Special cases.
		return nullptr;
	if (val < 0 && val > -gwin->get_num_npcs())
		obj = gwin->get_npc(-val);
	else if (val >= 0) {
		// Special case:  palace guards, Time Lord.
		if (val < 0x400 && !itemref.is_array() &&
		        caller_item && ((GAME_BG && val == 0x269) || val == caller_item->get_shapenum()))
			obj = caller_item.get();
		else
			return nullptr;
	}
	return obj;
}

/*
 *  Check for an actor.
 */

Actor *Usecode_internal::as_actor(
    Game_object *obj
) {
	if (!obj)
		return nullptr;
	return obj->as_actor();
}

/*
 *  Get a position.
 */

Tile_coord Usecode_internal::get_position(
    Usecode_value &itemval
) {
	Game_object *obj;       // An object?
	if ((itemval.get_array_size() == 1 || !itemval.get_array_size()) &&
	        (obj = get_item(itemval)))
		return obj->get_outermost()->get_tile();
	else if (itemval.get_array_size() == 3)
		// An array of coords.?
		return Tile_coord(itemval.get_elem(0).get_int_value(),
		                  itemval.get_elem(1).get_int_value(),
		                  itemval.get_elem(2).get_int_value());
	else if (itemval.get_array_size() == 4)
		// Result of click_on_item() with
		//  array = (null, tx, ty, tz)?
		return Tile_coord(itemval.get_elem(1).get_int_value(),
		                  itemval.get_elem(2).get_int_value(),
		                  itemval.get_elem(3).get_int_value());
	else                // Else assume caller_item.
		return caller_item->get_tile();
}

/*
 *  Make sure pending text has been seen.
 */

void Usecode_internal::show_pending_text(
) {
	if (book) {         // Book mode?
		int x;
		int y;
		while (book->show_next_page() &&
		        Get_click(x, y, Mouse::hand, nullptr, false, book, true))
			;
		gwin->paint();
	}
	// Normal conversation:
	else if (conv->is_npc_text_pending())
		click_to_continue();
}

/*
 *  Show book or scroll text.
 */

void Usecode_internal::show_book(
) {
	char *str = String;
	book->add_text(str);
	delete [] String;
	String = nullptr;
}

/*
 *  Say the current string and empty it.
 */

void Usecode_internal::say_string(
) {
	//  user_choice = 0;        // Clear user's response.
	if (!String)
		return;
	if (book) {         // Displaying a book?
		show_book();
		return;
	}
	show_pending_text();        // Make sure prev. text was seen.
	char *str = String;
	while (*str) {          // Look for stopping points ("~~").
		if (*str == '*') {  // Just gets an extra click.
			click_to_continue();
			str++;
			continue;
		}
		char *eol = strchr(str, '~');
		if (!eol) {     // Not found?
			conv->show_npc_message(str);
			click_to_continue();
			break;
		}
		*eol = 0;
		conv->show_npc_message(str);
		click_to_continue();
		str = eol + 1;
		if (*str == '~')
			str++;      // 2 in a row.
	}
	delete [] String;
	String = nullptr;
}

/*
 *  Stack error.
 */

void Usecode_internal::stack_error(
    int under           // 1 if underflow.
) {
	if (under)
		cerr << "Stack underflow." << endl;
	else
		cerr << "Stack overflow." << endl;
	exit(1);
}

/*
 *  Gets the face for an NPC.
 */
int Usecode_internal::get_face_shape(
    Usecode_value &arg1,
    Actor *&npc,
    int &frame
) {
	npc = as_actor(get_item(arg1));
	int shape = -1;
	if (arg1.is_int()) {
		shape = std::abs(arg1.get_int_value());
		if (shape == 356)   // Avatar.
			shape = 0;
	} else if (npc)
		shape = npc->get_face_shapenum();

	if (shape < 0)  // No need to do anything else.
		return shape;

	// Checks for Petra flag.
	shape = Shapeinfo_lookup::GetFaceReplacement(shape);

	if (Game::get_game_type() == SERPENT_ISLE) {
		// Special case: Nightmare Smith.
		//   (But don't mess up Guardian.)
		Actor *iact;
		if (shape == 296 && this->frame->caller_item &&
		        (iact = this->frame->caller_item->as_actor()) != nullptr &&
		        iact->get_npc_num() == 277)
			shape = 277;
	}

	// Another special case: map face shape 0 to
	// the avatar's correct face shape and frame:
	if (shape == 0) {
		Actor *ava = gwin->get_main_actor();
		bool sishapes = Shape_manager::get_instance()->have_si_shapes();
		Skin_data *skin = Shapeinfo_lookup::GetSkinInfoSafe(
		                      ava->get_skin_color(), npc ? (npc->get_type_flag(Actor::tf_sex))
		                      : (ava->get_type_flag(Actor::tf_sex)), sishapes);
		if (gwin->get_main_actor()->get_flag(Obj_flags::tattooed)) {
			shape = skin->alter_face_shape;
			frame = skin->alter_face_frame;
		} else {
			shape = skin->face_shape;
			frame = skin->face_frame;
		}
	}
	return shape;
}

/*
 *  Show an NPC's face.
 */

void Usecode_internal::show_npc_face(
    Usecode_value &arg1,        // Shape (NPC #).
    Usecode_value &arg2,        // Frame.
    int slot            // 0, 1, or -1 to find free spot.
) {
	show_pending_text();
	Actor *npc;
	int frame = arg2.get_int_value();
	int shape = get_face_shape(arg1, npc, frame);

	if (shape < 0)
		return;

	if (Game::get_game_type() == BLACK_GATE && npc) {
		// Only do this if the NPC is the caller item.
		if (npc->get_npc_num() != -1)
			npc->set_flag(Obj_flags::met);
	}

	if (!conv->get_num_faces_on_screen())
		gwin->get_effects()->remove_text_effects();
	// Only non persistent
	if (gumpman->showing_gumps(true)) {
		gumpman->close_all_gumps();
		gwin->set_all_dirty();
		init_conversation();    // jsf-Added 4/20/01 for SI-Lydia.
	}
	gwin->paint_dirty();
	conv->show_face(shape, frame, slot);
//	user_choice = 0;     // Seems like a good idea.
// Also seems to create a conversation bug in Test of Love :-(
}

/*
 *  Remove an NPC's face.
 */

void Usecode_internal::remove_npc_face(
    Usecode_value &arg1     // Shape (NPC #).
) {
	show_pending_text();
	Actor *npc;
	int shape = get_face_shape(arg1, npc);
	if (shape < 0)
		return;
	conv->remove_face(shape);
}

/*
 *  Set an item's shape.
 */

void Usecode_internal::set_item_shape(
    Usecode_value &item_arg,
    Usecode_value &shape_arg
) {
	int shape = shape_arg.get_int_value();
	Game_object *item = get_item(item_arg);
	if (!item)
		return;
	// See if light turned on/off.
	bool light_changed = item->get_info().is_light_source() !=
	                    ShapeID::get_info(shape).is_light_source();
	if (item->get_owner()) {    // Inside something?
		item->get_owner()->change_member_shape(item, shape);
		if (light_changed)  // Maybe we should repaint all.
			gwin->paint();  // Repaint finds all lights.
		else {
			Gump *gump = gumpman->find_gump(item);
			if (gump)
				gump->paint();
		}
		return;
	}
	gwin->add_dirty(item);
	// Get chunk it's in.
	Map_chunk *chunk = item->get_chunk();
	if (!chunk) {
		CERR("Object " << item << " not in chunk and not owned by another object");
		item->set_shape(shape);
		return;
	}
	Game_object_shared keep = item->shared_from_this();
	chunk->remove(item);        // Remove and add to update cache.
	item->set_shape(shape);
	chunk->add(item);
	gwin->add_dirty(item);
//	rect = gwin->get_shape_rect(item).add(rect);
//	rect.enlarge(8);
//	rect = gwin->clip_to_win(rect);
	if (light_changed)
		gwin->paint();      // Complete repaint refigures lights.
//	else
//		gwin->paint(rect);  // Not sure...
//	gwin->show();            // Not sure if this is needed.
}

/*
 *  Set an item's frame.
 */

void Usecode_internal::set_item_frame(
    Game_object *item,
    int frame,
    int check_empty,        // If 1, don't set empty frame.
    int set_rotated         // Set 'rotated' bit to one in 'frame'.
) {
	if (!item)
		return;
	// Added 9/16/2001:
	if (!set_rotated)       // Leave bit alone?
		frame = (item->get_framenum() & 32) | (frame & 31);
	if (frame == item->get_framenum())
		return;         // Already set to that.
	Actor *act = as_actor(item);
	// Actors have frame replacements for empty frames:
	if (act)
		act->change_frame(frame);
	else {
		// Check for empty frame.
		ShapeID sid(item->get_shapenum(), frame, item->get_shapefile());
		Shape_frame *shape = sid.get_shape();
		if (!shape || (check_empty && shape->is_empty()))
			return;
		// cout << "Set_item_frame: " << item->get_shapenum()
		//              << ", " << frame << endl;
		// (Don't mess up rotated frames.)
		if ((frame & 0xf) < item->get_num_frames())
			item->change_frame(frame);
	}
	gwin->set_painted();        // Make sure paint gets done.
}

/*
 *  Set to repaint an object.
 */

void Usecode_internal::add_dirty(
    Game_object *obj
) {
	gwin->add_dirty(obj);
}

/*
 *  Remove an item from the world.
 */

void Usecode_internal::remove_item(
    Game_object *obj
) {
	if (!obj)
		return;
	if (!last_created.empty() && obj == last_created.back().get())
		last_created.pop_back();
	add_dirty(obj);
	if (GAME_SI && frame && frame->function->id == 0x70e
	           && obj->get_shapenum() == 0x113 && obj->get_quality() == 0xd) {
		// Hack to fix broken trap switch in SI temple of Discipline.
		// This works better than the original, in that the trap will stay
		// disabled if you go away and come back, instead of returning and being
		// impossible to disarm.
		Egg_vector vec;         // Gets list.
		// Same parameters used in the egg to activate the trap.
		if (obj->find_nearby_eggs(vec, 0xc8, 0xf)) {
			for (auto *egg : vec) {
				egg->remove_this(nullptr);
			}
		}
	}
    Game_object_shared keep;
    obj->remove_this(obj->as_actor() ? &keep : nullptr);
}

/*
 *  Return an array containing the party, with the Avatar first.
 */

Usecode_value Usecode_internal::get_party(
) {
	int cnt = partyman->get_count();
	Usecode_value arr(1 + cnt, nullptr);
	// Add avatar.
	Usecode_value aval(gwin->get_main_actor());
	arr.put_elem(0, aval);
	int num_added = 1;
	for (int i = 0; i < cnt; i++) {
		Game_object *obj = gwin->get_npc(partyman->get_member(i));
		if (!obj)
			continue;
		Usecode_value val(obj);
		arr.put_elem(num_added++, val);
	}
	// cout << "Party:  "; arr.print(cout); cout << endl;
	return arr;
}

/*
 *  Put text near an item.
 */

void Usecode_internal::item_say(
    Usecode_value &objval,
    Usecode_value &strval
) {
	Game_object *obj = get_item(objval);
	const char *str = strval.get_str_value();
	if (obj && str && *str) {
		Effects_manager *eman = gwin->get_effects();
		// Added Nov01,01 to fix 'locate':
		eman->remove_text_effect(obj);
		if (gwin->failed_copy_protection())
			str = "Oink!";
		eman->add_text(str, obj);
	}
}

/*
 *  Activate all cached-in usecode eggs near a given spot.
 */

void Usecode_internal::activate_cached(
    Tile_coord const &pos
) {
	if (Game::get_game_type() != BLACK_GATE)
		return;         // ++++Since we're not sure about it.
	const int dist = 16;
	Egg_vector vec;         // Find all usecode eggs.
	Game_object::find_nearby_eggs(vec, pos, 275, dist, c_any_qual, 7);
	for (auto *egg : vec) {
		if (egg->get_criteria() == Egg_object::cached_in)
			egg->activate();
	}
}

/*
 *  For sorting up-to-down, right-to-left, and near-to-far:
 */
class Object_reverse_sorter {
public:
	bool operator()(const Game_object *o1, const Game_object *o2) {
		Tile_coord t1 = o1->get_tile();
		Tile_coord t2 = o2->get_tile();
		if (t1.ty > t2.ty)
			return true;
		else if (t1.ty == t2.ty) {
			if (t1.tx > t2.tx)
				return true;
			else
				return t1.tx == t2.tx && t1.tz > t2.tz;
		} else
			return false;
	}
};

/*
 *  Return an array of nearby objects.
 */

Usecode_value Usecode_internal::find_nearby(
    Usecode_value &objval,      // Find them near this.
    Usecode_value &shapeval,    // Shape to find, or -1 for any,
    //  c_any_shapenum for any npc.
    Usecode_value &distval,     // Distance in tiles?
    Usecode_value &mval     // Some kind of mask?  Guessing:
    //   4 == party members only.
    //   8 == non-party NPC's only.
    //  16 == something with eggs???
    //  32 == monsters? invisible?
) {
	Game_object_vector vec;         // Gets list.

	int shapenum;

	if (shapeval.is_array()) {
		// fixes 'lightning whip sacrifice' in Silver Seed
		shapenum = shapeval.get_elem(0).get_int_value();
		if (shapeval.get_array_size() > 1)
			cerr << "Calling find_nearby with an array > 1 !!!!"
			     << endl;
	} else
		shapenum = shapeval.get_int_value();

	// It might be (tx, ty, tz).
	int arraysize = objval.get_array_size();
	if (arraysize == 4) {   // Passed result of click_on_item.
		Game_object::find_nearby(vec,
		                         Tile_coord(objval.get_elem(1).get_int_value(),
		                                    objval.get_elem(2).get_int_value(),
		                                    objval.get_elem(3).get_int_value()),
		                         shapenum, distval.get_int_value(), mval.get_int_value());
	} else if (arraysize == 3 || arraysize == 5) {
		// Coords(x,y,z) [qual, frame]
		// Qual is 4th if there.
		int qual = arraysize == 5 ? objval.get_elem(3).get_int_value()
		           : c_any_qual;
		// Frame is 5th if there.
		int frnum = arraysize == 5 ? objval.get_elem(4).get_int_value()
		            : c_any_framenum;
		Game_object::find_nearby(vec,
		                         Tile_coord(objval.get_elem(0).get_int_value(),
		                                    objval.get_elem(1).get_int_value(),
		                                    objval.get_elem(2).get_int_value()),
		                         shapenum,
		                         distval.get_int_value(), mval.get_int_value(),
		                         qual, frnum);
	} else {
		Game_object *obj = get_item(objval);
		if (!obj)
			return Usecode_value(0, nullptr);
		obj = obj->get_outermost(); // Might be inside something.
		obj->find_nearby(vec, shapenum,
		                 distval.get_int_value(), mval.get_int_value());
	}
	if (vec.size() > 1)     // Sort right-left, near-far to fix
		//   SI/SS cask bug.
		std::sort(vec.begin(), vec.end(), Object_reverse_sorter());
	Usecode_value nearby(vec.size(), nullptr);    // Create return array.
	int i = 0;
	for (auto *each : vec) {
		Usecode_value val(each);
		nearby.put_elem(i++, val);
	}
	return nearby;
}

/*
 *  Look for a barge that an object is a part of, or on, using the same
 *  sort (right-left, front-back) as ::find_nearby().  If there are more
 *  than one beneath 'obj', the highest is returned.
 *
 *  Output: ->barge if found, else 0.
 */

Barge_object *Get_barge(
    Game_object *obj
) {
	// Check object itself.
	Barge_object *barge = obj->as_barge();
	if (barge)
		return barge;
	Game_object_vector vec;     // Find it within 20 tiles (egglike).
	obj->find_nearby(vec, 961, 20, 0x10);
	if (vec.size() > 1)     // Sort right-left, near-far.
		std::sort(vec.begin(), vec.end(), Object_reverse_sorter());
	// Object must be inside it.
	Tile_coord pos = obj->get_tile();
	Barge_object *best = nullptr;
	for (auto *it : vec) {
		barge = it->as_barge();
		if (barge && barge->get_tile_footprint().has_world_point(pos.tx, pos.ty)) {
			int lift = barge->get_lift();
			if (!best ||    // First qualifying?
			        // First beneath obj.?
			        (best->get_lift() > pos.tz && lift <= pos.tz) ||
			        // Highest beneath?
			        (lift <= pos.tz && lift > best->get_lift()))
				best = barge;
		}
	}
	return best;
}

/*
 *  Return object of given shape nearest given obj.
 */

Usecode_value Usecode_internal::find_nearest(
    Usecode_value &objval,      // Find near this.
    Usecode_value &shapeval,    // Shape to find
    Usecode_value &distval      // Guessing it's distance.
) {
	Game_object *obj = get_item(objval);
	if (!obj)
		return Usecode_value(static_cast<Game_object *>(nullptr));
	Game_object_vector vec;         // Gets list.
	obj = obj->get_outermost(); // Might be inside something.
	int dist = distval.get_int_value();
	int shnum = shapeval.get_int_value();
	// Kludge for Test of Courage:
	if (frame->function->id == 0x70a && shnum == 0x9a && dist == 0)
		dist = 16;      // Mage may have wandered.
	obj->find_nearby(vec, shnum, dist, 0);
	Game_object *closest = nullptr;
	uint32 bestdist = 100000;// Distance-squared in tiles.
	Tile_coord t1 = obj->get_tile();
	for (auto *each : vec) {
		Tile_coord t2 = each->get_tile();
		int dx = t1.tx - t2.tx;
		int dy = t1.ty - t2.ty;
		int dz = t1.tz - t2.tz;
		uint32 dist = dx * dx + dy * dy + dz * dz;
		if (dist < bestdist) {
			bestdist = dist;
			closest = each;
		}
	}
	return Usecode_value(closest);
}

/*
 *  Find the angle (0-7) from one object to another.
 */

Usecode_value Usecode_internal::find_direction(
    Usecode_value &from,
    Usecode_value &to
) {
	unsigned angle;         // Gets angle 0-7 (north - northwest)
	Tile_coord t1 = get_position(from);
	Tile_coord t2 = get_position(to);
	// Treat as cartesian coords.
	angle = static_cast<int>(Get_direction(t1.ty - t2.ty, t2.tx - t1.tx));
	return Usecode_value(angle);
}

/*
 *  Count objects of a given shape in a container, or in the whole party.
 */

Usecode_value Usecode_internal::count_objects(
    Usecode_value &objval,      // The container, or -357 for party.
    Usecode_value &shapeval,    // Object shape to count (c_any_shapenum=any).
    Usecode_value &qualval,     // Quality (c_any_qual=any).
    Usecode_value &frameval     // Frame (c_any_framenum=any).
) {
	long oval = objval.get_int_value();
	int shapenum = shapeval.get_int_value();
	int qualnum = qualval.get_int_value();
	int framenum = frameval.get_int_value();
	if (oval != -357) {
		Game_object *obj = get_item(objval);
		return Usecode_value(!obj ? 0 : obj->count_objects(
		            shapenum, qualnum, framenum));
	}
	// Look through whole party.
	Usecode_value party = get_party();
	int cnt = party.get_array_size();
	int total = 0;
	for (int i = 0; i < cnt; i++) {
		Game_object *obj = get_item(party.get_elem(i));
		if (obj)
			total += obj->count_objects(shapenum, qualnum,
			                            framenum);
	}
	return Usecode_value(total);
}

/*
 *  Get objects of a given shape in a container.
 */

Usecode_value Usecode_internal::get_objects(
    Usecode_value &objval,      // The container.
    Usecode_value &shapeval,    // Object shape to get or c_any_shapenum for any.
    Usecode_value &qualval,     // Quality (c_any_qual=any).
    Usecode_value &frameval     // Frame (c_any_framenum=any).
) {
	Game_object *obj = get_item(objval);
	if (!obj)
		return Usecode_value(static_cast<Game_object *>(nullptr));
	int shapenum = shapeval.get_int_value();
	int framenum = frameval.get_int_value();
	int qual = qualval.get_int_value();
	Game_object_vector vec;         // Gets list.
	obj->get_objects(vec, shapenum, qual, framenum);

//	cout << "Container objects found:  " << cnt << << endl;
	Usecode_value within(vec.size(), nullptr);    // Create return array.
	int i = 0;
	for (auto *each : vec) {
		Usecode_value val(each);
		within.put_elem(i++, val);
	}
	return within;
}

/*
 *  Remove a quantity of an item from the party.
 *
 *  Output: 1 (or the object) if successful, else 0.
 */

Usecode_value Usecode_internal::remove_party_items(
    Usecode_value &quantval,    // Quantity to remove.
    Usecode_value &shapeval,    // Shape.
    Usecode_value &qualval,     // Quality??
    Usecode_value &frameval,    // Frame.
    Usecode_value &flagval      // Flag??
) {
	ignore_unused_variable_warning(flagval);
	int quantity = quantval.need_int_value();
	int shapenum = shapeval.get_int_value();
	int framenum = frameval.get_int_value();
	int quality = qualval.get_int_value();
	Usecode_value party = get_party();
	int cnt = party.get_array_size();
	Usecode_value all(-357);    // See if they exist.
	Usecode_value avail = count_objects(all, shapeval, qualval, frameval);
	// Verified. Originally SI-only, allowing for BG too.
	if (quantity == c_any_quantity)
		quantity = avail.get_int_value();
	else if (avail.get_int_value() < quantity)
		return Usecode_value(0);
	int orig_cnt = quantity;
	// Look through whole party.
	for (int i = 0; i < cnt && quantity > 0; i++) {
		Game_object *obj = get_item(party.get_elem(i));
		if (obj)
			quantity = obj->remove_quantity(quantity, shapenum,
			                                quality, framenum);
	}
	return Usecode_value(quantity != orig_cnt);
}

/*
 *  Add a quantity of an item to the party.
 *
 *  Output: List of members who got objects.
 */

Usecode_value Usecode_internal::add_party_items(
    Usecode_value &quantval,    // Quantity to add.
    Usecode_value &shapeval,    // Shape.
    Usecode_value &qualval,     // Quality.
    Usecode_value &frameval,    // Frame.
    Usecode_value &temporary    // If the objects are to be temporary or not
) {
	int quantity = quantval.get_int_value();
	// ++++++First see if there's room.
	int shapenum = shapeval.get_int_value();
	int framenum = frameval.get_int_value();
	int quality = qualval.get_int_value();
	// Note: the temporary flag only applies to items placed on the
	// ground in SI.
	bool temp = temporary.get_int_value() != 0;
	// Look through whole party.
	Usecode_value party = get_party();
	int cnt = party.get_array_size();
	Usecode_value result(0, nullptr); // Start with empty array.
	for (int i = 0; i < cnt && quantity > 0; i++) {
		Game_object *obj = get_item(party.get_elem(i));
		if (!obj)
			continue;
		int prev = quantity;
		quantity = obj->add_quantity(quantity, shapenum,
		                             quality, framenum, false, GAME_BG && temp);
		if (quantity < prev)    // Added to this NPC.
			result.concat(party.get_elem(i));
	}
	if (GAME_BG || GAME_SIB)    // Black gate or Beta SI?  Just return result.
		return result;
	int todo = quantity;        // SI:  Put remaining on the ground.
	if (framenum == c_any_framenum)
		framenum = 0;
	while (todo > 0) {
		Tile_coord pos = Map_chunk::find_spot(
		                     gwin->get_main_actor()->get_tile(), 3,
		                     shapenum, framenum, 2);
		if (pos.tx == -1)   // Hope this rarely happens.
			break;
		const Shape_info &info = ShapeID::get_info(shapenum);
		// Create and place.
		Game_object_shared newobj = gmap->create_ireg_object(
		                          info, shapenum, framenum, 0, 0, 0);
		if (quality != c_any_qual)
			newobj->set_quality(quality); // set quality
		newobj->set_flag(Obj_flags::okay_to_take);
		if (temp)      // Mark as temporary.
			newobj->set_flag(Obj_flags::is_temporary);
		newobj->move(pos);
		todo--;
		if (todo > 0)       // Create quantity if possible.
			todo = newobj->modify_quantity(todo);
	}
	// SI?  Append # left on ground.
	Usecode_value ground(quantity - todo);
	result.concat(ground);
	return result;
}

/*
 *  Add a quantity of an item to a container
 *
 *  Output: Num created
 */

Usecode_value Usecode_internal::add_cont_items(
    Usecode_value &container,   // What do we add to
    Usecode_value &quantval,    // Quantity to add.
    Usecode_value &shapeval,    // Shape.
    Usecode_value &qualval,     // Quality.
    Usecode_value &frameval,    // Frame.
    Usecode_value &temporary    // If the objects are to be temporary or not
) {
	int quantity = quantval.get_int_value();
	int shapenum = shapeval.get_int_value();
	int framenum = frameval.get_int_value();
	int quality = qualval.get_int_value();
	bool temp = temporary.get_int_value() != 0;
	// e.g., Knight's Test wolf meat.
	if (quality == -359)
		quality = 0;

	Game_object *obj = get_item(container);
	if (obj) {
		// This fixes teleport storm in SI Beta.
		int numleft = obj->add_quantity(quantity, shapenum, quality, framenum, false, temp);
		if (GAME_SIB) return Usecode_value(quantity - numleft);
		else return Usecode_value(numleft);
	}
	return Usecode_value(0);
}

/*
 *  Remove a quantity of an item to a container
 *
 *  Output: Num removed
 */

Usecode_value Usecode_internal::remove_cont_items(
    Usecode_value &container,   // What do we add to
    Usecode_value &quantval,    // Quantity to add.
    Usecode_value &shapeval,    // Shape.
    Usecode_value &qualval,     // Quality.
    Usecode_value &frameval,    // Frame.
    Usecode_value &flagval      // Flag??
) {
	Game_object *obj = get_item(container);
	if (!obj) {
		if (container.get_int_value() == -357)
			return remove_party_items(quantval, shapeval, qualval, frameval, flagval);
		return Usecode_value(0);
	}

	int quantity = quantval.get_int_value();
	int shapenum = shapeval.get_int_value();
	int framenum = frameval.get_int_value();
	auto quality = static_cast<unsigned int>(qualval.get_int_value());

	if (quantity == c_any_quantity) {
		quantval = count_objects(container, shapeval, qualval, frameval);
		quantity = quantval.get_int_value();
	}

	return Usecode_value(quantity - obj->remove_quantity(quantity, shapenum, quality, framenum));
}

/*
 *  Create a new object and push it onto the last_created stack.
 */

Game_object_shared Usecode_internal::create_object(
    int shapenum,
    bool equip          // Equip monsters.
) {
	Game_object_shared obj;       // Create to be written to Ireg.
	const Shape_info &info = ShapeID::get_info(shapenum);
	modified_map = true;
	// +++Not sure if 1st test is needed.
	if (info.get_monster_info() || info.is_npc()) {
		// (Wait sched. added for FOV.)
		// don't add equipment (Erethian's transform sequence)
		Game_object_shared new_monster = Monster_actor::create(shapenum,
		                         Tile_coord(-1, -1, -1), Schedule::wait,
		                         static_cast<int>(Actor::neutral), true, equip);
		auto *monster = static_cast<Monster_actor *>(
					  		   	 				new_monster.get());
		// FORCE it to be neutral (dec04,01).
		monster->set_alignment(static_cast<int>(Actor::neutral));
		gwin->add_dirty(monster);
		gwin->add_nearby_npc(monster);
		gwin->show();
		last_created.push_back(monster->shared_from_this());
		return new_monster;
	} else {
		if (info.is_body_shape())
			obj = std::make_shared<Dead_body>(shapenum, 0, 0, 0, 0, -1);
		else {
			obj = gmap->create_ireg_object(shapenum, 0);
			// Be liberal about taking stuff.
			obj->set_flag(Obj_flags::okay_to_take);
		}
	}
	obj->set_invalid();     // Not in world yet.
	obj->set_flag(Obj_flags::okay_to_take);
	last_created.push_back(obj->shared_from_this());
	return obj;
}

/*
 *  Have an NPC walk somewhere and then execute usecode.
 *
 *  Output: true if successful, else false.
 */

bool Usecode_internal::path_run_usecode(
    Usecode_value &npcval,      // # or ref.
    Usecode_value &locval,      // Where to walk to.
    Usecode_value &useval,      // Usecode #.
    Usecode_value &itemval,     // Use as itemref in Usecode fun.
    Usecode_value &eventval,    // Eventid.
    bool find_free,         // Not sure.  For SI.
    bool always,            // Always run function, even if failed.
    bool companions         // For SI:  companions should follow.
) {
	Actor *npc = as_actor(get_item(npcval));
	if (!npc)
		return false;
	path_npc = npc;
	int usefun = useval.get_elem0().get_int_value();
	Game_object *obj = get_item(itemval);
	int sz = locval.get_array_size();
	if (!npc || sz < 2) {
		CERR("Path_run_usecode: bad inputs");
		return false;
	}
	Tile_coord src = npc->get_tile();
	Tile_coord dest(locval.get_elem(0).get_int_value(),
	                locval.get_elem(1).get_int_value(),
	                sz == 3 ? locval.get_elem(2).get_int_value() : 0);
	if (dest.tz < 0)        // ++++Don't understand this.
		dest.tz = 0;
	if (find_free) {
		/* Now works with SI lightning platform */
		// Allow rise of 3 (for SI lightning).
		Tile_coord d = Map_chunk::find_spot(dest, 3, npc, 3);
		if (d.tx == -1)     // No?  Try at source level.
			d = Map_chunk::find_spot(
			        Tile_coord(dest.tx, dest.ty, src.tz), 3, npc,
			        0);
		if (d.tx != -1)     // Found it?
			dest = d;
		if (usefun == 0x60a &&  // ++++Added 7/21/01 to fix Iron
		        src.distance(dest) <= 1)
			return true;   // Maiden loop in SI.  Kludge+++++++
	}
	if (!obj) {         // Just skip the usecode part.
		int res = npc->walk_path_to_tile(dest,
		                                 gwin->get_std_delay(), 0);
		if (res && companions && npc->get_action())
			npc->get_action()->set_get_party();
		return res;
	}
	// Walk there and execute.
	auto *action =
	    new If_else_path_actor_action(npc, dest,
	                                  new Usecode_actor_action(usefun, obj,
	                                          eventval.get_int_value()));
	if (companions)
		action->set_get_party();
	if (always)         // Set failure to same thing.
		action->set_failure(
		    new Usecode_actor_action(usefun, obj,
		                             eventval.get_int_value()));
	npc->set_action(action);    // Get into time queue.
	npc->start(gwin->get_std_delay(), 0);
	return !action->done_and_failed();
}

/*
 *  See if an actor can go to a given location.
 */

bool Usecode_internal::is_dest_reachable(
    Actor *npc,
    Tile_coord const &dest
) {
	if (dest.tz < 0)
		return false;
	if (npc->distance(dest) <= 1)   // Already OK.
		return true;
	Path_walking_actor_action action(nullptr, 6);
	return action.walk_to_tile(npc, npc->get_tile(), dest, 1) != nullptr;
}

/*
 *  Schedule a script.
 */

void Usecode_internal::create_script(
    Usecode_value &objval,
    Usecode_value &codeval,
    long delay          // Delay from current time.
) {
	Game_object *obj = get_item(objval);
	// Pure kludge for SI wells:
	if (objval.get_array_size() == 2 &&
	        Game::get_game_type() == SERPENT_ISLE &&
	        obj && obj->get_shapenum() == 470 && obj->get_lift() == 0) {
		// We want the TOP of the well.
		Usecode_value v2 = objval.get_elem(1);
		Game_object *o2 = get_item(v2);
		if (o2->get_shapenum() == obj->get_shapenum() &&
		        o2->get_lift() == 2) {
			objval = v2;
			obj = o2;
		}
	}
	if (!obj) {
		cerr << "Can't create script for nullptr object" << endl;
		return;
	}
	auto *code = new Usecode_value();
	code->steal_array(codeval); // codeval is undefined after this.
	auto *script = new Usecode_script(obj, code);
	script->start(delay);
}

#ifdef DEBUG
/*
 *  Report unhandled intrinsic.
 */

static void Usecode_Trace(
    const char *name,
    int intrinsic,
    int num_parms,
    Usecode_value parms[12]
) {
	boost::io::ios_flags_saver flags(cout);
	boost::io::ios_fill_saver fill(cout);
	cout << hex << "    [0x" << setfill('0') << setw(2)
	     << intrinsic << "]: " << name << "(";
	for (int i = 0; i < num_parms; i++) {
		parms[i].print(cout);
		if (i != num_parms - 1)
			cout << ", ";
	}
	cout << ") = ";
}

static  void    Usecode_TraceReturn(Usecode_value &v) {
	v.print(cout);
	cout << dec << endl;
}

#endif

Usecode_value no_ret;

Usecode_value Usecode_internal::Execute_Intrinsic(UsecodeIntrinsicFn func, const char *name, int intrinsic, int num_parms, Usecode_value parms[12]) {
#ifdef XWIN
#ifdef USECODE_DEBUGGER
	if (usecode_debugging) {
		// Examine the list of intrinsics for function breakpoints.
		if (std::find(intrinsic_breakpoints.begin(), intrinsic_breakpoints.end(), intrinsic) != intrinsic_breakpoints.end()) {
			raise(SIGIO);   // Breakpoint
		}
	}
#endif
#endif
#ifndef DEBUG
	ignore_unused_variable_warning(name, intrinsic);
#else
	if (intrinsic_trace) {
		Usecode_Trace(name, intrinsic, num_parms, parms);
		cout.flush();
		Usecode_value u = ((*this).*func)(num_parms, parms);
		Usecode_TraceReturn(u);
		return u;
	}
#endif
	return ((*this).*func)(num_parms, parms);
}

using UsecodeIntrinsicFn = Usecode_value(Usecode_internal::*)(int num_parms, Usecode_value parms[12]);

// missing from mingw32 header files, so included manually
#ifndef TO_STRING
#if defined __STDC__ && __STDC__
#define TO_STRING(x) #x
#else
#define TO_STRING(x) "x"
#endif
#endif

#define USECODE_INTRINSIC_PTR(NAME) { &Usecode_internal::UI_ ## NAME, TO_STRING(NAME) }

Usecode_internal::IntrinsicTableEntry
		Usecode_internal::intrinsic_table[] = {
#include "bgintrinsics.h"
};

// Serpent Isle Intrinsic Function Tablee
// It's different to the Black Gate one.
Usecode_internal::IntrinsicTableEntry
		Usecode_internal::serpent_table[] = {
#include "siintrinsics.h"
};

// Serpent Isle Beta Intrinsic Function Tablee
// It's different to the Black Gate and Seroent Isle one.
Usecode_internal::IntrinsicTableEntry
		Usecode_internal::serpentbeta_table[] = {
#include "sibetaintrinsics.h"
};

int max_bundled_intrinsics = 0x3ff; // Index of the last intrinsic in this table
/*
 *  Call an intrinsic function.
 */

Usecode_value Usecode_internal::call_intrinsic(
    int intrinsic,          // The ID.
    int num_parms           // # parms on stack.
) {
	Usecode_value parms[13];    // Get parms.
	for (int i = 0; i < num_parms; i++) {
		Usecode_value val = pop();
		parms[i] = val;
	}
	if (intrinsic <= max_bundled_intrinsics) {
		Usecode_internal::IntrinsicTableEntry *table_entry;

		if (Game::get_game_type() == SERPENT_ISLE) {
			if (Game::is_si_beta())
				table_entry = serpentbeta_table + intrinsic;
			else
				table_entry = serpent_table + intrinsic;
		} else
			table_entry = intrinsic_table + intrinsic;
		UsecodeIntrinsicFn func = (*table_entry).func;
		const char *name = (*table_entry).name;
		return Execute_Intrinsic(func, name, intrinsic,
		                         num_parms, parms);
	}
	return no_ret;
}

/*
 *  Wait for user to click inside a conversation.
 */

void Usecode_internal::click_to_continue(
) {
	if (!gwin->get_pal()->is_faded_out()) { // If black screen, skip!
		int xx;
		int yy;
		char c;
		gwin->paint();      // Repaint scenery.
		Get_click(xx, yy, Mouse::hand, &c, false, conv, true);
	}
	conv->clear_text_pending();
	//  user_choice = 0;        // Clear it.
}

/*
 *  Set book/scroll to display.
 */

void Usecode_internal::set_book(
    Text_gump *b            // Book/scroll.
) {
	if (touchui != nullptr) {
		Gump_manager *gumpman = gwin->get_gump_man();
		if ((book && !b) && !gumpman->gump_mode())
			touchui->showGameControls();
		else if (!book && b)
			touchui->hideGameControls();
	}
	delete book;
	book = b;
}

/*
 *  Get user's choice from among the possible responses.
 *
 *  Output: ->user choice string.
 *      0 if no possible choices or user quit.
 */

const char *Usecode_internal::get_user_choice(
) {
	if (!conv->get_num_answers())
		return nullptr;     // This does happen (Emps-honey).

	//  if (!user_choice)       // May have already been done.
	// (breaks conversation with Cyclops on Dagger Isle ('foul magic' option))

	get_user_choice_num();
	return user_choice;
}

/*
 *  Get user's choice from among the possible responses.
 *
 *  Output: User choice is set, with choice # returned.
 *      -1 if no possible choices.
 */

int Usecode_internal::get_user_choice_num(
) {
	delete [] user_choice;
	user_choice = nullptr;
	conv->show_avatar_choices();
	int x;
	int y;           // Get click.
	int choice_num;
	do {
		char chr;       // Allow '1', '2', etc.
		gwin->paint();      // Paint scenery.
		bool result = Get_click(x, y, Mouse::hand, &chr, false, conv, true);
		if (!result) {  // ESC pressed, select 'bye' if poss.
			choice_num = conv->locate_answer("bye");
		} else if (chr) {       // key pressed
			choice_num = -1;    //invalid key
			if (std::isalnum(static_cast<unsigned char>(chr))) {
				constexpr static const char optionKeys[] = "123456789abcdefghijklmnopqrstuvwxyz";
				const auto *it = std::find(std::cbegin(optionKeys), std::cend(optionKeys), chr);
				auto dist = std::distance(std::cbegin(optionKeys), it);
				if (it != std::cend(optionKeys) && dist < conv->get_num_answers()) {
					choice_num = dist;
				}
			}
		} else
			choice_num = conv->conversation_choice(x, y);
	}
	// Wait for valid choice.
	while (choice_num  < 0 || choice_num >= conv->get_num_answers());

	conv->clear_avatar_choices();
	// Store ->answer string.
	user_choice = newstrdup(conv->get_answer(choice_num));
	return choice_num;        // Return choice #.
}

/*
 *  Create for the outside world.
 */

Usecode_machine *Usecode_machine::create(
) {
	return new Usecode_internal();
}

/*
 *  Create machine from a 'usecode' file.
 */

Usecode_internal::Usecode_internal(
) : stack(new Usecode_value[1024]) {
	sp = stack;
	ifstream file;                // Read in usecode.
	std::cout << "Reading usecode file." << std::endl;
	try {
		U7open(file, USECODE);
		read_usecode(file);
		file.close();
	} catch (const file_exception &f) {
		if (!Game::is_editing())    // Ok if map-editing.
			throw;
		std::cerr << "Warning (map-editing): Couldn't open '" <<
		          USECODE << "'" << endl;
	}

	// Get custom usecode functions.
	if (is_system_path_defined("<PATCH>") && U7exists(PATCH_USECODE)) {
		U7open(file, PATCH_USECODE);
		read_usecode(file, true);
		file.close();
	}

	//  set_breakpoint();
}

/*
 *  Read in usecode functions.  These may override previously read
 *  functions.
 */

void Usecode_internal::read_usecode(
    istream &file,
    bool patch          // True if reading from 'patch'.
) {
	file.seekg(0, ios::end);
	int size = file.tellg();    // Get file size.
	file.seekg(0);
	if (Usecode_symbol_table::has_symbol_table(file)) {
		delete symtbl;
		symtbl = new Usecode_symbol_table();
		symtbl->read(file);
	}
	// Read in all the functions.
	while (file.tellg() < size) {
		auto *fun = new Usecode_function(file);
		unsigned int slotnum = fun->id / 0x100;
		if (slotnum >= funs.size())
			funs.resize(slotnum < 10 ? 10 : slotnum + 1);
		Funs256 &vec = funs[slotnum];
		unsigned int i = fun->id % 0x100;
		if (i >= vec.size())
			vec.resize(i + 1);
		else if (vec[i]) {
			// Already have one there.
			if (patch) { // Patching?
				if (vec[i]->orig) {
					// Patching a patch.
					fun->orig = vec[i]->orig;
					delete vec[i];
				} else      // Patching fun. from static.
					fun->orig = vec[i];
			} else {
				delete vec[i]->orig;
				delete vec[i];
			}
		}
		vec[i] = fun;
	}
}

/*
 *  Delete.
 */

Usecode_internal::~Usecode_internal(
) {
	delete [] stack;
	delete [] String;
	delete symtbl;
	int num_slots = funs.size();
	for (int i = 0; i < num_slots; i++) {
		Funs256 &slot = funs[i];
		int cnt = slot.size();
		for (int j = 0; j < cnt; j++)
			delete slot[j];
	}
	delete book;
}

#ifdef DEBUG
int debug = 2;              // 2 for more stuff.
static int ucbp_fun = -1, ucbp_ip = -1; // Breakpoint.
void Setbreak(int fun, int ip) {
	ucbp_fun = fun;
	ucbp_ip = ip;
}
void Clearbreak() {
	ucbp_fun = ucbp_ip = -1;
}
#endif

#define CERR_CURRENT_IP()\
	cerr << " (at function = " << hex << setw(4) << setfill('0')\
	     << frame->function->id << ", ip = " \
	     << current_IP << dec << setfill(' ') << ")" << endl

#define LOCAL_VAR_ERROR(x)\
	cerr << "Local variable #" << (x) << " out of range!";\
	CERR_CURRENT_IP()

#define DATA_SEGMENT_ERROR()\
	cerr << "Data pointer out of range!";\
	CERR_CURRENT_IP()

#define EXTERN_ERROR()\
	cerr << "Extern offset out of range!";\
	CERR_CURRENT_IP()

#define FLAG_ERROR(x)\
	cerr << "Global flag #" << (x) << " out of range!";\
	CERR_CURRENT_IP()

#define THIS_ERROR()\
	cerr << "nullptr class pointer!";\
	CERR_CURRENT_IP()

/*
 *  The main usecode interpreter
 *
 *  Output:
 */

int Usecode_internal::run() {
	bool aborted = false;
	bool initializing_loop = false;

	while ((frame = call_stack.front())) {
		int num_locals = frame->num_vars + frame->num_args;
		int offset;
		int sval;

		bool frame_changed = false;

		// set some variables for use in other member functions
		caller_item = frame->caller_item;

		/*
		 *  Main loop.
		 */
		while (!frame_changed) {
			if ((frame->ip >= frame->endp) ||
			        (frame->ip < frame->code)) {
				cerr << "Usecode: jumped outside of code segment of "
				     << "function " << hex << setw(4) << setfill('0')
				     << frame->function->id << dec << setfill(' ')
				     << " ! Aborting." << endl;

				Usecode_value msg("Out of bounds usecode execution!");
				abort_function(msg);
				frame_changed = true;
				continue;
			}

			int current_IP = frame->ip - frame->code;

			auto opcode = static_cast<UsecodeOps>(*(frame->ip));

			if (frame->ip + get_opcode_length(static_cast<int>(opcode)) > frame->endp) {
				cerr << "Operands lie outside of code segment. ";
				CERR_CURRENT_IP();
				continue;
			}

#ifdef DEBUG
			if (usecode_trace == 2) {
				uc_trace_disasm(frame);
			}
#endif

#ifdef USECODE_DEBUGGER
			// check breakpoints

			int bp = breakpoints.check(frame);
			if (bp != -1) {
				// we hit a breakpoint

				// allow handling extra debugging messages
				on_breakpoint = true;

				cout << "On breakpoint" << endl;

				// signal remote client that we hit a breakpoint
				auto c = static_cast<unsigned char>(Exult_server::dbg_on_breakpoint);
				if (client_socket >= 0)
					Exult_server::Send_data(client_socket,
					                        Exult_server::usecode_debugging,
					                        &c, 1);

#ifdef XWIN
				raise(SIGUSR1); // to allow trapping it in gdb too
#endif

#ifdef USECODE_CONSOLE_DEBUGGER
				// little console mode "debugger" (if you can call it that...)
				bool done = false;
				while (!done) {
					char userinput;
					cout << "s=step into, o=step over, f=finish, c=continue, "
					     << "b=stacktrace: ";
					std::cin >> userinput;

					if (userinput == 's') {
						breakpoints.add(new AnywhereBreakpoint());
						cout << "Stepping into..." << endl;
						done = true;
					} else if (userinput == 'o') {
						breakpoints.add(new StepoverBreakpoint(frame));
						cout << "Stepping over..." << endl;
						done = true;
					} else if (userinput == 'f') {
						breakpoints.add(new FinishBreakpoint(frame));
						cout << "Finishing function..." << endl;
						done = true;
					} else if (userinput == 'c') {
						done = true;
					} else if (userinput == 'b') {
						stack_trace(cout);
					}
				}
#elif (defined(USE_EXULTSTUDIO))
				breakpoint_action = -1;
				while (breakpoint_action == -1) {
					SDL_Delay(20);
					Server_delay(Handle_client_debug_message);
				}
#endif

				c = static_cast<unsigned char>(Exult_server::dbg_continuing);
				if (client_socket >= 0)
					Exult_server::Send_data(client_socket,
					                        Exult_server::usecode_debugging,
					                        &c, 1);
				// disable extra debugging messages again
				on_breakpoint = false;
			}
#endif

			frame->ip++;

			switch (opcode) {
			case UC_CONVERSE:     // start conversation
			case UC_CONVERSE32: { // (32 bit version)
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);

				found_answer = false;
				if (!get_user_choice())  // Exit conv. if no choices.
					frame->ip += offset; // (Emps and honey.)
				break;
			}
			case UC_JNE:        // JNE.
			case UC_JNE32: {    // JNE32
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);
				Usecode_value val = pop();
				if (val.is_false())
					frame->ip += offset;
				break;
			}
			case UC_JMP:        // JMP.
			case UC_JMP32:      // JMP32
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);
				frame->ip += offset;
				break;
			case UC_CMPS:     // CMPS.
			case UC_CMPS32: { // (32 bit version)
				int cnt = Read2(frame->ip); // # strings.
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);

				bool matched = false;

				// only try to match if we haven't found an answer yet
				while (!matched && !found_answer && cnt-- > 0) {
					Usecode_value s = pop();
					const char *str = s.get_str_value();
					if (str && strcmp(str, user_choice) == 0) {
						matched = true;
						found_answer = true;
					}
				}
				while (cnt-- > 0)   // Pop rest of stack.
					pop();
				if (!matched)       // Jump if no match.
					frame->ip += offset;
			}
			break;
			case UC_ADD: {    // ADD.
				Usecode_value v2 = pop();
				Usecode_value v1 = pop();
				Usecode_value retval = v1 + v2;
				push(retval);
				break;
			}
			case UC_SUB: {    // SUB.
				Usecode_value v2 = pop();
				Usecode_value v1 = pop();
				Usecode_value retval = v1 - v2;
				push(retval);
				break;
			}
			case UC_DIV: {    // DIV.
				Usecode_value v2 = pop();
				Usecode_value v1 = pop();
				Usecode_value retval = v1 / v2;
				push(retval);
				break;
			}
			case UC_MUL: {    // MUL.
				Usecode_value v2 = pop();
				Usecode_value v1 = pop();
				Usecode_value retval = v1 * v2;
				push(retval);
				break;
			}
			case UC_MOD: {    // MOD.
				Usecode_value v2 = pop();
				Usecode_value v1 = pop();
				Usecode_value retval = v1 % v2;
				push(retval);
				break;
			}
			case UC_AND: {    // AND.
				Usecode_value v1 = pop();
				Usecode_value v2 = pop();
				bool result = v1.is_true() && v2.is_true();
				pushi(result);
				break;
			}
			case UC_OR: {    // OR.
				Usecode_value v1 = pop();
				Usecode_value v2 = pop();
				bool result = v1.is_true() || v2.is_true();
				pushi(result);
				break;
			}
			case UC_NOT:      // NOT.
				pushi(!pop().is_true());
				break;
			case UC_POP: {    // POP into a variable.
				offset = Read2(frame->ip);
				// Get value.
				Usecode_value val = pop();
				if (offset < 0 || offset >= num_locals) {
					LOCAL_VAR_ERROR(offset);
				} else {
					frame->locals[offset] = val;
				}
				break;
			}
			case UC_PUSHTRUE:      // PUSH true.
				pushi(1);
				break;
			case UC_PUSHFALSE:     // PUSH false.
				pushi(0);
				break;
			case UC_CMPGT:      // CMPGT.
				sval = popi();
				pushi(popi() > sval);   // Order?
				break;
			case UC_CMPLT:      // CMPLT.
				sval = popi();
				pushi(popi() < sval);
				break;
			case UC_CMPGE:     // CMPGE.
				sval = popi();
				pushi(popi() >= sval);
				break;
			case UC_CMPLE:     // CMPLE.
				sval = popi();
				pushi(popi() <= sval);
				break;
			case UC_CMPNE: {   // CMPNE.
				Usecode_value val1 = pop();
				Usecode_value val2 = pop();
				pushi(!(val1 == val2));
				break;
			}
			case UC_ADDSI:        // ADDSI.
			case UC_ADDSI32:      // ADDSI32
				if (opcode < UC_EXTOPCODE)
					offset = Read2(frame->ip);
				else
					offset = Read4s(frame->ip);
				if (offset < 0 || frame->data + offset >= frame->externs - 6) {
					DATA_SEGMENT_ERROR();
					break;
				}
				append_string(frame->data + offset);
				break;
			case UC_PUSHS:        // PUSHS.
			case UC_PUSHS32:      // PUSHS32
				if (opcode < UC_EXTOPCODE)
					offset = Read2(frame->ip);
				else
					offset = Read4s(frame->ip);
				if (offset < 0 || frame->data + offset >= frame->externs - 6) {
					DATA_SEGMENT_ERROR();
					break;
				}
				pushs(frame->data + offset);
				break;
			case UC_ARRC: {    // ARRC.
				// Get # values to pop into array.
				int num = Read2(frame->ip);
				int cnt = num;
				Usecode_value arr(num, nullptr);
				int to = 0; // Store at this index.
				while (cnt--) {
					Usecode_value val = pop();
					to += arr.add_values(to, val);
				}
				if (to < num)// 1 or more vals empty arrays?
					arr.resize(to);
				push(arr);
				break;
			}
			case UC_PUSHI:        // PUSHI.
			case UC_PUSHI32: {    // PUSHI32
				// Might be negative.
				int ival;
				if (opcode < UC_EXTOPCODE)
					ival = Read2s(frame->ip);
				else
					ival = Read4s(frame->ip);
				pushi(ival);
				break;
			}
			case UC_PUSH:      // PUSH.
				offset = Read2(frame->ip);
				if (offset < 0 || offset >= num_locals) {
					LOCAL_VAR_ERROR(offset);
					pushi(0);
				} else {
					push(frame->locals[offset]);
				}
				break;
			case UC_CMPEQ: {    // CMPEQ.
				Usecode_value val1 = pop();
				Usecode_value val2 = pop();
				pushi(val1 == val2);
				break;
			}
			case UC_CALL: {    // CALL.
				offset = Read2(frame->ip);
				if (offset < 0 || offset >= frame->num_externs) {
					EXTERN_ERROR();
					break;
				}

				const uint8 *tempptr = frame->externs + 2 * offset;
				int funcid = Read2(tempptr);

				call_function(funcid, frame->eventid);
				frame_changed = true;
				break;
			}
			case UC_CALL32: {    // 32-bit CALL.
				offset = Read4s(frame->ip);
				call_function(offset, frame->eventid);
				frame_changed = true;
				break;
			}
			case UC_RET:       // RET. (End of procedure reached)
			case UC_RET2:      // RET. (Return from procedure)
				show_pending_text();

				return_from_procedure();
				frame_changed = true;
				break;
			case UC_AIDX:         // AIDX.
			case UC_AIDXS:        // AIDXS.
			case UC_AIDXTHV: {    // AIDXTHV.
				sval = popi();  // Get index into array.
				sval--;     // It's 1 based.
				// Get # of local to index.
				Usecode_value *val;
				if (opcode == UC_AIDX) {
					offset = Read2(frame->ip);
					if (offset < 0 || offset >= num_locals) {
						LOCAL_VAR_ERROR(offset);
						pushi(0);
						break;
					}
					val = &(frame->locals[offset]);
				} else if (opcode == UC_AIDXTHV) {
					offset = Read2(frame->ip);
					Usecode_value &ths = frame->get_this();
					if (offset < 0 || offset >= ths.get_class_var_count()) {
						cerr << "Class variable #" << (offset) << " out of range!";
						CERR_CURRENT_IP();
						break;
					}
					val = &(ths.nth_class_var(offset));
				} else {
					offset = Read2s(frame->ip);
					if (offset < 0) {// Global static.
						if (static_cast<unsigned>(-offset) < statics.size())
							val = &(statics[-offset]);
						else {
							cerr << "Global static variable #" << (offset) << " out of range!";
							pushi(0);
							break;
						}
					} else {
						if (static_cast<unsigned>(offset) < frame->function->statics.size())
							val = &(frame->function->statics[offset]);
						else {
							cerr << "Local static variable #" << (offset) << " out of range!";
							pushi(0);
							break;
						}
					}
				}
				if (sval < 0) {
					cerr << "AIDX: Negative array index: " << sval << endl;
					pushi(0);
					break;
				}
				if (val->is_array() && size_t(sval) >= val->get_array_size())
					pushi(0);   // Matches originals.
				else if (sval == 0) // needed for SS keyring (among others)
					push(val->get_elem0());
				else
					push(val->get_elem(sval));
				break;
			}
			case UC_RETV: {    // RET. (Return from function)
				// ++++ Testing.
				show_pending_text();
				Usecode_value r = pop();

				return_from_function(r);
				frame_changed = true;
				break;
			}
			case UC_LOOP:        // INITLOOP (1st byte of loop)
			case UC_LOOP32: {    // (32 bit version)
				int nextopcode = *(frame->ip);
				// No real reason to have 32-bit version of this instruction;
				// keeping it for backward compatibility only.
				nextopcode &= 0x7f;
				if (nextopcode != UC_LOOPTOP && nextopcode != UC_LOOPTOPS &&
				        nextopcode != UC_LOOPTOPTHV) {
					cerr << "Invalid 2nd byte in loop!" << endl;
					break;
				} else {
					initializing_loop = true;
				}
				break;
			}
			case UC_LOOPTOP:        // LOOP (2nd byte of loop)
			case UC_LOOPTOP32:      // (32 bit version)
			case UC_LOOPTOPS:       // LOOP (2nd byte of loop) using static array
			case UC_LOOPTOPS32:     // (32 bit version)
			case UC_LOOPTOPTHV:     // LOOP (2nd byte of loop) using class member array
			case UC_LOOPTOPTHV32: { // (32 bit version)
				// Counter (1-based).
				int local1 = Read2(frame->ip);
				// Total count.
				int local2 = Read2(frame->ip);
				// Current value of loop var.
				int local3 = Read2(frame->ip);
				// Array of values to loop over.
				int local4;
				bool is_32bit = (opcode >= UC_EXTOPCODE);
				// Mask off 32bit flag.
				opcode &= 0x7f;
				if (opcode == UC_LOOPTOPS)
					local4 = Read2s(frame->ip);
				else
					local4 = Read2(frame->ip);
				// Get offset to end of loop.
				if (is_32bit)
					offset = Read4s(frame->ip); // 32 bit offset
				else
					offset = Read2s(frame->ip);

				if (local1 < 0 || local1 >= num_locals) {
					LOCAL_VAR_ERROR(local1);
					break;
				}
				if (local2 < 0 || local2 >= num_locals) {
					LOCAL_VAR_ERROR(local2);
					break;
				}
				if (local3 < 0 || local3 >= num_locals) {
					LOCAL_VAR_ERROR(local3);
					break;
				}
				if (opcode == UC_LOOPTOPS) {
					if (local4 < 0) {// Global static.
						if (static_cast<unsigned>(-local4) >= statics.size()) {
							cerr << "Global static variable #" << (-local4) << " out of range!";
							CERR_CURRENT_IP();
							break;
						}
					} else {
						if (static_cast<unsigned>(local4) >= frame->function->statics.size()) {
							cerr << "Local static variable #" << (local4) << " out of range!";
							CERR_CURRENT_IP();
							break;
						}
					}
				} else if (opcode == UC_LOOPTOPTHV) {
					Usecode_value &ths = frame->get_this();
					if (local4 < 0 || local4 >= ths.get_class_var_count()) {
						cerr << "Class variable #" << (local4) << " out of range!";
						CERR_CURRENT_IP();
						break;
					}
				} else {
					if (local4 < 0 || local4 >= num_locals) {
						LOCAL_VAR_ERROR(local4);
						break;
					}
				}

				// Get array to loop over.
				Usecode_value &arr = opcode == UC_LOOPTOPS ?
				                     (local4 < 0 ? statics[-local4]
				                      : frame->function->statics[local4])
					                     : (opcode == UC_LOOPTOPTHV ?
					                        frame->get_this().nth_class_var(local4) :
					                        frame->locals[local4]);
				if (initializing_loop && arr.is_undefined()) {
					// If the local 'array' is not initialized, do not loop
					// (verified in FoV and SS):
					initializing_loop = false;
					frame->ip += offset;
					break;
				}

				int next = frame->locals[local1].get_int_value();

				if (initializing_loop) {
					// Initialize loop.
					initializing_loop = false;
					int cnt = arr.is_array() ?
					          arr.get_array_size() : 1;
					frame->locals[local2] = Usecode_value(cnt);
					frame->locals[local1] = Usecode_value(0);

					next = 0;
				}

				// in SI, the loop-array can be modified in-loop, it seems
				// (conv. with Spektran, 044D:00BE)

				// so, check for changes of the array size, and adjust
				// total count and next value accordingly.

				// Allowing this for BG too.

				int cnt = arr.is_array() ? arr.get_array_size() : 1;

				if (cnt != frame->locals[local2].get_int_value()) {
					// update new total count
					frame->locals[local2] = Usecode_value(cnt);

					if (std::abs(cnt - frame->locals[local2].get_int_value()) == 1) {
						// small change... we can fix this
						Usecode_value &curval = arr.is_array() ?
						                        arr.get_elem(next - 1) : arr;

						if (curval != frame->locals[local3]) {
							if (cnt > frame->locals[local2].get_int_value()) {
								//array got bigger, it seems
								//addition occured before the current value
								next++;
							} else {
								//array got smaller
								//deletion occured before the current value
								next--;
							}
						} else {
							//addition/deletion was after the current value
							//so don't need to update 'next'
						}
					} else {
						// big change...
						// just update total count to make sure
						// we don't crash
					}
				}

				if (cnt != frame->locals[local2].get_int_value()) {
					// update new total count
					frame->locals[local2] = Usecode_value(cnt);

					Usecode_value &curval = arr.is_array() ?
					                        arr.get_elem(next - 1) : arr;

					if (curval != frame->locals[local3]) {
						if (cnt > frame->locals[local2].get_int_value()) {
							// array got bigger, it seems
							// addition occured before the current value
							next++;
						} else {
							// array got smaller
							// deletion occured before the current value
							next--;
						}
					} else {
						// addition/deletion was after the current value
						// so don't need to update 'next'
					}
				}

				// End of loop?
				if (next >= frame->locals[local2].get_int_value()) {
					frame->ip += offset;
				} else {    // Get next element.
					frame->locals[local3] = arr.is_array() ?
					                        arr.get_elem(next) : arr;
					frame->locals[local1] = Usecode_value(next + 1);
				}
				break;
			}
			case UC_ADDSV: {    // ADDSV.
				offset = Read2(frame->ip);
				if (offset < 0 || offset >= num_locals) {
					LOCAL_VAR_ERROR(offset);
					break;
				}

				const char *str = frame->locals[offset].get_str_value();
				if (str)
					append_string(str);
				else {      // Convert integer.
					// 25-09-2001 - Changed to >= 0 to fix money-counting in SI.
					//              if (locals[offset].get_int_value() != 0) {
					if (frame->locals[offset].get_int_value() >= 0) {
						char buf[20];
						snprintf(buf, 20, "%ld",
						         frame->locals[offset].get_int_value());
						append_string(buf);
					}
				}
				break;
			}
			case UC_IN: {    // IN.  Is a val. in an array?
				Usecode_value arr = pop();
				// If an array, use 1st elem.
				Usecode_value val = pop().get_elem0();
				pushi(arr.find_elem(val) >= 0);
				break;
			}
			case UC_CONVSMTH:        // Unknown.
			case UC_CONVSMTH32:      // (32 bit version)
				// this opcode only occurs in the 'audition' usecode function (BG)
				// not sure what it's supposed to do, but this function results
				// in the same behaviour as the original
				frame->ip += 2;
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);

				if (!found_answer)
					found_answer = true;
				else
					frame->ip += offset;
				break;

			case UC_RETZ: {    // RET. (End of function reached)
				show_pending_text();

				Usecode_value zero(0);
				return_from_function(zero);
				frame_changed = true;
				break;
			}
			case UC_SAY:      // SAY.
				say_string();
				break;
			case UC_CALLIS: {    // CALLIS.
				offset = Read2(frame->ip);
				sval = *(frame->ip)++;  // # of parameters.
				Usecode_value ival = call_intrinsic(offset, sval);
				push(ival);
				frame_changed = true;
				break;
			}
			case UC_CALLI:       // CALLI.
				offset = Read2(frame->ip);
				sval = *(frame->ip)++; // # of parameters.
				call_intrinsic(offset, sval);
				frame_changed = true;
				break;
			case UC_PUSHITEMREF:      // PUSH ITEMREF.
				pushref(frame->caller_item);
				break;
			case UC_ABRT: {    // ABRT.
				show_pending_text();

				Usecode_value msg("abort executed");
				abort_function(msg);
				frame_changed = true;
				aborted = true;
				break;
			}
			case UC_THROW: {    // THROW.
				show_pending_text();

				Usecode_value r = pop();
				abort_function(r);
				frame_changed = true;
				aborted = true;
				break;
			}
			case UC_TRYSTART:
			case UC_TRYSTART32:
				if (opcode < UC_EXTOPCODE)
					offset = Read2s(frame->ip);
				else
					offset = Read4s(frame->ip);
				except_stack[frame] = frame->ip + offset;
				break;
			case UC_TRYEND: {
				auto it = except_stack.find(frame);
				if (it != except_stack.end()) {
					except_stack.erase(it);
				}
				break;
			}
			case UC_CONVERSELOC:      // end conversation
				found_answer = true;
				break;
			case UC_PUSHF:      // PUSHF.
			case UC_PUSHFVAR:   // PUSHF2.
				if (opcode >= UC_EXTOPCODE)
					offset = popi();
				else
					offset = Read2(frame->ip);
				if (offset < 0 || static_cast<unsigned>(offset) >= sizeof(gflags)) {
					FLAG_ERROR(offset);
					pushi(0);
				} else {
					pushi(gflags[offset]);
				}
				break;
			case UC_POPF:      // POPF.
			case UC_POPFVAR:   // POPF2.
				if (opcode >= UC_EXTOPCODE)
					offset = popi();
				else
					offset = Read2(frame->ip);
				if (offset < 0 || static_cast<unsigned>(offset) >= sizeof(gflags)) {
					FLAG_ERROR(offset);
				} else {
					gflags[offset] = static_cast<unsigned char>(popi());
					if (gflags[offset]) {
						Notebook_gump::add_gflag_text(offset);
#ifdef DEBUG
						cout << "Setting global flag: "
						     << offset << endl;
#endif
					}
					// ++++KLUDGE for Monk Isle:
					if (offset == 0x272 && GAME_SI)
						gflags[offset] = 0;
				}
				break;
			case UC_PUSHB:      // PUSHB.
				pushi(*(frame->ip)++);
				break;
			case UC_POPARR:         // Set array element.
			case UC_POPARRS:        // Set static array element.
			case UC_POPARRTHV: {    // Set class member array element.
				Usecode_value *arr;
				if (opcode == UC_POPARR) {
					offset = Read2(frame->ip);
					// Get # of local array.
					if (offset < 0 || offset >= num_locals) {
						LOCAL_VAR_ERROR(offset);
						break;
					}
					arr = &(frame->locals[offset]);
				} else if (opcode == UC_POPARRTHV) {
					offset = Read2(frame->ip);
					Usecode_value &ths = frame->get_this();
					if (offset < 0 || offset >= ths.get_class_var_count()) {
						cerr << "Class variable #" << (offset) << " out of range!";
						CERR_CURRENT_IP();
						break;
					}
					arr = &(ths.nth_class_var(offset));
				} else {
					offset = Read2s(frame->ip);
					if (offset < 0) {// Global static.
						if (static_cast<unsigned>(-offset) < statics.size())
							arr = &(statics[-offset]);
						else {
							cerr << "Global static variable #" << (offset) << " out of range!";
							CERR_CURRENT_IP();
							break;
						}
					} else {
						if (static_cast<unsigned>(offset) < frame->function->statics.size())
							arr = &(frame->function->statics[offset]);
						else {
							cerr << "Local static variable #" << (offset) << " out of range!";
							CERR_CURRENT_IP();
							break;
						}
					}
				}
				short index = popi();
				index--;    // It's 1-based.
				Usecode_value val = pop();
				int size = arr->get_array_size();
				if (index >= 0 &&
				        (index < size || arr->resize(index + 1)))
					arr->put_elem(index, val);
				break;
			}
			case UC_CALLE:        // CALLE.  Stack has caller_item.
			case UC_CALLE32: {    // 32-bit version.
				Usecode_value ival = pop();
				Game_object *caller = get_item(ival);
				if (opcode < UC_EXTOPCODE)
					offset = Read2(frame->ip);
				else
					offset = Read4s(frame->ip);
				call_function(offset, frame->eventid, caller);
				frame_changed = true;
				break;
			}
			case UC_PUSHEVENTID:      // PUSH EVENTID.
				pushi(frame->eventid);
				break;
			case UC_ARRA: {    // ARRA.
				Usecode_value val = pop();
				Usecode_value arr = pop();
				push(arr.concat(val));
				break;
			}
			case UC_POPEVENTID:      // POP EVENTID.
				frame->eventid = popi();
				break;
			case UC_DBGLINE: { // debugging opcode from spanish SI (line number)
				frame->line_number = Read2(frame->ip);
				break;
			}
			case UC_DBGFUNC:     // debugging opcode from spanish SI (function init)
			case UC_DBGFUNC32: { // 32 bit debugging function init
				int funcname;
				int paramnames;
				if (opcode < UC_EXTOPCODE) {
					funcname = Read2(frame->ip);
					paramnames = Read2(frame->ip);
				} else {
					funcname = Read4s(frame->ip);
					paramnames = Read4s(frame->ip);
				}
				if (funcname < 0 || frame->data + funcname >= frame->externs - 6) {
					DATA_SEGMENT_ERROR();
					break;
				}
				if (paramnames < 0 || frame->data + paramnames >= frame->externs - 6) {
					DATA_SEGMENT_ERROR();
					break;
				}
				cout << "Debug opcode found at function = " << hex << setw(4)
				     << setfill('0') << frame->function->id << ", ip = "
				     << current_IP << dec << setfill(' ') << "." << endl;
				cout << "Information is: funcname = '"
				     // This is a complete guess:
				     << (frame->data + funcname) << "'." << endl;
				char const *ptr = reinterpret_cast<char const *>(frame->data + paramnames);
				// This is an even bigger complete guess:
				if (*ptr) {
					int nargs = frame->num_args;
					if (is_object_fun(frame->function->id))
						nargs--;    // Function has an 'item'.
					if (nargs < 0)  // Just in case.
						nargs = 0;
					std::vector<std::string> names;
					names.resize(nargs);
					int i;
					// Reversed to match the order in which they are
					// passed in UCC.
					for (i = nargs - 1; i >= 0 && *ptr; i--) {
						std::string name(ptr);
						names[i] = name;
						ptr += name.length() + 1;
					}
					cout << "Parameter names follow: ";
					for (i = 0; i < nargs; i++) {
						cout << "#" << hex << setw(4) << setfill('0')
						     << i << " = ";
						if (names[i].length())
							cout << "'" << names[i] << "'";
						else
							cout << "(missing)";
						if (i < nargs)
							cout << ", ";
					}
					cout << endl << "Variable names follow: ";
					for (i = 0; i < frame->num_vars && *ptr; i++) {
						std::string name(ptr);
						ptr += name.length() + 1;
						cout << "#" << hex << setw(4) << setfill('0')
						     << (i + nargs) << " = ";
						if (name.length())
							cout << "'" << name << "'";
						else
							cout << "(missing)";
						if (i < frame->num_vars)
							cout << ", ";
					}
					for (; i < frame->num_vars; i++) {
						cout << "#" << hex << setw(4) << setfill('0')
						     << (i + nargs) << " = (missing)";
						if (i < frame->num_vars)
							cout << ", ";
					}
				} else
					cout << endl;
				break;
			}
			case UC_PUSHSTATIC:      // PUSH static.
				offset = Read2s(frame->ip);
				if (offset < 0) {// Global static.
					if (static_cast<unsigned>(-offset) < statics.size())
						push(statics[-offset]);
					else
						pushi(0);
				} else {
					if (static_cast<unsigned>(offset) < frame->function->statics.size())
						push(frame->function->statics[offset]);
					else
						pushi(0);
				}
				break;
			case UC_POPSTATIC: {    // POP static.
				offset = Read2s(frame->ip);
				// Get value.
				Usecode_value val = pop();
				if (offset < 0) {
					if (static_cast<unsigned>(-offset) >= statics.size())
						statics.resize(-offset + 1);
					statics[-offset] = val;
				} else {
					if (static_cast<unsigned>(offset) >= frame->function->statics.size())
						frame->function->statics.resize(offset + 1);
					frame->function->statics[offset] = val;
				}
				break;
			}
			case UC_CALLO: {    // CALLO (call original).
				// Otherwise, like CALLE.
				Usecode_value ival = pop();
				Game_object *caller = get_item(ival);
				offset = Read2(frame->ip);
				call_function(offset, frame->eventid, caller, false, true);
				frame_changed = true;
				break;
			}
			case UC_CALLIND:         // CALLIND:  call indirect.
			case UC_CALLINDEX_OLD:   // CALLINDEX_OLD:  call indirect with arguments.
			case UC_CALLINDEX: {     // CALLINDEX: call indirect with arguments.
				//  Function # is on stack.
				Usecode_value funval = pop();
				int offset = funval.get_int_value();
				Usecode_value ival = pop();
				Game_object *caller = get_item(ival);
				int numargs;
				if (opcode < UC_EXTOPCODE)
					numargs = 0;
				else if (opcode == UC_CALLINDEX_OLD)
					numargs = popi();
				else
					numargs = *(frame->ip)++;
				call_function(offset, frame->eventid, caller, false, false, numargs);
				frame_changed = true;
				break;
			}
			case UC_PUSHTHV: {    // PUSH class this->var.
				offset = Read2(frame->ip);
				Usecode_value &ths = frame->get_this();
				push(ths.nth_class_var(offset));
				break;
			}
			case UC_POPTHV: {    // POP class this->var.
				// Get value.
				Usecode_value val = pop();
				offset = Read2(frame->ip);
				Usecode_value &ths = frame->get_this();
				ths.nth_class_var(offset) = val;
				break;
			}
			case UC_CALLM:       // CALLM - call method, use pushed var vtable.
			case UC_CALLMS: {    // CALLMS - call method, use parameter vtable.
				offset = Read2(frame->ip);
				Usecode_class_symbol *c;
				if (opcode == UC_CALLM) {
					Usecode_value thisptr = peek();
					c = thisptr.get_class_ptr();
				} else {
					c = get_class(Read2(frame->ip));
				}
				if (!c) {
					THIS_ERROR();
					(void) pop();
					break;
				}
				int index = c->get_method_id(offset);
				call_function(index, frame->eventid);
				frame_changed = true;
				break;
			}
			case UC_CLSCREATE: {    // CLSCREATE
				int cnum = Read2(frame->ip);
				Usecode_class_symbol *cls = symtbl->get_class(cnum);
				if (!cls) {
					cerr << "Can't create obj. for class #" << cnum << endl;
					pushi(0);
					break;
				}
				int cnt = cls->get_num_vars();
				Usecode_value new_class = Usecode_value(0);
				new_class.class_new(cls, cnt);

				int to = 0; // Store at this index.
				// We are trusting UCC output here.
				while (cnt--) {
					Usecode_value val = pop();
					new_class.nth_class_var(to++) = val;
				}
				push(new_class);
				break;
			}
			case UC_CLASSDEL: {    //CLASSDEL
				Usecode_value cls = pop();
				cls.class_delete();
				break;
			}
			case UC_PUSHCHOICE:    // PUSHCHOICE
				pushs(user_choice);
				break;
			default:
				cerr << "Opcode " << opcode << " not known. ";
				CERR_CURRENT_IP();
				break;
			}
		}
	}

	if (call_stack.front() == nullptr) {
		// pop the nullptr frame from the stack
		call_stack.pop_front();
	}
	if (aborted)
		return 0;

	return 1;
}

/*
 *  This is the main entry for outside callers.
 *
 *  Output: -1 if not found.
 *      0 if can't execute now or if aborted.
 *      1 otherwise.
 */

int Usecode_internal::call_usecode(
    int id,                 // Function #.
    Game_object *item,      // Item ref.
    Usecode_events event
) {
	conv->clear_answers();

	int ret;
	if (call_function(id, event, item, true))
		ret = run();
	else
		ret = -1; // failed to call the function

	set_book(nullptr);

	// Left hanging (BG)?
	if (conv->get_num_faces_on_screen() > 0) {
		conv->init_faces(); // Remove them.
		gwin->set_all_dirty();  // Force repaint.
	}
	if (modified_map) {
		// On a barge, and we changed the map.
		Barge_object *barge = gwin->get_moving_barge();
		if (barge)
			barge->set_to_gather(); // Refigure what's on barge.
		modified_map = false;
	}
	return ret;
}

/*
 *  Call a 'method'.
 *  Output: Same as input, unless it's 'new', in which we return the new
 *      instance.
 */

bool Usecode_internal::call_method(
    Usecode_value *inst,        // Instance, or nullptr.
    int id,                     // Function # or -1 for free inst.
    Game_object *item           // Item ref.
) {
	if (id == -1) {
		// Only delete the class for now
		inst->class_delete();
		return false;
	}
	Usecode_function *fun = find_function(id);
	if (!fun)
		return false;

	auto *frame = new Stack_frame(fun, 0, item,
	                                     Stack_frame::getCallChainID(), 0);

	int oldstack = 0;
	while (frame->num_args > oldstack) { // Not enough args pushed?
		pushi(0); // add zeroes
		oldstack++;
	}

	// Store args in first num_args locals
	int i;
	for (i = 0; i < frame->num_args; i++) {
		Usecode_value val = pop();
		frame->locals[frame->num_args - i - 1] = val;
	}

	// save stack pointer
	frame->save_sp = sp;

	// add new stack frame to top of stack
	call_stack.push_front(frame);

#ifdef DEBUG
	Usecode_class_symbol *cls = inst->get_class_ptr();
	Usecode_symbol *fsym = cls ? (*cls)[id] : nullptr;
	cout << "Running usecode " << setw(4);
	if (cls)
		cout << cls->get_name();
	else    // Shouldn't happen.
		cout << "Unknown class";
	cout << "::";
	if (fsym)
		cout << fsym->get_name();
	else
		cout << hex << setfill('0')
		     << id << dec << setfill(' ');
	cout << " (";
	for (i = 0; i < frame->num_args; i++) {
		if (i)
			cout << ", ";
		frame->locals[i].print(cout);
	}
	cout << endl;
#endif

	return true;
}

/*
 *  Lookup function name in symbol table.  Prints error if not found.
 */

int Usecode_internal::find_function(
    const char *nm,
    bool noerr
) {
	Usecode_symbol *ucsym = symtbl ? (*symtbl)[nm] : nullptr;
	if (!ucsym) {
		if (!noerr)
			cerr << "Failed to find Usecode symbol '" << nm
			     << "'." << endl;
		return -1;
	}
	return ucsym->get_val();
}

/*
 *  Lookup function id in symbol table.
 */

const char *Usecode_internal::find_function_name(
    int funcid
) {
	Usecode_symbol *ucsym = symtbl ? (*symtbl)[funcid] : nullptr;
	if (!ucsym)
		return nullptr;
	return ucsym->get_name();
}

/*
 *  Start speech, or show text if speech isn't enabled.
 */

void Usecode_internal::do_speech(
    int num
) {
	speech_track = num;     // Used in Usecode function.
	if (!Audio::get_ptr()->start_speech(num))
		// No speech?  Call text function.
		call_usecode(SpeechUsecode, nullptr, double_click);
}

/*
 *  Are we in a usecode function for a given item and event?
 */

bool Usecode_internal::in_usecode_for(
    Game_object *item,
    Usecode_events event
) {
	for (auto *frame : call_stack) {
		if (frame->eventid == event && frame->caller_item.get() == item)
			return true;
	}
	return false;
}

/*
 *  Write out global data to 'gamedat/usecode.dat'.
 *  (and 'gamedat/keyring.dat')
 *
 *  Output: 0 if error.
 */

void Usecode_internal::write(
) {
	// Assume new games will have keyring.
	if (Game::get_game_type() != BLACK_GATE && !Game::is_si_beta())
		keyring->write();   // write keyring data

	{
		OFileDataSource out(FLAGINIT);
		out.write(gflags, sizeof(gflags));
	}
	{
		OFileDataSource out(USEDAT);
		out.write2(partyman->get_count()); // Write party.
		for (int i = 0; i < EXULT_PARTY_MAX; i++)
			out.write2(partyman->get_member(i));
		// Timers.
		out.write4(0xffffffffU);
		for (auto& timer : timers) {
			if (!timer.second)    // Don't write unused timers.
				continue;
			out.write2(timer.first);
			out.write4(timer.second);
		}
		out.write2(0xffff);
		out.write2(saved_pos.tx);  // Write saved pos.
		out.write2(saved_pos.ty);
		out.write2(saved_pos.tz);
		out.write2(saved_map);     // Write saved map.
	}
	// Static variables. 1st, globals.
	OFileDataSource nfile(USEVARS);
	nfile.write4(statics.size());  // # globals.
	for (auto& it : statics)
		if (!it.save(&nfile))
			throw file_exception("Could not write static usecode value");
	// Now do the local statics.
	int num_slots = funs.size();
	for (int i = 0; i < num_slots; i++) {
		Funs256 &slot = funs[i];
		for (auto *fun : slot) {
			if (!fun || fun->statics.empty())
				continue;
			Usecode_symbol *fsym = symtbl ? (*symtbl)[fun->id] : nullptr;
			if (fsym) {
				const char *nm = fsym->get_name();
				nfile.write4(0xfffffffeU);
				nfile.write2(strlen(nm));
				nfile.write(nm, strlen(nm));
			} else
				nfile.write4(fun->id);
			nfile.write4(fun->statics.size());
			for (auto& it : fun->statics) {
				if (!it.save(&nfile))
					throw file_exception("Could not write static usecode value");
			}
		}
	}
	nfile.write4(0xffffffffU); // End with -1.
}

/*
 *  Read in global data from 'gamedat/usecode.dat'.
 *  (and 'gamedat/keyring.dat')
 *
 *  Output: 0 if error.
 */

void Usecode_internal::read(
) {
	if (Game::get_game_type() != BLACK_GATE && !Game::is_si_beta())
		keyring->read();    // read keyring data

	ifstream in;
	try {
		U7open(in, FLAGINIT);   // Read global flags.
		in.seekg(0, ios::end);  // Get filesize.
		size_t filesize = in.tellg();
		in.seekg(0, ios::beg);
		if (filesize > sizeof(gflags))
			filesize = sizeof(gflags);
		memset(&gflags[0], 0, sizeof(gflags));
		in.read(reinterpret_cast<char *>(gflags), filesize);
		in.close();
	} catch (exult_exception const &e) {
		if (!Game::is_editing())
			throw;
		memset(&gflags[0], 0, sizeof(gflags));
	}

	clear_usevars(); // first clear all statics
	read_usevars();
	try {
		U7open(in, USEDAT);
	} catch (exult_exception &/*e*/) {
		partyman->set_count(0);
		partyman->link_party(); // Still need to do this.
		return;         // Not an error if no saved game yet.
	}
	partyman->set_count(Read2(in)); // Read party.
	size_t i;   // Blame MSVC
	for (i = 0; i < EXULT_PARTY_MAX; i++)
		partyman->set_member(i, Read2(in));
	partyman->link_party();
	// Timers.
	int cnt = Read4(in);
	if (cnt == -1) {
		int tmr = 0;
		while ((tmr = Read2(in)) != 0xffff)
			timers[tmr] = Read4(in);
	} else {
		timers[0] = cnt;
		for (size_t t = 1; t < 20; t++)
			timers[t] = Read4(in);
	}
	if (!in.good())
		throw file_read_exception(USEDAT);
	saved_pos.tx = Read2(in);   // Read in saved position.
	saved_pos.ty = Read2(in);
	saved_pos.tz = Read2(in);
	if (!in.good() ||       // Failed.+++++Can remove this later.
	        saved_pos.tz < 0 || saved_pos.tz > 13)
		saved_pos = Tile_coord(-1, -1, -1);
	saved_map = Read2(in);
	if (!in.good())         // For compat. with older saves.
		saved_map = -1;
}

/*
 *  Read in static variables from USEVARS.
 */

void Usecode_internal::read_usevars() {
	IFileDataSource nfile(USEVARS);
	if (!nfile.good()) {
		// Okay if this doesn't exist.
		return;
	}
	int cnt = nfile.read4();        // Global statics.
	statics.resize(cnt);
	int i;
	for (i = 0; i < cnt; i++)
		statics[i].restore(&nfile);
	unsigned long funid;
	while (!nfile.eof() && (funid = nfile.read4()) != 0xffffffffU) {
		if (funid == 0xfffffffeU) {
			// ++++ FIXME: Write code for the cases when symtbl == 0 or
			// fsym == 0 (neither of which *should* happen...)
			int len = nfile.read2();
			char *nm = new char[len + 1];
			nfile.read(nm, len);
			nm[len] = 0;
			Usecode_symbol *fsym = symtbl ? (*symtbl)[nm] : nullptr;
			if (fsym)
				funid = fsym->get_val();
			delete [] nm;
		}
		int cnt = nfile.read4();
		Usecode_function *fun = find_function(funid);
		if (!fun)
			continue;
		fun->statics.resize(cnt);
		for (i = 0; i < cnt; i++)
			fun->statics[i].restore(&nfile);
	}
}

void Usecode_internal::clear_usevars() {
	statics.clear();
	int nslots = funs.size();
	for (int i = 0; i < nslots; ++i) {
		vector<Usecode_function *> &slot = funs[i];
		for (auto *fun : slot) {
			if (fun) fun->statics.clear();
		}
	}
}

#ifdef USECODE_DEBUGGER

int Usecode_internal::get_callstack_size() const {
	return call_stack.size();
}

Stack_frame *Usecode_internal::get_stackframe(int i) {
	if (i >= 0 && static_cast<unsigned>(i) < call_stack.size())
		return call_stack[i];
	else
		return nullptr;
}

// return current size of the stack
int Usecode_internal::get_stack_size() const {
	return static_cast<int>(sp - stack);
}

// get an(y) element from the stack. (depth == 0 is top element)
Usecode_value *Usecode_internal::peek_stack(int depth) const {
	if (depth < 0 || depth >= get_stack_size())
		return nullptr;

	return sp - depth - 1;
}

// modify an(y) element on the stack. (depth == 0 is top element)
void Usecode_internal::poke_stack(int depth, Usecode_value &val) {
	if (depth < 0 || (sp - depth) < stack)
		return;

	*(sp - depth) = val;
}

void Usecode_internal::set_breakpoint() {
	breakpoints.add(new AnywhereBreakpoint());
}

void Usecode_internal::dbg_stepover() {
	if (on_breakpoint)
		breakpoints.add(new StepoverBreakpoint(call_stack.front()));
}

void Usecode_internal::dbg_finish() {
	if (on_breakpoint)
		breakpoints.add(new FinishBreakpoint(call_stack.front()));
}

int Usecode_internal::set_location_breakpoint(int funcid, int ip) {
	Breakpoint *bp = new LocationBreakpoint(funcid, ip);
	breakpoints.add(bp);

	return bp->id;
}

#endif
