root/games/rogue/move.c
/*      $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));
        }
}