/*	$NetBSD: move.c,v 1.13 2011/05/23 23:01:17 joerg Exp $	*/

/*
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Timothy C. Stoehr.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)move.c	8.1 (Berkeley) 5/31/93";
#else
__RCSID("$NetBSD: move.c,v 1.13 2011/05/23 23:01:17 joerg Exp $");
#endif
#endif /* not lint */

/*
 * move.c
 *
 * This source herein may be modified and/or distributed by anybody who
 * so desires, with the following restrictions:
 *    1.)  No portion of this notice shall be removed.
 *    2.)  Credit shall not be taken for the creation of this source.
 *    3.)  This code is not to be traded, sold, or used for personal
 *         gain or profit.
 *
 */

#include "rogue.h"

short m_moves = 0;
boolean jump = 0;
const char you_can_move_again[] = "you can move again";

static boolean can_turn(short, short);
static boolean check_hunger(boolean);
static char gr_dir(void);
static void heal(void);
static boolean next_to_something(int, int);
static void turn_passage(short, boolean);

int
one_move_rogue(short dirch, short pickup)
{
	short row, col;
	object *obj;
	char desc[DCOLS];
	short status, d = 0;	/* XXX: GCC */

	row = rogue.row;
	col = rogue.col;

	if (confused) {
		dirch = gr_dir();
	}
	(void)is_direction(dirch, &d);
	get_dir_rc(d, &row, &col, 1);

	if (!can_move(rogue.row, rogue.col, row, col)) {
		return(MOVE_FAILED);
	}
	if (being_held || bear_trap) {
		if (!(dungeon[row][col] & MONSTER)) {
			if (being_held) {
				messagef(1, "you are being held");
			} else {
				messagef(0, "you are still stuck in the bear trap");
				(void)reg_move();
			}
			return(MOVE_FAILED);
		}
	}
	if (r_teleport) {
		if (rand_percent(R_TELE_PERCENT)) {
			tele();
			return(STOPPED_ON_SOMETHING);
		}
	}
	if (dungeon[row][col] & MONSTER) {
		rogue_hit(object_at(&level_monsters, row, col), 0);
		(void)reg_move();
		return(MOVE_FAILED);
	}
	if (dungeon[row][col] & DOOR) {
		if (cur_room == PASSAGE) {
			cur_room = get_room_number(row, col);
			if (cur_room == NO_ROOM)
				clean_up("one_move_rogue: door to nowhere");
			light_up_room(cur_room);
			wake_room(cur_room, 1, row, col);
		} else {
			light_passage(row, col);
		}
	} else if ((dungeon[rogue.row][rogue.col] & DOOR) &&
		   (dungeon[row][col] & TUNNEL)) {
		light_passage(row, col);
		wake_room(cur_room, 0, rogue.row, rogue.col);
		darken_room(cur_room);
		cur_room = PASSAGE;
	} else if (dungeon[row][col] & TUNNEL) {
			light_passage(row, col);
	}
	mvaddch(rogue.row, rogue.col, get_dungeon_char(rogue.row, rogue.col));
	mvaddch(row, col, rogue.fchar);

	if (!jump) {
		refresh();
	}
	rogue.row = row;
	rogue.col = col;
	if (dungeon[row][col] & OBJECT) {
		if (levitate && pickup) {
			return(STOPPED_ON_SOMETHING);
		}
		if (pickup && !levitate) {
			if ((obj = pick_up(row, col, &status)) != NULL) {
				get_desc(obj, desc, sizeof(desc));
				if (obj->what_is == GOLD) {
					free_object(obj);
					messagef(1, "%s", desc);
					goto NOT_IN_PACK;
				}
			} else if (!status) {
				goto MVED;
			} else {
				goto MOVE_ON;
			}
		} else {
MOVE_ON:
			obj = object_at(&level_objects, row, col);
			get_desc(obj, desc, sizeof(desc));
			messagef(1, "moved onto %s", desc);
			goto NOT_IN_PACK;
		}
		messagef(1, "%s(%c)", desc, obj->ichar);
NOT_IN_PACK:
		(void)reg_move();
		return(STOPPED_ON_SOMETHING);
	}
	if (dungeon[row][col] & (DOOR | STAIRS | TRAP)) {
		if ((!levitate) && (dungeon[row][col] & TRAP)) {
			trap_player(row, col);
		}
		(void)reg_move();
		return(STOPPED_ON_SOMETHING);
	}
MVED:	if (reg_move()) {			/* fainted from hunger */
			return(STOPPED_ON_SOMETHING);
	}
	return((confused ? STOPPED_ON_SOMETHING : MOVED));
}

void
multiple_move_rogue(short dirch)
{
	short row, col;
	short m;

	switch(dirch) {
	case '\010':
	case '\012':
	case '\013':
	case '\014':
	case '\031':
	case '\025':
	case '\016':
	case '\002':
		do {
			row = rogue.row;
			col = rogue.col;
			if (((m = one_move_rogue((dirch + 96), 1)) == MOVE_FAILED) ||
				(m == STOPPED_ON_SOMETHING) ||
				interrupted) {
				break;
			}
		} while (!next_to_something(row, col));
		if (	(!interrupted) && passgo && (m == MOVE_FAILED) &&
				(dungeon[rogue.row][rogue.col] & TUNNEL)) {
			turn_passage(dirch + 96, 0);
		}
		break;
	case 'H':
	case 'J':
	case 'K':
	case 'L':
	case 'B':
	case 'Y':
	case 'U':
	case 'N':
		while ((!interrupted) && (one_move_rogue((dirch + 32), 1) == MOVED))
			;

		if (	(!interrupted) && passgo &&
				(dungeon[rogue.row][rogue.col] & TUNNEL)) {
			turn_passage(dirch + 32, 1);
		}
		break;
	}
}

boolean
is_passable(int row, int col)
{
	if ((row < MIN_ROW) || (row > (DROWS - 2)) || (col < 0) ||
		(col > (DCOLS-1))) {
		return(0);
	}
	if (dungeon[row][col] & HIDDEN) {
		return((dungeon[row][col] & TRAP) ? 1 : 0);
	}
	return(dungeon[row][col] & (FLOOR | TUNNEL | DOOR | STAIRS | TRAP));
}

static boolean
next_to_something(int drow, int dcol)
{
	short i, j, i_end, j_end, row, col;
	short pass_count = 0;
	unsigned short s;

	if (confused) {
		return(1);
	}
	if (blind) {
		return(0);
	}
	i_end = (rogue.row < (DROWS-2)) ? 1 : 0;
	j_end = (rogue.col < (DCOLS-1)) ? 1 : 0;

	for (i = ((rogue.row > MIN_ROW) ? -1 : 0); i <= i_end; i++) {
		for (j = ((rogue.col > 0) ? -1 : 0); j <= j_end; j++) {
			if ((i == 0) && (j == 0)) {
				continue;
			}
			if (((rogue.row+i) == drow) && ((rogue.col+j) == dcol)) {
				continue;
			}
			row = rogue.row + i;
			col = rogue.col + j;
			s = dungeon[row][col];
			if (s & HIDDEN) {
				continue;
			}
			/* If the rogue used to be right, up, left, down, or right of
			 * row,col, and now isn't, then don't stop */
			if (s & (MONSTER | OBJECT | STAIRS)) {
				if (((row == drow) || (col == dcol)) &&
					(!((row == rogue.row) || (col == rogue.col)))) {
					continue;
				}
				return(1);
			}
			if (s & TRAP) {
				if (!(s & HIDDEN)) {
					if (((row == drow) || (col == dcol)) &&
						(!((row == rogue.row) || (col == rogue.col)))) {
						continue;
					}
					return(1);
				}
			}
			if ((((i - j) == 1) || ((i - j) == -1)) && (s & TUNNEL)) {
				if (++pass_count > 1) {
					return(1);
				}
			}
			if ((s & DOOR) && ((i == 0) || (j == 0))) {
					return(1);
			}
		}
	}
	return(0);
}

boolean
can_move(int row1, int col1, int row2, int col2)
{
	if (!is_passable(row2, col2)) {
		return(0);
	}
	if ((row1 != row2) && (col1 != col2)) {
		if ((dungeon[row1][col1] & DOOR) || (dungeon[row2][col2] & DOOR)) {
			return(0);
		}
		if ((!dungeon[row1][col2]) || (!dungeon[row2][col1])) {
			return(0);
		}
	}
	return(1);
}

void
move_onto(void)
{
	short ch, d;
	boolean first_miss = 1;

	while (!is_direction(ch = rgetchar(), &d)) {
		sound_bell();
		if (first_miss) {
			messagef(0, "direction? ");
			first_miss = 0;
		}
	}
	check_message();
	if (ch != CANCEL) {
		(void)one_move_rogue(ch, 0);
	}
}

boolean
is_direction(short c, short *d)
{
	switch(c) {
	case 'h':
		*d = LEFT;
		break;
	case 'j':
		*d = DOWN;
		break;
	case 'k':
		*d = UPWARD;
		break;
	case 'l':
		*d = RIGHT;
		break;
	case 'b':
		*d = DOWNLEFT;
		break;
	case 'y':
		*d = UPLEFT;
		break;
	case 'u':
		*d = UPRIGHT;
		break;
	case 'n':
		*d = DOWNRIGHT;
		break;
	case CANCEL:
		break;
	default:
		return(0);
	}
	return(1);
}

static boolean
check_hunger(boolean msg_only)
{
	short i, n;
	boolean fainted = 0;

	if (rogue.moves_left == HUNGRY) {
		(void)strlcpy(hunger_str, "hungry", sizeof(hunger_str));
		messagef(0, "%s", hunger_str);
		print_stats(STAT_HUNGER);
	}
	if (rogue.moves_left == WEAK) {
		(void)strlcpy(hunger_str, "weak", sizeof(hunger_str));
		messagef(1, "%s", hunger_str);
		print_stats(STAT_HUNGER);
	}
	if (rogue.moves_left <= FAINT) {
		if (rogue.moves_left == FAINT) {
			(void)strlcpy(hunger_str, "faint", sizeof(hunger_str));
			messagef(1, "%s", hunger_str);
			print_stats(STAT_HUNGER);
		}
		n = get_rand(0, (FAINT - rogue.moves_left));
		if (n > 0) {
			fainted = 1;
			if (rand_percent(40)) {
				rogue.moves_left++;
			}
			messagef(1, "you faint");
			for (i = 0; i < n; i++) {
				if (coin_toss()) {
					mv_mons();
				}
			}
			messagef(1, "%s", you_can_move_again);
		}
	}
	if (msg_only) {
		return(fainted);
	}
	if (rogue.moves_left <= STARVE) {
		killed_by(NULL, STARVATION);
	}

	switch(e_rings) {
	/*case -2:
		Subtract 0, i.e. do nothing.
		break;*/
	case -1:
		rogue.moves_left -= (rogue.moves_left % 2);
		break;
	case 0:
		rogue.moves_left--;
		break;
	case 1:
		rogue.moves_left--;
		(void)check_hunger(1);
		rogue.moves_left -= (rogue.moves_left % 2);
		break;
	case 2:
		rogue.moves_left--;
		(void)check_hunger(1);
		rogue.moves_left--;
		break;
	}
	return(fainted);
}

boolean
reg_move(void)
{
	boolean fainted;

	if ((rogue.moves_left <= HUNGRY) || (cur_level >= max_level)) {
		fainted = check_hunger(0);
	} else {
		fainted = 0;
	}

	mv_mons();

	if (++m_moves >= 120) {
		m_moves = 0;
		wanderer();
	}
	if (halluc) {
		if (!(--halluc)) {
			unhallucinate();
		} else {
			hallucinate();
		}
	}
	if (blind) {
		if (!(--blind)) {
			unblind();
		}
	}
	if (confused) {
		if (!(--confused)) {
			unconfuse();
		}
	}
	if (bear_trap) {
		bear_trap--;
	}
	if (levitate) {
		if (!(--levitate)) {
			messagef(1, "you float gently to the ground");
			if (dungeon[rogue.row][rogue.col] & TRAP) {
				trap_player(rogue.row, rogue.col);
			}
		}
	}
	if (haste_self) {
		if (!(--haste_self)) {
			messagef(0, "you feel yourself slowing down");
		}
	}
	heal();
	if (auto_search > 0) {
		search(auto_search, auto_search);
	}
	return(fainted);
}

void
rest(int count)
{
	int i;

	interrupted = 0;

	for (i = 0; i < count; i++) {
		if (interrupted) {
			break;
		}
		(void)reg_move();
	}
}

static char
gr_dir(void)
{
	short d;

	d = get_rand(1, 8);

	switch(d) {
		case 1:
			d = 'j';
			break;
		case 2:
			d = 'k';
			break;
		case 3:
			d = 'l';
			break;
		case 4:
			d = 'h';
			break;
		case 5:
			d = 'y';
			break;
		case 6:
			d = 'u';
			break;
		case 7:
			d = 'b';
			break;
		case 8:
			d = 'n';
			break;
	}
	return(d);
}

static void
heal(void)
{
	static short heal_exp = -1, n, c = 0;
	static boolean alt;

	if (rogue.hp_current == rogue.hp_max) {
		c = 0;
		return;
	}
	if (rogue.exp != heal_exp) {
		heal_exp = rogue.exp;

		switch(heal_exp) {
		case 1:
			n = 20;
			break;
		case 2:
			n = 18;
			break;
		case 3:
			n = 17;
			break;
		case 4:
			n = 14;
			break;
		case 5:
			n = 13;
			break;
		case 6:
			n = 10;
			break;
		case 7:
			n = 9;
			break;
		case 8:
			n = 8;
			break;
		case 9:
			n = 7;
			break;
		case 10:
			n = 4;
			break;
		case 11:
			n = 3;
			break;
		case 12:
		default:
			n = 2;
		}
	}
	if (++c >= n) {
		c = 0;
		rogue.hp_current++;
		if ((alt = !alt) != 0) {
			rogue.hp_current++;
		}
		if ((rogue.hp_current += regeneration) > rogue.hp_max) {
			rogue.hp_current = rogue.hp_max;
		}
		print_stats(STAT_HP);
	}
}

static boolean
can_turn(short nrow, short ncol)
{
	if ((dungeon[nrow][ncol] & TUNNEL) && is_passable(nrow, ncol)) {
		return(1);
	}
	return(0);
}

static void
turn_passage(short dir, boolean fast)
{
	short crow = rogue.row, ccol = rogue.col, turns = 0;
	short ndir = 0;

	if ((dir != 'h') && can_turn(crow, ccol + 1)) {
		turns++;
		ndir = 'l';
	}
	if ((dir != 'l') && can_turn(crow, ccol - 1)) {
		turns++;
		ndir = 'h';
	}
	if ((dir != 'k') && can_turn(crow + 1, ccol)) {
		turns++;
		ndir = 'j';
	}
	if ((dir != 'j') && can_turn(crow - 1, ccol)) {
		turns++;
		ndir = 'k';
	}
	if (turns == 1) {
		multiple_move_rogue(ndir - (fast ? 32 : 96));
	}
}
