/*
 *  Schedule.cc - Schedules for characters.
 *
 *  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 "SDL_timer.h"
#include "actors.h"
#include "schedule.h"
#include "Zombie.h"
#include "gamewin.h"
#include "gameclk.h"
#include "gamemap.h"
#include "actions.h"
#include "dir.h"
#include "items.h"
#include "game.h"
#include "paths.h"
#include "ucmachine.h"
#include "ucsched.h"
#include "ucscriptop.h"
#include "monstinf.h"
#include "frameseq.h"
#include "ucsymtbl.h"
#include "useval.h"
#include "usefuns.h"
#include "combat.h"
#include "Audio.h"
#include "ignore_unused_variable_warning.h"
#include "array_size.h"

using std::cout;
using std::cerr;
using std::endl;
using std::rand;
using std::vector;

vector<std::string> Schedule_change::script_names;
int Patrol_schedule::num_path_eggs = -1;
bool Sleep_schedule::sleep_interrupted = false;

/*
 *  Create.
 */
Schedule::Schedule(
    Actor *n
) : npc(n), blocked(-1, -1, -1), start_pos(n->get_tile()),
	street_maintenance_failures(0),
	street_maintenance_time(0) {
	prev_type = npc ? npc->get_schedule_type() : -1;
}

/*
 *  Set up an action to get an actor to a location (via pathfinding), and
 *  then execute another action when he gets there.
 */

void Schedule::set_action_sequence(
    Actor *actor,           // Whom to activate.
    Tile_coord const &dest,     // Where to walk to.
    Actor_action *when_there,   // What to do when he gets there.
    bool from_off_screen,       // Have actor walk from off-screen.
    int delay           // Msecs. to delay.
) {
	bool persistant = actor->get_npc_num() == 0 || actor->get_party_id() >= 0;
	actor->set_action(Actor_action::create_action_sequence(
	                      actor, dest, when_there, from_off_screen, persistant));
	actor->start(250, delay);   // Get into time queue.
}

/*
 *  Try to procure an item. First, scans inventory. Then scans for a nearby
 *  match. Finally, it creates the item.
 *  Expects you to manually add the hammer as a client.
 */

Game_object *Schedule::set_procure_item_action(
    Actor *actor,
    Game_object *obj,
    int dist,
    int shnum,
    int frnum
) {
	Game_window *gwin = Game_window::get_instance();
	int delay = gwin->get_std_delay();
	if (obj) {
		// On inventory? Use it.
		if (obj->get_outermost() == actor)
			return obj;

		// If on map, pick it up.
		if (!obj->is_pos_invalid() && set_pickup_item_action(actor, obj, delay))
			return obj;

		// Not on inventory and not on map; someone took it. So fall through
		// and procure a new one.
	}
	// First, look in inventory.
	Game_object_vector vec;
	actor->get_objects(vec, shnum, c_any_qual, frnum);
	if (!vec.empty())
		return vec[0];   // If there, just use it.

	// Now find one around and get the nearest one.
	actor->find_nearby(vec, shnum, dist, 0, c_any_qual, frnum);
	Game_object *found = find_nearest(actor, vec);

	// If there, go pick it up.
	if (found && !set_pickup_item_action(actor, found, delay))
		found = nullptr;

	if (!found) {
		// Not there, so create it.
		Game_object_shared newobj =
						std::make_shared<Ireg_game_object>(shnum, frnum, 0, 0);
		found = newobj.get();
		actor->add(found, true);
	}
	return found;
}

bool Schedule::set_pickup_item_action(
    Actor *actor,
    Game_object *obj,
    int delay
) {
	Approach_object_pathfinder_client cost(actor, obj, 1);
	Actor_action *pact = Path_walking_actor_action::create_path(
	                         actor->get_tile(), obj->get_tile(), cost);
	if (pact) {
		actor->set_action(new Sequence_actor_action(pact,
		                  new Pickup_actor_action(obj, delay)));
		return true;
	} else
		return false;
}

Game_object *Schedule::find_nearest(
    Actor *actor,
    Game_object_vector &nearby
) {
	Game_object *nearest = nullptr;
	int best_dist = 1000;   // Find closest.
	for (auto *obj : nearby) {
		int dist;
		if ((dist = obj->distance(actor)) < best_dist) {
			// Found it.
			nearest = obj;
			best_dist = dist;
		}
	}
	return nearest;
}

/*
 *  Look for nearby enemies.
 *
 *  Output: true if any are found, which means npc's schedule has changed!
 */

bool Schedule::seek_foes(
) {
	Actor_vector vec;       // Look within 20 tiles, but check LOF.
	npc->find_nearby_actors(vec, c_any_shapenum, 20, 0x28);
	int npc_align = npc->get_effective_alignment();
	Actor *foe = nullptr;
	bool see_invisible = npc->can_see_invisible();
	for (auto *actor : vec) {
		if (actor->is_dead() || actor->get_flag(Obj_flags::asleep) ||
		        (!see_invisible && actor->get_flag(Obj_flags::invisible)) ||
		        !Fast_pathfinder_client::is_straight_path(npc, actor))
			continue;   // Dead, asleep or invisible and can't see invisible.
		if (Combat_schedule::is_enemy(npc_align,
		                              actor->get_effective_alignment())) {
			foe = actor;
			break;
		}
	}
	if (foe) {
		Actor *safenpc = npc;
		// May delete us!
		safenpc->set_schedule_type(Schedule::combat, nullptr);
		safenpc->set_target(foe);
		return true;
	}
	return false;
}

/*
 *  Look for lamps to light/unlight and shutters to open/close.
 *
 *  Output: 1 if successful, which means npc's schedule has changed!
 */

int Schedule::try_street_maintenance(
) {
	// What to look for: shutters, light sources,
	static int night[] =   {322, 372, 336, 997, 889};
	static int sinight[] = {290, 291, 336, 997, 889};
	static int day[] =     {290, 291, 338, 997, 526};

	long curtime = Game::get_ticks();
	if (curtime < street_maintenance_time)
		return 0;       // Not time yet.
	if (npc->get_npc_num() < 0 || !npc->is_sentient())
		return 0;       // Only want normal NPC's.
	// At least 30secs. before next one.
	street_maintenance_time = curtime + 30000 +
	                          street_maintenance_failures * 5000;
	int *shapes;
	int hour = gclock->get_hour();
	bool bg = GAME_BG;
	if (hour >= 9 && hour < 18)
		shapes = &day[0];
	else if (hour >= 18 || hour < 6)
		shapes = bg ? &night[0] : &sinight[0];
	else
		return 0;       // Dusk or dawn.
	Tile_coord npcpos = npc->get_tile();
	// Look at screen + 1/2.
	TileRect winrect = gwin->get_win_tile_rect();
	winrect.enlarge(winrect.w / 4);
	if (!winrect.has_world_point(npcpos.tx, npcpos.ty))
		return 0;
	// Get to within 1 tile.
	Game_object *found = nullptr;     // Find one we can get to.
	Actor_action *pact = nullptr;     // Gets ->action to walk there.
	for (size_t i = 0; !found && i < array_size(night); i++) {
		Game_object_vector objs;// Find nearby.
		int cnt = npc->find_nearby(objs, shapes[i], 20, 0);
		int j;
		for (j = 0; j < cnt; j++) {
			Game_object *obj = objs[j];
			int shnum = obj->get_shapenum();
			if (!bg) {					// Serpent isle?
			    int frnum = obj->get_framenum();
			    if ((shnum == 290 || shnum == 291) &&  // Shutters?
				    // Want closed during day.
				        (shapes == day) != (frnum <= 3))
					continue;
				// Serpent lamp posts can't toggle.
				if ((shnum == 526 || shnum == 889) &&
				    (frnum == 0 || frnum == 1 || frnum == 4 || frnum == 5))
				    continue;
			}
			Approach_object_pathfinder_client cost(npc, obj, 1);
			if ((pact = Path_walking_actor_action::create_path(
			                npcpos, obj->get_tile(), cost)) != nullptr) {
				found = obj;
				break;
			}
			street_maintenance_failures++;
		}
	}
	if (!found || !pact)
		return 0;       // Failed.
	// Set actor to walk there.
	npc->set_schedule_type(Schedule::street_maintenance,
	                       new Street_maintenance_schedule(npc, pact, found));
	// Warning: we are deleted here
	return 1;
}

void Schedule::im_dormant() {
	npc->set_action(nullptr);
}

/*
 *  Get actual schedule (for Usecode intrinsic).
 */

int Schedule::get_actual_type(
    Actor *npc
) const {
	return npc->get_schedule_type();
}

bool Schedule::try_proximity_usecode(int odds) {
	if ((rand() % odds) == 0) {
		// Note: since we can get here from within usecode, lets not
		// call the function directly but queue it instead.
		auto *scr = new Usecode_script(npc);
		(*scr) << Ucscript::dont_halt << Ucscript::usecode2
		       << npc->get_usecode() << Usecode_machine::npc_proximity;
		scr->start();
		npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
		return true;
	}
	return false;
}

/*
 *  Remove desk items NPC is holding (that we created).
 */

void Schedule_with_objects::cleanup()
{
	while (!created.empty()) {
		Game_object_shared item = created.back().lock();
		created.pop_back();
		if (item && item.get()->get_owner() == npc)
			item->remove_this();
	}
}

void Schedule_with_objects::set_current_item(Game_object *obj) {
	current_item = weak_from_obj(obj);
}
void Schedule_with_objects::add_object(Game_object *obj) {
	created.push_back(obj->weak_from_this());
}

Schedule_with_objects::~Schedule_with_objects()
{
	cleanup();
}

/*
 *  Walk to an item.
 *  Output: false if failed.
 */
bool Schedule_with_objects::walk_to_random_item(int dist) {
	Game_object_vector vec;
	current_item = Game_object_weak();
	int nitems = find_items(vec, dist);
	if (nitems) {
		Game_object *item = vec[rand() % nitems];
		current_item = weak_from_obj(item);
		Tile_coord spot = item->get_tile();
		int floor = npc->get_lift() / 5;
		spot.tz = floor*5;
		Tile_coord pos = Map_chunk::find_spot(spot, 1, npc);
		if (pos.tx != -1 &&
		        npc->walk_path_to_tile(pos, gwin->get_std_delay(),
		                               1000 + rand() % 1000))
			return true;
	}
	return false;
}

/*
 *  Drop an item on a table.
 */
bool Schedule_with_objects::drop_item(Game_object *to_drop, Game_object *table)
{
	Tile_coord spot = npc->get_tile();
	TileRect foot = table->get_footprint();
	// East/West of table?
	if (spot.ty >= foot.y && spot.ty < foot.y + foot.h)
		spot.tx = spot.tx <= foot.x ? foot.x
		                            : foot.x + foot.w - 1;
	else            // North/south.
		spot.ty = spot.ty <= foot.y ? foot.y
		                  : foot.y + foot.h - 1;
	const Shape_info &info = table->get_info();
	spot.tz = table->get_lift() + info.get_3d_height();
	Tile_coord pos = Map_chunk::find_spot(spot, 1,
			   to_drop->get_shapenum(), to_drop->get_framenum());
	if (pos.tx != -1 && pos.tz == spot.tz &&
	           foot.has_world_point(pos.tx, pos.ty)) {
		// Passes test.
		npc->set_action(new Pickup_actor_action(
			to_drop, pos, 250, false));
		--items_in_hand;
		return true;
	} else
		return false;
}

/*
 *  Run usecode function.
 */

void Scripted_schedule::run(
    int id              // A Usecode function #.
) {
	if (id)
		ucmachine->call_method(inst, id, npc);
}

/*
 *  Lookup Usecode 'method'.
 */

static int find_method(Usecode_class_symbol *cls, const char *meth, bool noerr) {
	Usecode_symbol *ucsym = (*cls)[meth];
	if (!ucsym) {
		if (!noerr)
			cerr << "Failed to find method '" << meth
			     << "' in class '" << cls->get_name() << "'." << endl;
		return 0;
	}
	return ucsym->get_val();
}

/*
 *  Create schedule that uses Usecode methods for the actions.
 */

Scripted_schedule::Scripted_schedule(
    Actor *n,
    int type
) : Schedule(n) {
	const char *nm = Schedule_change::get_script_name(type);
	Usecode_class_symbol *cls = ucmachine->get_class(nm);
	if (!cls) {
		cerr << "Could not find scripted schedule '" << nm <<
		     "'. Switching to 'Loiter' schedule instead." << endl;
		npc->set_schedule_type(loiter);
		return;
	} else {
		inst = new Usecode_value(0);
		inst->class_new(cls, cls->get_num_vars());
		now_what_id = find_method(cls, "now_what", false);
		im_dormant_id = find_method(cls, "im_dormant", true);
		ending_id = find_method(cls, "ending", true);
		set_weapon_id = find_method(cls, "set_weapon", true);
		set_bed_id = find_method(cls, "set_bed", true);
	}
}

/*
 *  Cleanup.
 */

Scripted_schedule::~Scripted_schedule(
) {
	ucmachine->call_method(inst, -1, nullptr); // Free 'inst'.
}

/*
 *  Create schedule to turn lamps on/off, open/close shutters.
 */

Street_maintenance_schedule::Street_maintenance_schedule(
    Actor *n,
    Actor_action *p,
    Game_object *o
) : Schedule(n), obj(weak_from_obj(o)), shapenum(o->get_shapenum()),
	framenum(o->get_framenum()), paction(p),
	oldloc(n->get_schedule()->get_start_pos()) {
}

/*
 *  Street-maintenance schedule.
 */

void Street_maintenance_schedule::now_what(
) {
	if (paction) {          // First time?
		// Set to follow given path.
		cout << npc->get_name() <<
		     " walking for street maintenance" << endl;
		npc->set_action(paction);
		npc->start(gwin->get_std_delay());
		paction = nullptr;
		return;
	}
	Game_object_shared objptr = obj.lock();
	if (objptr && npc->distance(objptr.get()) <= 2 &&   // We're there.
	        objptr->get_shapenum() == shapenum && objptr->get_framenum() == framenum) {
		cout << npc->get_name() <<
		     " is about to perform street maintenance" << endl;
		int dir = npc->get_direction(objptr.get());
		signed char frames[2];
		frames[0] = npc->get_dir_framenum(dir, Actor::standing);
		frames[1] = npc->get_dir_framenum(dir, 3);
		signed char standframe = frames[0];
		Actor_action *pact = nullptr;
		if (shapenum == 997) {  // Spent light source
			int hour = gclock->get_hour();
			int newshp;
			if (hour >= 18 || hour < 6)  // Night
				newshp = 338;      // Lit light source
			else
				newshp = 336;      // Unlit light source
			// Makes it last some 5-9 hours; quality seems to be within this
			// range in the original.
			int qual = 30 + rand() % 27;
			pact = new Change_actor_action(objptr.get(), newshp, framenum, qual);
		} else  // Just call the correct usecode.
			pact = new Activate_actor_action(objptr.get());

		npc->set_action(new Sequence_actor_action(
		                    new Frames_actor_action(frames, array_size(frames)),
		                    new Face_pos_actor_action(objptr->get_tile(), gwin->get_std_delay()),
		                    pact, new Frames_actor_action(&standframe, 1)));
		npc->start(gwin->get_std_delay());
		if (npc->can_speak()) {
			switch (shapenum) {
			case 322:       // Closing shutters.
			case 372:
				npc->say(first_close_shutters, last_close_shutters);
				break;
			case 290:       // Open shutters (or both for SI).
			case 291:
				if (Game::get_game_type() == BLACK_GATE)
					npc->say(first_open_shutters, last_open_shutters);
				else        // SI.
					if (framenum <= 3)
						npc->say(first_open_shutters, last_open_shutters);
					else
						npc->say(first_close_shutters, last_close_shutters);
				break;
			case 336:       // Turn on light source.
			case 889:       // Turn on lamp.
				npc->say(first_lamp_on, last_lamp_on);
				break;
			case 338:       // Turn off light source.
			case 526:       // Turn off lamp.
				npc->say(lamp_off);
				break;
			case 997:       // Replacing candle.
				npc->say(new_candle);
				break;
			}
		}
		shapenum = 0;       // Don't want to repeat.
		return;
	}
	cout << npc->get_name() <<
	     " is done with street maintenance" << endl;
	// Set back to old schedule.
	int period = gclock->get_hour() / 3;
	Actor *safenpc = npc;
	Tile_coord safeloc = oldloc;
	// This sometimes fails after a restore.
	safenpc->update_schedule(period, 0, &safeloc);
	if (safenpc->get_schedule_type() == static_cast<int>(street_maintenance))
		safenpc->update_schedule(period, 0, &safeloc);
}

void Street_maintenance_schedule::ending(int newtype) {
	ignore_unused_variable_warning(newtype);
}

/*
 *  Get actual schedule (for Usecode intrinsic).
 */

int Street_maintenance_schedule::get_actual_type(
    Actor * /* npc */
) const {
	return prev_type;
}

/*
 *  What do we do now?
 */

void Follow_avatar_schedule::now_what(
) {
	bool is_blocked = blocked.tx != -1;
	blocked = Tile_coord(-1, -1, -1);
	if (npc->get_flag(Obj_flags::asleep) || npc->is_dead() ||
	        npc->get_flag(Obj_flags::paralyzed) ||
	        gwin->main_actor_dont_move())   // Under Usecode control.
		return;         // Disabled.
	Actor *av = gwin->get_main_actor();
	Tile_coord leaderpos = av->get_tile();
	Tile_coord pos = npc->get_tile();
	int dist2lead = av->distance(pos);
	if (!av->is_moving() &&     // Avatar stopped.
	        dist2lead <= 12)        // And we're already close enough.
		return;
	uint32 curtime = SDL_GetTicks();// Want the REAL time here.
	if (!is_blocked) {      // Not blocked?
		npc->follow(av);    // Then continue following.
		return;
	}
	if (curtime < next_path_time) { // Failed pathfinding recently?
		// Wait a bit.
		npc->start(gwin->get_std_delay(), next_path_time - curtime);
		return;
	}
	// Find a free spot within 3 tiles.
	Map_chunk::Find_spot_where where = Map_chunk::anywhere;
	// And try to be inside/outside.
	where = gwin->is_main_actor_inside() ?
	        Map_chunk::inside : Map_chunk::outside;
	Tile_coord goal = Map_chunk::find_spot(leaderpos, 3, npc, 0, where);
	if (goal.tx == -1) {    // No free spot?  Give up.
		cout << npc->get_name() << " can't find free spot" << endl;
		next_path_time = SDL_GetTicks() + 1000;
		return;
	}
	// Get his speed.
	int speed = av->get_frame_time();
	if (!speed)         // Avatar stopped?
		speed = gwin->get_std_delay();
	if (pos.distance(goal) <= 3)
		return;         // Already close enough!
	// Succeed if within 3 tiles of goal.
	if (npc->walk_path_to_tile(goal, speed - speed / 4, 0, 3, 1))
		return;         // Success.
	cout << "... but failed to find path." << endl;
	// On screen (roughly)?
	int ok;
	// Get window rect. in tiles.
	TileRect wrect = gwin->get_win_tile_rect();
	if (wrect.has_world_point(pos.tx - pos.tz / 2, pos.ty - pos.tz / 2))
		// Try walking off-screen.
		ok = npc->walk_path_to_tile(Tile_coord(-1, -1, -1),
		                            speed - speed / 4, 0);
	else                // Off screen already?
		ok = npc->approach_another(av);
	if (!ok)            // Failed? Don't try again for a bit.
		next_path_time = SDL_GetTicks() + 1000;
}

/*
 *  Waiting...
 */

void Wait_schedule::now_what(
) {
}

/*
 *  Create a horizontal pace schedule.
 */

Pace_schedule *Pace_schedule::create_horiz(
    Actor *n
) {
	Tile_coord t = n->get_tile();   // Get his position.
	return new Pace_schedule(n, 1, t);
}

/*
 *  Create a vertical pace schedule.
 */

Pace_schedule *Pace_schedule::create_vert(
    Actor *n
) {
	Tile_coord t = n->get_tile();   // Get his position.
	return new Pace_schedule(n, 0, t);
}

void Pace_schedule::pace(
    Actor *npc,
    char &which,
    int &phase,
    Tile_coord &blocked,
    int delay
) {
	int dir = npc->get_dir_facing();    // Use NPC facing for starting direction
	switch (phase) {
	case 1: {
		bool changedir = false;
		switch (dir) {
		case north:
		case south:
			if (which)
				changedir = true;
			break;
		case east:
		case west:
			if (!which)
				changedir = true;
			break;
		}
		if (changedir) {
			phase = 4;
			npc->start(delay, delay);
			return;
		}
		if (blocked.tx != -1) {     // Blocked?
			Game_object *obj = npc->find_blocking(blocked, dir);
			if (obj) {
				if (obj->as_actor()) {
					if (npc->can_speak()) {
						npc->say(first_move_aside, last_move_aside);
						// Ask NPC to move aside.
						if (obj->move_aside(npc, dir))
							// Wait longer.
							npc->start(3 * delay, 3 * delay);
						else
							// Wait longer.
							npc->start(delay, delay);
						return;
					}
				}
			}
			blocked.tx = -1;
			changedir = true;
		}

		if (changedir)
			phase++;
		else {
			Tile_coord p0 = npc->get_tile().get_neighbor(dir);
			Frames_sequence *frames = npc->get_frames(dir);
			int &step_index = npc->get_step_index();
			if (!step_index)        // First time?  Init.
				step_index = frames->find_unrotated(npc->get_framenum());
			// Get next (updates step_index).
			int frame = frames->get_next(step_index);
			// One step at a time.
			npc->step(p0, frame);
		}
		npc->start(delay, delay);
		break;
	}
	case 2:
		phase++;
		npc->change_frame(npc->get_dir_framenum(
		                      npc->get_dir_facing(), Actor::standing));
		npc->start(2 * delay, 2 * delay);
		break;
	case 3:
	case 4: {
		phase++;
		npc->change_frame(npc->get_dir_framenum((dir + 6) % 8, Actor::standing));
		npc->start(2 * delay, 2 * delay);
		break;
	}
	default:
		phase = 1;
		npc->get_step_index() = 0;
		npc->start(2 * delay, 2 * delay);
		break;
	}
}

/*
 *  Schedule change for pacing:
 */

void Pace_schedule::now_what(
) {
	int delay = gwin->get_std_delay();
	if (!phase) {
		phase++;
		if (loc != npc->get_tile())
			npc->walk_to_tile(loc, delay, delay);
		else
			npc->start(delay, delay);
	} else
		Pace_schedule::pace(npc, which, phase, blocked, delay);
}

/*
 *  Just went dormant.
 */
void Eat_at_inn_schedule::im_dormant() {
	// Force NPCs to sit down again after cache-out/cache-in.
	//npc->change_frame(npc->get_dir_framenum(Actor::standing));
	npc->set_action(nullptr);
}

/*
 *  Eat at inn.
 */

void Eat_at_inn_schedule::now_what(
) {
	int frnum = npc->get_framenum() & 0xf;
	if (!sitting_at_chair || frnum != Actor::sit_frame) {
		// First have to sit down.
		static int chairs[2] = {873, 292};
		// Already good?
		if (frnum == Actor::sit_frame && npc->find_closest(chairs, 2, 1)) {
			sitting_at_chair = true;
		} else {
			if (!Sit_schedule::set_action(npc))
				// Try again in a while.
				npc->start(250, 5000);
			return;
		}
	}
	Game_object_vector foods;           // Food nearby?
	int cnt = npc->find_nearby(foods, 377, 2, 0);
	if (cnt) {          // Found?
		// Find closest.
		Game_object *food = nullptr;
		int dist = 500;
		for (auto *obj : foods) {
			int odist = obj->distance(npc);
			if (odist < dist) {
				dist = odist;
				food = obj;
			}
		}
		if (rand() % 5 == 0 && food) {
			gwin->add_dirty(food);
			food->remove_this();
		}
		if (npc->can_speak() && rand() % 4)
			npc->say(first_munch, last_munch);
	} else if (npc->can_speak() && rand() % 4)
		npc->say(first_more_food, last_more_food);
	// Wake up in a little while.
	npc->start(250, 5000 + rand() % 12000);
}

// TODO: This should be in a loop to remove food one at a time with a delay
void Eat_at_inn_schedule::ending(int new_type) { // new schedule type
	ignore_unused_variable_warning(new_type);
	Game_object_vector foods;           // Food nearby?
	int cnt = npc->find_nearby(foods, 377, 2, 0);
	if (cnt) {          // Found?
		for (auto *food : foods) {
			if (food) {
				gwin->add_dirty(food);
				food->remove_this();
				if (npc->can_speak())
					npc->say(first_munch, last_munch);
			}
		}
	}
}


/*
 *  Find someone listening to the preacher.
 */

Actor *Find_congregant(
    Actor *npc
) {
	Actor_vector vec;
	Actor_vector vec2;
	if (!npc->find_nearby_actors(vec, c_any_shapenum, 16))
		return nullptr;
	vec2.reserve(vec.size());   // Get list of ones to consider.
	for (auto *act : vec) {
		if (act->get_schedule_type() == Schedule::sit &&
		        !act->is_in_party())
			vec2.push_back(act);
	}
	return !vec2.empty() ? vec2[rand() % vec2.size()] : nullptr;
}

/*
 *  Preach:
 */

void Preach_schedule::now_what(
) {
	switch (state) {
	case find_podium: {
		Game_object_vector vec;
		if (!npc->find_nearby(vec, 697, 17, 0)) {
			npc->set_schedule_type(loiter);
			return;
		}
		Game_object *podium = vec[0];
		Tile_coord pos = podium->get_tile();
		static int deltas[4][2] = {{ -1, 0}, {1, 0}, {0, -2}, {0, 1}};
		int frnum = podium->get_framenum() % 4;
		pos.tx += deltas[frnum][0];
		pos.ty += deltas[frnum][1];
		Actor_pathfinder_client cost(npc, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npc->get_tile(), pos, cost);
		if (pact) {
			state = at_podium;
			npc->set_action(new Sequence_actor_action(pact,
			                new Face_pos_actor_action(podium, 200)));
			npc->start(gwin->get_std_delay());
			return;
		}
		npc->start(250, 5000 + rand() % 5000);  // Try again later.
		return;
	}
	case at_podium:
		if (rand() % 2)     // Just wait a little.
			npc->start(gwin->get_std_delay(), rand() % 3000);
		else {
			if (rand() % 3)
				state = exhort;
			else if ((GAME_SI) || rand() % 3)
				state = visit;
			else
				state = find_icon;
			npc->start(gwin->get_std_delay(), 2000 + rand() % 2000);
		}
		return;
	case exhort: {
		signed char frames[8];      // Frames.
		int cnt = 1 + rand() % (array_size(frames) - 1);
		// Frames to choose from:
		static char choices[3] = {0, 8, 9};
		for (int i = 0; i < cnt - 1; i++)
			frames[i] = npc->get_dir_framenum(
			                choices[rand() % (array_size(choices))]);
		// Make last one standing.
		frames[cnt - 1] = npc->get_dir_framenum(Actor::standing);
		npc->set_action(new Frames_actor_action(frames, cnt, 250));
		npc->start(gwin->get_std_delay());
		npc->say(first_preach, last_preach);
		state = at_podium;
		Actor *member = Find_congregant(npc);
		if (member) {
			auto *scr = new Usecode_script(member);
			scr->add(Ucscript::delay_ticks, 3);
			scr->add(Ucscript::face_dir, member->get_dir_facing());
			scr->add(Ucscript::npc_frame + Actor::standing);
			scr->add(Ucscript::say, get_text_msg(first_amen +
			                                  rand() % (last_amen - first_amen + 1)));
			scr->add(Ucscript::delay_ticks, 2);
			scr->add(Ucscript::npc_frame + Actor::sit_frame);
			scr->start();   // Start next tick.
		}
		return;
	}
	case visit: {
		state = find_podium;
		npc->start(gwin->get_std_delay(), 1000 + rand() % 2000);
		Actor *member = Find_congregant(npc);
		if (!member)
			return;
		Tile_coord pos = member->get_tile();
		Actor_pathfinder_client cost(npc, 1);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npc->get_tile(), pos, cost);
		if (!pact)
			return;
		npc->set_action(new Sequence_actor_action(pact,
		                new Face_pos_actor_action(member, 200)));
		state = talk_member;
		return;
	}
	case talk_member:
		state = find_podium;
		npc->say(first_preach2, last_preach2);
		npc->start(250, 2000);
		return;
	case find_icon: {
		state = find_podium;        // In case we fail.
		npc->start(2 * gwin->get_std_delay());
		Game_object *icon = npc->find_closest(724);
		if (!icon)
			return;
		Tile_coord pos = icon->get_tile();
		pos.tx += 2;
		pos.ty -= 1;
		Actor_pathfinder_client cost(npc, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npc->get_tile(), pos, cost);
		if (pact) {
			npc->set_action(pact);
			state = pray;
		}
		return;
	}
	case pray: {
		auto *scr = new Usecode_script(npc);
		(*scr) << Ucscript::face_dir << 6   // Face west.
		       << (Ucscript::npc_frame + Actor::standing)
		       << (Ucscript::npc_frame + Actor::bow_frame)
		       << Ucscript::delay_ticks << 3
		       << (Ucscript::npc_frame + Actor::kneel_frame);
		scr->add(Ucscript::say, get_text_msg(first_amen + rand() % 2));
		(*scr)  << Ucscript::delay_ticks << 5
		        << (Ucscript::npc_frame + Actor::bow_frame)
		        << Ucscript::delay_ticks << 3
		        << (Ucscript::npc_frame + Actor::standing);
		scr->start();   // Start next tick.
		state = find_podium;
		npc->start(2 * gwin->get_std_delay(), 4000);
		return;
	}
	default:
		state = find_podium;
		npc->start(250);
		return;
	}
}

Patrol_schedule::Patrol_schedule(
    Actor *n
) : Schedule(n), pathnum(-1), dir(1), state(-1),
	center(0, 0, 0), whichdir(0), phase(1), pace_count(0),
	seek_combat(false), forever(false) {
	if (num_path_eggs < 0) {
		Shapes_vga_file &shapes = Shape_manager::get_instance()->get_shapes();
		num_path_eggs = shapes.get_num_frames(PATH_SHAPE) - 1;
	}
}

/*
 *  Schedule change for patroling:
 */

void Patrol_schedule::now_what(
) {
	if (seek_combat && seek_foes()) // Check for nearby foes.
		return;
	int speed = gwin->get_std_delay();
	Game_object *path;
	bool gotpath = true;
	switch (state) {
	case -1: { // Initialization
		Game_object_vector nearby;
		npc->find_nearby(nearby, PATH_SHAPE, 25, 0x10, c_any_qual, c_any_framenum);
		gotpath = !nearby.empty();
	}
	// FALLTHROUGH
	case 0: { // Find next path.
		if (!gotpath) {
			// SI seems to try switching to preprogrammed schedules
			// first if no path.
			if (GAME_SI) {
				Schedule_change *list;
				int cnt;
				npc->get_schedules(list, cnt);
				if (cnt != 0 && list[npc->find_schedule_at_time(gclock->get_hour() / 3)].get_type() != Schedule::patrol) {
					npc->update_schedule(gclock->get_hour() / 3);
				} else {
					// when the preset schedule is patrol or there is no preset
					// change schedule to loiter
					npc->set_schedule_type(loiter);
				}
				// The above will have deleted us, so return immediately.
				return;
			}
			seek_combat = true;
			state = 4;
			npc->start(2 * speed + rand() % 500);
			break;
		} else if (forever) {
			// Flag 64 is 'Repeat Forever', and means 'just use the last
			// path egg again and again' (but see hack note below).
			state = 1;
			npc->start(speed, speed);
			break;
		}
		pathnum += dir;         // Find next path.
		if (pathnum == 0 && dir == -1)
			dir = 1;    // Start over from zero.
		else if (pathnum == num_path_eggs && dir == 1)
			dir = -1;   // Start backwards from num_path_eggs.
		if (pathnum >= static_cast<int>(paths.size()))
			paths.resize(pathnum + 1);
		// Already know its location?
		path = pathnum >= 0 ? paths[pathnum] : nullptr;
		if (!path) {        // No, so look around.
			Game_object_vector nearby;
			npc->find_nearby(nearby, PATH_SHAPE, 25, 0x10, c_any_qual, pathnum);
			path = find_nearest(npc, nearby);
			if (path)       // Save it.
				paths[pathnum] = path;
			else {          // Turn back if at end.
				dir = -dir;
				pathnum += dir;
				npc->start(speed, speed);
				break;
			}
		}
		Tile_coord d = path->get_tile();
		if (!npc->walk_path_to_tile(d, speed, 2 * speed + rand() % 500)) {
			// Look for free tile within 1 square.
			d = Map_chunk::find_spot(d, 1, npc->get_shapenum(),
			                         npc->get_framenum(), 1);
			if (d.tx == -1 || !npc->walk_path_to_tile(d,
			        speed, rand() % 1000)) {
				// Failed.  Later.
				npc->start(speed, 2000);
				return;
			}
		}
		state = 1;  // Walking to path.
		break;
	}
	case 1: // Walk to next path.
		if (pathnum >= 0 &&     // Arrived at path?
		        static_cast<unsigned int>(pathnum) < paths.size() &&
		        (path = paths[pathnum]) != nullptr &&
		        (forever || npc->distance(path) < 2)) {
			int delay = 2;
			// Scripts for all actions. At worst, display standing frame
			// once the path egg is reached.
			auto *scr = new Usecode_script(npc);
			(*scr) << Ucscript::npc_frame + Actor::standing;
			// Quality = type.  (I think high bits
			// are flags).
			int qual = path->get_quality();
			seek_combat = (qual & 32) != 0;
			forever = (qual & 64) != 0;
			// Hack to fix pirate fort at the Isle of the Avatar (BG).
			// It works "incorrectly" in the original.
			if (forever && GAME_BG && (qual & 31) == 0)
				forever = false;
			// TODO: Find out what flag 128 means. It would seem that it is
			// 'Exc. Reserved.', but what does those mean?
			switch (qual & 31) {
			case 0:         // None.
				break;
			case 25:        // 50% wrap to 0.
				if (rand() % 2)
					break;
				// FALLTHROUGH
			case 1:         // Wrap to 0.
				pathnum = -1;
				dir = 1;
				break;
			case 2:         // Pause; guessing 3 ticks.
				(*scr) << Ucscript::delay_ticks << 3;
				delay = 5;
				break;
			case 24:       // Read
				// Find the book which will be read.
				book = weak_from_obj(npc->find_closest(642, 4));
				// FALLTHROUGH
			case 3:         // Sit.
				if (Sit_schedule::set_action(npc)) {
					scr->start();   // Start next tick.
					state = 2;
					return;
				}
				break;
			case 4:         // Kneel at tombstone.
			case 5:         // Kneel
				// TODO: Both are identical in SI, which has no tombs.
				// In BG, placing this path egg near a tomb causes the
				// NPC to kneel in front of the tomb, drops flowers in
				// front of it (shape 999, frames 1, 2, 3, 6 or 7 at
				// random, then stands up again after a while.
				// If anyone complains enough I will add it.
				// -- Marzo Jan 22/2013
				(*scr) << Ucscript::delay_ticks << 2 <<
				       Ucscript::npc_frame + Actor::bow_frame <<
				       Ucscript::delay_ticks << 4 <<
				       Ucscript::npc_frame + Actor::kneel_frame <<
				       Ucscript::delay_ticks << 20 <<
				       Ucscript::npc_frame + Actor::bow_frame <<
				       Ucscript::delay_ticks << 4 <<
				       Ucscript::npc_frame + Actor::standing;
				delay = 36;
				break;
			case 6:         // Loiter.
				scr->start();   // Start next tick.
				center = path->get_tile();
				state = 4;
				npc->start(speed, speed * delay);
				return;
			case 7:         // Left about-face.
			case 8: {       // Right about-face.
				const int dirs_left[] = {6, 4};
				const int dirs_right[] = {2, 4};
				const int *face_dirs;
				if ((path->get_quality() & 31) == 7)
					face_dirs = dirs_right;
				else
					face_dirs = dirs_left;
				int facing = npc->get_dir_facing();
				for (int i = 0; i < 2; i++) {
					(*scr) << Ucscript::delay_ticks << 2 <<
					       Ucscript::face_dir << (facing + face_dirs[i]) % 8;
				}
				delay = 8;
				break;
			}
			case 9:         // Horiz. pace.
			case 10:        // Vert. pace.
				// Note: both do vertical pacing in the originals.
				// I am basing functionality on the names given in
				// text.flx; this causes Iolo in SI prison to be
				// correct, but causes Fawn guards to perform an
				// horizontal pace. This actually looks better, and
				// seems to be what the world builders intended.
				whichdir = (qual & 1);
				pace_count = -1;
				phase = 1;
				scr->start();   // Start next tick.
				center = path->get_tile();
				state = 5;
				npc->start(speed, speed * delay);
				return;
			case 11:        // 50% reverse.
				if (rand() % 2)
					dir *= -1;
				break;
			case 12:        // 50% skip next.
				if (rand() % 2) {
					pathnum += dir;
					// This must be done to ensure valid ranges.
					if (pathnum == 0 && dir == -1)
						dir = 1;    // Start over from zero.
					else if (pathnum == num_path_eggs && dir == 1)
						dir = -1;   // Start backwards from num_path_eggs.
				}
				break;
			case 13: {        // Hammer.
				// Try to use an existing hammer.
				Game_object_shared existing = hammer.lock();
				hammer = weak_from_obj(
					   set_procure_item_action(npc, existing.get(),
														15, 623, 0));
				scr->start();   // Start next tick.
				state = 6;
				npc->start(speed, speed * delay);
				return;
			}
			case 14:    // Check area.
				// Force check for lamps, etc.
				street_maintenance_time = 0;
				if (try_street_maintenance()) {
					delete scr;
					return;     // We no longer exist.
				}
				delay += 2;
				(*scr) << Ucscript::delay_ticks << 2;
				break;
			case 15:    // Usecode.
				// Don't let this script halt others,
				//   as it messes up automaton in
				//   SI-Freedom.
				(*scr) << Ucscript::dont_halt;
				// HACK: This is needed for SS Maze: it makes the
				// party wait outside instead of endlessly looping
				// at the maze's door.
				// TODO: Need to check if both functions get called
				// on this case or if only one is, as is done here.
				if (GAME_SI && npc->get_ident() == 31)
					(*scr) << Ucscript::usecode2 << SSMazePartyWait <<
					       static_cast<int>(Usecode_machine::internal_exec);
				else
					(*scr) << Ucscript::usecode2 << npc->get_usecode() <<
					       static_cast<int>(Usecode_machine::npc_proximity);
				delay = 3;
				break;
			case 16:        // Bow to ground.
				// Trying to differentiate this from 17, below.
				(*scr) << Ucscript::delay_ticks << 2 <<
				       Ucscript::npc_frame + Actor::bow_frame <<
				       Ucscript::delay_ticks << 2;
				delay = 8;
				break;
			case 17:        // Bow from ground.
				// Trying to differentiate this from 16, above.
				(*scr) << Ucscript::npc_frame + Actor::bow_frame <<
				       Ucscript::delay_ticks << 2 <<
				       Ucscript::npc_frame + Actor::standing;
				delay = 8;
				break;
			case 20:        // Ready weapon
				npc->ready_best_weapon();
				break;
			case 21:        // Unready weapon
				npc->empty_hands();
				break;
			case 22:        // One-handed swing.
			case 23: {      // Two-handed swing.
				int dir = npc->get_dir_facing();
				signed char frames[14];     // Get frames to show.
				Game_object *weap = npc->get_readied(rhand);
				int cnt = npc->get_attack_frames(weap ? weap->get_shapenum() : 0,
				                                 false, dir, frames);
				frames[cnt++] = npc->get_dir_framenum(dir, Actor::ready_frame);
				frames[cnt++] = npc->get_dir_framenum(dir, Actor::standing);
				if (cnt)
					npc->set_action(new Frames_actor_action(frames, cnt, speed));
				delay = cnt;        // Get back into time queue.
				break;
			}
			// These two don't seem to be used at all in the originals.
			// Their text.flx names suggest that they are meant to do some
			// sort of synchronization: a working hypothesis based on multi-
			// thread programming concepts is that 18 (wait for semaphore)
			// puts NPCs in some sort of wait state until another NPC also
			// in patrol schedule reaches 19 (release semaphore).
			// TODO: Come up with a way to test hypothesis above.
			case 18:        // Wait for semaphore
			case 19:        // Release semaphore
			default:
#ifdef DEBUG
				cout << "Unhandled path egg quality in patrol schedule: " << qual << endl;
#endif
				break;
			}
			scr->start();   // Start next tick.
			state = 0;  // THEN, find next path.
			npc->start(speed, speed * delay);
		} else {
			state = 0;  // Walking to path.
			npc->start(speed, speed);
		}
		break;
	case 2: // Sitting/reading.
		// Stay 5-15 secs.
		if ((npc->get_framenum() & 0xf) == Actor::sit_frame) {
		    Game_object_shared book_obj = book.lock();
			if (book_obj && npc->distance(book_obj.get()) < 4) {
				// Open book if reading.
				int frnum = book_obj->get_framenum();
				if (frnum % 3)
					book_obj->change_frame(frnum - frnum % 3);
			}
			npc->start(250, 5000 + rand() % 10000);
		} else          // Not sitting.
			npc->start(250, rand() % 1000);
		state = 3;  // Continue on afterward.
		break;
	case 3: // Stand up.
		if ((npc->get_framenum() & 0xf) == Actor::sit_frame) {
		    Game_object_shared book_obj = book.lock();
			if (book_obj && npc->distance(book_obj.get()) < 4) {
				// Close book.
				int frnum = book_obj->get_framenum();
				book_obj->change_frame(frnum - frnum % 3 + 1);
				book = Game_object_weak();
			}
			// Standing up animation.
			auto *scr = new Usecode_script(npc);
			(*scr) << Ucscript::delay_ticks << 2 <<
			       Ucscript::npc_frame + Actor::bow_frame <<
			       Ucscript::delay_ticks << 2 <<
			       Ucscript::npc_frame + Actor::standing;
			scr->start();   // Start next tick.
			npc->start(speed, speed * 7);
		}
		state = 0;
		break;
	case 4: // Loiter.
		if (rand() % 5 == 0) {
			state = 0;
			npc->start(speed, speed);
		} else {
			int dist = 12;
			int newx = center.tx - dist + rand() % (2 * dist);
			int newy = center.ty - dist + rand() % (2 * dist);
			// Wait a bit.
			npc->walk_to_tile(newx, newy, center.tz, speed,
			                  rand() % 2000);
		}
		break;
	case 5: // Pacing.
		if (npc->get_tile().distance(center) < 1) {
			pace_count++;
			if (pace_count == 6) {
				npc->change_frame(npc->get_dir_framenum(
				                      npc->get_dir_facing(), Actor::standing));
				npc->start(3 * speed, 3 * speed);
				state = 0;
				break;
			}
			if (phase >= 2 && phase <= 4)
				pace_count--;
		}
		Pace_schedule::pace(npc, whichdir, phase, blocked, speed);
		break;
	case 6: { // Ready hammer and swing it.
		// Hammer should be in inventory by now.
		Game_object_shared hammer_obj = hammer.lock();
		Game_object_shared keep;
		if (!hammer_obj) {
			// uh-oh... this should be impossible.
			// Try finding hammer again
			state = 1;
			npc->start(speed, speed);
		}
		hammer_obj->remove_this(&keep);
		// For safety, unready weapon first.
		npc->empty_hands();
		// Ready the hammer in the weapon hand.
		npc->add_readied(hammer_obj.get(), lhand, false, true);
		npc->add_dirty();
		// Number of swings.
		int repcnt = 1 + rand() % 3;

		auto *scr = new Usecode_script(npc);
		(*scr) << Ucscript::delay_ticks << 2 <<
		       Ucscript::npc_frame + Actor::ready_frame <<
		       Ucscript::delay_ticks << 2 <<
		       Ucscript::npc_frame + Actor::raise1_frame <<
		       Ucscript::delay_ticks << 2 <<
		       Ucscript::sfx << Audio::game_sfx(45) <<
		       Ucscript::npc_frame + Actor::out_frame <<
		       Ucscript::delay_ticks << 2 <<
		       Ucscript::repeat << -13 << repcnt <<
		       Ucscript::delay_ticks << 2 <<
		       Ucscript::npc_frame + Actor::ready_frame <<
		       Ucscript::delay_ticks << 2 <<
		       Ucscript::npc_frame + Actor::standing;
		int delay = 11 * (repcnt + 1) + 6;
		scr->start();   // Start next tick.
		state = 0;  // THEN, find next path.
		npc->start(speed, speed * delay);
		break;
	}
	default:
		// Just in case.
		break;
	}
}

/*
 *  End of patrol
 */

void Patrol_schedule::ending(
    int new_type            // New schedule.
) {
	ignore_unused_variable_warning(new_type);
	Game_object_shared hammer_obj = hammer.lock();
	if (hammer_obj) {
		hammer_obj->remove_this();
	}
}

Talk_schedule::Talk_schedule(
    Actor *n,
    int fb,
    int lb,
    int eid
) : Schedule(n), firstbark(fb), lastbark(lb), eventid(eid), phase(0) {
}

Talk_schedule::Talk_schedule(
    Actor *n
) : Schedule(n), firstbark(first_talk), lastbark(last_talk),
	eventid(GAME_BG ? Usecode_machine::double_click : Usecode_machine::chat),
	phase(0) {
}

/*
 *  Schedule change for 'talk':
 */

void Talk_schedule::now_what(
) {
	int speed = gwin->get_std_delay();
	// Switch to phase 3 if we are reasonable close
	if (phase < 3 &&
	        npc->distance(gwin->get_main_actor()) < 6) {
		phase = 3;
		npc->start(speed, 250);
		return;
	}
	// Don't talk to invisible avatar unless NPC can see invisible.
	if (gwin->get_main_actor()->get_flag(Obj_flags::invisible) &&
	        !npc->can_see_invisible()) {
		// Try a little later.
		phase = 0;
		npc->start(speed, 5000);
		return;
	}
	switch (phase) {
	case 0: {           // Start by approaching Avatar.
		if (npc->distance(gwin->get_main_actor()) > 50) {
			// Too far?
			// Try a little later.
			npc->start(speed, 5000);
			return;
		}
		// Aim for within 5 tiles.
		Actor_pathfinder_client cost(npc, 5);
		Actor_action *pact = Approach_actor_action::create_path(
		                         npc->get_tile(),
		                         gwin->get_main_actor(), 5, cost);
		if (!pact) {
#ifdef DEBUG
			cout << "Talk: Failed to find path for " <<
			     npc->get_name() << endl;
#endif
			// No path found; try again a little later.
			npc->start(speed, 500);
			return;
		} else {
			if (Fast_pathfinder_client::is_grabable(npc,
			                                        gwin->get_main_actor())) {
				if (rand() % 3 == 0)
					npc->say(firstbark, lastbark);
			}
			// Walk there, and retry if
			//   blocked.
			npc->set_action(pact);
			npc->start(speed);  // Start walking.
		}
		phase++;
		return;
	}
	case 1:             // Wait a second.
	case 2: {
		if (Fast_pathfinder_client::is_grabable(npc,
		                                        gwin->get_main_actor())) {
			if (rand() % 3 == 0)
				npc->say(firstbark, lastbark);
		}
		// Step towards Avatar.
		Tile_coord pos = npc->get_tile();
		Tile_coord dest = gwin->get_main_actor()->get_tile();
		int dx = dest.tx > pos.tx ? 1 : (dest.tx < pos.tx ? -1 : 0);
		int dy = dest.ty > pos.ty ? 1 : (dest.ty < pos.ty ? -1 : 0);
		npc->walk_to_tile(pos + Tile_coord(dx, dy, 0), speed, 500);
		phase = 3;
		return;
	}
	case 3: {           // Talk.
		int dist = npc->distance(gwin->get_main_actor());
		// Got to be close & reachable.
		if (dist > 5 ||
		        !Fast_pathfinder_client::is_grabable(npc,
		                gwin->get_main_actor())) {
			phase = 0;
			npc->start(speed, 500);
			return;
		}
		// But first face Avatar.
		npc->change_frame(npc->get_dir_framenum(npc->get_direction(
		        gwin->get_main_actor()), Actor::standing));
		phase++;
		npc->start(speed, 250); // Wait another 1/4 sec.
		break;
	}
	case 4:
		npc->stop();        // Stop moving.
		// NOTE:  This could DESTROY us!
		npc->activate(eventid);
		// SO don't refer to any instance
		//   variables from here on.
		gwin->paint();
		return;
	default:
		break;
	}
}

/*
 *  Schedule change for 'arrest':
 */

Arrest_avatar_schedule::Arrest_avatar_schedule(
    Actor *n
) : Talk_schedule(n, first_arrest, last_arrest, Usecode_machine::double_click) {
	npc->set_usecode(ArrestUsecode);
}

/*
 *  Schedule change for 'arrest avatar':
 */

void Arrest_avatar_schedule::ending(
    int newtype
) {
	if (newtype != combat)
		npc->set_alignment(Actor::neutral);
}
// For Usecode intrinsic.
int Arrest_avatar_schedule::get_actual_type(Actor *npc) const {
	ignore_unused_variable_warning(npc);
	return combat;
}

/*
 *  Create a loiter schedule.
 */

Loiter_schedule::Loiter_schedule(
    Actor *n,
    int d               // Distance in tiles to roam.
) : Schedule(n), center(n->get_tile()), dist(d) {
}

/*
 *  Schedule change for 'loiter':
 */

void Loiter_schedule::now_what(
) {
	if (rand() % 3 == 0)        // Check for lamps, etc.
		if (try_street_maintenance())
			return;     // We no longer exist.
	// Pure guess. Since only some schedules seem to call proximity
	// usecode, I guess I should put it in here.
	// Seems rare for pure loiter, quite frequent for tend shop.
	if ((npc->get_schedule_type() == Schedule::loiter
	     && !(GAME_SI && npc->get_shapenum() == 0x355)
	     && try_proximity_usecode(12))
	    || (npc->get_schedule_type() == Schedule::tend_shop
	        && try_proximity_usecode(8)))
		return;
	int newx = center.tx - dist + rand() % (2 * dist);
	int newy = center.ty - dist + rand() % (2 * dist);
	// Wait a bit.
	npc->walk_to_tile(newx, newy, center.tz, 2 * gwin->get_std_delay(),
	                  rand() % 2000);
}

/*
 *  Schedule change for graze.
 */

void Graze_schedule::now_what(
) {
	int delay = 2 * gwin->get_std_delay();
	switch (phase) {
	case 0: {
		int newx = center.tx - dist + rand() % (2 * dist);
		int newy = center.ty - dist + rand() % (2 * dist);
		// Wait a bit.
		npc->walk_to_tile(newx, newy, center.tz, 2 * gwin->get_std_delay(),
		                  rand() % 2000);
		phase++;
		break;
	}
	case 1:
	case 2:
		if (npc->get_shapenum() == 0x1fd) { // Fish?
			if (rand() % 12 == 0) {
				signed char frames[3];
				int dir = npc->get_dir_facing();
				frames[0] = npc->get_dir_framenum(dir, 0xe);
				frames[1] = npc->get_dir_framenum(dir, 0xf);
				frames[2] = npc->get_dir_framenum(dir, Actor::standing);
				npc->set_action(new Frames_actor_action(frames,
				                                        array_size(frames)));
			}
		} else {
			npc->add_dirty();
			npc->change_frame(npc->get_dir_framenum(npc->get_dir_facing(),
			                                     Actor::standing));
			npc->add_dirty();
		}
		phase++;
		break;
	case 3:
		if (npc->get_shapenum() == 0x1fd)   // Fish?
			phase = 0;
		else
			phase++;
		break;
	default: {
		// Non-fish only.
		signed char frames[12];
		int dir = npc->get_dir_facing();
		int cnt = 0;
		int max = 2 + rand() % 4;
		for (; cnt < max; cnt++)
			frames[cnt] = npc->get_dir_framenum(dir, 0xe);
		max += 2 + rand() % 4;
		for (; cnt < max; cnt++)
			frames[cnt] = npc->get_dir_framenum(dir, 0xf);
		npc->set_action(new Frames_actor_action(frames, cnt));
		if (4 + rand() % 4 < phase)
			phase = 0;
		else
			phase++;
		break;
	}
	}
	npc->start(delay, delay);
}

/*
 *  Schedule change for kid games.
 */

void Kid_games_schedule::now_what(
) {
	Tile_coord pos = npc->get_tile();
	Actor *kid = nullptr;         // Get a kid to chase.
	// But don't run too far.
	while (!kids.empty()) {
		kid = kids.back();
		kids.pop_back();
		if (npc->distance(kid) < 16)
			break;
		kid = nullptr;
	}

	if (kid) {
		Fast_pathfinder_client cost(npc, kid->get_tile(), 1);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         pos, kid->get_tile(), cost);
		if (pact) {
			npc->set_action(pact);
			npc->start(100, 250); // Run.
			return;
		}
	} else {            // No more kids?  Search.
		Actor_vector vec;
		npc->find_nearby_actors(vec, c_any_shapenum, 16);
		for (auto *act : vec) {
			if (act->get_schedule_type() == kid_games)
				kids.push_back(act);
		}
	}
	Loiter_schedule::now_what();    // Wander around the start.
}

/*
 *  Schedule change for 'dance':
 */

void Dance_schedule::now_what(
) {
	Tile_coord dest = center;   // Pick new spot to walk to.
	dest.tx += -dist + rand() % (2 * dist);
	dest.ty += -dist + rand() % (2 * dist);
	Tile_coord cur = npc->get_tile();
	int dir = static_cast<int>(Get_direction4(cur.ty - dest.ty,
	                           dest.tx - cur.tx));
	// Pure guess. Since only some schedules seem to call proximity
	// usecode, I guess I should put it in here.
	// Seems quite rare.
	if (try_proximity_usecode(8))
		return;
	signed char *frames;
	int nframes;
	static char base_frames[] = {Actor::standing, Actor::up_frame, Actor::out_frame};
	static int framecount = array_size(base_frames);
	int danceroutine = rand() % framecount;
	int speed = 2 * gwin->get_std_delay();
	switch (danceroutine) {
	default: {
		// Spin in place using one of several frames.
		signed char basefr = base_frames[danceroutine];
		nframes = 6;
		frames = new signed char[nframes];
		int i;
		for (i = 0; i < nframes - 2; i++)
			frames[i] = npc->get_dir_framenum((dir + 2 * i) % 8, basefr);
		frames[i] = npc->get_dir_framenum((dir + 2 * i) % 8, basefr);
		frames[i + 1] = npc->get_dir_framenum((dir + 2 * i) % 8, Actor::standing);
		break;
	}
	case 3: { // Flap arms
		// Seems to repeat a random number of times.
		static char flap_frames[] = {Actor::up_frame, Actor::out_frame};
		nframes = 5 + 4 * (rand() % 4);
		frames = new signed char[nframes];
		int i;
		for (i = 0; i < nframes - 1; i++)
			frames[i] = npc->get_dir_framenum(dir, flap_frames[i % 2]);
		frames[i] = npc->get_dir_framenum(dir, Actor::standing);
		break;
	}
	case 4: { // Punch
		static signed char swing_frames[] = {Actor::ready_frame, Actor::raise1_frame,
		                                     Actor::reach1_frame, Actor::strike1_frame, Actor::ready_frame
		                                    };
		nframes = array_size(swing_frames);
		frames = new signed char[nframes];
		for (int i = 0; i < nframes; i++)
			frames[i] = npc->get_dir_framenum(dir, swing_frames[i]);
		break;
	}
	}
	// Create action to walk.
	Actor_action *walk = new Path_walking_actor_action(new Zombie());
	walk->walk_to_tile(npc, cur, dest);
	// Walk, then spin.
	npc->set_action(new Sequence_actor_action(walk,
	                new Frames_actor_action(frames, nframes, speed)));
	delete [] frames;
	npc->start(gwin->get_std_delay(), 500);     // Start in 1/2 sec.
}

void Tool_schedule::get_tool(
) {
	Game_object_shared newobj =
	     std::make_shared<Ireg_game_object>(toolshape, 0, 0, 0, 0);
	Game_object *tool_obj = newobj.get();
	tool = newobj;
	// Free up both hands.
	Game_object *obj = npc->get_readied(rhand);
	if (obj)
		obj->remove_this();
	if ((obj = npc->get_readied(lhand)) != nullptr)
		obj->remove_this();
	npc->add_readied(tool_obj, lhand);
}

/*
 *  End of mining/farming:
 */

void Tool_schedule::ending(
    int newtype
) {
	ignore_unused_variable_warning(newtype);
    Game_object_shared tool_obj = tool.lock();
	if (tool_obj) {
		tool_obj->remove_this();    // Should safely remove from NPC.
	}
}

static int cropshapes[] = {423};
static void Grow_crops(Actor *npc, int frame_group0)
{
	Game_object_vector crops;
	npc->find_closest(crops, cropshapes, array_size(cropshapes));
	// Grow the farther ones.
	int cnt = crops.size();
	for (auto it = crops.begin() + cnt/2; it != crops.end(); ++it) {
		if (rand()%4)
			continue;
		Game_object *c = *it;
		int frnum = c->get_framenum();
		int growth = ((frnum&3) + 1) & 3;
		if ((frnum & ~3) == frame_group0 && growth != 3)
			c->change_frame((frnum & ~3) | growth);
	}
}

/*
 *  Schedule change for tool-user (currently only farming).
 */

void Farmer_schedule::now_what(
) {
	int delay = 0;
	Game_object_shared tool_obj = tool.lock();
	if (!tool_obj)          // First time?
		get_tool();
	switch (state) {
	case start:
		state = find_crop;
		/* FALL THROUGH */
	case find_crop: {
		Game_object_vector crops;
		crop = Game_object_weak();
		Game_object *crop_obj = nullptr;
		if (npc->can_speak() && rand() % 5 == 0)
			npc->say(first_farmer2, last_farmer2);
		npc->find_closest(crops, cropshapes, array_size(cropshapes));
		// Filter out frame #3 (already cut).
		for (auto *crop : crops) {
			if (crop->get_framenum()%4 != 3) {
				crop_obj = crop;
				if (rand()%3 == 0)		// A little randomness.
					break;
			}
		}
		crop = weak_from_obj(crop_obj);
		if (crop_obj) {
			Actor_pathfinder_client cost(npc, 2);
			Actor_action *pact =
			    Path_walking_actor_action::create_path(
			        npc->get_tile(), crop_obj->get_tile(), cost);
			if (pact) {
				state = attack_crop;
				npc->set_action(new Sequence_actor_action(pact,
				                new Face_pos_actor_action(crop_obj, 200)));
				break;
			} else
				crop = Game_object_weak();
		}
		state = wander;
		delay = 1000 + rand() % 1000; // Try again later.
		break;
	}
	case attack_crop: {
	    Game_object_shared crop_obj = crop.lock();
		if (!crop_obj || crop_obj->is_pos_invalid() || npc->distance(crop_obj.get()) > 2) {
			state = find_crop;
			break;
		}
		signed char frames[20];     // Use tool.
		int dir = npc->get_direction(crop_obj.get());
		int cnt = npc->get_attack_frames(toolshape, false, dir,
		                                 frames);
		if (cnt) {
		    frame_group0 = crop_obj.get()->get_framenum() & ~3;
			frames[cnt++] = npc->get_dir_framenum(
			                    dir, Actor::standing);
			npc->set_action(new Frames_actor_action(frames, cnt));
			state = crop_attacked;
		} else
			state = wander;
		break;
	}
	case crop_attacked: {
	    Game_object_shared crop_obj = crop.lock();
		if (!crop_obj || crop_obj->is_pos_invalid()) {
			state = find_crop;
			break;
		}
		if (npc->can_speak() && rand() % 8 == 0)
			npc->say(first_farmer, last_farmer);
		if (rand() % 3 == 0) {      // Cut down crop.
			int frnum = crop_obj->get_framenum();
			// Frame 3 in each group of 4 is 'cut'.
			crop_obj->change_frame(frnum | 3);
			crop = Game_object_weak();
			// Wander now and then.
			state = (rand()%4 == 0) ? wander : find_crop;
			if (frame_group0 >= 0 && grow_cnt < 4 && rand()%2) {
				Grow_crops(npc, frame_group0);
				++grow_cnt;
			}
			delay = 500 + rand() % 2000;
		} else {
			state = attack_crop;
			delay = 250;
		}
		break;
	}
	case wander:
		Loiter_schedule::now_what();
		if (rand() % 2 == 0)
			state = find_crop;
		return;
	}
	npc->start(gwin->get_std_delay(), delay);
}

/*
 *  Miners attack ore.
 */

void Miner_schedule::now_what(
) {
	int delay = 0;
    Game_object_shared tool_obj = tool.lock();
	if (!tool_obj)          // First time?
		get_tool();
	switch (state) {
	case find_ore: {
		static int oreshapes[] = {915, 916};
		Game_object_vector ores;
		npc->find_closest(ores, oreshapes, array_size(oreshapes));
		int from;
		int to;
		int cnt = ores.size();
		// Filter out frame #3 (dust).
		for (from = to = 0; from < cnt; ++from)
			if (ores[from]->get_framenum() < 3)
				ores[to++] = ores[from];
		cnt = to;
		if (cnt) {
			Game_object *ore_obj = ores[rand() % cnt];
			ore = weak_from_obj(ore_obj);
			Actor_pathfinder_client cost(npc, 2);
			Actor_action *pact =
			    Path_walking_actor_action::create_path(
			        npc->get_tile(), ore_obj->get_tile(), cost);
			if (pact) {
				state = attack_ore;
				npc->set_action(new Sequence_actor_action(pact,
				                new Face_pos_actor_action(ore_obj, 200)));
				break;
			}
		}
		ore = Game_object_weak();
		state = wander;
		delay = 1000 + rand() % 1000; // Try again later.
		break;
	}
	case attack_ore: {
		Game_object_shared ore_obj = ore.lock();
		if (!ore_obj || ore_obj->is_pos_invalid() ||
		   			 	npc->distance(ore_obj.get()) > 2) {
			state = find_ore;
			break;
		}
		signed char frames[20];     // Use pick.
		int dir = npc->get_direction(ore_obj.get());
		int cnt = npc->get_attack_frames(toolshape, false, dir,
		                                 frames);
		if (cnt) {
			frames[cnt++] = npc->get_dir_framenum(
			                    dir, Actor::standing);
			npc->set_action(new Frames_actor_action(frames, cnt));
			state = ore_attacked;
		} else
			state = wander;
		break;
	}
	case ore_attacked: {
		Game_object_shared ore_obj = ore.lock();
		if (!ore_obj || ore_obj->is_pos_invalid()) {
			state = find_ore;
			break;
		}
		state = attack_ore;
		if (rand() % 6 == 0) {      // Break up piece.
			int frnum = ore_obj->get_framenum();
			if (frnum == 3)
				state = find_ore;   // Dust.
			else if (rand() % (4 + 2 * frnum) == 0) {
				if (npc->can_speak())
					npc->say(first_miner_gold, last_miner_gold);
				Tile_coord pos = ore_obj->get_tile();
				ore_obj->remove_this();
				int shnum;
				if (frnum == 0) {   // Gold.
					shnum = 645;
					frnum = rand() % 2;
				} else {        // Gem.
					shnum = 760;
					frnum = rand() % 10;
				}
				Game_object_shared newobj = std::make_shared<Ireg_game_object>(
												shnum, frnum, 0, 0);
				newobj->move(pos);
				newobj->set_flag(Obj_flags::is_temporary);
				state = find_ore;
				break;
			} else {
				ore_obj->change_frame(frnum + 1);
				if (ore_obj->get_framenum() == 3)
					state = find_ore;// Dust.
			}
		}
		if (npc->can_speak() && rand() % 4 == 0) {
			npc->say(first_miner, last_miner);
		}
		delay = 500 + rand() % 2000;
		break;
	}
	case wander:
		if (rand() % 2 == 0) {
			Loiter_schedule::now_what();
			return;
		} else
			state = find_ore;
		break;
	}
	npc->start(gwin->get_std_delay(), delay);
}

/*
 *  Schedule change for hounding the Avatar.
 */

void Hound_schedule::now_what(
) {
	Actor *av = gwin->get_main_actor();
	Tile_coord avpos = av->get_tile();
	Tile_coord npcpos = npc->get_tile();
	// How far away is Avatar?
	int dist = npc->distance(av);
	// Pure guess. Since only some schedules seem to call proximity
	// usecode, I guess I should put it in here.
	// Seems quite rare.
	if (try_proximity_usecode(12))
		return;
	if (dist < 3) { // Close enough?
		// Face avatar and check again soon.
		int dir = npc->get_direction(av);
		npc->change_frame(npc->get_dir_framenum(dir, Actor::standing));
		npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
		return;
	} else if (dist > 20) { // Too far?
		// Check again in a few seconds.
		npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
		return;
	}
	int newdist = 1 + rand() % 2; // Aim for about 3 tiles from Avatar.
	avpos.tx += rand() % 3 - 1; // Vary a bit randomly.
	avpos.ty += rand() % 3 - 1;
	Fast_pathfinder_client cost(npc, avpos, newdist);
	Actor_action *pact = Path_walking_actor_action::create_path(npcpos,
	                     avpos, cost);
	if (pact) {
		npc->set_action(pact);
		npc->start(gwin->get_std_delay(), 50);
	} else              // Try again.
		npc->start(gwin->get_std_delay(), 2000 + rand() % 3000);
}

/*
 *  Schedule change for 'wander':
 */

void Wander_schedule::now_what(
) {
	if (rand() % 2)         // 1/2 time, check for lamps, etc.
		if (try_street_maintenance())
			return;     // We no longer exist.
	Tile_coord pos = npc->get_tile();
	const int legdist = 32;
	// Go a ways from current pos.
	pos.tx += -legdist + rand() % (2 * legdist);
	pos.ty += -legdist + rand() % (2 * legdist);
	// Don't go too far from center.
	if (pos.tx - center.tx > dist)
		pos.tx = center.tx + dist;
	else if (center.tx - pos.tx > dist)
		pos.tx = center.tx - dist;
	if (pos.ty - center.ty > dist)
		pos.ty = center.ty + dist;
	else if (center.ty - pos.ty > dist)
		pos.ty = center.ty - dist;
	// Find a free spot.
	Tile_coord dest = Map_chunk::find_spot(pos, 4, npc->get_shapenum(), 0,
	                                       1);
	if (dest.tx == -1 || !npc->walk_path_to_tile(dest,
	        gwin->get_std_delay(), rand() % 2000))
		// Failed?  Try again a little later.
		npc->start(250, rand() % 3000);
}

/*
 *  Stand up if not already.
 */

static void Stand_up(
    Actor *npc
) {
	if ((npc->get_framenum() & 0xf) != Actor::standing)
		// Stand.
		npc->change_frame(Actor::standing);
}

/*
 *  Create a sleep schedule.
 */

Sleep_schedule::Sleep_schedule(
    Actor *n
) : Schedule(n), state(0), for_nap_time(false) {
	floorloc.tx = -1;       // Set to invalid loc.
	if (Game::get_game_type() == BLACK_GATE) {
		spread0 = 3;
		spread1 = 16;
	} else {            // Serpent Isle.
		spread0 = 7;
		spread1 = 20;
	}
}

/*
 * Checks if a bed is being used by a sleeping NPC. The second
 * parameter is a 'whitelist' NPC which will be ignored, and
 * should be used for preventing an NPC from detecting himself
 * as occupying the bed.
 */

bool Sleep_schedule::is_bed_occupied(
    Game_object *bed,
    Actor *npc
) {
	// Check if someone is already in the bed.
	Actor_vector occ;   // Unless there's another occupant.
	bed->find_nearby_actors(occ, c_any_shapenum, 2);
	TileRect foot = bed->get_footprint();
	int bedz = bed->get_tile().tz / 5;
	for (auto *it : occ) {
		if (npc == it)
			continue;
		Tile_coord tn = it->get_tile();
		// Don't check frame -- prevents wounded man shape from being
		// considered as an occupant.
		if (tn.tz / 5 == bedz /*&& ((*it)->get_framenum()&0xf) == Actor::sleep_frame*/ &&
		        foot.has_world_point(tn.tx, tn.ty))
			return true;
	}
	return false;
}

/*
 *  Schedule change for 'sleep':
 */

void Sleep_schedule::now_what(
) {
    Game_object_shared bed_obj = bed.lock();
	if (bed_obj && state <= 1 && is_bed_occupied(bed_obj.get(), npc)) {
		bed_obj = nullptr;
		bed = Game_object_weak(bed_obj);
		state = 0;
		npc->stop();
	}
	if (!bed_obj && state == 0 &&       // Always find bed.
	        !npc->get_flag(Obj_flags::asleep)) { // Unless flag already set
		// Find closest EW or NS bed.
		vector<int> bedshapes;
		bedshapes.push_back(696);
		bedshapes.push_back(1011);
		if (GAME_BG) {
			bedshapes.push_back(363);
			bedshapes.push_back(312);
		}
		Game_object_vector beds;    // Want to find top of bed.
		for (int bedshape : bedshapes)
			npc->find_nearby(beds, bedshape, 24, 0);
		int best_dist = 100;
		for (auto *newbed : beds) {
			int newdist = npc->distance(newbed);
			if (newdist < best_dist && !is_bed_occupied(newbed, npc)) {
				best_dist = newdist;
				bed_obj = newbed->shared_from_this();
			}
		}
		bed = Game_object_weak(bed_obj);
	}
	if (!bed_obj && npc == gwin->get_main_actor()) {
		npc->start(gwin->get_std_delay());
		return;
	}
	int frnum = npc->get_framenum();
	if ((frnum & 0xf) == Actor::sleep_frame)
		return;         // Already sleeping.

	switch (state) {
	case 0: {           // Find path to bed.
		if (!bed_obj) {
			// Just lie down at current spot.
			int dirbits = npc->get_framenum() & (0x30);
			npc->change_frame(Actor::sleep_frame | dirbits);
			npc->force_sleep();
			return;
		}
		state = 1;
		Game_object_vector tops;    // Want to find top of bed.
		bed_obj->find_nearby(tops, bed_obj->get_shapenum(), 1, 0);
		int floor = (bed_obj->get_tile().tz) / 5;
		for (auto *top : tops) {
			int frnum = top->get_framenum();
			Tile_coord tpos = top->get_tile();
			// Restrict to sheets on the same floor as the selected bed.
			if (frnum >= spread0 && frnum <= spread1 && floor == (tpos.tz / 5)) {
				bed_obj = top->shared_from_this();
				bed = Game_object_weak(bed_obj);
				break;
			}
		}
		Tile_coord bloc = bed_obj->get_tile();
		bloc.tz -= bloc.tz % 5; // Round down to floor level.
		const Shape_info &info = bed_obj->get_info();
		bloc.tx -= info.get_3d_xtiles(bed_obj->get_framenum()) / 2;
		bloc.ty -= info.get_3d_ytiles(bed_obj->get_framenum()) / 2;
		// Get within 3 tiles.
		Actor_pathfinder_client cost(npc, 3);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npc->get_tile(), bloc, cost);
		if (pact)
			npc->set_action(pact);
		npc->start(200);    // Start walking.
		break;
	}
	case 1: {           // Go to bed.
		npc->stop();        // Just to be sure.
		if (npc->distance(bed_obj.get()) > 3) {
			state = 0;
			npc->start(200);    // Try again.
		}
		int bedshape = bed_obj->get_shapenum();
		int dir = (bedshape == 696 || bedshape == 363) ? west : north;
		npc->change_frame(npc->get_dir_framenum(dir, Actor::sleep_frame));
		// Get bed info.
		const Shape_info &info = bed_obj->get_info();
		Tile_coord bedloc = bed_obj->get_tile();
		floorloc = npc->get_tile();
		Usecode_script::terminate(bed_obj.get());
		int bedframe = bed_obj->get_framenum();// Unmake bed.
		if (bedframe >= spread0 && bedframe < spread1 && (bedframe % 2)) {
			bedframe++;
			bed_obj->change_frame(bedframe);
		}
		bool bedspread = (bedframe >= spread0 && !(bedframe % 2));
		// Put NPC on top of bed, making sure that children are
		// not completely covered by sheets.
		int delta = npc->get_info().get_3d_height();
		delta = (delta < 4) ? (delta - 4) : 0;
		npc->move(bedloc.tx + delta, bedloc.ty + delta, bedloc.tz +
		          (bedspread ? 0 : info.get_3d_height()));
		// Either call bed usecode for avatar or put NPC to sleep.
		npc->force_sleep();
		state = 2;
		if (for_nap_time) {     // Usecode 622 handles sleeping.
			// Calling it may delete us, though.
			ucmachine->call_usecode(SleepUsecode, bed_obj.get(), Usecode_machine::double_click);
			return;             // So leave nothing to chance.
		}
		break;
	}
	default:
		break;
	}
}

/*
 *  Just went dormant.
 */
void Sleep_schedule::im_dormant() {
	// Force NPCs to lay down again after cache-out/cache-in.
	//npc->change_frame(npc->get_dir_framenum(Actor::standing));
	npc->set_action(nullptr);
}

/*
 *  Wakeup time.
 */

void Sleep_schedule::ending(
    int new_type            // New schedule.
) {
	// Needed for Skara Brae.
	if (new_type == static_cast<int>(wait) ||
	        // Not time to get up, Penumbra!
	        new_type == static_cast<int>(sleep))
		return;         // ++++Does this leave NPC's stuck?++++

	bool makebed = false;
	int dir = 0;
	Game_object_shared bed_obj = bed.lock();
	if (bed_obj &&          // Still in bed?
	        (npc->get_framenum() & 0xf) == Actor::sleep_frame &&
	        npc->distance(bed_obj.get()) < 8) {
		// Locate free spot.
		if (floorloc.tx == -1) {
			// Want spot on floor.
			floorloc = npc->get_tile();
			// Note: doing this in all cases may cause incorrect
			// placement when getting out of bed (e.g., top floor
			// bed in Spark's house.
			floorloc.tz -= floorloc.tz % 5;
		}
		Tile_coord pos = Map_chunk::find_spot(floorloc,
		                                      6, npc->get_shapenum(),
		                                      static_cast<int>(Actor::standing), 0);
		if (pos.tx == -1)   // Failed?  Allow change in lift.
			pos = Map_chunk::find_spot(floorloc,
			                           6, npc->get_shapenum(),
			                           static_cast<int>(Actor::standing), 1);
		floorloc = pos;
		// Make bed.
		int frnum = bed_obj->get_framenum();
		if (new_type != static_cast<int>(combat) &&  // Not if going into combat
		        frnum >= spread0 && frnum <= spread1 && !(frnum % 2) &&
		        !is_bed_occupied(bed_obj.get(), npc)) { // And not if there is another occupant.
			// Make the bed set itself after a small delay.
			makebed = true;
			auto *scr = new Usecode_script(bed_obj.get());
			(*scr) << Ucscript::finish
			       << Ucscript::delay_ticks << 3 << Ucscript::frame << frnum - 1;
			scr->start();
			Tile_coord bloc = bed_obj->get_center_tile();
			// Treat as cartesian coords.
			dir = static_cast<int>(Get_direction(floorloc.ty - bloc.ty, bloc.tx - floorloc.tx));
		}
	}
	if (floorloc.tx >= 0)       // Get back on floor.
		npc->move(floorloc);
	npc->clear_sleep();
	npc->change_frame(npc->get_dir_framenum(Actor::standing));
	if (makebed && !sleep_interrupted) {
		// Animation for making bed.
		auto *scr = new Usecode_script(npc);
		(*scr) << Ucscript::dont_halt << Ucscript::face_dir << dir
		       << Ucscript::npc_frame + Actor::ready_frame
		       << Ucscript::delay_ticks << 1 << Ucscript::npc_frame + Actor::raise1_frame
		       << Ucscript::delay_ticks << 1 << Ucscript::npc_frame + Actor::ready_frame
		       << Ucscript::delay_ticks << 1 << Ucscript::npc_frame + Actor::standing;
		scr->start();
	}
	gwin->set_all_dirty();      // Update all, since Av. stands up.
	if (sleep_interrupted)
		sleep_interrupted = false;
	state = 0;          // In case we go back to sleep.
}

void Sleep_schedule::set_bed(Game_object *b) {
	npc->set_action(nullptr);
	bed = weak_from_obj(b);
	state = 0;
	for_nap_time = true;
}

/*
 *  Create a 'sit' schedule.
 */

Sit_schedule::Sit_schedule(
    Actor *n,
    Game_object *ch         // Chair, or null to find one.
) : Schedule(n), chair(weak_from_obj(ch)), sat(false),did_barge_usecode(false) {
}

/*
 *  Schedule change for 'sit':
 */

void Sit_schedule::now_what(
) {
	int frnum = npc->get_framenum();
	Game_object_shared chair_obj = chair.lock();
	if (chair_obj && (frnum & 0xf) == Actor::sit_frame &&
	        npc->distance(chair_obj.get()) <= 1) {
		// Already sitting.
		// Seat on barge?
		if (chair_obj->get_info().get_barge_type() != Shape_info::barge_seat)
			return;
		if (did_barge_usecode)
			return;     // But NOT more than once for party.
		did_barge_usecode = true;
		if (gwin->get_moving_barge())
			return;     // Already moving.
		if (!npc->is_in_party())
			return;     // Not a party member.
		Actor *party[9];    // See if all sitting.
		int cnt = gwin->get_party(&party[0], 1);
		for (int i = 0; i < cnt; i++)
			if ((party[i]->get_framenum() & 0xf) != Actor::sit_frame)
				return; // Nope.
		// Find barge.
		Game_object *barge = chair_obj->find_closest(961);
		if (!barge)
			return;
		// Special usecode for barge pieces:
		// (Call with item=Avatar to avoid
		//   running nearby barges.)
		ucmachine->call_usecode(BargeUsecode, gwin->get_main_actor(),
		                        Usecode_machine::double_click);
		return;
	}
	// Wait a while if we got up.
	Game_object *new_chair = chair_obj.get();
	if (!set_action(npc, new_chair,
	   					 sat ? (1000 + rand() % 1000) : 0, &new_chair)) {
		npc->start(200, 1000);  // Failed?  Try again later.
	} else {
		sat = true;
	}
	chair = weak_from_obj(new_chair);
}

/*
 *  Just went dormant.
 */
void Sit_schedule::im_dormant() {
	// Force NPCs to sit down again after cache-out/cache-in.
	//npc->change_frame(npc->get_dir_framenum(Actor::standing));
	npc->set_action(nullptr);
}

/*
 *  Action to sit in the chair NPC is in front of.
 */

class Sit_actor_action : public Frames_actor_action, public Game_singletons {
	Game_object_weak chair;     // Chair.
	Tile_coord chairloc;        // Original chair location.
	Tile_coord sitloc;      // Actually where NPC sits.
	signed char frames[2];
	static short offsets[8];    // Offsets where NPC should sit.
	signed char *init(Game_object *chairobj, Actor *actor) {
		// Frame 0 faces N, 1 E, etc.
		int dir = 2 * (chairobj->get_framenum() % 4);
		frames[0] = actor->get_dir_framenum(dir, Actor::bow_frame);
		frames[1] = actor->get_dir_framenum(dir, Actor::sit_frame);
		return frames;
	}
	static bool is_occupied(Tile_coord const &sitloc, Actor *actor) {
		Game_object_vector occ; // See if occupied.
		if (Game_object::find_nearby(occ, sitloc, c_any_shapenum, 0, 8))
			for (auto *npc : occ) {
				if (npc == actor)
					continue;
				int frnum = npc->get_framenum() & 15;
				if (frnum == Actor::sit_frame ||
				        frnum == Actor::bow_frame)
					return true;
			}
		if (actor->get_tile() == sitloc)
			return false;   // We're standing there.
		// See if spot is blocked.
		Tile_coord pos = sitloc;// Careful, .tz gets updated.
		return Map_chunk::is_blocked(pos,
		                          actor->get_info().get_3d_height(), MOVE_WALK, 0);
	}
public:
	Sit_actor_action(Game_object *o, Actor *actor)
		: Frames_actor_action(init(o, actor), 2), chair(weak_from_obj(o)) {
		sitloc = chairloc = o->get_tile();
		// Frame 0 faces N, 1 E, etc.
		int nsew = o->get_framenum() % 4;
		sitloc.tx += offsets[2 * nsew];
		sitloc.ty += offsets[2 * nsew + 1];
	}
	Tile_coord get_sitloc() const {
		return sitloc;
	}
	static bool is_occupied(Game_object *chair, Actor *actor) {
		int dir = 2 * (chair->get_framenum() % 4);
		return is_occupied(chair->get_tile() +
		                   Tile_coord(offsets[dir], offsets[dir + 1], 0), actor);
	}
	// Handle time event.
	int handle_event(Actor *actor) override;
};

// Offsets where NPC should sit:
short Sit_actor_action::offsets[8] = {0, -1, 1, 0, 0, 1, -1, 0};

/*
 *  Show frame for sitting down.
 */

int Sit_actor_action::handle_event(
    Actor *actor
) {
	if (get_index() == 0) {     // First time?
		if (is_occupied(sitloc, actor))
			return 0;   // Abort.
		Game_object_shared chair_obj = chair.lock();
		if (!chair_obj || chair_obj->get_tile() != chairloc) {
			// Chair was moved!
			actor->say(first_chair_thief, last_chair_thief);
			return 0;
		}
	}
	return Frames_actor_action::handle_event(actor);
}

/*
 *  Is chair occupied, other than by 'actor'?
 */

bool Sit_schedule::is_occupied(
    Game_object *chairobj,
    Actor *actor
) {
	return Sit_actor_action::is_occupied(chairobj, actor);
}

/*
 *  Set up action.
 *
 *  Output: false if couldn't find a free chair.
 */

bool Sit_schedule::set_action(
    Actor *actor,
    Game_object *chairobj,      // May be 0 to find it.
    int delay,          // Msecs. to delay.
    Game_object **chair_found   // ->chair ret'd if not nullptr.
) {
	Game_object_vector chairs;
	if (chair_found)
		*chair_found = nullptr;   // Init. in case we fail.
	if (!chairobj) {        // Find chair if not given.
		static int chairshapes[] = {873, 292};
		actor->find_closest(chairs, chairshapes, array_size(chairshapes));
		for (auto& chair : chairs)
			if (!Sit_actor_action::is_occupied(chair, actor)) {
				Path_walking_actor_action action(nullptr, 6);
				bool reachable = action.walk_to_tile(actor, actor->get_tile(), chair->get_tile(), 1) != nullptr;
				// Found an unused one.
				if (reachable) {
					chairobj = chair;
					break;
				}
			}
		if (!chairobj)
			return false;
	} else if (Sit_actor_action::is_occupied(chairobj, actor))
		return false;       // Given chair is occupied.
	auto *act = new Sit_actor_action(chairobj, actor);
	// Walk there, then sit.
	set_action_sequence(actor, act->get_sitloc(), act, false, delay);
	if (chair_found)
		*chair_found = chairobj;
	return true;
}

/*
 *  Desk work.
 */

Desk_schedule::Desk_schedule(
    Actor *n
) : Schedule_with_objects(n), state(desk_setup) {
}

/*
 *  Find tables.
 */
void Desk_schedule::find_tables(
    int shapenum
) {
	Game_object_vector vec;
	npc->find_nearby(vec, shapenum, 16, 0);
	int floor = npc->get_lift() / 5; // Make sure it's on same floor.
	for (auto *table : vec) {
		if (table->get_lift() / 5 != floor)
			continue;
		tables.push_back(table->weak_from_this());
	}
}

/*
 *  Walk to table.
 *  Output: false if failed.
 */
bool Desk_schedule::walk_to_table() {
	while (!tables.empty()) {
		int ind = rand() % tables.size();
		table = tables[ind];
		Game_object_shared table_keep = table.lock();
		if (!table_keep) {
			tables.erase(tables.begin() + ind);
			continue;
		}
		table = table_keep;
		// This will find a random spot around the table:
		Tile_coord pos = Map_chunk::find_spot(table_keep->get_tile(), 1,
				   npc->get_shapenum(), npc->get_framenum());

		if (pos.tx != -1 &&
		        npc->walk_path_to_tile(pos, gwin->get_std_delay(),
		                               1000 + rand() % 1000))
			return true;
	}
	table = Game_object_weak();
	return false;
}

static char desk_frames[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15};
#define DESK_FRAMES_CNT   array_size(desk_frames)
int Desk_schedule::find_items(Game_object_vector &vec, int dist)
{
	npc->find_nearby(vec, 675, dist, 0);
	int floor = npc->get_lift() / 5; // Make sure it's on same floor.
	vec.erase(std::remove_if(vec.begin(), vec.end(),
		[floor](Game_object *item) {
			return item->get_lift() / 5 != floor ||
			       memchr(desk_frames, item->get_framenum(), DESK_FRAMES_CNT) == nullptr;
		}
		), vec.end());
	return vec.size();
}

// Pick a 'reasonable' desk item frame to create.
static int Desk_item_frame()
{
	static int quills[] = {1, 14, 15};
	static int documents[] = {6, 13};
	static int wax[] = {7, 12};
	static int non_docs = 0;					// Make sure to create docs.

	int n = non_docs >= 2 ? 6 : rand() % 10;
	++non_docs;
	switch (n) {
	case 0:  return 0;							// Gavel.
	case 1:  return quills[rand()%3];
	case 2:  return 2;							// Inkwell
	case 3:  return 3;							// Quill holder
	case 4:  return 4;							// Book mark.
	case 5:  return 5;							// Letter opener.
	case 6:  non_docs = 0; return documents[rand()%2];
	case 7:  return wax[rand()%2];
	case 8:  return 8;							// Seal.
	case 9:  return 9;							// Blotter.
	default: return 0;
	}
}

/*
 *  Schedule change for 'desk work':
 */

void Desk_schedule::now_what(
) {
	if (rand() % 3 == 0)        // Check for lamps, etc.
		if (try_street_maintenance())
			return;     // We no longer exist.

	switch (state) {
	case desk_setup: {
		static int desks[2] = {283, 407};
		Stand_up(npc);
		if (tables.empty()) {
			find_tables(890);
			find_tables(633);
			find_tables(1000);
			find_tables(283);			// Desks too.
			find_tables(407);
		}
		// Create desk items if needed.
		items_in_hand = npc->count_objects(675);
		Game_object_vector vec;
		int nearby = Desk_schedule::find_items(vec, 16);
		int nitems = (7 + rand()%5) - items_in_hand - nearby;
		if (nitems > 0) {
			items_in_hand += nitems;
			for (int i = 0; i < nitems; ++i) {
				int frame = Desk_item_frame();
				Game_object_shared item =
				     std::make_shared<Ireg_game_object>(675, frame, 0, 0, 0);
				npc->add(item.get(), true);
				add_object(item.get());
			}
		}
		Game_object *found = npc->find_closest(desks, 2);
		if (found) {
			static int chairs[] = {873, 292};
			desk = found->weak_from_this();
			found = found->find_closest(chairs, array_size(chairs));
			if (found)
			    chair = found->weak_from_this();
		}
		if (chair.expired()) {   // Failed.
			// Try again in a few seconds.
			npc->start(200, 5000);
			return;
		}
		state = sit_at_desk;
		}
		/* FALL THROUGH */
	case sit_at_desk: {
		int frnum = npc->get_framenum();
		// Not sitting?
		if ((frnum & 0xf) != Actor::sit_frame) {
		    Game_object_shared chair_keep = chair.lock();
			if (!chair_keep ||
			   		!Sit_schedule::set_action(npc, chair_keep.get(), 0)) {
				chair = Game_object_weak();  // Look for any nearby chair.
				state = desk_setup;
				npc->start(200, 5000);  // Failed?  Try again later.
			} else
				npc->start(250, 0);
		} else if (rand() % (1 + items_in_hand) && walk_to_table()) {
			state = work_at_table;
		} else if (rand() % 2 && walk_to_random_item()) {
			state = get_desk_item;
		} else {            // Stand up a second.
			signed char frames[5];
			frames[0] = npc->get_dir_framenum(Actor::standing);
			frames[1] = npc->get_dir_framenum(Actor::reach1_frame);
			frames[2] = npc->get_dir_framenum(Actor::standing);
			frames[3] = npc->get_dir_framenum(Actor::bow_frame);
			frames[4] = npc->get_dir_framenum(Actor::sit_frame);
			npc->set_action(new Frames_actor_action(frames,
			                                array_size(frames)));
			npc->start(250, 3000 + rand() % 2000);
		}
		break;
		}
	case get_desk_item: {
	    Game_object *item = get_current_item();
		if (item && npc->distance(item) <= 3) {
			// Turn to face it.
			npc->change_frame(npc->get_dir_framenum(
			        npc->get_direction(item), Actor::standing));
			npc->set_action(new Pickup_actor_action(item, 250));
			state = picked_up_item;
		} else
			state = sit_at_desk;
		npc->start(250, 500 + rand()%1000);
		break;
	}
	case picked_up_item:
		set_current_item(nullptr);
		if (rand() % 2 && walk_to_table()) {
			state = work_at_table;
			break;
		} else
			state = sit_at_desk;
		npc->start(250, 1000 + rand() % 500);
		break;
	case work_at_table:
		// Turn to face it.
		Game_object_shared tbl = table.lock();
		if (tbl)
		    npc->change_frame(npc->get_dir_framenum(
		           npc->get_facing_direction(tbl.get()), Actor::standing));
		if (!tbl || rand() % 3 == 0) {
			state = sit_at_desk;		// Back to desk.
		} else if (items_in_hand && rand() % 6) {		// Put down an item.
			Game_object_vector items;
			items_in_hand = npc->get_objects(items, 675,
			                                 c_any_qual, c_any_framenum);
			if (items_in_hand) {
				Game_object *to_drop = items[rand()%items_in_hand];
				if (drop_item(to_drop, tbl.get())) {
					if (rand() % 2)
						state = sit_at_desk;
				}
			} else
				state = sit_at_desk;
		} else {
			int dir = npc->get_facing_direction(tbl.get());
			signed char frames[3];
			frames[0] = npc->get_dir_framenum(dir, Actor::standing);
			frames[1] = npc->get_dir_framenum(dir, (rand()%2)
					  ? Actor::reach1_frame : Actor::reach2_frame);
			frames[2] = npc->get_dir_framenum(dir, Actor::standing);
			Actor_action *face = new Face_pos_actor_action(tbl.get(), 200);;
			npc->set_action(new Sequence_actor_action(face,
			                    new Frames_actor_action(frames,
			                            array_size(frames))));
		}
		npc->start(250, 1000 + rand() % 500);
		break;
	}
}

/*
 *  Just went dormant.
 */
void Desk_schedule::im_dormant() {
	// Force NPCs to sit down again after cache-out/cache-in.
	//npc->change_frame(npc->get_dir_framenum(Actor::standing));
	npc->set_action(nullptr);
}

/*
 *	Done.
 */
void Desk_schedule::ending(
    int new_type				// New schedule.
) {
	ignore_unused_variable_warning(new_type);
	cleanup();
}

/*
 *  A class for indexing the perimeter of a rectangle.
 */
class Perimeter {
	TileRect perim;        // Outside given rect.
	int sz;             // # squares.
public:
	Perimeter(TileRect &r) : sz(2 * r.w + 2 * r.h + 4) {
		perim = r;
		perim.enlarge(1);
	}
	int size() {
		return sz;
	}
	// Get i'th tile.
	void get(int i, Tile_coord &ptile, Tile_coord &atile);
};

/*
 *  Get the i'th perimeter tile and the tile in the original rect.
 *  that's adjacent.
 */

void Perimeter::get(
    int i,
    Tile_coord &ptile,      // Perim. tile returned.
    Tile_coord &atile       // Adjacent tile returned.
) {
	if (i < perim.w - 1) {      // Spiral around from top-left.
		ptile = Tile_coord(perim.x + i, perim.y, 0);
		atile = ptile + Tile_coord(!i ? 1 : 0, 1, 0);
		return;
	}
	i -= perim.w - 1;
	if (i < perim.h - 1) {
		ptile = Tile_coord(perim.x + perim.w - 1, perim.y + i, 0);
		atile = ptile + Tile_coord(-1, !i ? 1 : 0, 0);
		return;
	}
	i -= perim.h - 1;
	if (i < perim.w - 1) {
		ptile = Tile_coord(perim.x + perim.w - 1 - i,
		                   perim.y + perim.h - 1, 0);
		atile = ptile + Tile_coord(!i ? -1 : 0, -1, 0);
		return;
	}
	i -= perim.w - 1;
	if (i < perim.h - 1) {
		ptile = Tile_coord(perim.x, perim.y + perim.h - 1 - i, 0);
		atile = ptile + Tile_coord(1, !i ? -1 : 0, 0);
		return;
	}
	// Bad index if here.
	get(i % sz, ptile, atile);
}

/*
 *  Initialize.
 */

void Lab_schedule::init(
) {
	Game_object *book_obj = nullptr;
	Game_object *chair_obj = nullptr;
	cauldron = weak_from_obj(npc->find_closest(995, 20));
	// Find 'lab' tables.
	vector<Game_object *> table_objs;
	npc->find_nearby(table_objs, 1003, 20, 0);
	npc->find_nearby(table_objs, 1018, 20, 0);
	int cnt = table_objs.size();    // Look for book, chair.
	tables.resize(cnt);
	for (int i = 0; (!book_obj || !chair_obj) && i < cnt; i++) {
		Game_object *table = table_objs[i];
		tables[i] = weak_from_obj(table);
		TileRect foot = table->get_footprint();
		// Book on table?
		if (!book_obj && (book_obj = table->find_closest(642, 4)) != nullptr) {
			Tile_coord p = book_obj->get_tile();
			if (!foot.has_world_point(p.tx, p.ty))
				book_obj = nullptr;
		}
		if (!chair_obj) {
			static int chairs[2] = {873, 292};
			chair_obj = table->find_closest(chairs, 2, 4);
		}
	}
	book = weak_from_obj(book_obj);
	chair = weak_from_obj(chair_obj);
}

/*
 *  Create lab schedule.
 */

Lab_schedule::Lab_schedule(
    Actor *n
) : Schedule(n), state(start) {
	init();
}

/*
 *  Lab work.
 */

void Lab_schedule::now_what(
) {
	Tile_coord npcpos = npc->get_tile();
	int delay = 100;        // 1/10 sec. to next action.
	Game_object_shared cauldron_obj = cauldron.lock();
	Game_object_shared book_obj = book.lock();
	// Often want to get within 1 tile.
	Actor_pathfinder_client cost(npc, 1);
	switch (state) {
	case start:
	default: {
		if (!cauldron_obj) {
			// Try looking again.
			init();
			if (!cauldron_obj)  // Again a little later.
				delay = 6000;
			break;
		}
		int r = rand() % 5; // Pick a state.
		if (!r)         // Sit less often.
			state = sit_down;
		else
			state = r <= 2 ? walk_to_cauldron : walk_to_table;
		break;
	}
	case walk_to_cauldron: {
		state = start;      // In case we fail.
		if (!cauldron_obj)
			break;
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, cauldron_obj->get_tile(), cost);
		if (pact) {
			npc->set_action(new Sequence_actor_action(pact,
		                new Face_pos_actor_action(cauldron_obj.get(), 200)));
			state = use_cauldron;
		}
		break;
	}
	case use_cauldron: {
		int dir = npc->get_direction(cauldron_obj.get());
		// Set random frame (skip last frame).
		cauldron_obj->change_frame(rand() % (cauldron_obj->get_num_frames() - 1));
		npc->change_frame(
		    npc->get_dir_framenum(dir, Actor::bow_frame));
		int r = rand() % 5;
		state = !r ? use_cauldron : (r <= 2 ? sit_down
		                             : walk_to_table);
		break;
	}
	case sit_down: {
	    Game_object_shared chair_obj = chair.lock();
		if (!chair_obj || !Sit_schedule::set_action(npc, chair_obj.get(), 200))
			state = start;
		else
			state = read_book;
		break;
	}
	case read_book: {
		state = stand_up;
		if (!book_obj || npc->distance(book_obj.get()) > 4)
			break;
		// Read a little while.
		delay = 1000 + 1000 * (rand() % 5);
		// Open book.
		int frnum = book_obj->get_framenum();
		book_obj->change_frame(frnum - frnum % 3);
		break;
	}
	case stand_up: {
		if (book_obj && npc->distance(book_obj.get()) < 4) {
			// Close book.
			int frnum = book_obj->get_framenum();
			book_obj->change_frame(frnum - frnum % 3 + 1);
		}
		state = start;
		break;
	}
	case walk_to_table: {
		state = start;      // In case we fail.
		int ntables = tables.size();
		if (!ntables) {
			break;
		}
		int tableindex = rand() % ntables;
		Game_object_shared table = (tables[tableindex]).lock();
		if (!table) {
			break;
		}
		TileRect r = table->get_footprint();
		Perimeter p(r);     // Find spot adjacent to table.
		Tile_coord spot;    // Also get closest spot on table.
		p.get(rand() % p.size(), spot, spot_on_table);
		Actor_pathfinder_client cost0(npc, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, spot, cost0);
		if (!pact)
			break;      // Failed.
		const Shape_info &info = table->get_info();
		spot_on_table.tz += info.get_3d_height();
		npc->set_action(new Sequence_actor_action(pact,
		                new Face_pos_actor_action(spot_on_table, 200)));
		state = use_potion;
		break;
	}
	case use_potion: {
		state = start;
		vector<Game_object *> potions;
		Game_object::find_nearby(potions, spot_on_table, 340, 0, 0);
		if (!potions.empty()) { // Found a potion.  Remove it.
			gwin->add_dirty(potions[0]);
			potions[0]->remove_this();
		} else {        // Create potion if spot is empty.
			Tile_coord t = Map_chunk::find_spot(spot_on_table, 0,
			                                    340, 0, 0);
			if (t.tx != -1 && t.tz == spot_on_table.tz) {
				// create a potion randomly, but don't use the last frame
				int nframes = ShapeID(340, 0).get_num_frames() - 1;
				Game_object_shared p = gmap->create_ireg_object(
				                     ShapeID::get_info(340), 340,
				                     rand() % nframes, 0, 0, 0);
				p->move(t);
			}
		}
		signed char frames[2];
		int dir = npc->get_direction(spot_on_table);
		// Reach out hand:
		frames[0] = npc->get_dir_framenum(dir, 1);
		frames[1] = npc->get_dir_framenum(dir, Actor::standing);
		npc->set_action(new Frames_actor_action(frames, 2));
		break;
	}
	}
	npc->start(gwin->get_std_delay(), delay);   // Back in queue.
}

/*
 *  Schedule change for 'shy':
 */

void Shy_schedule::now_what(
) {
	Actor *av = gwin->get_main_actor();
	Tile_coord avpos = av->get_tile();
	Tile_coord npcpos = npc->get_tile();
	// How far away is Avatar?
	int dist = npc->distance(av);
	if (dist > 10) {        // Far enough?
		// Check again in a few seconds.
		if (rand() % 3)     // Just wait.
			npc->start(250, 1000 + rand() % 1000);
		else            // Sometimes wander.
			npc->walk_to_tile(
			    Tile_coord(npcpos.tx + rand() % 6 - 3,
			               npcpos.ty + rand() % 6 - 3, npcpos.tz));
		return;
	}
	// Get deltas.
	int dx = npcpos.tx - avpos.tx;
	int dy = npcpos.ty - avpos.ty;
	int adx = dx < 0 ? -dx : dx;
	int ady = dy < 0 ? -dy : dy;
	// Which is farthest?
	int farthest = adx < ady ? ady : adx;
	int factor = farthest < 2 ? 9 : farthest < 4 ? 4
	             : farthest < 7 ? 2 : 1;
	// Walk away.
	Tile_coord dest = npcpos + Tile_coord(dx * factor, dy * factor, 0);
	Tile_coord delta = Tile_coord(rand() % 3, rand() % 3, 0);
	dest = dest + delta;
	Monster_pathfinder_client cost(npc, dest, 4);
	Actor_action *pact = Path_walking_actor_action::create_path(
	                         npcpos, dest, cost);
	if (pact) {         // Found path?
		npc->set_action(pact);
		npc->start(200, 100 + rand() % 200); // Start walking.
	} else                  // Try again in a couple secs.
		npc->start(250, 500 + rand() % 1000);
}

/*
 *  Steal.
 */

void Thief_schedule::steal(
    Actor *from
) {
	if (npc->can_speak())
		npc->say(first_thief, last_thief);
	int shnum = rand() % 3;     // Gold coin, nugget, bar.
	Game_object *obj = nullptr;
	for (int i = 0; !obj && i < 3; ++i) {
		obj = from->find_item(644 + shnum, c_any_qual, c_any_framenum);
		shnum = (shnum + 1) % 3;
	}
	if (obj) {
	    Game_object_shared keep;
		obj->remove_this(&keep);
		npc->add(obj, false);
	}
}

/*
 *  Next decision for thief.
 */

void Thief_schedule::now_what(
) {
	unsigned long curtime = SDL_GetTicks();
	if (curtime < next_steal_time) {
		// Not time?  Wander.
		Tile_coord center = gwin->get_main_actor()->get_tile();
		const int dist = 6;
		int newx = center.tx - dist + rand() % (2 * dist);
		int newy = center.ty - dist + rand() % (2 * dist);
		// Wait a bit.
		npc->walk_to_tile(newx, newy, center.tz,
		                  2 * gwin->get_std_delay(), rand() % 4000);
		return;
	}
	if (npc->distance(gwin->get_main_actor()) <= 1) {
		// Next to Avatar.
		if (rand() % 3)
			steal(gwin->get_main_actor());

		next_steal_time = curtime + 8000 + rand() % 8000;
		npc->start(250, 1000 + rand() % 2000);
	} else {
		// Get within 1 tile of Avatar.
		Tile_coord dest = gwin->get_main_actor()->get_tile();
		Monster_pathfinder_client cost(npc, dest, 1);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npc->get_tile(), dest, cost);
		if (pact) {     // Found path?
			npc->set_action(pact);
			npc->start(gwin->get_std_delay(), 1000 + rand() % 1000);
		} else          // Try again in a couple secs.
			npc->start(250, 2000 + rand() % 2000);
	}
}

/*
 *  Create a 'waiter' schedule.
 */

Waiter_schedule::Waiter_schedule(
    Actor *n
) : Schedule_with_objects(n), startpos(n->get_tile()), customer(nullptr),
	cooking(false), state(waiter_setup) {
}

static int waiter_shapes[] = {616, 628, 944};
int Waiter_schedule::find_items(Game_object_vector &vec, int dist)
{
	if (cooking) {					// Find food, pot.
		npc->find_nearby(vec, 377, dist, 0);
		npc->find_nearby(vec, 944, dist, 0);
	}
	if (!cooking || vec.empty()) {
		// Bottle, cup, pot
		for (int waiter_shape : waiter_shapes)
			npc->find_nearby(vec, waiter_shape, dist, 0);
	}
	int floor = npc->get_lift() / 5; // Make sure it's on same floor.
	vec.erase(std::remove_if(vec.begin(), vec.end(),
		[floor](Game_object *item) {
			return item->get_lift() / 5 != floor;
		}
		), vec.end());
	return vec.size();
}

static int Count_waiter_objects(Actor *npc)
{
	int cnt = 0;
	for (int waiter_shape : waiter_shapes)
		cnt += npc->count_objects(waiter_shape);
	return cnt;
}

static int Get_waiter_objects(Actor *npc, Game_object_vector& items,
                              bool cooking = false)
{
	int cnt = 0;
	if (cooking) {					// Find food, pot.
	    cnt += npc->get_objects(items, 377, c_any_qual, c_any_framenum);
		cnt += npc->get_objects(items, 944, c_any_qual, c_any_framenum);
	}
	if (!cooking || !cnt) {
		for (int waiter_shape : waiter_shapes) {
			cnt += npc->get_objects(items, waiter_shape,
			                               c_any_qual, c_any_framenum);
		}
	}
	return cnt;
}

/*
 *	Fill in 'plates' list with ones we may have created, ordered by closest
 *  first.  Return true if not empty.
 */
bool Waiter_schedule::find_unattended_plate()
{
	if (unattended_plates.empty()) {
		static int shapes[] = {717};
		const int shapecnt = array_size(shapes);
		vector<Game_object *> plates;
		(void) npc->find_closest(plates, shapes, shapecnt, 32);
		int floor = npc->get_lift() / 5; // Make sure it's on same floor.

		for (auto *plate : plates) {
			Actor_vector custs;
			if (plate->get_lift() / 5 == floor &&
				plate->get_flag(Obj_flags::is_temporary) &&
				!plate->find_nearby_actors(custs, c_any_shapenum, 2)) {
				unattended_plates.push_back(weak_from_obj(plate));
			}
		}
	}
	return !unattended_plates.empty();
}

/*
 *  Find a new customer.
 */

bool Waiter_schedule::find_customer(
) {
	if (customers.empty()) {        // Got to search?
		Actor_vector vec;       // Look within 32 tiles;
		npc->find_nearby_actors(vec, c_any_shapenum, 32);
		for (auto *each : vec) {
			// Filter them.
			if (each->get_schedule_type() == Schedule::eat_at_inn)
				customers.push_back(each);
		}
	}

	if (!customers.empty()) {
		customer = customers.back();
		customers.pop_back();
	} else
		customer = nullptr;
	return customer != nullptr;
}

/*
 *  Walk to prep. table.
 *  Output: false if failed.
 */
bool Waiter_schedule::walk_to_work_spot(
    bool counter
) {
	vector <Game_object_weak> &tables = counter ? counters : prep_tables;
	while (!tables.empty()) {   // Walk to a 'prep' table.
		int index = rand() % tables.size();
		prep_table = tables[index];
		Game_object_shared prep_table_obj = prep_table.lock();
		if (prep_table_obj) {
			Tile_coord pos = Map_chunk::find_spot(
								prep_table_obj->get_tile(), 1, npc);
			if (pos.tx != -1 &&
					npc->walk_path_to_tile(pos, gwin->get_std_delay(),
										1000 + rand() % 1000))
				return true;
		}
		// Failed, so remove this table from the list.
		tables.erase(tables.begin() + index);
	}
	prep_table = Game_object_weak();
	const int dist = 8;     // Bad luck?  Walk randomly.
	int newx = startpos.tx - dist + rand() % (2 * dist);
	int newy = startpos.ty - dist + rand() % (2 * dist);
	npc->walk_to_tile(newx, newy, startpos.tz, 2 * gwin->get_std_delay(),
	                  rand() % 2000);
	return false;
}

/*
 *  Walk to customer
 *  Output: false if failed.
 */
bool Waiter_schedule::walk_to_customer(
    int min_delay           // Min. delay in msecs.
) {
	if (customer) {
		if (customer->get_schedule_type() != Schedule::eat_at_inn)
			// Customer schedule changed. Tell schedule to refresh the list
			// (this happens with Hawk & others in SI).
			customers.clear();
		else {
			Tile_coord dest = Map_chunk::find_spot(customer->get_tile(),
			                                       3, npc);
			if (dest.tx != -1 && npc->walk_path_to_tile(dest,
			        gwin->get_std_delay(), min_delay + rand() % 1000))
				return true;        // Walking there.
		}
	}
	npc->start(200, 2000 + rand() % 4000);  // Failed so try again later.
	return false;
}

/*
 *  Find tables and categorize them.
 */

void Waiter_schedule::find_tables(
    int shapenum,
    int dist,
    bool is_prep
) {
	Game_object_vector vec;
	npc->find_nearby(vec, shapenum, dist, 0);
	int floor = npc->get_lift() / 5; // Make sure it's on same floor.
	for (auto *table_obj : vec) {
		if (table_obj->get_lift() / 5 != floor)
			continue;
		Game_object_weak table = weak_from_obj(table_obj);
		Game_object_vector chairs;      // No chairs by it?
		                                // TODO: check for nearby stove.
		if (!table_obj->find_nearby(chairs, 873, 3, 0) &&
		        !table_obj->find_nearby(chairs, 292, 3, 0)) {
			if (is_prep)
				prep_tables.push_back(table);
			else if (shapenum == 847)
				counters.push_back(table);
		} else
			eating_tables.push_back(table);
	}
}

void Waiter_schedule::find_prep_tables()
{
	static int shapes[] = {333, 1018, 1003,		// Tables.
	                       664, 872,       		// Stoves
	                       995};           		// Cauldron
	for (int shape : shapes)
		find_tables(shape, 26, true);
	if (prep_tables.empty()) {		// Look farther.
		for (int shape : shapes)
			find_tables(shape, 36, true);
	}
	if (prep_tables.empty()) {
		for (int shape : shapes)
			find_tables(shape, 50, true);
	}
}

static Game_object *Find_customer_table(Actor *customer,
                                        vector<Game_object_weak>& tables)
{
	for (auto& it : tables) {
		Game_object_shared table = it.lock();
		if (table && customer->distance(table.get()) < 3)
			return table.get();
	}
	return nullptr;
}

static Game_object *Find_customer_plate(Actor *customer)
{
	Game_object_vector plates;  // First look for a nearby plate.
	Game_object *plate = nullptr;

	(void) customer->find_nearby(plates, 717, 1, 0);
	int floor = customer->get_lift() / 5; // Make sure it's on same floor.
	for (auto *it : plates) {
		plate = it;
		int platez = plate->get_lift();
		if (platez / 5 == floor && platez%5 != 0) {
			return plate;
		}
	}
	return nullptr;
}

Game_object *Waiter_schedule::create_customer_plate(
) {
	Tile_coord cpos = customer->get_tile();

	// First, erase all deleted tables.
	eating_tables.erase(std::remove_if(eating_tables.begin(), eating_tables.end(),
		[](Game_object_weak& elem) {
			Game_object_shared table = elem.lock();
			return table != nullptr;
		}
		), eating_tables.end());

	// Go through tables.
	for (auto& eating_table : eating_tables) {
		Game_object_shared table = eating_table.lock();
		if (!table) {
			// This should not be possible.
			// In any case, lets ignore these for now, and they
			// will be cleared next time through this function.
			continue;
		}
		TileRect foot = table->get_footprint();
		if (foot.distance(cpos.tx, cpos.ty) > 2) {
			continue;
		}
		// Found it.
		Tile_coord spot = cpos;        // Start here.
		// East/West of table?
		if (cpos.ty >= foot.y && cpos.ty < foot.y + foot.h)
			spot.tx = cpos.tx <= foot.x ? foot.x
			          : foot.x + foot.w - 1;
		else            // North/south.
			spot.ty = cpos.ty <= foot.y ? foot.y
			          : foot.y + foot.h - 1;
		if (foot.has_world_point(spot.tx, spot.ty)) {
			// Passes test.
			const Shape_info &info = table->get_info();
			spot.tz = table->get_lift() + info.get_3d_height();
			// Small plates:  frames 4, 5.  Seems random.
			Game_object_shared plate = gmap->create_ireg_object(
													717, 4 + rand()%2);
			plate->set_flag(Obj_flags::is_temporary);
			plate->move(spot);
			return plate.get();
		}
	}
	return nullptr;           // Failed.
}

static void Ready_food(Actor *npc)
{
	Game_object *food;
	Game_object *obj = npc->get_readied(lhand);
	Game_object_shared food_keep;
	Game_object_shared obj_keep;
	if (obj) {							// Already something there?
	    obj_keep = obj->shared_from_this();
		if (obj->get_shapenum() == 377)
			return;						// Food, so done.
		npc->remove(obj);				// Make space.
	}
	Game_object_vector items;
    if (npc->get_objects(items, 377, c_any_qual, c_any_framenum)) {
		// Already have one, so just move it.
		food = items[0];
		food_keep = food->shared_from_this();
		npc->remove(food);
	} else {
		// Acquire some food.
		int nfoods = ShapeID(377, 0).get_num_frames();
		int frame = rand() % nfoods;
		food_keep = 
		     std::make_shared<Ireg_game_object>(377, frame, 0, 0, 0);
	    food = food_keep.get();
		food->set_flag(Obj_flags::is_temporary);
		food->clear_flag(Obj_flags::okay_to_take);
	}
	npc->add_readied(food, lhand);
	if (obj)
		npc->add(obj, true);			// Add back what was there before.
}

/*
 *  Find serving spot for a customer.
 *
 *  Output: Plate if found, with spot set.  Plate is created if needed.
 */

Game_object *Waiter_schedule::find_serving_spot(
    Tile_coord &spot
) {
	// First look for a nearby plate.
	Game_object *plate = Find_customer_plate(customer);
	if (!plate)
		plate = create_customer_plate();
	if (plate) {
		spot = plate->get_tile();
		spot.tz++;  // Just above plate.
	}
	return plate;
}

static void Prep_animation(Actor *npc, Game_object *table)
{
	npc->change_frame(npc->get_dir_framenum(npc->get_facing_direction(table),
	                                  Actor::standing));
	auto *scr = new Usecode_script(npc);
	(*scr) << Ucscript::face_dir << npc->get_dir_facing();
	for (int cnt = 1 + rand() % 3; cnt; --cnt) {
		(*scr) << (Ucscript::npc_frame + Actor::ready_frame)
		       << Ucscript::delay_ticks << 1
		       << (Ucscript::npc_frame + Actor::raise1_frame)
		       << Ucscript::delay_ticks << 1;
	}
	(*scr) << (Ucscript::npc_frame + Actor::standing);
	scr->start();   // Start next tick.
	int shapenum = table->get_shapenum();
	// Cauldron or stove?  Animate a little.
	if (shapenum == 995) {
		// Don't use last cauldron frame.
		table->change_frame(rand() % (table->get_num_frames() - 1));
	} else if (shapenum == 664 || shapenum == 872) {
		do {
			table->change_frame(rand() % table->get_num_frames());
		} while (table->is_frame_empty());
	}
}

/*
 *  Schedule change for 'waiter':
 */

void Waiter_schedule::now_what(
) {
	Game_object *food;

	if (state == wait_at_counter &&
	        rand() % 4 == 0)        // Check for lamps, etc.
		if (try_street_maintenance())
			return;     // We no longer exist.
	if (state == get_order || state == serve_food) {
		int dist = customer ? npc->distance(customer) : 5000;
		if (dist > 32) {    // Need a new customer?
			state = get_customer;
			npc->start(200, 1000 + rand() % 1000);
			return;
		}
		// Not close enough, so try again.
		if (dist >= 3 && !walk_to_customer()) {
			state = get_customer;
			return;
		}
	}
	switch (state) {
	case waiter_setup:
		items_in_hand = Count_waiter_objects(npc);
		find_tables(971, 24);
		find_tables(633, 24);
		find_tables(847, 24);
		find_tables(890, 24);
		find_tables(964, 24);
		find_prep_tables();
		state = get_customer;
		/* FALL THROUGH */
	case get_customer:
		if (!find_customer()) {
			walk_to_prep();
			state = prep_food;
		} else if (walk_to_customer())
			state = get_order;
		break;
	case get_order: {
		Game_object_vector foods;
		// Close enough to customer?
		if (customer->find_nearby(foods, 377, 1, 0) > 0) {
			if (rand() % 4)
				npc->say(first_waiter_banter, last_waiter_banter);
		} else {
			// Ask for order.
			npc->say(first_waiter_ask, last_waiter_ask);
			if (!Find_customer_plate(customer)) {
				state = give_plate;
				npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
				break;
			}
			customers_ordered.push_back(customer);
		}
		state = took_order;
	}
	/* FALL THROUGH */
	case took_order:
		// Get up to 4 orders before serving them.
		if (customers_ordered.size() >= 4 || customers.empty()) {
			walk_to_prep();
			state = prep_food;
		} else {
			state = get_customer;
			npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
		}
		break;
	case give_plate: {
		create_customer_plate();
		auto *scr = new Usecode_script(npc);
		(*scr) << Ucscript::face_dir << npc->get_dir_facing()
		       << (Ucscript::npc_frame + Actor::ready_frame)
		       << Ucscript::delay_ticks << 2
		       << (Ucscript::npc_frame + Actor::standing);
		scr->start();   // Start next tick.
		state = took_order;
		customers_ordered.push_back(customer);
		npc->start(gwin->get_std_delay(), 500 + rand() % 1000);
		break;
	}
	case prep_food: {
		if (cooking && items_in_hand < 4 &&
		            rand()%2 == 0 && walk_to_random_item(4)) {
			state = get_waiter_item;
			break;
		}
		Game_object_shared prep_table_obj = prep_table.lock();
		Ready_food(npc);					// So we can drop it.
		if ((!cooking || rand()%3) &&
	              prep_table_obj && npc->distance(prep_table_obj.get()) <= 3) {
			Game_object *to_drop = nullptr;
			if (rand()%3) {
				Game_object_vector items;
				items_in_hand = Get_waiter_objects(npc, items, true);
				to_drop = items_in_hand ? items[rand()%items_in_hand] : nullptr;
			}
			cooking = true;
			if (to_drop)
				(void) drop_item(to_drop, prep_table_obj.get());
			else
				Prep_animation(npc, prep_table_obj.get());
			npc->start(gwin->get_std_delay(), 500 + rand() % 500);
			break;
		}
		if (rand()%3 == 1 && prep_tables.size() > 1) {
			// A little more cooking.
			walk_to_prep();
			break;
		}
		Ready_food(npc);
		cooking = false;
		state = bring_food;
	}
		/* FALL THROUGH. */
	case bring_food:
		if (customers_ordered.empty()) {		// All done serving them?
			if (rand() % 3 == 0)
				gwin->get_usecode()->call_usecode(
				     npc->get_usecode(), npc,
				    Usecode_machine::npc_proximity);
			if (walk_to_counter())
				state = wait_at_counter;
			else
				state = get_customer;
		} else {
			customer = customers_ordered.back();
			customers_ordered.pop_back();
			Ready_food(npc);
			if (walk_to_customer(3000)) {
				state = serve_food;
			} else {
				state = get_customer;
			}
		}
		break;
	case wait_at_counter: {
		Game_object_shared prep_table_obj = prep_table.lock();
		/* Check for customers who have left: */
		if (rand()%2 && find_unattended_plate()) {
			state = walk_to_cleanup_food;
		} else if (items_in_hand < 3 && rand() % 2 && walk_to_random_item(12)) {
			state = get_waiter_item;
			break;
		} else if (prep_table_obj) {
			npc->change_frame(npc->get_dir_framenum(
		                      npc->get_facing_direction(prep_table_obj.get()),
			                      Actor::standing));
			if (rand()%2)
				Prep_animation(npc, prep_table_obj.get());
			if (rand()%3 == 1)
				state = get_customer;
		} else
			state = get_customer;
		npc->start(gwin->get_std_delay(), 2000 + rand() % 2000);
		break;
	}
	case get_waiter_item: {
        Game_object *item = get_current_item();
		if (item && npc->distance(item) <= 3) {
			npc->set_action(new Pickup_actor_action(item, 250));
			state = picked_up_item;
			npc->start(250, 500 + rand()%1000);
			++items_in_hand;
			break;
		}
	}
		/* FALL THROUGH */
	case picked_up_item:
		set_current_item(nullptr);
		if (cooking) {
			walk_to_prep();
			state = prep_food;
		} else if (walk_to_counter()) {
			state = wait_at_counter;
		} else
			state = get_customer;
		break;
	case serve_food: {
		food = npc->get_readied(lhand);
		Tile_coord spot;
		if (food && food->get_shapenum() == 377 &&
		        find_serving_spot(spot)) {
			npc->change_frame(npc->get_dir_framenum(
			                      npc->get_direction(customer),
			                      Actor::standing));
			Game_object_shared food_keep = food->shared_from_this();
			npc->remove(food);
			food->set_invalid();
			food->move(spot);
			if (rand() % 3)
				npc->say(first_waiter_serve,
				         last_waiter_serve);
			auto *scr = new Usecode_script(npc);
			(*scr) << Ucscript::face_dir << npc->get_dir_facing()
			       << (Ucscript::npc_frame + Actor::ready_frame)
			       << Ucscript::delay_ticks << 2
			       << (Ucscript::npc_frame + Actor::standing);
			scr->start();   // Start next tick.
		}
		state = served_food;
		npc->start(250, 1000 + rand() % 1000);
		break;
	}
	case served_food:
		// Randomly drop or pick up an item.
		if (rand() % 2) {
			if (customer && items_in_hand > 0) {
				Game_object_vector items;
				items_in_hand = Get_waiter_objects(npc, items);
				if (items_in_hand) {
					Game_object *table = Find_customer_table(customer,
														eating_tables);
					Game_object *to_drop = items[rand()%items_in_hand];
					if (table)
						(void) drop_item(to_drop, table);
				}
			}
		} else if (items_in_hand < 4) {
			Game_object_vector items;	// Look near by to pick up an item.
			int cnt = Waiter_schedule::find_items(items, 4);
			if (cnt) {
				npc->set_action(
						new Pickup_actor_action(items[rand()%cnt], 250));
				++items_in_hand;
			}
		}
		customer = nullptr;       // Done with this one.
		state = bring_food;		// On to the next.
		npc->start(gwin->get_std_delay(), 1000 + rand() % 1000);
		break;
	case walk_to_cleanup_food:
		if (!unattended_plates.empty()) {
			// Closest.
			Game_object_shared plate = unattended_plates.front().lock();
			if (plate && npc->walk_path_to_tile(plate->get_tile(),
			        gwin->get_std_delay(), 500 + rand() % 1000, 2)) {
				state = cleanup_food;
				break;
			} else {
				unattended_plates.erase(unattended_plates.begin());
				npc->start(gwin->get_std_delay(), 0);
			}
		} else {
			state = walk_to_counter() ? wait_at_counter : get_customer;
		}
		break;
	case cleanup_food:
		if (!unattended_plates.empty()) {
			Game_object_shared plate = unattended_plates.front().lock();
			unattended_plates.erase(unattended_plates.begin());
			// Delete after picking up.
			if (!plate) {
				state = walk_to_cleanup_food;
				npc->start(gwin->get_std_delay(), 500 + rand()%1000);
				break;
			}
			Actor_action *act = new Pickup_actor_action(plate.get(), 250, true);
			Game_object *food = plate->find_closest(377, 2);
			if (food) {
				act = new Sequence_actor_action(act,
					       new Pickup_actor_action(food, 250, true));
			}
			npc->set_action(act);
			state = walk_to_cleanup_food;
			npc->start(gwin->get_std_delay(), 500 + rand()%1000);
		} else {
			state = walk_to_counter() ? wait_at_counter : get_customer;
		}
		break;
	}
}

/*
 *  Waiter schedule is done.
 */

void Waiter_schedule::ending(
    int new_type            // New schedule.
) {
	ignore_unused_variable_warning(new_type);
	// Remove what he/she is carrying.
	Game_object *obj = npc->get_readied(lhand);
	if (obj)
		obj->remove_this();
	obj = npc->get_readied(rhand);
	if (obj)
		obj->remove_this();
}


/*
 *  Sew/weave schedule.
 */

Sew_schedule::Sew_schedule(
    Actor *n
) : Schedule(n), sew_clothes_cnt(0), state(get_wool) {
}

/*
 *  Sew/weave.
 */

void Sew_schedule::now_what(
) {
	Tile_coord npcpos = npc->get_tile();
	// Often want to get within 1 tile.
	Actor_pathfinder_client cost(npc, 1);
	// Pure guess. Since only some schedules seem to call proximity
	// usecode, I guess I should put it in here.
	// Seems quite rare.
	if (try_proximity_usecode(12))
		return;
	switch (state) {
	case get_wool: {
	    Game_object_shared keep;
		// Clean up any remainders.
		if ((keep = spindle.lock()))
			keep->remove_this();
		if ((keep = cloth.lock()))
			keep->remove_this();
		cloth = spindle = Game_object_weak();
		npc->remove_quantity(2, 654, c_any_qual, c_any_framenum);
		npc->remove_quantity(2, 851, c_any_qual, c_any_framenum);

		Game_object *bale_obj = npc->find_closest(653);
		bale = weak_from_obj(bale_obj);
		if (!bale_obj) {    // Just skip this step.
			state = sit_at_wheel;
			break;
		}
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, bale_obj->get_tile(), cost);
		if (pact)
			npc->set_action(new Sequence_actor_action(pact,
			                new Pickup_actor_action(bale_obj, 250),
			                new Pickup_actor_action(bale_obj,
			                                       bale_obj->get_tile(), 250)));
		state = sit_at_wheel;
		break;
	}
	case sit_at_wheel: {
		Game_object *chair_obj = npc->find_closest(873);
		chair = weak_from_obj(chair_obj);
		if (!chair_obj || !Sit_schedule::set_action(npc, chair_obj, 200)) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			return;
		}
		state = spin_wool;
		break;
	}
	case spin_wool: {         // Cycle spinning wheel 8 times.
		Game_object *spinwheel_obj = npc->find_closest(651);
		spinwheel = weak_from_obj(spinwheel_obj);
		if (!spinwheel_obj) {
			// uh-oh... try again in a few seconds?
			npc->start(250, 2500);
			return;
		}
		npc->set_action(new Object_animate_actor_action(spinwheel_obj,
		                8, 200));
		state = get_thread;
		break;
	}
	case get_thread: {
		Game_object_shared spinwheel_obj = spinwheel.lock();
		if (!spinwheel_obj) {
		    state = get_wool;
			break;
		}
		Tile_coord t = Map_chunk::find_spot(
		                   spinwheel_obj->get_tile(), 1, 654, 0);
		if (t.tx != -1) {   // Space to create thread?
		    Game_object_shared newobj =
			      std::make_shared<Ireg_game_object>(654, 0, 0, 0);
			spindle = Game_object_weak(newobj);
			newobj->move(t);
			gwin->add_dirty(newobj.get());
			npc->set_action(
			    new Pickup_actor_action(newobj.get(), 250));
		}
		state = weave_cloth;
		break;
	}
	case weave_cloth: {
		Game_object_shared spindle_obj = spindle.lock();
		if (spindle_obj)        // Should be held by NPC.
			spindle_obj->remove_this();
		spindle = Game_object_weak();
		Game_object *loom_obj = npc->find_closest(261);
		loom = weak_from_obj(loom_obj);
		if (!loom_obj) {    // No loom found?
			state = get_wool;
			break;
		}
		Tile_coord lpos = loom_obj->get_tile() +
		                  Tile_coord(-1, 0, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, lpos, cost);
		if (pact)
			npc->set_action(new Sequence_actor_action(pact,
			                new Face_pos_actor_action(loom_obj, 250),
			                new Object_animate_actor_action(loom_obj,
			                        4, 200)));
		state = get_cloth;
		break;
	}
	case get_cloth: {
	    Game_object_shared loom_obj = loom.lock();
		Tile_coord t = loom_obj ? Map_chunk::find_spot(loom_obj->get_tile(),
		                                    1, 851, 0) : Tile_coord(-1, -1, -1);
		if (t.tx != -1) {   // Space to create it?
			Game_object_shared newobj =
			     std::make_shared<Ireg_game_object>(851, rand() % 2, 0, 0);
			cloth = Game_object_weak(newobj);
			newobj->move(t);
			gwin->add_dirty(newobj.get());
			npc->set_action(
			    new Pickup_actor_action(newobj.get(), 250));
		}
		state = to_work_table;
		break;
	}
	case to_work_table: {
		Game_object *work_table_obj = npc->find_closest(971);
		work_table = weak_from_obj(work_table_obj);
		Game_object_shared cloth_obj = cloth.lock();
		if (!work_table_obj || !cloth_obj) {
			state = get_wool;
			break;
		}
		Tile_coord tpos = work_table_obj->get_tile() +
		                  Tile_coord(1, -2, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);
		// Find where to put cloth.
		TileRect foot = work_table_obj->get_footprint();
		const Shape_info &info = work_table_obj->get_info();
		Tile_coord cpos(foot.x + foot.w / 2, foot.y + foot.h / 2,
		                work_table_obj->get_lift() + info.get_3d_height());
		if (pact)
			npc->set_action(new Sequence_actor_action(pact,
			          new Face_pos_actor_action(
			                    work_table_obj, 250),
			              new Pickup_actor_action(cloth_obj.get(), cpos, 250)));
		state = set_to_sew;
		break;
	}
	case set_to_sew: {
		Game_object *shears = npc->get_readied(lhand);
		if (shears && shears->get_shapenum() != 698) {
			// Something's not right.
			shears->remove_this();
			shears = nullptr;
		}
		if (!shears) {
			// Shears on table?
			Game_object_vector vec;
			Game_object_shared keep;
			if (npc->find_nearby(vec, 698, 3, 0)) {
				shears = vec[0];
				gwin->add_dirty(shears);
				shears->remove_this(&keep);
			} else {
				keep = std::make_shared<Ireg_game_object>(698, 0, 0, 0);
				shears = keep.get();
			}
			npc->add_readied(shears, lhand);
		}
		state = sew_clothes;
		sew_clothes_cnt = 0;
		break;
	}
	case sew_clothes: {
	    Game_object_shared cloth_obj = cloth.lock();
		if (!cloth_obj) {
		    state = get_wool;
			break;
		}
		int dir = npc->get_direction(cloth_obj.get());
		signed char frames[5];
		int nframes = npc->get_attack_frames(698, false,
		                                     dir, frames);
		if (nframes)
			npc->set_action(new Frames_actor_action(frames, nframes));
		sew_clothes_cnt++;
		if (sew_clothes_cnt > 1 && sew_clothes_cnt < 5) {
			int num_cloth_frames = ShapeID(851, 0).get_num_frames();
			cloth_obj->change_frame(rand() % num_cloth_frames);
		} else if (sew_clothes_cnt == 5) {
			gwin->add_dirty(cloth_obj.get());
			Tile_coord pos = cloth_obj->get_tile();
			Game_object_shared keep;
			cloth_obj->remove_this(&keep);
			// Top or pants.
			int shnum = GAME_SI ? 403 : (rand() % 2 ? 738 : 249);
			cloth_obj->set_shape(shnum);
			int nframes = ShapeID(shnum, 0).get_num_frames();
			cloth_obj->change_frame(rand() % nframes);
			cloth_obj->move(pos);
			state = get_clothes;
		}
		break;
	}
	case get_clothes: {
		Game_object *shears = npc->get_readied(lhand);
	    Game_object_shared cloth_obj = cloth.lock();
		if (shears && cloth_obj) {
			Tile_coord pos = cloth_obj->get_tile();
			npc->set_action(new Sequence_actor_action(
			                    new Pickup_actor_action(cloth_obj.get(), 250),
			                    new Pickup_actor_action(shears, pos, 250)));
		} else {
			// ++++ maybe create shears? anyway, leaving this till after
			// possible/probable schedule system rewrite
			if (cloth_obj)
			    npc->set_action(new Pickup_actor_action(cloth_obj.get(), 250));
			else {
			    state = get_wool;
				break;
			}
		}
		state = display_clothes;
		break;
	}
	case display_clothes: {
		state = done;
		Game_object *wares_table_obj = npc->find_closest(890);
		wares_table = weak_from_obj(wares_table_obj);
	    Game_object_shared cloth_obj = cloth.lock();
		if (!wares_table_obj || !cloth_obj) {
			if (cloth_obj) {
		        cloth_obj->remove_this();
			    cloth = Game_object_weak();
			}
			break;
		}
		Tile_coord tpos = wares_table_obj->get_tile() +
		                  Tile_coord(1, -2, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);
		// Find where to put cloth.
		TileRect foot = wares_table_obj->get_footprint();
		const Shape_info &info = wares_table_obj->get_info();
		Tile_coord cpos(foot.x + rand() % foot.w, foot.y + rand() % foot.h,
		                wares_table_obj->get_lift() + info.get_3d_height());
		if (pact)
			npc->set_action(new Sequence_actor_action(pact,
	                new Pickup_actor_action(cloth_obj.get(), cpos, 250, true)));
		cloth = Game_object_shared();          // Leave it be.
		break;
	}
	case done: {            // Just put down clothing.
		state = get_wool;
		Game_object_vector vec;     // Don't create too many.
		int cnt = 0;
		if (GAME_SI)
			cnt += npc->find_nearby(vec, 403, 5, 0);
		else {              // BG shapes.
			cnt += npc->find_nearby(vec, 738, 5, 0);
			cnt += npc->find_nearby(vec, 249, 5, 0);
		}
		if (cnt >= 3) {
			Game_object *obj = vec[rand() % cnt];
			gwin->add_dirty(obj);
			obj->remove_this();
		}
		break;
	}
	default:            // Back to start.
		state = get_wool;
		break;
	}
	npc->start(250, 100);       // Back in queue.
}

/*
 *  Sewing schedule is done.
 */

void Sew_schedule::ending(
    int new_type            // New schedule.
) {
	ignore_unused_variable_warning(new_type);
	// Remove shears.
	Game_object *obj = npc->get_readied(lhand);
	if (obj)
		obj->remove_this();
    Game_object_shared cloth_keep;
	if ((cloth_keep = cloth.lock())) {
	    // Don't leave cloth lying around.
		if (!cloth_keep->get_owner())
			gwin->add_dirty(cloth_keep.get());
		cloth_keep->remove_this();
	}
}

/*
 *  Bake bread/pastries
 *
 *  Dough gets set to quality 50 and dough placed in oven is quality 51
 */

Bake_schedule::Bake_schedule(Actor *n) : Schedule(n),
	clearing(false), state(find_leftovers)
{ }

void Bake_schedule::now_what() {
	Tile_coord npcpos = npc->get_tile();
	Actor_pathfinder_client cost(npc, 1);
	Actor_pathfinder_client cost2(npc, 2);
	int delay = 100;
	int dough_shp(GAME_SI ? 863 : 658);
	Game_object *stove = npc->find_closest(664);
	// Pure guess. Since only some schedules seem to call proximity
	// usecode, I guess I should put it in here.
	// Seems quite common.
	if (try_proximity_usecode(8))
		return;

	switch (state) {
	case find_leftovers: {  // Look for misplaced dough already made by this schedule
		state = to_flour;
		Game_object_shared dough_in_oven_obj = dough_in_oven.lock();
		Game_object_shared dough_obj = dough.lock();
		if (!dough_in_oven_obj) {
			// look for baking dough
			Game_object_vector baking_dough;
			int frnum(GAME_SI ? 18 : 2);
			Actor::find_nearby(baking_dough, npcpos, dough_shp, 20, 0, 51, frnum, false);
			if (!baking_dough.empty() && baking_dough[0] != dough_obj.get()) {
			    // found dough
				dough_in_oven_obj = baking_dough[0]->shared_from_this();
				dough_in_oven_obj->clear_flag(Obj_flags::okay_to_take);
				// doesn't save okay_to_take
				dough_in_oven = Game_object_weak(dough_in_oven_obj);
				state = remove_from_oven;
				break;
			}
			// looking for cooked food left in oven
			Game_object *oven_obj = npc->find_closest(831);
			if (!oven_obj)
				oven_obj = stove;
			oven = weak_from_obj(oven_obj);
			if (oven_obj) {
				Game_object_vector food;
				Tile_coord Opos = oven_obj->get_tile();
				Actor::find_nearby(food, Opos, 377, 2, 0, c_any_qual, c_any_framenum, true);
				if (!food.empty()) { // found food
					dough_in_oven_obj = food[0]->shared_from_this();
					dough_in_oven = Game_object_weak(dough_in_oven_obj);
					dough_in_oven_obj->clear_flag(Obj_flags::okay_to_take); // doesn't save okay_to_take
					state = remove_from_oven;
					break;
				}
			}
		}
		if (!dough_obj) {   // Looking for unused dough on tables
			Game_object_vector leftovers;
			if (GAME_SI) {
				Actor::find_nearby(leftovers, npcpos, dough_shp, 20, 0, 50, 16, false);
				Actor::find_nearby(leftovers, npcpos, dough_shp, 20, 0, 50, 17, false);
				Actor::find_nearby(leftovers, npcpos, dough_shp, 20, 0, 50, 18, false);
			} else
				Actor::find_nearby(leftovers, npcpos, dough_shp, 20, 0, 50, c_any_framenum, false);
			if (!leftovers.empty() && leftovers[0] != dough_in_oven_obj.get()) {  // found dough
				dough_obj = leftovers[0]->shared_from_this();
				dough = Game_object_weak(dough_obj);
				dough_obj->clear_flag(Obj_flags::okay_to_take); // doesn't save okay_to_take
				state = make_dough;
				delay = 0;
				Actor_action *pact = Path_walking_actor_action::create_path(
				                         npcpos, dough_obj->get_tile(), cost2);
				if (pact)   // walk to dough if we can
					npc->set_action(pact);
			}
		}
		break;
	}
	case to_flour: {    // Looks for flourbag and walks to it if found
		Game_object_vector items;
		Actor::find_nearby(items, npcpos, 863, -1, 0, c_any_qual, 0);
		Actor::find_nearby(items, npcpos, 863, -1, 0, c_any_qual, 13);
		Actor::find_nearby(items, npcpos, 863, -1, 0, c_any_qual, 14);

		if (items.empty()) {
			state = to_table;
			break;
		}

		int nr = rand() % items.size();
		Game_object *flourbag_obj = items[nr];
		flourbag = weak_from_obj(flourbag_obj);

		Tile_coord tpos = flourbag_obj->get_tile();
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);
		if (pact) {
			npc->set_action(pact);
		} else {
			// just ignore it
			state = to_table;
			break;
		}

		state = get_flour;
		break;
	}
	case get_flour: {   // Bend over flourbag and change the frame to zero if nonzero
	    Game_object_shared flourbag_obj = flourbag.lock();
		if (!flourbag_obj) {
			// what are we doing here then? back to start
			state = to_flour;
			break;
		}

		int dir = npc->get_direction(flourbag_obj.get());
		npc->change_frame(
		    npc->get_dir_framenum(dir, Actor::bow_frame));

		if (flourbag_obj->get_framenum() != 0)
			flourbag_obj->change_frame(0);

		delay = 750;
		state = to_table;
		break;
	}
	case to_table: {    // Walk over to worktable and create flour
		Game_object *table1 = npc->find_closest(1003);
		Game_object *table2 = npc->find_closest(1018);
		Game_object *worktable_obj = nullptr;
		if (stove) {
			Game_object_vector table;
			Actor::find_nearby(table, npcpos, 890, -1, 0, c_any_qual, 5);
			if (table.size() == 1)
				worktable_obj = table[0];
			else if (table.size() > 1) {
				if (rand() % 2)
					worktable_obj = table[0];
				else
					worktable_obj = table[1];
			}
		} else if (!table1)
			worktable_obj = table2;
		else if (!table2)
			worktable_obj = table1;
		else if (table1->distance(npc) < table2->distance(npc))
			worktable_obj = table1;
		else
			worktable_obj = table2;

		if (!worktable_obj)
			worktable_obj = npc->find_closest(1018);
	    worktable = weak_from_obj(worktable_obj);
		if (!worktable_obj) {
			// problem... try again in a few seconds
			delay = 2500;
			state = to_flour;
			break;
		}
		// Find where to put dough.
		TileRect foot = worktable_obj->get_footprint();
		const Shape_info &info = worktable_obj->get_info();
		Tile_coord cpos(foot.x + rand() % foot.w, foot.y + rand() % foot.h,
		                worktable_obj->get_lift() + info.get_3d_height());
		Tile_coord tablepos = cpos;
		cpos.tz = 0;

		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, cpos, cost);
		if (pact) {
		    Game_object_shared dough_obj = dough.lock();
			if (dough_obj) {
				dough_obj->remove_this();
			}
			if (Game::get_game_type() == SERPENT_ISLE)
				dough_obj = std::make_shared<Ireg_game_object>(
														dough_shp, 16, 0, 0);
			else
				dough_obj = std::make_shared<Ireg_game_object>(
														dough_shp, 0, 0, 0);
			dough = Game_object_weak(dough_obj);
			npc->add(dough_obj.get(), true);
			dough_obj->set_quality(50); // doesn't save okay_to_take
			npc->set_action(new Sequence_actor_action(pact,
	                new Pickup_actor_action(dough_obj.get(), tablepos, 250)));
		} else {
			// not good... try again
			delay = 2500;
			state = to_flour;
			break;
		}

		state = make_dough;
		break;
	}
	case make_dough: {  // Changes flour to flat dough then dough ball
	    Game_object_shared dough_obj = dough.lock();
		if (!dough_obj) {
			// better try again...
			delay = 2500;
			state = to_table;
			break;
		}
		dough_obj->clear_flag(Obj_flags::okay_to_take); // doesn't save okay_to_take

		int dir = npc->get_direction(dough_obj.get());
		signed char fr[2];
		fr[0] = npc->get_dir_framenum(dir, 3);
		fr[1] = npc->get_dir_framenum(dir, 0);

		int frame = dough_obj->get_framenum();
		Game_object *dobj = dough_obj.get();
		if (GAME_SI ? frame == 16 : frame == 0)
			npc->set_action(new Sequence_actor_action(
			                    new Frames_actor_action(fr, 2, 500),
			                    ((GAME_SI) ?
			                     new Frames_actor_action(0x11, 250, dobj) :
			                     new Frames_actor_action(0x01, 250, dobj)),
			                    new Frames_actor_action(fr, 2, 500),
			                    ((GAME_SI) ?
			                     new Frames_actor_action(0x12, 250, dobj) :
			                     new Frames_actor_action(0x02, 250, dobj))
			                ));
		else if (GAME_SI ? frame == 17 : frame == 1)
			npc->set_action(new Sequence_actor_action(
			                    new Frames_actor_action(fr, 2, 500),
			                    ((GAME_SI) ?
			                     new Frames_actor_action(0x12, 250, dobj) :
			                     new Frames_actor_action(0x02, 250, dobj))
			                ));

		state = remove_from_oven;
		break;
	}
	case remove_from_oven: { // Changes dough in oven to food %7 and picks it up
	    Game_object_shared dough_in_oven_obj = dough_in_oven.lock();
		if (!dough_in_oven_obj) {
			// nothing in oven yet
			state = get_dough;
			break;
		}
		Game_object *oven_obj;
		if (stove)
			oven_obj = stove;
		else
			oven_obj = npc->find_closest(831);
		oven = weak_from_obj(oven_obj);
		if (!oven_obj) {
			// this really shouldn't happen...
			dough_in_oven_obj->remove_this();
			delay = 2500;
			state = to_table;
			break;
		}
		if (dough_in_oven_obj->get_shapenum() != 377) {
			gwin->add_dirty(dough_in_oven_obj.get());
			dough_in_oven_obj->set_shape(377);
			dough_in_oven_obj->change_frame(rand() % 7);
			dough_in_oven_obj->clear_flag(Obj_flags::okay_to_take);  // doesn't save okay_to_take
			gwin->add_dirty(dough_in_oven_obj.get());
		}

		Tile_coord tpos = oven_obj->get_tile() +
		                  Tile_coord(1, 1, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);
		if (pact) {
			npc->set_action(new Sequence_actor_action(
                    pact,
                    new Pickup_actor_action(dough_in_oven_obj.get(), 250)));
		} else {
			// just pick it up
			npc->set_action(
			    new Pickup_actor_action(dough_in_oven_obj.get(), 250));
		}

		state = display_wares;
		break;
	}
	case display_wares: {
	    // Walk to displaytable. Put food on it. If table full, go to
	    Game_object_shared dough_in_oven_obj = dough_in_oven.lock();
		// clear_display which eventualy comes back here to place food
		if (!dough_in_oven_obj) {
			// try again
			delay = 2500;
			state = find_leftovers;
			break;
		}
		Game_object *displaytable_obj = npc->find_closest(633); // Britain
		if (!displaytable_obj) {
			Game_object_vector table;
			int table_shp = 890; // Moonshade table
			int table_frm = 1;
			if (stove) {
				table_shp = 1003;
				table_frm = 2;
			}
			Actor::find_nearby(table, npcpos, table_shp, -1, 0, c_any_qual, table_frm);
			if (table.size() == 1)
				displaytable_obj = table[0];
			else if (table.size() > 1) {
				if (rand() % 2)
					displaytable_obj = table[0];
				else
					displaytable_obj = table[1];
			}
		}
		displaytable = weak_from_obj(displaytable_obj);
		if (!displaytable_obj) {
			// uh-oh...
			dough_in_oven_obj->remove_this();
			delay = 2500;
			state = find_leftovers;
			break;
		}

		TileRect r = displaytable_obj->get_footprint();
		Perimeter p(r);     // Find spot adjacent to table.
		Tile_coord spot;    // Also get closest spot on table.
		Tile_coord spot_on_table;
		p.get(rand() % p.size(), spot, spot_on_table);
		Actor_pathfinder_client COST = (GAME_SI ? cost : cost2);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, spot, COST);
		const Shape_info &info = displaytable_obj->get_info();
		spot_on_table.tz += info.get_3d_height();

		// Place baked goods if spot is empty.
		Tile_coord t = Map_chunk::find_spot(spot_on_table, 0,
		                                    377, 0, 0);
		if (t.tx != -1 && t.tz == spot_on_table.tz) {
			npc->set_action(new Sequence_actor_action(pact,
			                new Pickup_actor_action(dough_in_oven_obj.get(),
			                                        spot_on_table, 250)));
			dough_in_oven = Game_object_weak();
			state = get_dough;
		} else {
			delete pact;
			pact = Path_walking_actor_action::create_path(
			                         npcpos, displaytable_obj->get_tile(), cost);
			npc->set_action(new Sequence_actor_action(pact,
			                new Face_pos_actor_action(displaytable_obj->get_tile(), 250)));
			delay = 250;
			state = clear_display;
		}
		clearing = false;
		break;
	}
	case clear_display: {   // Mark food for deletion by remove_food
		Game_object_vector food;
		Actor::find_nearby(food, npcpos, 377, 4, 0, c_any_qual, c_any_framenum, true);
		if (food.empty() && !clearing) { // none of our food on the table
			// so we can't clear it
			Game_object_shared dough_in_oven_obj = dough_in_oven.lock();
			if (dough_in_oven_obj)
				dough_in_oven_obj->remove_this();
			state = get_dough;
			break;
		}
		clearing = true;
		if (!food.empty()) {
			delay = 500;
			state = remove_food;
		} else {
			state = display_wares;
		}
		break;
	}
	case remove_food: { // Delete food on display table one by one with a slight delay
		Game_object_vector food;
		Actor::find_nearby(food, npcpos, 377, 4, 0, c_any_qual, c_any_framenum, true);
		if (!food.empty()) {
			delay = 500;
			state = clear_display;
			gwin->add_dirty(food[0]);
			food[0]->remove_this();
		}
		break;
	}
	case get_dough: {   // Walk to work table and pick up dough
	    Game_object_shared dough_obj = dough.lock();
		if (!dough_obj) {
			// try again
			delay = 2500;
			state = find_leftovers;
			break;
		}
		Game_object *oven_obj;
		if (stove)
			oven_obj = stove;
		else
			oven_obj = npc->find_closest(831);
		oven = weak_from_obj(oven_obj);
		if (!oven_obj) {
			// wait a while
			delay = 2500;
			state = find_leftovers;
			break;
		}
		Tile_coord tpos = dough_obj->get_tile();
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost2);
		if (pact) {
			npc->set_action(new Sequence_actor_action(
			                    pact,
			                    new Pickup_actor_action(dough_obj.get(), 250)));
		} else {
			// just pick it up
			npc->set_action(new Pickup_actor_action(dough_obj.get(), 250));
		}

		state = put_in_oven;
		break;
	}
	case put_in_oven: { // Walk to oven and put dough on in.
	    Game_object_shared dough_obj = dough.lock();
		if (!dough_obj) {
			// try again
			delay = 2500;
			state = find_leftovers;
			break;
		}
		Game_object *oven_obj;
		if (stove)
			oven_obj = stove;
		else
			oven_obj = npc->find_closest(831);
		oven = weak_from_obj(oven_obj);
		if (!oven_obj) {
			// oops... retry
			dough_obj->remove_this();
			delay = 2500;
			state = to_table;
			break;
		}
		Tile_coord tpos = oven_obj->get_tile() +
		                  Tile_coord(1, 1, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);

		// offsets for oven placement
		int offX = +1;
		int offY = 0;
		int offZ = 0;
		if (stove) { // hide dough
			offX = -3;
			offY = 0;
			offZ = -2;
		}
		TileRect foot = oven_obj->get_footprint();
		const Shape_info &info = oven_obj->get_info();
		Tile_coord cpos(foot.x + offX, foot.y + offY,
		                oven_obj->get_lift() + info.get_3d_height() + offZ);

		if (pact) {
			npc->set_action(new Sequence_actor_action(
			             pact,
			             new Pickup_actor_action(dough_obj.get(), cpos, 250)));

			dough_obj->set_quality(51); // doesn't save okay_to_take
			dough_in_oven = dough;
			dough = Game_object_weak();
		} else {
			dough_obj->remove_this();
		}

		state = find_leftovers;
		break;
	}
	}
	npc->start(250, delay);     // Back in queue.
}

/*
 *  TODO: Eventually, this should be used to finish baking all
 *  leftovers which is basically duplicating the regular schedule
 *  minus to_flour, get_flour, and any future added kitchen items
 *  usage. Special checks for when to just give up should be added.
 */

void Bake_schedule::ending(int new_type) {
	ignore_unused_variable_warning(new_type);
    Game_object_shared dough_obj = dough.lock();
    Game_object_shared dough_in_oven_obj = dough_in_oven.lock();
	if (dough_obj) {
		dough_obj->remove_this();
	}
	if (dough_in_oven_obj) {
		dough_in_oven_obj->remove_this();
	}
}

/*
 *  Blacksmith.
 *
 * Note: the original kept the tongs & hammer, and put them on a nearby table
 */

Forge_schedule::Forge_schedule(Actor *n) : Schedule(n),
	state(put_sword_on_firepit)
{ }

void Forge_schedule::now_what(
) {
	Tile_coord npcpos = npc->get_tile();
	// Often want to get within 1 tile.
	Actor_pathfinder_client cost(npc, 1);

	switch (state) {
	case put_sword_on_firepit: {
	    Game_object_shared blank_obj = blank.lock();
		if (!blank_obj) {
		    Game_object *found = npc->find_closest(668);
		    if (found)
			    blank_obj = found->shared_from_this();
			//TODO: go and get it...

		}
		if (!blank_obj) {
			blank_obj = std::make_shared<Ireg_game_object>(668, 0, 0, 0);
		}
		blank = Game_object_weak(blank_obj);
		Game_object *firepit_obj = npc->find_closest(739);
		firepit = weak_from_obj(firepit_obj);
		if (!firepit_obj) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			return;
		}
		Tile_coord tpos = firepit_obj->get_tile();
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);

		TileRect foot = firepit_obj->get_footprint();
		const Shape_info &info = firepit_obj->get_info();
		Tile_coord bpos(foot.x + foot.w / 2 + 1, foot.y + foot.h / 2,
		                firepit_obj->get_lift() + info.get_3d_height());
		if (pact) {
			npc->set_action(new Sequence_actor_action(pact,
		                new Pickup_actor_action(blank_obj.get(), bpos, 250)));
		} else {
			npc->set_action(
			    new Pickup_actor_action(blank_obj.get(), bpos, 250));
		}

		state = use_bellows;
		break;
	}
	case use_bellows: {
		Game_object *bellows_obj = npc->find_closest(431);
		Game_object *firepit_obj = npc->find_closest(739);
		Game_object_shared blank_obj = blank.lock();
		bellows = weak_from_obj(bellows_obj);
		firepit = weak_from_obj(firepit_obj);
		if (!bellows_obj || !firepit_obj || !blank_obj) {
			// uh-oh... try again in a few second
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}

		Tile_coord tpos = bellows_obj->get_tile() +
		                  Tile_coord(3, 0, 0);
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);

		auto **a = new Actor_action*[35];
		a[0] = pact;
		a[1] = new Face_pos_actor_action(bellows_obj, 250);
		a[2] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[3] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[4] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[5] = new Frames_actor_action(0x01, 0, firepit_obj);
		a[6] = new Frames_actor_action(0x01, 0, blank_obj.get());
		a[7] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[8] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[9] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[10] = new Frames_actor_action(0x02, 0, blank_obj.get());
		a[11] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[12] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[13] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[14] = new Frames_actor_action(0x02, 0, firepit_obj);
		a[15] = new Frames_actor_action(0x03, 0, blank_obj.get());
		a[16] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[17] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[18] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[19] = new Frames_actor_action(0x03, 0, firepit_obj);
		a[20] = new Frames_actor_action(0x04, 0, blank_obj.get());
		a[21] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[22] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[23] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[24] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[25] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[26] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[27] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[28] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[29] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[30] = new Frames_actor_action(0x20 | Actor::bow_frame, 0);
		a[31] = new Object_animate_actor_action(bellows_obj, 3, 1, 300);
		a[32] = new Frames_actor_action(0x20 | Actor::standing, 0);
		a[33] = new Frames_actor_action(0x00, 0, bellows_obj);
		a[34] = nullptr;


		npc->set_action(new Sequence_actor_action(a));
		state = get_tongs;
		break;
	}
	case get_tongs: {
		Game_object_shared tongs_obj = tongs.lock();
		if (!tongs_obj) {
			tongs_obj = std::make_shared<Ireg_game_object>(994, 0, 0, 0);
			tongs = Game_object_weak(tongs_obj);
		}
		npc->empty_hands(); // make sure the tongs can be equipped
		npc->add_readied(tongs_obj.get(), lhand);
		npc->add_dirty();

		state = sword_on_anvil;
		break;
	}
	case sword_on_anvil: {
		Game_object *anvil_obj = npc->find_closest(991);
		Game_object *firepit_obj = npc->find_closest(739);
		anvil = weak_from_obj(anvil_obj);
		firepit = weak_from_obj(firepit_obj);
		Game_object_shared blank_obj = blank.lock();
		if (!anvil_obj || !firepit_obj || !blank_obj) {
			// uh-oh... try again in a few second
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}

		Tile_coord tpos = firepit_obj->get_tile();
		Actor_action *pact = Path_walking_actor_action::create_path(
		                         npcpos, tpos, cost);
		Tile_coord tpos2 = anvil_obj->get_tile() +
		                   Tile_coord(0, 1, 0);
		Actor_action *pact2 = Path_walking_actor_action::create_path(
		                          tpos, tpos2, cost);

		TileRect foot = anvil_obj->get_footprint();
		const Shape_info &info = anvil_obj->get_info();
		Tile_coord bpos(foot.x + 2, foot.y,
		                anvil_obj->get_lift() + info.get_3d_height());
		if (pact && pact2) {
			npc->set_action(new Sequence_actor_action(pact,
			             new Pickup_actor_action(blank_obj.get(), 250),
			             pact2,
			             new Pickup_actor_action(blank_obj.get(), bpos, 250)));
		} else {
			// Don't leak the paths
			delete pact;
			delete pact2;
			npc->set_action(new Sequence_actor_action(
		                 new Pickup_actor_action(blank_obj.get(), 250),
		                 new Pickup_actor_action(blank_obj.get(), bpos, 250)));
		}
		state = get_hammer;
		break;
	}
	case get_hammer: {
		Game_object_shared hammer_obj = hammer.lock();
		if (!hammer_obj) {
			hammer_obj = std::make_shared<Ireg_game_object>(623, 0, 0, 0);
			hammer = Game_object_weak(hammer_obj);
		}
		npc->add_dirty();
		Game_object_shared tongs_obj = tongs.lock();
		if (tongs_obj) {
			tongs_obj->remove_this();
			tongs = Game_object_weak();
		}
		npc->empty_hands(); // make sure the hammer can be equipped
		npc->add_readied(hammer_obj.get(), lhand);
		npc->add_dirty();

		state = use_hammer;
		break;
	}
	case use_hammer: {
		Game_object *anvil_obj = npc->find_closest(991);
		Game_object *firepit_obj = npc->find_closest(739);
		anvil = weak_from_obj(anvil_obj);
		firepit = weak_from_obj(firepit_obj);
		Game_object_shared blank_obj = blank.lock();
		if (!anvil_obj || !firepit_obj || !blank_obj) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}

		signed char frames[12];
		int cnt = npc->get_attack_frames(623, false, 0, frames);
		if (cnt)
			npc->set_action(new Frames_actor_action(frames, cnt));

		auto **a = new Actor_action*[10];
		a[0] = new Frames_actor_action(frames, cnt);
		a[1] = new Frames_actor_action(0x03, 0, blank_obj.get());
		a[2] = new Frames_actor_action(0x02, 0, firepit_obj);
		a[3] = new Frames_actor_action(frames, cnt);
		a[4] = new Frames_actor_action(0x02, 0, blank_obj.get());
		a[5] = new Frames_actor_action(0x01, 0, firepit_obj);
		a[6] = new Frames_actor_action(frames, cnt);
		a[7] = new Frames_actor_action(0x01, 0, blank_obj.get());
		a[8] = new Frames_actor_action(0x00, 0, firepit_obj);
		a[9] = nullptr;
		npc->set_action(new Sequence_actor_action(a));

		state = walk_to_trough;
		break;
	}
	case walk_to_trough: {
		npc->add_dirty();
		Game_object_shared hammer_obj = hammer.lock();
		if (hammer_obj) {
			hammer_obj->remove_this();
		}
		npc->add_dirty();

		Game_object *trough_obj = npc->find_closest(719);
		trough = weak_from_obj(trough_obj);
		if (!trough_obj) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}

		if (trough_obj->get_framenum() == 0) {
			Tile_coord tpos = trough_obj->get_tile() +
			                  Tile_coord(0, 2, 0);
			Actor_action *pact = Path_walking_actor_action::
			                     create_path(npcpos, tpos, cost);
			npc->set_action(pact);
			state = fill_trough;
			break;
		}

		state = get_tongs2;
		break;
	}
	case fill_trough: {
		Game_object *trough_obj = npc->find_closest(719);
		trough = weak_from_obj(trough_obj);
		if (!trough_obj) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}
		int dir = npc->get_direction(trough_obj);
		trough_obj->change_frame(3);
		npc->change_frame(
		    npc->get_dir_framenum(dir, Actor::bow_frame));

		state = get_tongs2;
		break;
	}
	case get_tongs2: {
		Game_object_shared tongs_obj = tongs.lock();
		if (!tongs_obj) {
			tongs_obj = std::make_shared<Ireg_game_object>(994, 0, 0, 0);
			tongs = Game_object_weak(tongs_obj);
		}
		npc->empty_hands(); // make sure the tongs can be equipped
		npc->add_readied(tongs_obj.get(), lhand);
		npc->add_dirty();

		state = use_trough;
		break;
	}
	case use_trough: {
		Game_object *trough_obj = npc->find_closest(719);
		Game_object *anvil_obj = npc->find_closest(991);
		trough = weak_from_obj(trough_obj);
		anvil = weak_from_obj(anvil_obj);
		Game_object_shared blank_obj = blank.lock();
		if (!trough_obj || !anvil_obj || !blank_obj) {
			// uh-oh... try again in a few seconds
			npc->start(250, 2500);
			state = put_sword_on_firepit;
			return;
		}

		Tile_coord tpos = anvil_obj->get_tile() +
		                  Tile_coord(0, 1, 0);
		Actor_action *pact = Path_walking_actor_action::
		                     create_path(npcpos, tpos, cost);

		Tile_coord tpos2 = trough_obj->get_tile() +
		                   Tile_coord(0, 2, 0);
		Actor_action *pact2 = Path_walking_actor_action::
		                      create_path(tpos, tpos2, cost);

		if (pact && pact2) {
			signed char troughframe = trough_obj->get_framenum() - 1;
			if (troughframe < 0) troughframe = 0;

			int dir = npc->get_direction(trough_obj);
			signed char npcframe = npc->get_dir_framenum(dir, Actor::bow_frame);

			auto **a = new Actor_action*[7];
			a[0] = pact;
			a[1] = new Pickup_actor_action(blank_obj.get(), 250);
			a[2] = pact2;
			a[3] = new Frames_actor_action(&npcframe, 1, 250);
			a[4] = new Frames_actor_action(&troughframe, 1, 0, trough_obj);
			a[5] = new Frames_actor_action(0x00, 0, blank_obj.get());
			a[6] = nullptr;
			npc->set_action(new Sequence_actor_action(a));
		} else {
			// Don't leak the paths
			delete pact;
			delete pact2;
			// no path found, just pick up sword blank
			npc->set_action(new Sequence_actor_action(
			                    new Pickup_actor_action(blank_obj.get(), 250),
			                    new Frames_actor_action(0, 0, blank_obj.get())));
		}

		state = done;
		break;
	}
	case done: {
		npc->add_dirty();
		Game_object_shared tongs_obj = tongs.lock();
		if (tongs_obj) {
			tongs_obj->remove_this();
		}
		npc->add_dirty();
		state = put_sword_on_firepit;
	}
	}
	npc->start(250, 100);       // Back in queue.
}

/*
 *  Forge schedule is done.
 */

void Forge_schedule::ending(
    int new_type            // New schedule.
) {
	ignore_unused_variable_warning(new_type);
	// Remove any tools.
	Game_object_shared tongs_obj = tongs.lock();
	if (tongs_obj) {
		tongs_obj->remove_this();
	}
	Game_object_shared hammer_obj = hammer.lock();
	if (hammer_obj) {
		hammer_obj->remove_this();
	}

	Game_object *firepit_obj = npc->find_closest(739);
	Game_object *bellows_obj = npc->find_closest(431);
	Game_object_shared blank_obj = blank.lock();

	if (firepit_obj && firepit_obj->get_framenum() != 0)
		firepit_obj->change_frame(0);
	if (bellows_obj && bellows_obj->get_framenum() != 0)
		bellows_obj->change_frame(0);
	if (blank_obj && blank_obj->get_framenum() != 0)
		blank_obj->change_frame(0);

}

/*
 *  Eat without a server
 *  The original seems to pathfind to a plate, then place the food on
 *  the plate, sit down, and then eat it if the food is close enough.
 *  They only seemed to eat one food item and then sit there doing nothing.
 *  If there is no plate, they will wander around in a loiter like
 *  schedule saying 0x410-0x413 randomly until a plate becomes available.
 *  When eating, there are no barks.
 *  TODO: Add plate pathfinding
 */
Eat_schedule::Eat_schedule(Actor *n): Schedule(n),
	state(find_plate) {}

/*
 *  Just went dormant.
 */
void Eat_schedule::im_dormant() {
	// Force NPCs to sit down again after cache-out/cache-in.
	//npc->change_frame(npc->get_dir_framenum(Actor::standing));
	npc->set_action(nullptr);
}

void Eat_schedule::now_what() {
	int delay = 5000 + rand() % 12000;
	int frnum = npc->get_framenum();
	if ((frnum & 0xf) != Actor::sit_frame) {
		if (!Sit_schedule::set_action(npc)) // First have to sit down.
			npc->start(250, 5000); // Try again in a while.
		return;
	}
	switch (state) {
	case eat: {
		Game_object_vector foods;           // Food nearby?
		int cnt = npc->find_nearby(foods, 377, 2, 0);
		if (cnt) {          // Found?
			Game_object *food = nullptr; // Find closest.
			int dist = 500;
			for (auto *obj : foods) {
				int odist = obj->distance(npc);
				if (odist < dist) {
					dist = odist;
					food = obj;
				}
			}
			if (rand() % 5 == 0 && food) {
				gwin->add_dirty(food);
				food->remove_this();
			}
		}       // loops back to itself since npc can be pushed
		break;  // out of their chair and not eat right away
	}
	case find_plate: {
		state = serve_food;
		delay = 0;
		// make sure moved plate doesn't get food sent to it
		plate = Game_object_weak();
		Game_object_vector plates;  // First look for a nearby plate.
		Game_object *plate_obj = nullptr;
		npc->find_nearby(plates, 717, 1, 0);
		int floor = npc->get_lift() / 5; // Make sure it's on same floor.
		for (auto *plate : plates) {
			plate_obj = plate;
			if (plate_obj->get_lift() / 5 == floor)
				break;
			else
				plate_obj = nullptr;
		}
		plate = weak_from_obj(plate_obj);
		break;
	}
	case serve_food: {
		state = eat;
		Game_object_shared plate_obj = plate.lock();
		if (!plate_obj) {
			state = find_plate;
			break;
		}
		Tile_coord t = plate_obj->get_tile();
		Game_object_shared food = gmap->create_ireg_object(377, rand() % 31);
		food->move(t.tx, t.ty, t.tz + 1);
		break;
	}
	}
	npc->start(250, delay);
}

// TODO: This should be in a loop to remove food one at a time with a delay
void Eat_schedule::ending(int new_type) { // new schedule type
	ignore_unused_variable_warning(new_type);
	Game_object_vector foods;           // Food nearby?
	int cnt = npc->find_nearby(foods, 377, 2, 0);
	if (cnt) {          // Found?
		for (auto *food : foods) {
			if (food) {
				gwin->add_dirty(food);
				food->remove_this();
			}
		}
	}
}

/*
 *  Modify goal to walk off the screen.
 */

void Walk_to_schedule::walk_off_screen(
    TileRect &screen,      // In tiles, area slightly larger than
    //   actual screen.
    Tile_coord &goal        // Modified for path offscreen.
) {
	// Destination.
	if (goal.tx >= screen.x + screen.w) {
		goal.tx = screen.x + screen.w - 1;
		goal.ty = -1;
	} else if (goal.tx < screen.x) {
		goal.tx = screen.x;
		goal.ty = -1;
	} else if (goal.ty >= screen.y + screen.h) {
		goal.ty = screen.y + screen.h - 1;
		goal.tx = -1;
	} else if (goal.ty < screen.y) {
		goal.ty = screen.y;
		goal.tx = -1;
	}
}

/*
 *  Create schedule for walking to the next schedule's destination.
 */

Walk_to_schedule::Walk_to_schedule(
    Actor *n,
    Tile_coord const &d,            // Destination.
    int new_sched,          // Schedule when we get there.
    int delay           // Msecs, or -1 for random delay.
) : Schedule(n), dest(d), new_schedule(new_sched), retries(0), legs(0) {
	// Delay 0-5 secs.
	first_delay = delay >= 0 ? delay : (rand() % 5000);
}

/*
 *  Schedule change for walking to another schedule destination.
 */

void Walk_to_schedule::now_what(
) {
	if (npc->distance(dest) <= 3) {
		// Close enough!
		npc->set_schedule_type(new_schedule);
		return;
	}
	if (legs >= 40 || retries >= 2) // Trying too hard?  (Following
		//   Patterson takes about 30.)
	{
		// Going to jump there.
		npc->move(dest.tx, dest.ty, dest.tz);
		// Force actor to sit down/lie down again
		npc->change_frame(npc->get_dir_framenum(Actor::standing));
		npc->set_schedule_type(new_schedule);
		return;
	}
	// Get screen rect. in tiles.
	TileRect screen = gwin->get_win_tile_rect();
	screen.enlarge(6);      // Enlarge in all dirs.
	// Might do part of it first.
	Tile_coord from = npc->get_tile();
	Tile_coord to = dest;
	// Destination off the screen?
	if (!screen.has_world_point(to.tx, to.ty)) {
		if (!screen.has_world_point(from.tx, from.ty)) {
			// Force teleport on next tick.
			retries = 100;
			npc->start(200, 100);
			return;
		}
		// Don't walk off screen if close, or
		//   if lots of legs, indicating that
		//   Avatar is following this NPC.
		if (from.distance(to) > 80 || legs < 10)
			// Modify 'dest'. to walk off.
			walk_off_screen(screen, to);
	} else if (!screen.has_world_point(from.tx, from.ty))
		// Modify src. to walk from off-screen.
		// Causes NPCs to teleport to on-screen far too often
		// (e.g., Blue Boar bartenders)
		//walk_off_screen(screen, from);
		from = Tile_coord(-1, -1, -1);
	blocked = Tile_coord(-1, -1, -1);
	cout << "Finding path to schedule for " << npc->get_name() << endl;
	// Create path to dest., delaying
	//   0 to 1 seconds.
	if (!npc->walk_path_to_tile(from, to, gwin->get_std_delay(),
	                            first_delay + rand() % 1000)) {
		// Wait 1 sec., then try again.
#ifdef DEBUG
		cout << "Failed to find path for " << npc->get_name() << endl;
#endif
		npc->walk_to_tile(dest, gwin->get_std_delay(), 1000);
		retries++;      // Failed.  Try again next tick.
	} else {            // Okay.  He's walking there.
		legs++;
		retries = 0;
	}
	first_delay = 0;
}

/*
 *  Don't go dormant when walking to a schedule.
 */

void Walk_to_schedule::im_dormant(
) {
	npc->set_action(nullptr); // Prevent segfault when opening/closing doors.
	Walk_to_schedule::now_what();   // Get there by any means.
}

/*
 *  Get actual schedule (for Usecode intrinsic).
 */

int Walk_to_schedule::get_actual_type(
    Actor * /* npc */
) const {
	return new_schedule;
}

/*
 *  Clear out names.
 */

void Schedule_change::clear(
) {
	script_names.clear();
}

/*
 *  Set a schedule from a U7 'schedule.dat' entry.
 */

void Schedule_change::set4(
    const unsigned char *entry        // 4 bytes read from schedule.dat.
) {
	time = entry[0] & 7;
	type = entry[0] >> 3;
	days = 0x7f;            // All days of the week.
	unsigned char schunk = entry[3];
	unsigned char x = entry[1];
	unsigned char y = entry[2];
	int sx = schunk % c_num_schunks;
	int sy = schunk / c_num_schunks;
	pos = Tile_coord(sx * c_tiles_per_schunk + x,
	                 sy * c_tiles_per_schunk + y, 0);
}

/*
 *  Set a schedule from an Exult 'schedule.dat' entry (vers. -1).
 */

void Schedule_change::set8(
    const unsigned char *entry        // 8 bytes read from schedule.dat.
) {
	pos.tx = Read2(entry);
	pos.ty = Read2(entry);
	pos.tz = *entry++;
	time = *entry++;
	type = *entry++;
	days = *entry++;
}

/*
 *  Write out schedule for Exult's 'schedule.dat'.
 */

void Schedule_change::write8(
    unsigned char *entry        // 8 bytes to write to schedule.dat.
) const {
	Write2(entry, pos.tx);
	Write2(entry, pos.ty);      // 4
	*entry++ = static_cast<uint8>(pos.tz);      // 5
	*entry++ = time;        // 6
	*entry++ = type;        // 7
	*entry++ = days;        // 8
}

/*
 *  Set a schedule.
 */

void Schedule_change::set(
    int ax,
    int ay,
    int az,
    unsigned char stype,
    unsigned char stime
) {
	time = stime;
	type = stype;
	pos = Tile_coord(ax, ay, az);
}




