/* 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/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/actors/actor_manager.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/game_clock.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/save/obj_list.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/core/weather.h"
#include "ultima/nuvie/script/script.h"
#include "ultima/nuvie/core/player.h"

namespace Ultima {
namespace Nuvie {

static const int PLAYER_BASE_MOVEMENT_COST = 5;

Player::Player(const Configuration *cfg) : config(cfg), _clock(nullptr),
		party(nullptr), actor(nullptr), actor_manager(nullptr), obj_manager(nullptr),
		map_window(nullptr), karma(0), gender(0), questf(0), gargishf(0), alcohol(0),
		current_weapon(0), party_mode(false), mapwindow_centered(false) {
	config->value("config/GameType", game_type);
}

bool Player::init(ObjManager *om, ActorManager *am, MapWindow *mw, GameClock *c, Party *p) {

	_clock = c;
	actor_manager = am;
	obj_manager = om;
	map_window = mw;
	party = p;

	current_weapon = -1;

	init();

	return true;
}

void Player::init() {
	actor = nullptr;

	party_mode = true;
	mapwindow_centered = true;
}

bool Player::load(NuvieIO *objlist) {
	uint8 solo_member_num = OBJLIST_PARTY_MODE;

	init();

// We can get the name from the player actor. --SB-X
	/* objlist->seek(0xf00);

	 objlist->readToBuf((unsigned char *)name,14); // read in Player name.*/

	if (game_type == NUVIE_GAME_U6) {
		objlist->seek(OBJLIST_OFFSET_U6_QUEST_FLAG); // U6 Quest Flag
		questf = objlist->read1();

		objlist->seek(OBJLIST_OFFSET_U6_KARMA); // Player Karma.
		karma = objlist->read1();

		objlist->seek(OBJLIST_OFFSET_U6_ALCOHOL); // Alcohol consumed
		alcohol = objlist->read1();

		objlist->seek(OBJLIST_OFFSET_U6_GARGISH_LANG); // U6 Gargish Flag
		gargishf = objlist->read1();

		objlist->seek(OBJLIST_OFFSET_U6_SOLO_MODE); //Party Mode = 0xff other wise it is solo mode party member number starting from 0.
		solo_member_num = objlist->read1();

		objlist->seek(OBJLIST_OFFSET_U6_GENDER); // Player Gender.
		gender = objlist->read1();
	}

	if (game_type == NUVIE_GAME_MD) {
		objlist->seek(OBJLIST_OFFSET_MD_GENDER); // Player Gender.
		gender = objlist->read1();
	}

	if (solo_member_num == OBJLIST_PARTY_MODE) {
		party_mode = true;
		set_party_mode(find_actor());
	} else
		set_solo_mode(party->get_actor(solo_member_num));

	return true;
}

bool Player::save(NuvieIO *objlist) {
	if (game_type == NUVIE_GAME_U6) {
		objlist->seek(OBJLIST_OFFSET_U6_QUEST_FLAG); // U6 Quest Flag
		objlist->write1(questf);

		objlist->seek(OBJLIST_OFFSET_U6_KARMA); // Player Karma.
		objlist->write1(karma);

		objlist->seek(OBJLIST_OFFSET_U6_ALCOHOL); // Alcohol consumed
		objlist->write1(alcohol);

		objlist->seek(OBJLIST_OFFSET_U6_GARGISH_LANG); // U6 Gargish Flag
		objlist->write1(gargishf);

		objlist->seek(OBJLIST_OFFSET_U6_SOLO_MODE); // solo member num.
		if (party_mode)
			objlist->write1(OBJLIST_PARTY_MODE); // 0xff
		else
			objlist->write1(party->get_member_num(actor)); //write the party member number of the solo actor

		objlist->seek(OBJLIST_OFFSET_U6_GENDER); // Player Gender.
		objlist->write1(gender);
	}

	if (game_type == NUVIE_GAME_MD) {
		objlist->seek(OBJLIST_OFFSET_MD_GENDER); // Player Gender.
		objlist->write1(gender);
	}

	return true;
}

Actor *Player::find_actor() {
	for (int p = 0; p < ACTORMANAGER_MAX_ACTORS; p++) {
		Actor *theActor = actor_manager->get_actor(p);
		if (theActor->get_worktype() == 0x02 && theActor->is_immobile() == false) // WT_U6_PLAYER
			return theActor;
	}

	sint8 party_leader = party->get_leader();

	if (party_leader != -1)
		return party->get_actor(party_leader);

	return actor_manager->get_avatar();
}


// keep MapWindow focused on Player actor, or remove focus
void Player::set_mapwindow_centered(bool state) {
	uint16 x, y;
	uint8 z;

	mapwindow_centered = state;
	if (mapwindow_centered == false)
		return;
	map_window->centerMapOnActor(actor); // center immediately

	get_location(&x, &y, &z);
	actor_manager->updateActors(x, y, z);
	obj_manager->update(x, y, z); // spawn eggs when teleporting. eg red moongate.
}

void Player::set_actor(Actor *new_actor) {
	MsgScroll *scroll = Game::get_game()->get_scroll();
	if (new_actor == nullptr) {
		return;
	}

	if (actor != nullptr) {
		if (party->contains_actor(actor))
			actor->set_worktype(0x01); //WT_U6_IN_PARTY
		else
			actor->set_worktype(0x00); //no worktype
	}
	bool same_actor = (actor == new_actor);
	actor = new_actor;

	actor->set_worktype(0x02); // WT_U6_PLAYER
	actor->delete_pathfinder();

	current_weapon = ACTOR_NO_READIABLE_LOCATION;
	map_window->centerCursor();
	if (same_actor)
		return;
	actor_manager->set_player(actor);
	Std::string prompt = get_name();

	if (game_type == NUVIE_GAME_U6) {
		prompt += ":\n";
	}

	prompt += ">";

	scroll->set_prompt(prompt.c_str());
}

Actor *Player::get_actor() {
	return actor;
}

const Actor *Player::get_actor() const {
	return actor;
}

void Player::get_location(uint16 *ret_x, uint16 *ret_y, uint8 *ret_level) const {
	actor->get_location(ret_x, ret_y, ret_level);
}

uint8 Player::get_location_level() const {
	return actor->z;
}

const char *Player::get_name() {
	if (actor->get_actor_num() == ACTOR_VEHICLE_ID_N)
		return actor_manager->get_avatar()->get_name();

	return actor->get_name(true);
}


/* Add to Player karma. Handle appropriately the karma min/max limits. */
void Player::add_karma(uint8 val) {
	karma = ((karma + val) <= 99) ? karma + val : 99;
}

/* Subtract from Player karma. Handle appropriately the karma min/max limits. */
void Player::subtract_karma(uint8 val) {
	karma = ((karma - val) >= 0) ? karma - val : 0;
}

void Player::subtract_movement_points(uint8 points) {
	Game::get_game()->get_script()->call_actor_subtract_movement_points(get_actor(), points);
}

const char *Player::get_gender_title() const {
	switch (game_type) {
	case NUVIE_GAME_U6 :
		if (gender == 0)
			return "milord";
		else
			return "milady";
	case NUVIE_GAME_MD :
		if (gender == 0)
			return "Sir";
		else
			return "Madam";
	default :
		break;
	}

	return "Sir"; //FIXME is this needed for SE?
}

bool Player::check_moveRelative(sint16 rel_x, sint16 rel_y) {
	if (!actor->moveRelative(rel_x, rel_y, ACTOR_IGNORE_DANGER)) { /**MOVE**/
		ActorError *ret = actor->get_error();
		// FIXME: When in combat, U6 attempts to move party members with role "front" forward instead of swapping.
		if (ret->err == ACTOR_BLOCKED_BY_ACTOR
		        && (game_type != NUVIE_GAME_U6 || actor->obj_n != OBJ_U6_MOUSE) // Only exchange positions if player is not U6 mouse
		        && party->contains_actor(ret->blocking_actor) && ret->blocking_actor->is_immobile() == false)
			ret->blocking_actor->push(actor, ACTOR_PUSH_HERE);
		// There could be more party members at the destination, but U6 ignores them - hence the ACTOR_IGNORE_PARTY_MEMBERS.
		if (!actor->moveRelative(rel_x, rel_y, ACTOR_IGNORE_DANGER | ACTOR_IGNORE_PARTY_MEMBERS)) /**MOVE**/
			return false;
	}
	return true;
}

// walk to adjacent square
void Player::moveRelative(sint16 rel_x, sint16 rel_y, bool mouse_movement) {
	const NuvieDir raft_movement_tbl[] = {
		NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_N, NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_NW, NUVIE_DIR_N,
		NUVIE_DIR_NE, NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_N, NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_NE, NUVIE_DIR_N,
		NUVIE_DIR_NE, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_E, NUVIE_DIR_E, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_NE,
		NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_E, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_SE,
		NUVIE_DIR_S, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_SE, NUVIE_DIR_S, NUVIE_DIR_S, NUVIE_DIR_SW,
		NUVIE_DIR_W, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_SW, NUVIE_DIR_SW, NUVIE_DIR_S, NUVIE_DIR_SW, NUVIE_DIR_W,
		NUVIE_DIR_NW, NUVIE_DIR_W, NUVIE_DIR_SW, NUVIE_DIR_W, NUVIE_DIR_NW, NUVIE_DIR_SW, NUVIE_DIR_W, NUVIE_DIR_W,
		NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_W, NUVIE_DIR_NW, NUVIE_DIR_N, NUVIE_DIR_NW, NUVIE_DIR_W, NUVIE_DIR_NW
	};
	const uint8 ship_cost[8] = {0xA, 5, 3, 4, 5, 4, 3, 5};
	const uint8 skiff_cost[8] = {3, 4, 5, 7, 0xA, 7, 5, 4};

	MovementStatus movementStatus = CAN_MOVE;
	bool can_change_rel_dir = true;
	NuvieDir wind_dir = Game::get_game()->get_weather()->get_wind_dir();
	uint16 x, y;
	uint8 z;
	actor->get_location(&x, &y, &z);

	if (game_type == NUVIE_GAME_U6) {
		if (actor->id_n == 0) { // vehicle actor
			if (actor->obj_n == OBJ_U6_INFLATED_BALLOON &&
			        (!Game::get_game()->has_free_balloon_movement() || !party->has_obj(OBJ_U6_FAN, 0, false))) {
				can_change_rel_dir = false;
				NuvieDir dir = get_reverse_direction(Game::get_game()->get_weather()->get_wind_dir());
				if (dir == NUVIE_DIR_NONE) {
					Game::get_game()->get_scroll()->display_string("Thou canst not move without wind!\n\n");
					Game::get_game()->get_scroll()->display_prompt();
					actor->set_moves_left(0);
					rel_x = 0;
					rel_y = 0;
				} else {
					get_relative_dir(dir, &rel_x, &rel_y);
				}
			} else if (actor->obj_n == OBJ_U6_RAFT) {
				NuvieDir dir = NUVIE_DIR_N;
				can_change_rel_dir = false;
				const Tile *t = Game::get_game()->get_game_map()->get_tile(x, y, z, true);
				if (t->flags1 & TILEFLAG_BLOCKING) { //deep water tiles are blocking. Shore tiles should allow player movement.
					//deep water, so take control away from player.
					if (t->tile_num >= 8 && t->tile_num < 16) {
						dir = static_cast<NuvieDir>(t->tile_num - 8);
					}
					if (wind_dir != NUVIE_DIR_NONE) {
						dir = raft_movement_tbl[dir * 8 + get_reverse_direction(wind_dir)];
					} else {
						dir = get_nuvie_dir_code(dir);
					}

					get_relative_dir(dir, &rel_x, &rel_y);
				}
			}
		} else { // normal actor
			if (alcohol > 3 && NUVIE_RAND() % 4 != 0) {
				rel_x = NUVIE_RAND() % 3 - 1; // stumble and change direction
				rel_y = NUVIE_RAND() % 3 - 1;
				can_change_rel_dir = false;
				Game::get_game()->get_scroll()->display_string("Hic!\n");
			}
		}

		ActorMoveFlags move_flags = ACTOR_IGNORE_DANGER | ACTOR_IGNORE_PARTY_MEMBERS;
		// don't allow diagonal move between blocked tiles (player only)
		if (rel_x && rel_y && !actor->check_move(x + rel_x, y + 0, z, move_flags)
		        && !actor->check_move(x + 0, y + rel_y, z, move_flags)) {
			movementStatus = BLOCKED;
		}
	} else if (game_type == NUVIE_GAME_MD) {
		if (Game::get_game()->get_clock()->get_timer(GAMECLOCK_TIMER_MD_BLUE_BERRY) != 0 && NUVIE_RAND() % 2 == 0) {
			rel_x = NUVIE_RAND() % 3 - 1; // stumble and change direction
			rel_y = NUVIE_RAND() % 3 - 1;
			can_change_rel_dir = false;
			Game::get_game()->get_scroll()->display_string("you are dizzy!\n"); //FIXME need i18n support here.
		}
	}

	if (actor->is_immobile() && actor->id_n != 0)
		movementStatus = BLOCKED;

	if (movementStatus != BLOCKED && game_type != NUVIE_GAME_U6) {
		movementStatus = Game::get_game()->get_script()->call_player_before_move_action(&rel_x, &rel_y);
	}

	if (movementStatus != BLOCKED) {
		if (movementStatus == FORCE_MOVE) {
			actor->moveRelative(rel_x, rel_y, ACTOR_FORCE_MOVE);
		} else if (!check_moveRelative(rel_x, rel_y)) {
			movementStatus = BLOCKED;
			if (mouse_movement && rel_x != 0 && rel_y != 0 && can_change_rel_dir) {
				if (check_moveRelative(rel_x, 0)) { // try x movement only
					rel_y = 0;
					movementStatus = CAN_MOVE;
				} else if (check_moveRelative(0, rel_y)) { // try y movement only
					rel_x = 0;
					movementStatus = CAN_MOVE;
				}
			}
			// Try opening a door FIXME: shouldn't be U6 specific
			if (movementStatus == BLOCKED) {
				if (obj_manager->is_door(x + rel_x, y + rel_y, z))
					try_open_door(x + rel_x, y + rel_y, z);
			}
			if (movementStatus == BLOCKED) {
				Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_BLOCKED);
				if (actor->id_n == 0) //vehicle actor.
					actor->set_moves_left(0); //zero movement points here so U6 can change wind direction by advancing game time.
			}
		}
	}
	actor->set_direction(rel_x, rel_y);

	// post-move
	if (movementStatus != BLOCKED) {
		if (party_mode && party->is_leader(actor)) { // lead party
			party->follow(rel_x, rel_y);
		} else if (actor->id_n == 0 && game_type != NUVIE_GAME_MD) { // using vehicle; drag party along
			MapCoord new_xyz = actor->get_location();
			party->move(new_xyz.x, new_xyz.y, new_xyz.z);
		}

		if (game_type == NUVIE_GAME_U6 && (actor->obj_n == OBJ_U6_INFLATED_BALLOON || actor->obj_n == OBJ_U6_RAFT)) {
			actor->set_moves_left(actor->get_moves_left() - PLAYER_BASE_MOVEMENT_COST);
		} else if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SHIP && wind_dir != WEATHER_WIND_CALM) {
			NuvieDir nuvie_dir = get_direction_code(rel_x, rel_y);
			if (nuvie_dir != NUVIE_DIR_NONE) {
				sint8 dir = get_original_dir_code(nuvie_dir);

				actor->set_moves_left(actor->get_moves_left() - ship_cost[abs(dir - wind_dir)]);
				//DEBUG(0, LEVEL_DEBUGGING, "Ship movement cost = %d\n", ship_cost[abs(dir-wind_dir)]);
			}
		} else if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SKIFF) {
			NuvieDir nuvie_dir = get_direction_code(rel_x, rel_y);
			if (nuvie_dir != NUVIE_DIR_NONE) {
				sint8 dir = get_original_dir_code(nuvie_dir);
				sint8 water_dir = dir;
				const Tile *t = Game::get_game()->get_game_map()->get_tile(x, y, z, true);
				if (t->tile_num >= 8 && t->tile_num < 16) {
					dir = t->tile_num - 8;
				}
				actor->set_moves_left(actor->get_moves_left() - skiff_cost[abs(dir - water_dir)]);
				//DEBUG(0, LEVEL_DEBUGGING, "Skiff movement cost = %d\n", skiff_cost[abs(dir-water_dir)]);
			}
		} else {
			actor->set_moves_left(actor->get_moves_left() - (PLAYER_BASE_MOVEMENT_COST + Game::get_game()->get_game_map()->get_impedance(x, y, z)));
			if (rel_x != 0 && rel_y != 0) // diagonal move, double cost
				actor->set_moves_left(actor->get_moves_left() - PLAYER_BASE_MOVEMENT_COST);
		}
	}

	if (game_type != NUVIE_GAME_U6) {
		Game::get_game()->get_script()->call_player_post_move_action(movementStatus != BLOCKED);
		actor->get_location(&x, &y, &z); //update location in case we have moved.
	}

	// update world around player
	actor_manager->updateActors(x, y, z);
	obj_manager->update(x, y, z); // remove temporary objs, hatch eggs
	_clock->inc_move_counter(); // doesn't update time
	actor_manager->startActors(); // end player turn
}

void Player::try_open_door(uint16 x, uint16 y, uint8 z) {
	UseCode *usecode = Game::get_game()->get_usecode();
	Obj *obj = obj_manager->get_obj(x, y, z);
	if (!usecode->is_door(obj))
		return;
	usecode->use_obj(obj, get_actor());
	subtract_movement_points(MOVE_COST_USE);
	map_window->updateBlacking();
}

// teleport-type move
void Player::move(sint16 new_x, sint16 new_y, uint8 new_level, bool teleport) {
	if (actor->move(new_x, new_y, new_level, ACTOR_FORCE_MOVE)) {
		//map_window->centerMapOnActor(actor);
		if (party->is_leader(actor)) { // lead party
			if (actor_manager->get_avatar()->get_hp() == 0) { // need to end turn if Avatar died
				actor_manager->startActors();
				return;
			}
			party->move(new_x, new_y, new_level); // center everyone first
			party->follow(0, 0); // then try to move them to correct positions
		}
		actor_manager->updateActors(new_x, new_y, new_level);
		if (teleport && new_level != 0 && new_level != 5)
			Game::get_game()->get_weather()->set_wind_dir(NUVIE_DIR_NONE);
		obj_manager->update(new_x, new_y, new_level, teleport); // remove temporary objs, hatch eggs
		// it's still the player's turn
	}
}

void Player::moveLeft() {
	moveRelative(-1, 0);
}

void Player::moveRight() {
	moveRelative(1, 0);
}

void Player::moveUp() {
	moveRelative(0, -1);
}

void Player::moveDown() {
	moveRelative(0, 1);
}

void Player::pass() {
	Game::get_game()->get_script()->call_player_pass();
// uint16 x = actor->x, y = actor->y;
// uint8 z = actor->z;

// Move balloon / raft if required.
	if (game_type == NUVIE_GAME_U6 && (actor->obj_n == OBJ_U6_INFLATED_BALLOON || actor->obj_n == OBJ_U6_RAFT)) {
		if (Game::get_game()->get_weather()->get_wind_dir() != NUVIE_DIR_NONE) {
			moveRelative(0, 0);
			//return;
		}
	}

	if (actor->get_moves_left() > 0)
		actor->set_moves_left(0); // Pass and use up moves

// update world around player
	if (party_mode && party->is_leader(actor)) // lead party
		party->follow(0, 0);
// actor_manager->updateActors(x, y, z); // not needed because position is unchanged
	_clock->inc_move_counter_by_a_minute(); // doesn't update time
	actor_manager->startActors(); // end player turn
//actor_manager->moveActors();
	Game::get_game()->time_changed();
}



/* Enter party mode, with everyone following actor. (must be in the party)
 */
bool Player::set_party_mode(Actor *new_actor) {
	if (party->contains_actor(new_actor) || party->is_in_vehicle()) {
		party_mode = true;
		set_actor(new_actor);
		return true;
	}
	return false;
}


/* Enter solo mode as actor. (must be in the party)
 */
bool Player::set_solo_mode(Actor *new_actor) {
	if (party->contains_actor(new_actor)) {
		if (new_actor->is_charmed()) {
			Game::get_game()->get_scroll()->display_fmt_string("%s fails to respond.\n\n", new_actor->get_name());
			return false;
		}
		party_mode = false;
		set_actor(new_actor);
		return true;
	}
	return false;
}


/* Returns the delay in continuous movement for the actor type we control.
 */
uint32 Player::get_walk_delay() const {
	if (game_type != NUVIE_GAME_U6)
		return 125; // normal movement about 8 spaces per second

	if (actor->obj_n == OBJ_U6_BALLOON_BASKET)
		return 10; // 10x normal (wow!)
	else if (actor->obj_n == OBJ_U6_SHIP)
		return 20; // 5x normal
	else if (actor->obj_n == OBJ_U6_SKIFF)
		return 50; // 2x normal
	else if (actor->obj_n == OBJ_U6_RAFT)
		return 100; // normal
	else if (actor->obj_n == OBJ_U6_HORSE_WITH_RIDER && party->is_everyone_horsed())
		return 50; // 2x normal
	else
		return 125; // normal movement about 8 spaces per second
}


/* Returns true if it's time for the player to take another step.
 * (call during continuous movement)
 */
bool Player::check_walk_delay() {
	static uint32 walk_delay = 0, // start with no delay
	              last_time = _clock->get_ticks();
	uint32 this_time = _clock->get_ticks();
	uint32 time_passed = this_time - last_time;

	// subtract time_passed until delay is 0
	if (sint32(walk_delay - time_passed) < 0)
		walk_delay = 0;
	else
		walk_delay -= time_passed;
	last_time = this_time; // set each call to get time_passed
	if (walk_delay == 0) {
		walk_delay = get_walk_delay(); // reset
		return true;
	}
	return false; // not time yet
}

bool Player::weapon_can_hit(uint16 x, uint16 y) {
	return actor->weapon_can_hit(actor->get_weapon(current_weapon), x, y);
}

void Player::attack_select_init(bool use_attack_text) {
	uint16 x, y;
	uint8 z;

	current_weapon = ACTOR_NO_READIABLE_LOCATION;

	if (attack_select_next_weapon(false, use_attack_text) == false)
		attack_select_weapon_at_location(ACTOR_NO_READIABLE_LOCATION, use_attack_text); // attack with hands.

	map_window->centerCursor();

	CombatTarget target = party->get_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
	Actor *target_actor = nullptr;

	switch (target.type) {
	case TARGET_ACTOR :
		target_actor = actor_manager->get_actor(target.actor_num);
		uint16 target_x, target_y;
		map_window->get_pos(&x, &y, &z);
		target_x = x;
		target_y = y;
		if (target_actor && target_actor->is_onscreen() && target_actor->is_alive() && target_actor->is_visible() && actor->weapon_can_hit(actor->get_weapon(current_weapon), target_actor, &target_x, &target_y)) {
			map_window->moveCursor(target_x - x, target_y - y);
		} else {
			party->clear_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
		}
		break;

	case TARGET_LOCATION :
		if (target.loc.z == actor->get_z() && weapon_can_hit(target.loc.x, target.loc.y)) {
			map_window->get_pos(&x, &y, &z);
			map_window->moveCursor(target.loc.x - x, target.loc.y - y);
		} else {
			party->clear_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor));
		}
		break;

	default :
		break;
	}

	return;
}

bool Player::attack_select_next_weapon(bool add_newline, bool use_attack_text) {
	sint8 i;

	for (i = current_weapon + 1; i < ACTOR_MAX_READIED_OBJECTS; i++) {
		if (attack_select_weapon_at_location(i, add_newline, use_attack_text) == true)
			return true;
	}

	return false;
}

bool Player::attack_select_weapon_at_location(sint8 location, bool add_newline, bool use_attack_text) {
	const CombatType *weapon;
	MsgScroll *scroll = Game::get_game()->get_scroll();

	if (location == ACTOR_NO_READIABLE_LOCATION) {
		current_weapon = location;
		if (use_attack_text == false)
			return true;
		if (add_newline)
			scroll->display_string("\n");
		if (game_type == NUVIE_GAME_U6 && actor->obj_n == OBJ_U6_SHIP)
			scroll->display_string("Attack with ship cannons-");
		else
			scroll->display_string("Attack with bare hands-");

		return true;
	}

	weapon = actor->get_weapon(location);

	if (weapon && weapon->attack > 0) {
		current_weapon = location;
		if (use_attack_text == false)
			return true;
		if (add_newline)
			scroll->display_string("\n");
		scroll->display_fmt_string("Attack with %s-", obj_manager->get_obj_name(weapon->obj_n));
		return true;
	}

	return false;
}

void Player::attack(MapCoord target, Actor *target_actor) {
	MsgScroll *scroll = Game::get_game()->get_scroll();

	if (weapon_can_hit(target.x, target.y)) {
		if (!target_actor)
			target_actor = actor_manager->get_actor(target.x, target.y, actor->get_z());

		actor->attack(current_weapon, target, target_actor);

		if (target_actor) {
			party->set_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor), target_actor);
		} else {
			Obj *target_obj = obj_manager->get_obj(target.x, target.y, actor->get_z());
			if (target_obj) {
				party->set_combat_target(actor->id_n == 0 ? 0 : party->get_member_num(actor), MapCoord(target_obj));
			}
		}
	} else
		scroll->display_string("\nOut of range!\n");

//actor_manager->startActors(); // end player turn
	return;
}

// Switch to controlling another actor
void Player::update_player(Actor *next_player) {
	MsgScroll *scroll = Game::get_game()->get_scroll();
	bool same_actor = (next_player == get_actor());
	set_actor(next_player); // redirects to ActorManager::set_player()
	set_mapwindow_centered(true);

	if (!scroll->can_display_prompt() && same_actor)
		return;
	scroll->display_string("\n");
	scroll->display_prompt();
}

/* Rest and repair ship. */
void Player::repairShip() {
	MsgScroll *scroll = Game::get_game()->get_scroll();
	Actor *ship = get_actor();

	if (ship->get_obj_n() != OBJ_U6_SHIP)
		return;
	// ship actor's health is hull strength
	if (ship->get_hp() + 5 > 100) ship->set_hp(100);
	else                       ship->set_hp(ship->get_hp() + 5);

	scroll->display_fmt_string("Hull Strength: %d%%\n", ship->get_hp());

	Game::get_game()->get_script()->call_advance_time(5);
	Game::get_game()->time_changed();
}

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