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

#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/misc/u6_llist.h"

#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/usecode/u6_usecode.h"
#include "ultima/nuvie/pathfinder/sched_path_finder.h"
#include "ultima/nuvie/pathfinder/u6_astar_path.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/actors/u6_actor.h"

#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/views/view_manager.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/core/converse.h"
#include "ultima/nuvie/script/script.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/pathfinder/combat_path_finder.h"

#include "ultima/nuvie/actors/u6_actor_types.h"
#include "ultima/nuvie/actors/u6_work_types.h"
#include "ultima/nuvie/core/weather.h"

namespace Ultima {
namespace Nuvie {

U6Actor::U6Actor(Map *m, ObjManager *om, GameClock *c): Actor(m, om, c),
		actor_type(nullptr), walk_frame_inc(1), base_actor_type(nullptr),
		current_movetype(MOVETYPE_U6_NONE) {
}

U6Actor::~U6Actor() {
}

bool U6Actor::init(uint8 obj_status) {
	Actor::init();
	base_actor_type = get_actor_type(base_obj_n);
	if (base_actor_type->base_obj_n != base_obj_n) {
		base_obj_n = base_actor_type->base_obj_n;
	}

	set_actor_obj_n(obj_n); //set actor_type

	current_movetype = actor_type->movetype;

	body_armor_class = base_actor_type->body_armor_class;

	if (actor_type->tile_type == ACTOR_QT && frame_n == 0) //set the two quad tile actors to correct frame number.
		frame_n = 3;

	discover_direction();

	if (has_surrounding_objs())
		clear_surrounding_objs_list(); //clean up the old list if required.

	if (is_alive() && x != 0 && y != 0) { //only try to init multi-tile actors if they are alive.
		switch (obj_n) { //gather surrounding objects from map if required
		case OBJ_U6_SHIP :
			init_ship();
			break;

		case OBJ_U6_HYDRA :
			init_hydra();
			break;

		case OBJ_U6_DRAGON :
			init_dragon();
			break;

		case OBJ_U6_SILVER_SERPENT :
			init_silver_serpent();
			break;

		case OBJ_U6_GIANT_SCORPION :
		case OBJ_U6_GIANT_ANT :
		case OBJ_U6_COW :
		case OBJ_U6_ALLIGATOR :
		case OBJ_U6_HORSE :
		case OBJ_U6_HORSE_WITH_RIDER :
			init_splitactor(obj_status);
			break;


		default :
			break;
		}
	}

	if (actor_type->can_sit) { // For some reason U6 starts with actors standing on their chairs.
		// We need to sit them down.
		Obj *obj = obj_manager->get_obj(x, y, z);
		sit_on_chair(obj); // attempt to sit on obj.
	}

	inventory_make_all_objs_ok_to_take();

	return true;
}

bool U6Actor::init_ship() {
	Obj *obj;
	uint16 obj1_x, obj1_y, obj2_x, obj2_y;

	obj1_x = x;
	obj1_y = y;
	obj2_x = x;
	obj2_y = y;

	switch (direction) {
	case NUVIE_DIR_N :
		obj1_y = y + 1;
		obj2_y = y - 1;
		break;
	case NUVIE_DIR_E :
		obj1_x = x + 1;
		obj2_x = x - 1;
		break;
	case NUVIE_DIR_S :
		obj1_y = y - 1;
		obj2_y = y + 1;
		break;
	case NUVIE_DIR_W :
		obj1_x = x - 1;
		obj2_x = x + 1;
		break;
	default:
		error("Invalid direction in U6Actor::init_ship");
	}

	obj = obj_manager->get_obj(obj1_x, obj1_y, z);
	if (obj == nullptr)
		return false;
	add_surrounding_obj(obj);

	obj = obj_manager->get_obj(obj2_x, obj2_y, z);
	if (obj == nullptr)
		return false;
	add_surrounding_obj(obj);

	return true;
}

bool U6Actor::init_splitactor(uint8 obj_status) {
	uint16 obj_x, obj_y;

	obj_x = x;
	obj_y = y;

	switch (direction) {
	case NUVIE_DIR_N :
		obj_y = WRAPPED_COORD(y + 1, z);
		break;
	case NUVIE_DIR_E :
		obj_x = WRAPPED_COORD(x - 1, z);
		break;
	case NUVIE_DIR_S :
		obj_y = WRAPPED_COORD(y - 1, z);
		break;
	case NUVIE_DIR_W :
		obj_x = WRAPPED_COORD(x + 1, z);
		break;
	default:
		error("Invalid direction in U6Actor::init_splitactor");
	}

// init back object
	if (obj_status & OBJ_STATUS_MUTANT) {
		init_surrounding_obj(obj_x, obj_y, z, obj_n, (get_reverse_direction(direction) * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1));
	} else {
		init_surrounding_obj(obj_x, obj_y, z, obj_n, frame_n + 8);
	}

	return true;
}

bool U6Actor::init_dragon() {
	uint16 head_x, head_y, tail_x, tail_y;
	uint16 wing1_x, wing1_y, wing2_x, wing2_y;

	head_x = tail_x = x;
	wing1_x = wing2_x = x;
	head_y = tail_y = y;
	wing1_y = wing2_y = y;

	switch (direction) {
	case NUVIE_DIR_N :
		head_y = y - 1;
		tail_y = y + 1;
		wing1_x = x - 1;
		wing2_x = x + 1;
		break;
	case NUVIE_DIR_E :
		head_x = x + 1;
		tail_x = x - 1;
		wing1_y = y - 1;
		wing2_y = y + 1;
		break;
	case NUVIE_DIR_S :
		head_y = y + 1;
		tail_y = y - 1;
		wing1_x = x + 1;
		wing2_x = x - 1;
		break;
	case NUVIE_DIR_W :
		head_x = x - 1;
		tail_x = x + 1;
		wing1_y = y + 1;
		wing2_y = y - 1;
		break;
	default:
		error("Invalid direction in U6Actor::init_dragon");
	}

	init_surrounding_obj(head_x, head_y, z, obj_n, frame_n + 8);
	init_surrounding_obj(tail_x, tail_y, z, obj_n, frame_n + 16);
	init_surrounding_obj(wing1_x, wing1_y, z, obj_n, frame_n + 24);
	init_surrounding_obj(wing2_x, wing2_y, z, obj_n, frame_n + 32);

	return true;
}

bool U6Actor::init_hydra() {
// For some reason a Hydra has a different object number for its tenticles. :-(

	init_surrounding_obj(x,   y - 1, z, OBJ_U6_HYDRA_BODY, 0);
	init_surrounding_obj(x + 1, y - 1, z, OBJ_U6_HYDRA_BODY, 4);
	init_surrounding_obj(x + 1, y, z, OBJ_U6_HYDRA_BODY, 8);
	init_surrounding_obj(x + 1, y + 1, z, OBJ_U6_HYDRA_BODY, 12);
	init_surrounding_obj(x,   y + 1, z, OBJ_U6_HYDRA_BODY, 16);
	init_surrounding_obj(x - 1, y + 1, z, OBJ_U6_HYDRA_BODY, 20);
	init_surrounding_obj(x - 1, y, z, OBJ_U6_HYDRA_BODY, 24);
	init_surrounding_obj(x - 1, y - 1, z, OBJ_U6_HYDRA_BODY, 28);

	return true;
}

bool U6Actor::init_silver_serpent() {
	uint16 sx, sy, sz;
	Obj *obj;
	uint8 tmp_frame_n = 0;

	sx = x;
	sy = y;
	sz = z;

	switch (direction) {
	case NUVIE_DIR_N :
		sy++;
		tmp_frame_n = 1;
		break;
	case NUVIE_DIR_E :
		sx--;
		tmp_frame_n = 3;
		break;
	case NUVIE_DIR_S :
		sy--;
		tmp_frame_n = 5;
		break;
	case NUVIE_DIR_W :
		sx++;
		tmp_frame_n = 7;
		break;
	default:
		error("Invalid direction in U6Actor::init_silver_serpent");
	}

	obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, 1, id_n, sx, sy, sz);

	if (obj != nullptr) //old snake
		gather_snake_objs_from_map(obj, x, y, z);
	else { //new snake
		//FIXME: we need to make long, randomly laid out snakes here!
		init_new_silver_serpent();
	}

	// FIXME: Unused variable
	(void)tmp_frame_n;

	return true;
}

void U6Actor::init_new_silver_serpent() {
	const struct {
		uint8 body_frame_n;
		uint8 tail_frame_n;
		sint8 x_offset;
		sint8 y_offset;
	} movetbl[4] = { {10, 1, 0, 1}, {13, 7, 1, 0}, {12, 5, 0, -1}, {11, 3, -1, 0} };

	uint8 i, j;
	uint16 nx, ny;
	Obj *obj;
	uint8 length = 4 + NUVIE_RAND() % 5; //FIXME. The original worked out length from qty in the serpent embryo obj.

	nx = x;
	ny = y;

	set_direction(NUVIE_DIR_N); //make sure we are facing north.

	for (i = 0, j = 0; i < length; i++) {
		nx += movetbl[j].x_offset;
		ny += movetbl[j].y_offset;

		init_surrounding_obj(nx, ny, z, OBJ_U6_SILVER_SERPENT, (i == length - 1 ? movetbl[j].tail_frame_n : movetbl[j].body_frame_n));

		obj = (Obj *)surrounding_objects.back();
		obj->quality = i + 1; //body segment number
		obj->qty = id_n; //actor id number

		j = (j + 1) % 4;
	}

	return;
}

void U6Actor::gather_snake_objs_from_map(Obj *start_obj, uint16 ax, uint16 ay, uint16 az) {
	Obj *obj;
	uint16 px, py; // , pz;
	uint16 nx, ny, nz;
	uint8 seg_num;

	px = ax;
	py = ay;
//	pz = az;

	obj = start_obj;
	add_surrounding_obj(obj);

	for (seg_num = 2; obj && obj->frame_n >= 8; seg_num++) {

		nx = obj->x;
		ny = obj->y;
		nz = obj->z;
		//work out the location of the next obj based on the current frame_n and relative movement.
		switch (obj->frame_n) {
		//up down
		case  8 :
			if (ny - 1 == py)
				ny++;
			else
				ny--;
			break;
		//left right
		case  9 :
			if (nx - 1 == px)
				nx++;
			else
				nx--;
			break;
		//up right
		case 10 :
			if (ny - 1 == py)
				nx++;
			else
				ny--;
			break;
		//down right
		case 11 :
			if (ny + 1 == py)
				nx++;
			else
				ny++;
			break;
		//left down
		case 12 :
			if (nx - 1 == px)
				ny++;
			else
				nx--;
			break;
		//left up
		case 13 :
			if (nx - 1 == px)
				ny--;
			else
				nx--;
			break;
		}

		px = obj->x;
		py = obj->y;
		//pz = obj->z;

		obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, seg_num, id_n, nx, ny, nz);

		if (obj)
			add_surrounding_obj(obj);
	}

}

uint16 U6Actor::get_downward_facing_tile_num() const {
	uint8 shift = 0;

	if (base_actor_type->frames_per_direction > 1) //we want the second frame for most actor types.
		shift = 1;

	return get_tile_num(base_actor_type->base_obj_n) + base_actor_type->tile_start_offset + (NUVIE_DIR_S * base_actor_type->tiles_per_direction + base_actor_type->tiles_per_frame - 1) + shift;
}

bool U6Actor::updateSchedule(uint8 hour, bool teleport) {
	bool ret;
	handle_lightsource(hour);

	if ((ret = Actor::updateSchedule(hour, teleport)) == true) { //walk to next schedule location if required.
		if (sched[sched_pos] != nullptr && (sched[sched_pos]->x != x || sched[sched_pos]->y != y || sched[sched_pos]->z != z
		                                 || worktype == WORKTYPE_U6_SLEEP)) { // needed to go underneath bed if teleporting
			set_worktype(WORKTYPE_U6_WALK_TO_LOCATION);
			MapCoord loc(sched[sched_pos]->x, sched[sched_pos]->y, sched[sched_pos]->z);
			pathfind_to(loc);
		}
	}

	return ret;
}

// workout our direction based on actor_type and frame_n
inline void U6Actor::discover_direction() {
	if (actor_type->frames_per_direction != 0)
		direction = static_cast<NuvieDir>((frame_n - actor_type->tile_start_offset) / actor_type->tiles_per_direction);
	else
		direction = NUVIE_DIR_S;
}

void U6Actor::change_base_obj_n(uint16 val) {
	Actor::change_base_obj_n(val);
	clear_surrounding_objs_list(REMOVE_SURROUNDING_OBJS);
	init();
}

void U6Actor::set_direction(NuvieDir d) {
	if (is_alive() == false || is_immobile())
		return;

	uint8 frames_per_dir = (actor_type->frames_per_direction != 0)
	                       ? actor_type->frames_per_direction : 4;

	if (d >= 4) // ignore diagonals
		return;

	if (walk_frame == 0)
		walk_frame_inc = 1; // loop forward
	else if (walk_frame == (frames_per_dir - 1))
		walk_frame_inc = -1; // loop backward
	walk_frame = (walk_frame + walk_frame_inc) % frames_per_dir;

	if (has_surrounding_objs()) {
		if (direction != d)
			set_direction_of_surrounding_objs(d);
		else {
			if (can_move && actor_type->twitch_rand) //only twitch actors with a non zero twitch_rand.
				twitch_surrounding_objs();
		}
	}

	direction = d;

//only change direction frame if the actor can twitch ie isn't sitting or in bed etc.
	if (can_move && obj_n != OBJ_U6_SLIME)
		frame_n = actor_type->tile_start_offset + (direction * actor_type->tiles_per_direction +
		          (walk_frame * actor_type->tiles_per_frame) + actor_type->tiles_per_frame - 1);

// tangle vines' north and east frames are in the wrong direction
// FIXME: see if the ActorType values can be changed to fix this
	if (obj_n == OBJ_U6_TANGLE_VINE)
		if (direction == NUVIE_DIR_N || direction == NUVIE_DIR_E)
			frame_n += 3;
}

void U6Actor::face_location(uint16 lx, uint16 ly) {
	if (obj_n != OBJ_U6_SILVER_SERPENT //snakes cannot turn on the spot.
	        && obj_n != OBJ_U6_TANGLE_VINE && obj_n != OBJ_U6_TANGLE_VINE_POD)
		Actor::face_location(lx, ly);

	return;
}

void U6Actor::clear() {
	if (has_surrounding_objs()) {
		remove_surrounding_objs_from_map();
		clear_surrounding_objs_list(REMOVE_SURROUNDING_OBJS);
	}

	Actor::clear();

	return;
}



bool U6Actor::move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) {
	assert(new_z < 6);

// bool force_move = flags & ACTOR_FORCE_MOVE;
	bool ret;
	sint16 rel_x, rel_y;
//MsgScroll *scroll = Game::get_game()->get_scroll();
	Player *player = Game::get_game()->get_player();
//Party *party = player->get_party();
	MapCoord old_pos = get_location();

	if (has_surrounding_objs())
		remove_surrounding_objs_from_map();

	rel_x = new_x - x;
	rel_y = new_y - y;

	if ((flags & ACTOR_OPEN_DOORS) && worktype != WORKTYPE_U6_WALK_TO_LOCATION)
		flags ^= ACTOR_OPEN_DOORS; // only use doors when walking to schedule location
	ret = Actor::move(new_x, new_y, new_z, flags);

	if (ret == true) {
		if (has_surrounding_objs())
			move_surrounding_objs_relative(rel_x, rel_y);

		Obj *obj = obj_manager->get_obj(new_x, new_y, new_z, false); // Ouch, we get obj in Actor::move() too :(
		if (obj) {
			if (actor_type->can_sit)
				sit_on_chair(obj); // make the Actor sit if they are on top of a chair.
		}

		set_hit_flag(false);
		Game::get_game()->get_script()->call_actor_map_dmg(this, get_location());
	}


	// temp. fix; this too should be done with UseCode (and don't move the mirror)
	if (old_pos.y > 0 && new_y > 0) {
		Obj *old_mirror = obj_manager->get_obj_of_type_from_location(OBJ_U6_MIRROR, old_pos.x, old_pos.y - 1, old_pos.z);
		Obj *mirror = obj_manager->get_obj_of_type_from_location(OBJ_U6_MIRROR, new_x, new_y - 1, new_z);
		if (old_mirror && old_mirror->frame_n != 2) old_mirror->frame_n = 0;
		if (mirror && mirror->frame_n != 2)     mirror->frame_n = 1;
	}

	// Cyclops: shake ground if player is near
	if (actor_type->base_obj_n == OBJ_U6_CYCLOPS && is_nearby(player->get_actor())) {
		Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_EARTH_QUAKE);
		new QuakeEffect(1, 200, player->get_actor());
	}

	if (has_surrounding_objs()) //add our surrounding objects back onto the map.
		add_surrounding_objs_to_map();

	return ret;
}

bool U6Actor::check_move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) {
// bool ignore_actors = flags & ACTOR_IGNORE_OTHERS;
	const Tile *map_tile;

	if (Actor::check_move(new_x, new_y, new_z, flags) == false)
		return false;

	if (obj_n == OBJ_U6_SILVER_SERPENT && check_move_silver_serpent(new_x, new_y) == false)
		return false;

	switch (current_movetype) {
	case MOVETYPE_U6_ETHEREAL:
		return true;
	case MOVETYPE_U6_NONE :
		return false;
	case MOVETYPE_U6_WATER_HIGH : // for HIGH we only want to move to open water.
		// No shorelines.
		map_tile = map->get_tile(new_x, new_y, new_z, MAP_ORIGINAL_TILE);
		if (map_tile->tile_num >= 16 && map_tile->tile_num <= 47)
			return false;

		if (!map->is_water(new_x, new_y, new_z))
			return false;
		break;

	case MOVETYPE_U6_WATER_LOW :
		if (!map->is_water(new_x, new_y, new_z))
			return false;
		break;

	case MOVETYPE_U6_AIR_LOW :
		map_tile = map->get_tile(new_x, new_y, new_z, MAP_ORIGINAL_TILE);
		if (map_tile->flags1 & TILEFLAG_WALL) //low air boundary
			return false;

		map_tile = obj_manager->get_obj_tile(new_x, new_y, new_z, false);
		if (map_tile && ((map_tile->flags1 & TILEFLAG_WALL) ||
		                 (map_tile->flags2 & (TILEFLAG_DOUBLE_WIDTH | TILEFLAG_DOUBLE_HEIGHT)) == (TILEFLAG_DOUBLE_WIDTH | TILEFLAG_DOUBLE_HEIGHT)))
			return false;
		break;

	case MOVETYPE_U6_AIR_HIGH :
		if (map->is_boundary(new_x, new_y, new_z))
			return false; //FIX for proper air boundary
		break;
	case MOVETYPE_U6_LAND :
	default :
		if (map->is_passable(new_x, new_y, new_z) == false) {
			if (obj_n == OBJ_U6_MOUSE // try to go through mousehole
			        && (obj_manager->get_obj_of_type_from_location(OBJ_U6_MOUSEHOLE, new_x, new_y, new_z) != nullptr
			            || obj_manager->get_obj_of_type_from_location(OBJ_U6_BARS, new_x, new_y, new_z) != nullptr
			            || obj_manager->get_obj_of_type_from_location(OBJ_U6_PORTCULLIS, new_x, new_y, new_z) != nullptr))
				return true;
			if (obj_n == OBJ_U6_SILVER_SERPENT //silver serpents can crossover themselves
			        && obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, new_x, new_y, new_z) != nullptr)
				return true;

			return false;
		}

	}

	return true;
}

bool U6Actor::check_move_silver_serpent(uint16 new_x, uint16 new_y) {
	if (new_x != x && new_y != y) //snakes can't move diagonally
		return false;

	Obj *obj = (Obj *)surrounding_objects.front(); //retrieve the first body segment.

	if (obj->x == new_x && obj->y == new_y) //snakes can't move backwards.
		return false;

	return true;
}

// attempt to sit if obj is a chair.

bool U6Actor::sit_on_chair(Obj *obj) {
	if (actor_type->can_sit && obj) {
		if (obj->obj_n == OBJ_U6_CHAIR) { // make the actor sit on a chair.
			if (obj_n == OBJ_U6_MUSICIAN_PLAYING)
				frame_n = (obj->frame_n * 2);
			else
				frame_n = (obj->frame_n * 4) + 3;
			direction = static_cast<NuvieDir>(obj->frame_n);
			can_move = false;
			return true;
		}

		//make actor sit on LB's throne.
		if (obj->obj_n == OBJ_U6_THRONE  && obj->x != x) { //throne is a double width obj. We only sit on the left tile.
			frame_n = 8 + 3; //sitting facing south.
			direction = NUVIE_DIR_S;
			can_move = false;
			return true;
		}
	}

	return false;
}

uint8 U6Actor::get_object_readiable_location(Obj *obj) {
	uint16 i;

	for (i = 0; readiable_objects[i].obj_n != OBJ_U6_NOTHING; i++) {
		if (obj->obj_n == readiable_objects[i].obj_n)
			return readiable_objects[i].readiable_location;
	}

	return ACTOR_NOT_READIABLE;
}

const CombatType *U6Actor::get_object_combat_type(uint16 objN) {
	uint16 i;

	for (i = 0; u6combat_objects[i].obj_n != OBJ_U6_NOTHING; i++) {
		if (objN == u6combat_objects[i].obj_n)
			return &u6combat_objects[i];
	}

	return nullptr;
}

const CombatType *U6Actor::get_hand_combat_type() const {
	if (obj_n == OBJ_U6_SHIP)
		return &u6combat_ship_cannon;

	return &u6combat_hand;
}

bool U6Actor::weapon_can_hit(const CombatType *weapon, Actor *target, uint16 *hit_x, uint16 *hit_y) {
	if (Actor::weapon_can_hit(weapon, target->get_x(), target->get_y())) {
		*hit_x = target->get_x();
		*hit_y = target->get_y();
		return true;
	}

	const Std::list<Obj *> &surrounding_objs = target->get_surrounding_obj_list();
	for (Obj *obj : surrounding_objs) {
		if (Actor::weapon_can_hit(weapon, obj->x, obj->y)) {
			*hit_x = obj->x;
			*hit_y = obj->y;
			return true;
		}
	}

	uint16 target_x = target->get_x();
	uint16 target_y = target->get_y();

	Tile *tile = target->get_tile();
	if (tile->dbl_width && tile->dbl_height) {
		if (Actor::weapon_can_hit(weapon, target_x - 1, target_y - 1)) {
			*hit_x = target_x - 1;
			*hit_y = target_y - 1;
			return true;
		}
	}
	if (tile->dbl_width) {
		if (Actor::weapon_can_hit(weapon, target_x - 1, target_y)) {
			*hit_x = target_x - 1;
			*hit_y = target_y;
			return true;
		}
	}
	if (tile->dbl_height) {
		if (Actor::weapon_can_hit(weapon, target_x, target_y - 1)) {
			*hit_x = target_x;
			*hit_y = target_y - 1;
			return true;
		}
	}

	return false;
}

void U6Actor::twitch() {

	if (can_twitch() == false)
		return;

	if (NUVIE_RAND() % actor_type->twitch_rand == 1)
		do_twitch();

	return;
}

void U6Actor::do_twitch() {
	if (actor_type->frames_per_direction == 0)
		walk_frame = (walk_frame + 1) % 4;
	else
		walk_frame = NUVIE_RAND() % actor_type->frames_per_direction;

	if (has_surrounding_objs()) {
		switch (obj_n) {
		case OBJ_U6_HYDRA :
			twitch_surrounding_hydra_objs();
			break;
		case OBJ_U6_DRAGON :
		default :
			twitch_surrounding_objs();
			break;
		}
	}

	frame_n = actor_type->tile_start_offset + (direction * actor_type->tiles_per_direction + (walk_frame * actor_type->tiles_per_frame)  + actor_type->tiles_per_frame - 1);
	if (obj_n == OBJ_U6_WISP) {
		Game::get_game()->get_map_window()->updateAmbience();
	}
}

void U6Actor::set_paralyzed(bool paralyzed) {
	if (paralyzed) {
		status_flags |= ACTOR_STATUS_PARALYZED;
	} else {
		status_flags &= (0xff ^ ACTOR_STATUS_PARALYZED);
	}
}

void U6Actor::set_protected(bool val) {
	if (val) {
		status_flags |= ACTOR_STATUS_PROTECTED;
	} else {
		status_flags &= (0xff ^ ACTOR_STATUS_PROTECTED);
	}
}

void U6Actor::set_charmed(bool val) {
	if (val) {
		obj_flags |= OBJ_STATUS_CHARMED;
	} else {
		obj_flags &= (0xff ^ OBJ_STATUS_CHARMED);
	}
}

void U6Actor::set_corpser_flag(bool val) {
	if (val) {
		movement_flags |= ACTOR_MOVEMENT_FLAGS_CORPSER;
	} else {
		movement_flags &= (0xff ^ ACTOR_MOVEMENT_FLAGS_CORPSER);
	}
}

void U6Actor::set_cursed(bool val) {
	if (val) {
		obj_flags |= OBJ_STATUS_CURSED;
	} else {
		obj_flags &= (0xff ^ OBJ_STATUS_CURSED);
	}
}

void U6Actor::set_asleep(bool val) {
	if (val) {
		status_flags |= ACTOR_STATUS_ASLEEP;
		if (actor_type->dead_obj_n != OBJ_U6_NOTHING && actor_type->can_laydown) {
			obj_n = actor_type->dead_obj_n;
			frame_n = actor_type->dead_frame_n;
		}
	} else {
		status_flags &= (0xff ^ ACTOR_STATUS_ASLEEP);
		if (obj_n == base_actor_type->dead_obj_n || obj_n ==  OBJ_U6_PERSON_SLEEPING) {
			if (worktype == WORKTYPE_U6_SLEEP)
				can_move = true;
			actor_type = base_actor_type;
			obj_n = base_actor_type->base_obj_n;
			frame_n = 0;
		}
	}
}

void U6Actor::set_worktype(uint8 new_worktype, bool init) {
	if (new_worktype == worktype)
		return;

	if (worktype == WORKTYPE_U6_SLEEP || worktype == WORKTYPE_U6_PLAY_LUTE) {
		frame_n = old_frame_n;
	}

//reset to base obj_n
	if ((!is_in_party() || worktype > 0xe) && base_actor_type->base_obj_n != OBJ_U6_NOTHING) //don't revert for party worktypes as they might be riding a horse.
		set_actor_obj_n(base_actor_type->base_obj_n);

	if (worktype == WORKTYPE_U6_SLEEP && (status_flags & ACTOR_STATUS_ASLEEP)) //FIXME do we still need this??
		status_flags ^= ACTOR_STATUS_ASLEEP;

	Actor::set_worktype(new_worktype);

	if (worktype == WORKTYPE_U6_WALK_TO_LOCATION) {
		setup_walk_to_location();
	}

//FIX from here.

	switch (worktype) {
	case WORKTYPE_U6_FACE_NORTH :
		set_direction(NUVIE_DIR_N);
		break;
	case WORKTYPE_U6_FACE_EAST  :
		set_direction(NUVIE_DIR_E);
		break;
	case WORKTYPE_U6_FACE_SOUTH :
		set_direction(NUVIE_DIR_S);
		break;
	case WORKTYPE_U6_FACE_WEST  :
		set_direction(NUVIE_DIR_W);
		break;

	case WORKTYPE_U6_SLEEP :
		wt_sleep(init);
		break;
	case WORKTYPE_U6_PLAY_LUTE :
		wt_play_lute();
		break;
	}
}


void U6Actor::pathfind_to(const MapCoord &d) {
	if (pathfinder) {
		pathfinder->set_actor(this);
		pathfinder->set_goal(d);
	} else
		set_pathfinder(new SchedPathFinder(this, d, new U6AStarPath));

	pathfinder->update_location();
}

void U6Actor::setup_walk_to_location() {
	if (sched[sched_pos] != nullptr) {
		if (x == sched[sched_pos]->x && y == sched[sched_pos]->y
		        && z == sched[sched_pos]->z) {
			set_worktype(sched[sched_pos]->worktype);
			delete_pathfinder();
			return;
		}
		if (!pathfinder) {
			work_location.x = sched[sched_pos]->x;
			work_location.y = sched[sched_pos]->y;
			work_location.z = sched[sched_pos]->z;
//            if(!work_location.is_visible() || !get_location().is_visible())
//                set_pathfinder(new OffScreenPathFinder(this, work_location, new U6AStarPath));
//            else
			set_pathfinder(new SchedPathFinder(this, work_location, new U6AStarPath));
		}
	}
}



// wander around but don't cross boundaries or fences. Used for cows and horses.
// now that hazards are working properly, this isn't needed --SB-X
/*void U6Actor::wt_farm_animal_wander()
{
 uint8 new_direction;
 sint8 rel_x = 0, rel_y = 0;

 if(NUVIE_RAND()%8 == 1)
   {
	new_direction = NUVIE_RAND()%4;

	switch(new_direction)
	  {
	   case NUVIE_DIR_N : rel_y = -1; break;
	   case NUVIE_DIR_E : rel_x = 1; break;
	   case NUVIE_DIR_S : rel_y = 1; break;
	   case NUVIE_DIR_W : rel_x = -1; break;
	  }

	if(obj_manager->get_obj_of_type_from_location(OBJ_U6_FENCE,x + rel_x, y + rel_y, z) == nullptr)
		{
		 if(moveRelative(rel_x,rel_y))
			set_direction(new_direction);
		}

   }
 else set_moves_left(moves - 5);

 return;
}*/

void U6Actor::wt_sleep(bool init) {
	if (init && !is_sleeping())
		return;
	Obj *obj = obj_manager->get_obj(x, y, z);

	can_move = false;
	status_flags |= ACTOR_STATUS_ASLEEP;
	if (obj) {
		if (obj->obj_n == OBJ_U6_BED) {
			if (obj->frame_n == 1 || obj->frame_n == 5) { //horizontal bed
				old_frame_n = frame_n;
				obj_n = OBJ_U6_PERSON_SLEEPING;
				frame_n = 0;
			}
			if (obj->frame_n == 7 || obj->frame_n == 10) { //vertical bed
				old_frame_n = frame_n;
				obj_n = OBJ_U6_PERSON_SLEEPING;
				frame_n = 1;
			}
			return;
		}
	}

// lay down on the ground using the dead body frame
	if (actor_type->can_laydown) {
		old_frame_n = frame_n;
		obj_n = actor_type->dead_obj_n;
		frame_n = actor_type->dead_frame_n;
	}

}

void U6Actor::wt_play_lute() {
	set_actor_obj_n(OBJ_U6_MUSICIAN_PLAYING);

	frame_n = direction * actor_type->tiles_per_direction;
	Obj *obj = obj_manager->get_obj(x, y, z);
	sit_on_chair(obj); // attempt to sit on obj.

	return;
}

void U6Actor::set_actor_obj_n(uint16 new_obj_n) {
	old_frame_n = frame_n;

	obj_n = new_obj_n;
	actor_type = get_actor_type(new_obj_n);

	return;
}

inline const U6ActorType *U6Actor::get_actor_type(uint16 new_obj_n) {
	const U6ActorType *type;

	for (type = u6ActorTypes; type->base_obj_n != OBJ_U6_NOTHING; type++) {
		if (type->base_obj_n == new_obj_n)
			break;
	}

	return type;
}

inline bool U6Actor::has_surrounding_objs() {
	if (actor_type->tile_type == ACTOR_DT || actor_type->tile_type == ACTOR_MT)
		return true;

	return false;
}

inline void U6Actor::remove_surrounding_objs_from_map() {
	for (Obj *obj : surrounding_objects)
		obj_manager->remove_obj_from_map(obj);

	return;
}

inline void U6Actor::add_surrounding_objs_to_map() {
	for (Obj *obj : surrounding_objects)
		obj_manager->add_obj(obj, OBJ_ADD_TOP);

	return;
}

inline void U6Actor::move_surrounding_objs_relative(sint16 rel_x, sint16 rel_y) {
	if (obj_n == OBJ_U6_SILVER_SERPENT) {
		move_silver_serpent_objs_relative(rel_x, rel_y);
	} else {
		for (Obj *obj : surrounding_objects) {
			obj->x = WRAPPED_COORD(obj->x + rel_x, z);
			obj->y = WRAPPED_COORD(obj->y + rel_y, z);
		}
	}

	return;
}

inline void U6Actor::move_silver_serpent_objs_relative(sint16 rel_x, sint16 rel_y) {
	static const uint8 new_frame_n_tbl[5][5] = {
		{ 8, 10, 0, 13,  0},
		{12,  9, 0,  0, 13},
		{ 0,  0, 0,  0,  0},
		{11,  0, 0,  9, 10},
		{ 0, 11, 0, 12,  8}
	};

	static const uint8 new_tail_frame_n_tbl[8][6] = {
		{0, 0, 0, 0, 0, 0},
		{1, 0, 0, 3, 7, 0},
		{0, 0, 0, 0, 0, 0},
		{0, 3, 0, 0, 5, 1},
		{0, 0, 0, 0, 0, 0},
		{5, 0, 3, 0, 0, 7},
		{0, 0, 0, 0, 0, 0},
		{0, 7, 1, 5, 0, 0}
	};

	if (surrounding_objects.empty())
		return;

	Std::list<Obj *>::iterator obj = surrounding_objects.begin();

	sint8 new_pos = 2 + rel_x + (rel_y * 2);

	uint16 old_x = (*obj)->x;
	uint16 old_y = (*obj)->y;

	(*obj)->x = x - rel_x; // old actor x
	(*obj)->y = y - rel_y; // old actor y

	sint8 old_pos = 2 + ((*obj)->x - old_x) + (((*obj)->y - old_y) * 2);

	uint8 objFrameN = (*obj)->frame_n;
	(*obj)->frame_n = new_frame_n_tbl[new_pos][old_pos];
	obj++;
	for (; obj != surrounding_objects.end(); obj++) {
		uint16 tmp_x = (*obj)->x;
		uint16 tmp_y = (*obj)->y;
		uint8 tmp_frame_n = (*obj)->frame_n;

		(*obj)->x = old_x;
		(*obj)->y = old_y;

		if (tmp_frame_n < 8) //tail, work out new tail direction
			(*obj)->frame_n = new_tail_frame_n_tbl[tmp_frame_n][objFrameN - 8];
		else
			(*obj)->frame_n = objFrameN;

		old_x = tmp_x;
		old_y = tmp_y;
		objFrameN = tmp_frame_n;
	}

	return;
}


inline void U6Actor::set_direction_of_surrounding_objs(NuvieDir new_direction) {
	remove_surrounding_objs_from_map();

	switch (obj_n) {
	case OBJ_U6_SHIP :
		set_direction_of_surrounding_ship_objs(new_direction);
		break;

	case OBJ_U6_GIANT_SCORPION :
	case OBJ_U6_GIANT_ANT :
	case OBJ_U6_COW :
	case OBJ_U6_ALLIGATOR :
	case OBJ_U6_HORSE :
	case OBJ_U6_HORSE_WITH_RIDER :
		set_direction_of_surrounding_splitactor_objs(new_direction);
		break;

	case OBJ_U6_DRAGON :
		set_direction_of_surrounding_dragon_objs(new_direction);
		break;
	}

	add_surrounding_objs_to_map();

	return;
}

inline void U6Actor::set_direction_of_surrounding_ship_objs(NuvieDir new_direction) {
	Std::list<Obj *>::iterator obj = surrounding_objects.begin();
	if (obj == surrounding_objects.end())
		return;

	uint16 pitch = map->get_width(z);

	(*obj)->x = x;
	(*obj)->y = y;

	(*obj)->frame_n =  new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1;
	switch (new_direction) {
	case NUVIE_DIR_N :
		if (y == 0)
			(*obj)->y = pitch - 1;
		else
			(*obj)->y = y - 1;
		break;

	case NUVIE_DIR_E :
		if (x == pitch - 1)
			(*obj)->x = 0;
		else
			(*obj)->x = x + 1;
		break;

	case NUVIE_DIR_S :
		if (y == pitch - 1)
			(*obj)->y = 0;
		else
			(*obj)->y = y + 1;
		break;

	case NUVIE_DIR_W :
		if (x == 0)
			(*obj)->x = pitch - 1;
		else
			(*obj)->x = x - 1;
		break;

	default:
		error("Invalid dir for U6Actor::set_direction_of_surrounding_ship_objs");
	}

	obj++;
	if (obj == surrounding_objects.end())
		return;

	(*obj)->x = x;
	(*obj)->y = y;

	(*obj)->frame_n =  16 + (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);
	switch (new_direction) {
	case NUVIE_DIR_N :
		if (y == pitch - 1)
			(*obj)->y = 0;
		else
			(*obj)->y = y + 1;
		break;

	case NUVIE_DIR_E :
		if (x == 0)
			(*obj)->x = pitch - 1;
		else
			(*obj)->x = x - 1;
		break;

	case NUVIE_DIR_S :
		if (y == 0)
			(*obj)->y = pitch - 1;
		else
			(*obj)->y = y - 1;
		break;

	case NUVIE_DIR_W :
		if (x == pitch - 1)
			(*obj)->x = 0;
		else
			(*obj)->x = x + 1;
		break;

	default:
		error("Invalid dir for U6Actor::set_direction_of_surrounding_ship_objs");
	}

}

inline void U6Actor::set_direction_of_surrounding_splitactor_objs(NuvieDir new_direction) {
	if (surrounding_objects.empty())
		return;

	uint16 pitch = map->get_width(z);
	Obj *obj = surrounding_objects.back();

	if (obj->frame_n < 8)
		obj->frame_n = (get_reverse_direction(new_direction) * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1); //mutant actor
	else
		obj->frame_n =  8 + (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);

	obj->x = x;
	obj->y = y;

	switch (new_direction) {
	case NUVIE_DIR_N :
		if (y == pitch - 1)
			obj->y = 0;
		else
			obj->y = y + 1;
		break;

	case NUVIE_DIR_E :
		if (x == 0)
			obj->x = pitch - 1;
		else
			obj->x = x - 1;
		break;

	case NUVIE_DIR_S :
		if (y == 0)
			obj->y = pitch - 1;
		else
			obj->y = y - 1;
		break;

	case NUVIE_DIR_W :
		if (x == pitch - 1)
			obj->x = 0;
		else
			obj->x = x + 1;
		break;

	default:
		error("Invalid direction in U6Actor::set_direction_of_surrounding_splitactor_objs");
	}

}

inline void U6Actor::set_direction_of_surrounding_dragon_objs(NuvieDir new_direction) {
	Std::list<Obj *>::iterator obj;
	uint8 frame_offset = (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);
	Obj *head, *tail, *wing1, *wing2;

//NOTE! this is dependent on the order the in which the objects are loaded in U6Actor::init_dragon()

	obj = surrounding_objects.begin();
	if (obj == surrounding_objects.end())
		return;
	head = *obj;
	head->frame_n =  8 + frame_offset;
	head->x = x;
	head->y = y;

	obj++;
	if (obj == surrounding_objects.end())
		return;
	tail = *obj;
	tail->frame_n =  16 + frame_offset;
	tail->x = x;
	tail->y = y;

	obj++;
	if (obj == surrounding_objects.end())
		return;
	wing1 = *obj;
	wing1->frame_n =  24 + frame_offset;
	wing1->x = x;
	wing1->y = y;

	obj++;
	if (obj == surrounding_objects.end())
		return;
	wing2 = *obj;
	wing2->frame_n =  32 + frame_offset;
	wing2->x = x;
	wing2->y = y;

	switch (new_direction) {
	case NUVIE_DIR_N :
		head->y = y - 1;
		tail->y = y + 1;
		wing1->x = x - 1;
		wing2->x = x + 1;
		break;

	case NUVIE_DIR_E :
		head->x = x + 1;
		tail->x = x - 1;
		wing1->y = y - 1;
		wing2->y = y + 1;
		break;

	case NUVIE_DIR_S :
		head->y = y + 1;
		tail->y = y - 1;
		wing1->x = x + 1;
		wing2->x = x - 1;
		break;

	case NUVIE_DIR_W :
		head->x = x - 1;
		tail->x = x + 1;
		wing1->y = y + 1;
		wing2->y = y - 1;
		break;

	default:
		error("Invalid direction in U6Actor::set_direction_of_surrounding_dragon_objs");

	}

}

inline void U6Actor::twitch_surrounding_objs() {
	for (Obj *obj : surrounding_objects)
		twitch_obj(obj);
}

inline void U6Actor::twitch_surrounding_dragon_objs() {
}

inline void U6Actor::twitch_surrounding_hydra_objs() {
	Std::list<Obj *>::iterator obj;
	int i;

	//Note! list order is important here. As it corresponds to the frame order in the tile set. This is defined in init_hydra()
	for (i = 0, obj = surrounding_objects.begin(); obj != surrounding_objects.end(); obj++, i += 4) {
		if (NUVIE_RAND() % 4 == 0)
			(*obj)->frame_n = i + (((*obj)->frame_n - i + 1) % 4);
	}
}

inline void U6Actor::twitch_obj(Obj *obj) {
	if (actor_type->frames_per_direction == 0) {
		DEBUG(0, LEVEL_WARNING, "FIXME: %s frames_per_direction == 0\n", get_name());
		obj->frame_n = (obj->frame_n / (1 * 4) * (1 * 4)) + direction * actor_type->tiles_per_direction +
		               walk_frame * actor_type->tiles_per_frame;
		return;
	}

	switch (obj->obj_n) {
	case OBJ_U6_GIANT_SCORPION :
	case OBJ_U6_GIANT_ANT :
	case OBJ_U6_COW :
	case OBJ_U6_ALLIGATOR :
	case OBJ_U6_HORSE :
		if (obj->frame_n < 8) { //mutant actor with two heads
			obj->frame_n = get_reverse_direction(direction) * actor_type->tiles_per_direction +
			               walk_frame * actor_type->tiles_per_frame;
			return;
		}
		break;
	default :
		break;
	}


	obj->frame_n = (obj->frame_n / (actor_type->frames_per_direction * 4) * (actor_type->frames_per_direction * 4)) + direction * actor_type->tiles_per_direction +
	               walk_frame * actor_type->tiles_per_frame;
}

inline void U6Actor::clear_surrounding_objs_list(bool delete_objs) {
	if (surrounding_objects.empty())
		return;

	if (delete_objs == false) {
		surrounding_objects.clear();
		return;
	}

	while (!surrounding_objects.empty()) {
		Obj *obj = surrounding_objects.front();
		obj_manager->remove_obj_from_map(obj);
		delete_obj(obj);
		surrounding_objects.pop_front();
	}

	return;
}

inline void U6Actor::init_surrounding_obj(uint16 x_, uint16 y_, uint8 z_, uint16 actor_obj_n, uint16 objFrame_n) {
	Obj *obj;

	obj = obj_manager->get_obj_of_type_from_location(actor_obj_n, id_n, -1, x_, y_, z_);
	if (obj == nullptr)
		obj = obj_manager->get_obj_of_type_from_location(actor_obj_n, 0, -1, x_, y_, z_);

	if (obj == nullptr) {
		obj = new Obj();
		obj->x = x_;
		obj->y = y_;
		obj->z = z_;
		obj->obj_n = actor_obj_n;
		obj->frame_n = objFrame_n;
		obj_manager->add_obj(obj);
	}

	obj->quality = id_n;
	add_surrounding_obj(obj);

	return;
}


void U6Actor::die(bool create_body) {
	Game *game = Game::get_game();
	Party *party = game->get_party();
	Player *player = game->get_player();
	MapCoord actor_loc = get_location();

	if (party->get_member_num(this) == 0) //avatar
		return; //The avatar can't die. They just get teleported back to LB's castle.

	if (has_surrounding_objs())
		clear_surrounding_objs_list(true);

	set_dead_flag(true); // needed sooner for unready usecode of torches
	if (game->is_armageddon())
		inventory_drop_all();
	else if (base_actor_type->dead_obj_n != OBJ_U6_NOTHING) {
		if (create_body) {
			Obj *dead_body = new Obj;
			dead_body->obj_n = base_actor_type->dead_obj_n;
			if (base_actor_type->dead_frame_n == 255) // dog, cat, mouse, deer, wolf, drake, mongbat
				dead_body->frame_n = frame_n; // same frame the actor died
			else if (base_actor_type->dead_obj_n == OBJ_U6_BLOOD)
				dead_body->frame_n = NUVIE_RAND() % 3;
			else
				dead_body->frame_n = base_actor_type->dead_frame_n;
			dead_body->x = actor_loc.x;
			dead_body->y = actor_loc.y;
			dead_body->z = actor_loc.z;
			dead_body->quality = id_n;
			dead_body->status = OBJ_STATUS_OK_TO_TAKE;
			if (temp_actor)
				dead_body->status |= OBJ_STATUS_TEMPORARY;

			if (base_actor_type->dead_obj_n == OBJ_U6_BLOOD)
				inventory_drop_all();
			else // move my inventory into the dead body container
				all_items_to_container(dead_body, false);
			obj_manager->add_obj(dead_body, true);
		}
	} else if (create_body)
		inventory_drop_all();

	Actor::die();

	if (is_in_party()) {
		party->remove_actor(this, true);
		if (player->get_actor() == this)
			player->set_party_mode(party->get_actor(0)); //set party mode with the avatar as the leader.
	}

	if (party->get_member_num(this) != 0)
		move(0, 0, 0, ACTOR_FORCE_MOVE); // FIXME: move to another plane, same coords
}

// frozen by worktype or status
bool U6Actor::is_immobile() const {
	return (((worktype == WORKTYPE_U6_MOTIONLESS
	          || worktype == WORKTYPE_U6_IMMOBILE) && !is_in_party())
	        || get_corpser_flag() || is_sleeping() || is_paralyzed()
	        /*|| can_move == false*/); // can_move really means can_twitch/animate
}

bool U6Actor::can_twitch() {
	return ((can_move || obj_n == OBJ_U6_MUSICIAN_PLAYING)
	        && visible_flag
	        && actor_type->twitch_rand != 0
	        && !get_corpser_flag()
	        && !is_sleeping()
	        && !is_paralyzed());
}

bool U6Actor::can_be_passed(const Actor *other, bool ignoreParty) const {
	// FIXME: Original U6 instead uses a function that checks if a game object (actor or object)
	// can occupy a world location:
	// Figure out which of the remaining tests are relevant to actors and add them here.

	const U6Actor *other_ = static_cast<const U6Actor *>(other);

	if (other_->ethereal)
		return true;

	if (other_ == this)
		return true;

	if (!other_->isFlying() && is_passable())
		goto FinalTest;

	// Flying actors can not pass each other unless both are in the party
	if (other_->isFlying() && !isFlying())
		goto FinalTest;

	// If the ignoreParty flag is set and we are in the party and not immobilized,
	// other members can pass us.
	if (ignoreParty && is_in_party() && other_->is_in_party() && !is_immobile()
			&& this != Game::get_game()->get_player()->get_actor())
		return true;

	return false; // default

	FinalTest:

	// Same type can not pass us
	if (obj_n == other_->get_obj_n())
		return false;

	// Some actors do not block others (e.g. mice, rabbits etc.)
	if (!isNonBlocking())
		return false;

	return true;
}

void U6Actor::print() {
	Actor::print();
// might print U6Actor members here
}

/* Returns name of NPC worktype/activity (game specific) or nullptr. */
const char *U6Actor::get_worktype_string(uint32 wt) const {
	const char *wt_string = nullptr;
	if (wt == WORKTYPE_U6_MOTIONLESS) wt_string = "Motionless";
	else if (wt == WORKTYPE_U6_PLAYER) wt_string = "Player";
	else if (wt == WORKTYPE_U6_IN_PARTY) wt_string = "In Party";
	else if (wt == WORKTYPE_U6_ANIMAL_WANDER) wt_string = "Graze (animal wander)";
	else if (wt == WORKTYPE_U6_WALK_TO_LOCATION) wt_string = "Walk to Schedule";
	else if (wt == WORKTYPE_U6_FACE_NORTH) wt_string = "Stand (North)";
	else if (wt == WORKTYPE_U6_FACE_SOUTH) wt_string = "Stand (South)";
	else if (wt == WORKTYPE_U6_FACE_EAST) wt_string = "Stand (East)";
	else if (wt == WORKTYPE_U6_FACE_WEST) wt_string = "Stand (West)";
	else if (wt == WORKTYPE_U6_WALK_NORTH_SOUTH) wt_string = "Guard North/South";
	else if (wt == WORKTYPE_U6_WALK_EAST_WEST) wt_string = "Guard East/West";
	else if (wt == WORKTYPE_U6_WANDER_AROUND) wt_string = "Wander";
	else if (wt == WORKTYPE_U6_WORK) wt_string = "Loiter (work)";
	else if (wt == WORKTYPE_U6_SLEEP) wt_string = "Sleep";
	else if (wt == WORKTYPE_U6_PLAY_LUTE) wt_string = "Play";
	else if (wt == WORKTYPE_U6_BEG) wt_string = "Converse";
	else if (wt == WORKTYPE_U6_COMBAT_FRONT) wt_string = "Combat Front";
	else if (wt == 0x04) wt_string = "Combat Rear";
	else if (wt == 0x05) wt_string = "Combat Flank";
	else if (wt == 0x06) wt_string = "Combat Berserk";
	else if (wt == 0x07) wt_string = "Combat Retreat";
	else if (wt == 0x08) wt_string = "Combat Assault/Wild";
	else if (wt == 0x09) wt_string = "Shy";
	else if (wt == 0x0a) wt_string = "Like";
	else if (wt == 0x0b) wt_string = "Unfriendly";
	else if (wt == 0x0d) wt_string = "Tangle";
	else if (wt == 0x0e) wt_string = "Immobile";
	else if (wt == 0x92) wt_string = "Sit";
	else if (wt == 0x93) wt_string = "Eat";
	else if (wt == 0x94) wt_string = "Farm";
	else if (wt == 0x98) wt_string = "Ring Bell";
	else if (wt == 0x99) wt_string = "Brawl";
	else if (wt == 0x9a) wt_string = "Mousing";
	else if (wt == 0x9b) wt_string = "Attack Party";
	return wt_string;
}

/* Return the first food or drink object in inventory. */
Obj *U6Actor::inventory_get_food(Obj *container) {
	U6UseCode *uc = (U6UseCode *)Game::get_game()->get_usecode();
	U6LList *inv = container ? container->container : get_inventory_list();
	U6Link *link = nullptr;
	for (link = inv->start(); link != nullptr; link = link->next) {
		Obj *obj = (Obj *)link->data;
		if (uc->is_food(obj))
			return obj;
		if (obj->container) { // search within container
			if ((obj = inventory_get_food(obj)))
				return obj;
		}
	}
	return 0;
}

void U6Actor::inventory_make_all_objs_ok_to_take() {
	U6LList *inventory = get_inventory_list();

	if (!inventory)
		return;

	for (U6Link *link = inventory->start(); link != nullptr;) {
		Obj *obj = (Obj *)link->data;
		link = link->next;

		obj->set_ok_to_take(true, true);
	}

	return;
}
/* Set worktype to normal non-combat activity. */
void U6Actor::revert_worktype() {
	const Party *party = Game::get_game()->get_party();
	if (is_in_party())
		set_worktype(WORKTYPE_U6_IN_PARTY);
	if (party->get_leader_actor() == this)
		set_worktype(WORKTYPE_U6_PLAYER);
}

/* Maximum magic points is derived from Intelligence and base_obj_n. */
uint8 U6Actor::get_maxmagic() const {
	return Game::get_game()->get_script()->actor_get_max_magic_points(this);
}

bool U6Actor::will_not_talk() const {
	if (worktype == WORKTYPE_U6_COMBAT_RETREAT || worktype == 0x12 // guard arrest player
	        || Game::get_game()->is_armageddon()
	        || worktype == WORKTYPE_U6_ATTACK_PARTY || worktype == 0x13) // repel undead and retreat
		return true;
	return false;
}

void U6Actor::handle_lightsource(uint8 hour) {
	Obj *torch = inventory_get_readied_object(ACTOR_ARM);
	if (torch && torch->obj_n != OBJ_U6_TORCH)
		torch = nullptr;
	Obj *torch2 = inventory_get_readied_object(ACTOR_ARM_2);
	if (torch2 && torch2->obj_n != OBJ_U6_TORCH)
		torch2 = nullptr;
	if (torch || torch2) {
		U6UseCode *useCode = (U6UseCode *)Game::get_game()->get_usecode();
		if ((hour < 6 || hour > 18 || (z != 0 && z != 5)
		        || Game::get_game()->get_weather()->is_eclipse())) {
			if (torch && torch->frame_n == 0) {
				if (torch->qty != 1)
					torch->qty = 1;
				useCode->torch(torch, USE_EVENT_USE);
			}
			if (torch2 && torch2->frame_n == 0) {
				if (torch2->qty != 1)
					torch2->qty = 1;
				useCode->torch(torch2, USE_EVENT_USE);
			}
		} else {
			if (torch && torch->frame_n == 1)
				useCode->torch(torch, USE_EVENT_USE);
			if (torch2 && torch2->frame_n == 1)
				useCode->torch(torch2, USE_EVENT_USE);
		}
	}
}

uint8 U6Actor::get_hp_text_color() const {
	uint8 hp_text_color = 0x48; // standard text color

	if (is_poisoned()) // actor is poisoned, display their hp in green
		hp_text_color = 0xa;
	else if (get_hp() < 10) // actor is critical, display their hp in red.
		hp_text_color = 0x0c;

	return hp_text_color;
}

} // End of namespace Nuvie
} // End of namespace Ultima
