diff options
Diffstat (limited to 'src/cmd2.cc')
-rw-r--r-- | src/cmd2.cc | 5013 |
1 files changed, 5013 insertions, 0 deletions
diff --git a/src/cmd2.cc b/src/cmd2.cc new file mode 100644 index 00000000..768e79c0 --- /dev/null +++ b/src/cmd2.cc @@ -0,0 +1,5013 @@ +/* + * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke + * + * This software may be copied and distributed for educational, research, and + * not for profit purposes provided that this copyright and statement are + * included in all such copies. + */ + +#include "cmd2.hpp" + +#include "bldg.hpp" +#include "cave.hpp" +#include "cave_type.hpp" +#include "cmd1.hpp" +#include "dungeon_info_type.hpp" +#include "feature_type.hpp" +#include "files.hpp" +#include "gods.hpp" +#include "hook_chat_in.hpp" +#include "hook_enter_dungeon_in.hpp" +#include "hook_give_in.hpp" +#include "hook_stair_in.hpp" +#include "hook_stair_out.hpp" +#include "hooks.hpp" +#include "levels.hpp" +#include "monster2.hpp" +#include "monster3.hpp" +#include "monster_race.hpp" +#include "monster_type.hpp" +#include "object1.hpp" +#include "object2.hpp" +#include "object_kind.hpp" +#include "options.hpp" +#include "player_type.hpp" +#include "skills.hpp" +#include "spells1.hpp" +#include "spells2.hpp" +#include "spells3.hpp" +#include "stats.hpp" +#include "tables.hpp" +#include "trap_type.hpp" +#include "traps.hpp" +#include "util.hpp" +#include "util.h" +#include "variable.h" +#include "variable.hpp" +#include "wilderness_map.hpp" +#include "wilderness_type_info.hpp" +#include "xtra1.hpp" +#include "xtra2.hpp" + +#include <chrono> +#include <thread> + +using std::this_thread::sleep_for; +using std::chrono::milliseconds; + +void do_cmd_immovable_special(void); + +/* + * Try to bash an altar + */ +static bool_ do_cmd_bash_altar(int y, int x) +{ + msg_print("Are you mad? You want to anger the gods?"); + return (FALSE); +} + + +/* + * Try to bash a fountain + */ +static bool_ do_cmd_bash_fountain(int y, int x) +{ + int bash, temp; + + bool_ more = TRUE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_BASH_DOOR)) + { + msg_print("You cannot do that."); + + return (FALSE); + } + + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("You smash into the fountain!"); + + /* Hack -- Bash power based on strength */ + /* (Ranges from 3 to 20 to 100 to 200) */ + bash = adj_str_blow[p_ptr->stat_ind[A_STR]]; + + /* Compare bash power to door power XXX XXX XXX */ + temp = (bash - 50); + + /* Hack -- always have a chance */ + if (temp < 1) temp = 1; + + /* Hack -- attempt to bash down the door */ + if (rand_int(200) < temp) + { + /* Message */ + msg_print("The fountain breaks!"); + + fire_ball(GF_WATER, 5, damroll(6, 8), 2); + + cave_set_feat(y, x, FEAT_DEEP_WATER); + more = FALSE; + } + + return (more); +} + +/* + * Stair hooks + */ +static bool_ stair_hooks(stairs_direction direction) +{ + hook_stair_in in = { direction }; + hook_stair_out out = { TRUE }; /* Allow by default */ + process_hooks_new(HOOK_STAIR, &in, &out); + return (!out.allow); +} + + +/* + * Go up one level + */ +void do_cmd_go_up(void) +{ + bool_ go_up = FALSE, go_up_many = FALSE, prob_traveling = FALSE; + + cave_type *c_ptr; + + int oldl = dun_level; + + dungeon_info_type *d_ptr = &d_info[dungeon_type]; + + + /* Player grid */ + c_ptr = &cave[p_ptr->py][p_ptr->px]; + + /* Can we ? */ + if (stair_hooks(STAIRS_UP)) + { + return; + } + + /* Normal up stairs */ + if ((c_ptr->feat == FEAT_LESS) || (c_ptr->feat == FEAT_WAY_LESS)) + { + if (!dun_level) + { + go_up = TRUE; + } + else if ((dungeon_flags2 & DF2_ASK_LEAVE)) + { + go_up = get_check("Leave this unique level forever? "); + } + else if (confirm_stairs) + { + go_up = get_check("Really leave the level? "); + } + else + { + go_up = TRUE; + } + } + + /* Shaft up */ + else if (c_ptr->feat == FEAT_SHAFT_UP) + { + if (dun_level == 1) + { + go_up = TRUE; + } + else if ((dungeon_flags2 & DF2_ASK_LEAVE)) + { + go_up = get_check("Leave this unique level forever? "); + } + else if (confirm_stairs) + { + go_up_many = get_check("Really leave the level? "); + } + else + { + go_up_many = TRUE; + } + } + + /* Quest exit */ + else if (c_ptr->feat == FEAT_QUEST_EXIT) + { + leaving_quest = p_ptr->inside_quest; + + if ((dungeon_flags2 & DF2_ASK_LEAVE) && + !get_check("Leave this unique level forever? ")) + return; + + p_ptr->inside_quest = c_ptr->special; + dun_level = 0; + p_ptr->oldpx = 0; + p_ptr->oldpy = 0; + p_ptr->leaving = TRUE; + + return; + } + + /* Exits to previous area in flat terrains */ + else if (!(dungeon_flags1 & DF1_FLAT) && + p_ptr->prob_travel && !p_ptr->inside_quest) + { + if (d_ptr->mindepth == dun_level) return; + + if (dungeon_flags2 & DF2_NO_EASY_MOVE) + { + msg_print("Some powerful force prevents your from teleporting."); + return; + } + + prob_traveling = TRUE; + + if (confirm_stairs) + { + if (get_check("Really leave the level? ")) + go_up = TRUE; + } + else + { + go_up = TRUE; + } + } + else + { + msg_print("I see no up staircase here."); + return; + } + + if (go_up || go_up_many) + { + + energy_use = 0; + + /* Success */ + if (c_ptr->feat == FEAT_WAY_LESS) + msg_print("You enter the previous area."); + else + msg_print("You enter a maze of up staircases."); + + autosave_checkpoint(); + + if (p_ptr->inside_quest) + { + dun_level = 1; + leaving_quest = p_ptr->inside_quest; + + p_ptr->inside_quest = c_ptr->special; + } + + /* Create a way back */ + if (go_up_many) + create_down_shaft = TRUE; + else + create_down_stair = TRUE; + + /* New depth */ + if (go_up) + dun_level--; + else + { + dun_level -= randint(3) + 1; + if (dun_level <= 0) dun_level = 0; + } + + if (c_ptr->special && (!prob_traveling)) + { + dun_level = oldl; + dun_level = get_flevel(); + dungeon_type = c_ptr->special; + dun_level += d_info[dungeon_type].mindepth; + } + + /* Leaving */ + p_ptr->leaving = TRUE; + } +} + + +/* + * Returns TRUE if we are in the Between... + */ +static bool_ between_effect(void) +{ + byte bx, by; + + + if (cave[p_ptr->py][p_ptr->px].feat == FEAT_BETWEEN) + { + + bx = cave[p_ptr->py][p_ptr->px].special & 255; + by = cave[p_ptr->py][p_ptr->px].special >> 8; + + msg_print("You fall into the void."); + msg_print("Brrrr! It's deadly cold."); + + swap_position(by, bx); + + /* To avoid being teleported back */ + energy_use = 100; + + return (TRUE); + } + + else if (cave[p_ptr->py][p_ptr->px].feat == FEAT_BETWEEN2) + { + between_exit *be_ptr = &between_exits[cave[p_ptr->py][p_ptr->px].special]; + + p_ptr->wild_mode = FALSE; + p_ptr->wilderness_x = be_ptr->wild_x; + p_ptr->wilderness_y = be_ptr->wild_y; + p_ptr->oldpx = p_ptr->px = be_ptr->px; + p_ptr->oldpy = p_ptr->py = be_ptr->py; + dungeon_type = be_ptr->d_idx; + dun_level = be_ptr->level; + p_ptr->leaving = TRUE; + + return (TRUE); + } + else + return (FALSE); +} + +/* + * Go down one level + */ +void do_cmd_go_down(void) +{ + cave_type *c_ptr; + + bool_ go_down = FALSE, go_down_many = FALSE, prob_traveling = FALSE; + + bool_ fall_trap = FALSE; + + char i; + + int old_dun = dun_level; + + dungeon_info_type *d_ptr = &d_info[dungeon_type]; + + + /* MUST be actived now */ + if (between_effect()) return; + + /* Player grid */ + c_ptr = &cave[p_ptr->py][p_ptr->px]; + + if (p_ptr->astral && (dun_level == 98)) return; + + if (c_ptr->t_idx == TRAP_OF_SINKING) fall_trap = TRUE; + + /* test if on special level */ + if ((dungeon_flags2 & DF2_ASK_LEAVE)) + { + prt("Leave this unique level forever (y/n) ? ", 0, 0); + flush(); + i = inkey(); + prt("", 0, 0); + if (i != 'y') return; + } + + /* Can we ? */ + if (stair_hooks(STAIRS_DOWN)) + { + return; + } + + /* Normal up stairs */ + if (c_ptr->feat == FEAT_SHAFT_DOWN) + { + if (!dun_level) + { + go_down = TRUE; + + /* Save old player position */ + p_ptr->oldpx = p_ptr->px; + p_ptr->oldpy = p_ptr->py; + } + else + { + if (confirm_stairs) + { + if (get_check("Really leave the level? ")) + go_down_many = TRUE; + } + else + { + go_down_many = TRUE; + } + } + } + + /* Normal stairs */ + else if ((c_ptr->feat == FEAT_MORE) || (c_ptr->feat == FEAT_WAY_MORE)) + { + if (p_ptr->prob_travel) + { + if (d_ptr->maxdepth == dun_level) return; + } + if (!dun_level) + { + go_down = TRUE; + + /* Save old player position */ + p_ptr->oldpx = p_ptr->px; + p_ptr->oldpy = p_ptr->py; + } + else + { + if (confirm_stairs) + { + if (get_check("Really leave the level? ")) + go_down = TRUE; + } + else + { + go_down = TRUE; + } + } + } + + /* Handle quest areas -KMW- */ + else if (c_ptr->feat == FEAT_QUEST_ENTER) + { + /* Enter quest level */ + enter_quest(); + + return; + } + + else if (!(dungeon_flags1 & DF1_FLAT) && + p_ptr->prob_travel && !p_ptr->inside_quest) + { + if (d_ptr->maxdepth == dun_level) return; + + if (dungeon_flags2 & DF2_NO_EASY_MOVE) + { + msg_print("Some powerfull force prevents your from teleporting."); + return; + } + + prob_traveling = TRUE; + + if (confirm_stairs) + { + if (get_check("Really leave the level? ")) + go_down = TRUE; + } + else + { + go_down = TRUE; + } + } + + else if (!(fall_trap)) + { + msg_print("I see no down staircase here."); + return; + } + + if (go_down || go_down_many) + { + energy_use = 0; + + if (fall_trap) + msg_print("You deliberately jump through the trap door."); + else + { + if (c_ptr->feat == FEAT_WAY_MORE) + msg_print("You enter the next area."); + else + msg_print("You enter a maze of down staircases."); + } + + autosave_checkpoint(); + + /* Go down */ + if (go_down) + { + dun_level++; + } + else if (go_down_many) + { + int i = randint(3) + 1, j; + + for (j = 1; j < i; j++) + { + dun_level++; + if (is_quest(dun_level + i - 1)) break; + if (d_ptr->maxdepth == dun_level) break; + } + } + + /* We change place */ + if (c_ptr->special && (!prob_traveling)) + { + if (d_info[c_ptr->special].min_plev <= p_ptr->lev) + { + dungeon_info_type *d_ptr = &d_info[c_ptr->special]; + + /* Do the lua scripts refuse ? ;) */ + { + struct hook_enter_dungeon_in in = { c_ptr->special }; + if (process_hooks_new(HOOK_ENTER_DUNGEON, &in, NULL)) + { + dun_level = old_dun; + return; + } + } + + /* Ok go in the new dungeon */ + dungeon_type = c_ptr->special; + d_ptr = &d_info[dungeon_type]; + + if ((p_ptr->wilderness_x == d_ptr->ix) && + (p_ptr->wilderness_y == d_ptr->iy)) + { + dun_level = d_ptr->mindepth; + } + else if ((p_ptr->wilderness_x == d_ptr->ox) && + (p_ptr->wilderness_y == d_ptr->oy)) + { + dun_level = d_ptr->maxdepth; + } + else + { + dun_level = d_ptr->mindepth; + } + + msg_format("You go into %s", d_info[dungeon_type].text); + } + else + { + msg_print + ("You don't feel yourself experienced enough to go there..."); + dun_level = old_dun; + return; + } + } + + /* Leaving */ + p_ptr->leaving = TRUE; + + if (!fall_trap) + { + /* Create a way back */ + if (go_down_many) + create_up_shaft = TRUE; + else + create_up_stair = TRUE; + } + } +} + + + +/* + * Simple command to "search" for one turn + */ +void do_cmd_search(void) +{ + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Take a turn */ + energy_use = 100; + + /* Search */ + search(); +} + + +/* + * Hack -- toggle search mode + */ +void do_cmd_toggle_search(void) +{ + p_ptr->update |= (PU_BONUS); + p_ptr->redraw |= (PR_FRAME); + p_ptr->searching = !p_ptr->searching; +} + + + +/* + * Determine if a grid contains a chest + */ +static s16b chest_check(int y, int x) +{ + cave_type *c_ptr = &cave[y][x]; + + /* Scan all objects in the grid */ + for (auto const this_o_idx: c_ptr->o_idxs) + { + object_type * o_ptr; + + /* Acquire object */ + o_ptr = &o_list[this_o_idx]; + + /* Skip unknown chests XXX XXX */ + /* if (!o_ptr->marked) continue; */ + + /* Check for chest */ + if (o_ptr->tval == TV_CHEST) return (this_o_idx); + } + + /* No chest */ + return (0); +} + + +/* + * Allocates objects upon opening a chest -BEN- + * + * Disperse treasures from the given chest, centered at (x,y). + * + * Small chests often contain "gold", while Large chests always contain + * items. Wooden chests contain 2 items, Iron chests contain 4 items, + * and Steel chests contain 6 items. The "value" of the items in a + * chest is based on the "power" of the chest, which is in turn based + * on the level on which the chest is generated. + */ +static void chest_death(int y, int x, s16b o_idx) +{ + int number; + + bool_ small; + + object_type forge; + object_type *q_ptr; + + object_type *o_ptr = &o_list[o_idx]; + + + /* Small chests often hold "gold" */ + small = (o_ptr->sval < SV_CHEST_MIN_LARGE); + + /* Determine how much to drop (see above) */ + number = (o_ptr->sval % SV_CHEST_MIN_LARGE) * 2; + + /* Zero pval means empty chest */ + if (!o_ptr->pval) number = 0; + + /* Opening a chest */ + opening_chest = TRUE; + + /* Determine the "value" of the items */ + object_level = ABS(o_ptr->pval) + 10; + + /* Drop some objects (non-chests) */ + for (; number > 0; --number) + { + /* Get local object */ + q_ptr = &forge; + + /* Wipe the object */ + object_wipe(q_ptr); + + /* Small chests often drop gold */ + if (small && (rand_int(100) < 75)) + { + /* Make some gold */ + if (!make_gold(q_ptr)) continue; + } + + /* Otherwise drop an item */ + else + { + /* Make an object */ + if (!make_object(q_ptr, FALSE, FALSE, d_info[dungeon_type].objs)) + continue; + } + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + } + + /* Reset the object level */ + object_level = dun_level; + + /* No longer opening a chest */ + opening_chest = FALSE; + + /* Empty */ + o_ptr->pval = 0; + o_ptr->pval2 = 0; + + /* Known */ + object_known(o_ptr); +} + + +/* + * Chests have traps too. + * + * Exploding chest destroys contents (and traps). + * Note that the chest itself is never destroyed. + */ +static void chest_trap(int y, int x, s16b o_idx) +{ + int trap; + + object_type *o_ptr = &o_list[o_idx]; + + bool_ ident = FALSE; + + + /* Ignore disarmed chests */ + if (o_ptr->pval <= 0) return; + + /* Obtain the trap */ + trap = o_ptr->pval; + + /* Message */ + msg_print("You found a trap!"); + + /* Set off trap */ + ident = player_activate_trap_type(y, x, o_ptr, o_idx); + if (ident) + { + t_info[o_ptr->pval].ident = TRUE; + msg_format("You identified the trap as %s.", + t_info[trap].name); + } +} + + +/* + * Attempt to open the given chest at the given location + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_open_chest(int y, int x, s16b o_idx) +{ + int i, j; + + bool_ flag = TRUE; + + bool_ more = FALSE; + + object_type *o_ptr = &o_list[o_idx]; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_OPEN_DOOR)) + { + msg_print("You cannot open chests."); + + return (FALSE); + } + + /* Take a turn */ + energy_use = 100; + + /* Attempt to unlock it */ + if (o_ptr->pval > 0) + { + /* Assume locked, and thus not open */ + flag = FALSE; + + /* Get the "disarm" factor */ + i = p_ptr->skill_dis; + + /* Penalize some conditions */ + if (p_ptr->blind || no_lite()) i = i / 10; + if (p_ptr->confused || p_ptr->image) i = i / 10; + + /* Extract the difficulty */ + j = i - o_ptr->pval; + + /* Always have a small chance of success */ + if (j < 2) j = 2; + + /* Success -- May still have traps */ + if (rand_int(100) < j) + { + msg_print("You have picked the lock."); + gain_exp(1); + flag = TRUE; + } + + /* Failure -- Keep trying */ + else + { + /* We may continue repeating */ + more = TRUE; + + if (flush_failure) flush(); + + msg_print("You failed to pick the lock."); + } + } + + /* Allowed to open */ + if (flag) + { + /* Apply chest traps, if any */ + chest_trap(y, x, o_idx); + + /* Let the Chest drop items */ + chest_death(y, x, o_idx); + } + + /* Result */ + return (more); +} + + +/* + * Original code by TNB, improvement for Angband 2.9.3 by rr9 + * Slightly modified for ToME because of its trap implementation + */ + +/* + * Return TRUE if the given grid is an open door + */ +static bool_ is_open(cave_type *c_ptr) +{ + return (c_ptr->feat == FEAT_OPEN); +} + + +/* + * Return TRUE if the given grid is a closed door + */ +static bool_ is_closed(cave_type *c_ptr) +{ + byte feat; + + if (c_ptr->mimic) feat = c_ptr->mimic; + else feat = c_ptr->feat; + + return ((feat >= FEAT_DOOR_HEAD) && (feat <= FEAT_DOOR_TAIL)); +} + + +/* + * Return TRUE if the given grid has a trap + */ +static bool_ is_trap(cave_type *c_ptr) +{ + return ((c_ptr->info & (CAVE_TRDT)) != 0); +} + + +/* + * Return the number of doors/traps around (or under) + * the character using the filter function 'test' + */ +static int count_feats(int *y, int *x, bool_ (*test) (cave_type *c_ptr), + bool_ under) +{ + int d; + + int xx, yy; + + int count; + + + /* Clear match counter */ + count = 0; + + /* Check around (and under) the character */ + for (d = 0; d < 9; d++) + { + /* Ignore current grid if told so -- See tables.c */ + if ((d == 8) && !under) continue; + + /* Extract adjacent (legal) location */ + yy = p_ptr->py + ddy_ddd[d]; + xx = p_ptr->px + ddx_ddd[d]; + + /* Paranoia */ + if (!in_bounds(yy, xx)) continue; + + /* Must have knowledge */ + if (!(cave[yy][xx].info & (CAVE_MARK))) continue; + + /* Not looking for this feature */ + if (!(*test) (&cave[yy][xx])) continue; + + /* Count it */ + count++; + + /* Remember the location. Only meaningful if there's + exactly one match */ + *y = yy; + *x = xx; + } + + /* All done */ + return (count); +} + + +/* + * Return the number of chests around (or under) the character. + * If requested, count only trapped chests. + */ +static int count_chests(int *y, int *x, bool_ trapped) +{ + int d, count, o_idx; + + object_type *o_ptr; + + + /* Count how many matches */ + count = 0; + + /* Check around (and under) the character */ + for (d = 0; d < 9; d++) + { + + /* Extract adjacent (legal) location */ + int yy = p_ptr->py + ddy_ddd[d]; + int xx = p_ptr->px + ddx_ddd[d]; + + /* No (visible) chest is there */ + if ((o_idx = chest_check(yy, xx)) == 0) continue; + + /* Grab the object */ + o_ptr = &o_list[o_idx]; + + /* Already open */ + if (o_ptr->pval == 0) continue; + + /* No (known) traps here */ + if (trapped && (!object_known_p(o_ptr) || !o_ptr->pval)) continue; + + /* OK */ + ++count; + + /* Remember the location. Only useful if only one match */ + *y = yy; + *x = xx; + } + + /* All done */ + return (count); +} + + +/* + * Convert an adjacent location to a direction. + */ +static int coords_to_dir(int y, int x) +{ + int d[3][3] = + { + {7, 4, 1}, + {8, 5, 2}, + {9, 6, 3} }; + + int dy, dx; + + + dy = y - p_ptr->py; + dx = x - p_ptr->px; + + /* Paranoia */ + if (ABS(dx) > 1 || ABS(dy) > 1) return (0); + + return d[dx + 1][dy + 1]; +} + + +/* + * Perform the basic "open" command on doors + * + * Assume destination is a closed/locked/jammed door + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_open_aux(int y, int x, int dir) +{ + int i, j; + + cave_type *c_ptr; + + bool_ more = FALSE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_OPEN_DOOR)) + { + msg_print("You cannot open doors."); + + return (FALSE); + } + + /* Take a turn */ + energy_use = 100; + + /* Get requested grid */ + c_ptr = &cave[y][x]; + + /* Jammed door */ + if (c_ptr->feat >= FEAT_DOOR_HEAD + 0x08) + { + /* Stuck */ + msg_print("The door appears to be stuck."); + } + + /* Locked door */ + else if (c_ptr->feat >= FEAT_DOOR_HEAD + 0x01) + { + /* Disarm factor */ + i = p_ptr->skill_dis; + + /* Penalize some conditions */ + if (p_ptr->blind || no_lite()) i = i / 10; + if (p_ptr->confused || p_ptr->image) i = i / 10; + + /* Extract the lock power */ + j = c_ptr->feat - FEAT_DOOR_HEAD; + + /* Extract the difficulty XXX XXX XXX */ + j = i - (j * 4); + + /* Always have a small chance of success */ + if (j < 2) j = 2; + + /* Success */ + if (rand_int(100) < j) + { + /* Message */ + msg_print("You have picked the lock."); + + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + + /* Open the door */ + cave_set_feat(y, x, FEAT_OPEN); + + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_MONSTERS | PU_MON_LITE); + + /* Sound */ + sound(SOUND_OPENDOOR); + + /* Experience */ + gain_exp(1); + } + + /* Failure */ + else + { + /* Failure */ + if (flush_failure) flush(); + + /* Message */ + msg_print("You failed to pick the lock."); + + /* We may keep trying */ + more = TRUE; + } + } + + /* Closed door */ + else + { + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + + /* Open the door */ + cave_set_feat(y, x, FEAT_OPEN); + + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_MONSTERS | PU_MON_LITE); + + /* Sound */ + sound(SOUND_OPENDOOR); + } + + /* Result */ + return (more); +} + + + +/* + * Open a closed/locked/jammed door or a closed/locked chest. + * + * Unlocking a locked door/chest is worth one experience point. + */ +void do_cmd_open(void) +{ + int y, x, dir; + + s16b o_idx; + + cave_type *c_ptr; + + bool_ more = FALSE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_OPEN_DOOR)) + { + msg_print("You cannot open doors."); + + return; + } + + /* Pick a direction if there's an obvious target */ + { + int num_doors, num_chests; + + /* Count closed doors (locked or jammed) */ + num_doors = count_feats(&y, &x, is_closed, FALSE); + + /* Count chests (locked) */ + num_chests = count_chests(&y, &x, FALSE); + + /* There is nothing the player can open */ + if ((num_doors + num_chests) == 0) + { + /* Message */ + msg_print("You see nothing there to open."); + + /* Done */ + return; + } + + /* Set direction if there is only one target */ + else if ((num_doors + num_chests) == 1) + { + command_dir = coords_to_dir(y, x); + } + } + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Get requested location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get requested grid */ + c_ptr = &cave[y][x]; + + /* Check for chest */ + o_idx = chest_check(y, x); + + /* Nothing useful */ + if (!((c_ptr->feat >= FEAT_DOOR_HEAD) && + (c_ptr->feat <= FEAT_DOOR_TAIL)) && !o_idx) + { + /* Message */ + msg_print("You see nothing there to open."); + } + + /* Monster in the way */ + else if (c_ptr->m_idx) + { + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + /* Handle chests */ + else if (o_idx) + { + /* Open the chest */ + more = do_cmd_open_chest(y, x, o_idx); + } + + /* Handle doors */ + else + { + /* Open the door */ + more = do_cmd_open_aux(y, x, dir); + } + } + + /* Cancel repeat unless we may continue */ + if (!more) disturb(0); +} + + + +/* + * Perform the basic "close" command + * + * Assume destination is an open/broken door + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_close_aux(int y, int x, int dir) +{ + cave_type *c_ptr; + + bool_ more = FALSE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_OPEN_DOOR)) + { + msg_print("You cannot close doors."); + + return (FALSE); + } + + /* Take a turn */ + energy_use = 100; + + /* Get grid and contents */ + c_ptr = &cave[y][x]; + + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + + /* Broken door */ + if (c_ptr->feat == FEAT_BROKEN) + { + /* Message */ + msg_print("The door appears to be broken."); + } + + /* Open door */ + else + { + /* Close the door */ + cave_set_feat(y, x, FEAT_DOOR_HEAD + 0x00); + + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_MONSTERS | PU_MON_LITE); + + /* Sound */ + sound(SOUND_SHUTDOOR); + } + + /* Result */ + return (more); +} + + +/* + * Close an open door. + */ +void do_cmd_close(void) +{ + int y, x, dir; + + cave_type *c_ptr; + + bool_ more = FALSE; + + + /* Pick a direction if there's an obvious choice */ + { + int num_doors; + + /* Count open doors */ + num_doors = count_feats(&y, &x, is_open, FALSE); + + /* There are no doors the player can close */ + if (num_doors == 0) + { + /* Message */ + msg_print("You see nothing there to close."); + + /* Done */ + return; + } + + /* Exactly one closeable door */ + else if (num_doors == 1) + { + command_dir = coords_to_dir(y, x); + } + } + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Get requested location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid and contents */ + c_ptr = &cave[y][x]; + + /* Require open/broken door */ + if ((c_ptr->feat != FEAT_OPEN) && (c_ptr->feat != FEAT_BROKEN)) + { + /* Message */ + msg_print("You see nothing there to close."); + } + + /* Monster in the way */ + else if (c_ptr->m_idx) + { + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + /* Close the door */ + else + { + /* Close the door */ + more = do_cmd_close_aux(y, x, dir); + } + } + + /* Cancel repeat unless we may continue */ + if (!more) disturb(0); +} + + +/* + * Determine if a given grid may be "tunneled" + */ +static bool_ do_cmd_tunnel_test(int y, int x) +{ + /* Must have knowledge(execpt on "forget" levels) */ + if (!(cave[y][x].info & (CAVE_MARK))) + { + /* Message */ + msg_print("You see nothing there."); + + /* Nope */ + return (FALSE); + } + + /* Must be a wall/door/etc */ + if (cave_floor_bold(y, x)) + { + /* Message */ + msg_print("You see nothing there to tunnel."); + + /* Nope */ + return (FALSE); + } + + /* Must be tunnelable */ + if (!(f_info[cave[y][x].feat].flags1 & FF1_TUNNELABLE)) + { + /* Message */ + msg_print(f_info[cave[y][x].feat].tunnel); + + /* Nope */ + return (FALSE); + } + + /* Okay */ + return (TRUE); +} + + + +/* + * Tunnel through wall. Assumes valid location. + * + * Note that it is impossible to "extend" rooms past their + * outer walls (which are actually part of the room). + * + * This will, however, produce grids which are NOT illuminated + * (or darkened) along with the rest of the room. + */ +static bool_ twall(int y, int x, byte feat) +{ + cave_type *c_ptr = &cave[y][x]; + + + /* Paranoia -- Require a wall or door or some such */ + if (cave_floor_bold(y, x)) return (FALSE); + + /* Forget the wall */ + c_ptr->info &= ~(CAVE_MARK); + + /* Remove the feature */ + cave_set_feat(y, x, feat); + + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_FLOW | PU_MONSTERS | PU_MON_LITE); + + /* Result */ + return (TRUE); +} + + + +/* + * Perform the basic "tunnel" command + * + * Assumes that the destination is a wall, a vein, a secret + * door, or rubble. + * + * Assumes that no monster is blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_tunnel_aux(int y, int x, int dir) +{ + int skill_req = 0, skill_req_1pct = 0; + cave_type *c_ptr = &cave[y][x]; + + feature_type *f_ptr = &f_info[c_ptr->feat]; + + bool_ more = FALSE; + + + /* Must be have something to dig with (except for sandwalls) */ + if ((c_ptr->feat < FEAT_SANDWALL) || (c_ptr->feat > FEAT_SANDWALL_K)) + { + if (!p_ptr->inventory[INVEN_TOOL].k_idx || + (p_ptr->inventory[INVEN_TOOL].tval != TV_DIGGING)) + { + msg_print("You need to have a shovel or pick in your tool slot."); + + return (FALSE); + } + } + + /* Verify legality */ + if (!do_cmd_tunnel_test(y, x)) return (FALSE); + + /* Take a turn */ + energy_use = 100; + + /* Get grid */ + c_ptr = &cave[y][x]; + + /* Sound */ + sound(SOUND_DIG); + + /* Titanium */ + if (f_ptr->flags1 & FF1_PERMANENT) + { + msg_print(f_ptr->tunnel); + } + + else if ((c_ptr->feat == FEAT_TREES) || (c_ptr->feat == FEAT_DEAD_TREE)) + { + /* Chop Down */ + skill_req = 10; + skill_req_1pct = 14; + if ((p_ptr->skill_dig > 10 + rand_int(400)) && twall(y, x, FEAT_GRASS)) + { + msg_print("You have cleared away the trees."); + } + + /* Keep trying */ + else + { + /* We may continue chopping */ + msg_print(f_ptr->tunnel); + more = TRUE; + + /* Occasional Search XXX XXX */ + if (rand_int(100) < 25) search(); + } + } + + + /* Granite */ + else if ((c_ptr->feat >= FEAT_WALL_EXTRA) && + (c_ptr->feat <= FEAT_WALL_SOLID)) + { + /* Tunnel */ + skill_req = 40; + skill_req_1pct = 56; + if ((p_ptr->skill_dig > 40 + rand_int(1600)) && twall(y, x, FEAT_FLOOR)) + { + msg_print("You have finished the tunnel."); + } + + /* Keep trying */ + else + { + /* We may continue tunelling */ + msg_print(f_ptr->tunnel); + more = TRUE; + } + } + + + /* Quartz / Magma / Sandwall */ + else if (((c_ptr->feat >= FEAT_MAGMA) && + (c_ptr->feat <= FEAT_QUARTZ_K)) || + ((c_ptr->feat >= FEAT_SANDWALL) && + (c_ptr->feat <= FEAT_SANDWALL_K))) + { + bool_ okay = FALSE; + bool_ gold = FALSE; + bool_ hard = FALSE; + bool_ soft = FALSE; + + /* Found gold */ + if ((c_ptr->feat >= FEAT_MAGMA_H) && + (c_ptr->feat <= FEAT_QUARTZ_K)) gold = TRUE; + + if ((c_ptr->feat == FEAT_SANDWALL_H) || + (c_ptr->feat == FEAT_SANDWALL_K)) + { + gold = TRUE; + soft = TRUE; + } + else + /* Extract "quartz" flag XXX XXX XXX */ + if ((c_ptr->feat - FEAT_MAGMA) & 0x01) hard = TRUE; + + /* Quartz */ + if (hard) + { + skill_req = 20; + skill_req_1pct = 28; + okay = (p_ptr->skill_dig > 20 + rand_int(800)); + } + + /* Sandwall */ + else if (soft) + { + skill_req = 5; + skill_req_1pct = 8; + okay = (p_ptr->skill_dig > 5 + rand_int(250)); + } + + /* Magma */ + else + { + skill_req = 10; + skill_req_1pct = 14; + okay = (p_ptr->skill_dig > 10 + rand_int(400)); + } + + /* Success */ + if (okay && twall(y, x, FEAT_FLOOR)) + { + /* Found treasure */ + if (gold) + { + /* Place some gold */ + place_gold(y, x); + + /* Message */ + msg_print("You have found something!"); + } + + /* Found nothing */ + else + { + /* Message */ + msg_print("You have finished the tunnel."); + } + } + + /* Failure */ + else + { + /* Message, continue digging */ + msg_print(f_ptr->tunnel); + more = TRUE; + } + } + + /* Rubble */ + else if (c_ptr->feat == FEAT_RUBBLE) + { + /* Remove the rubble */ + skill_req = 0; + skill_req_1pct = 2; + if ((p_ptr->skill_dig > rand_int(200)) && + twall(y, x, d_info[dungeon_type].floor1)) + { + /* Message */ + msg_print("You have removed the rubble."); + + /* Hack -- place an object */ + if (rand_int(100) < 10) + { + /* Create a simple object */ + place_object(y, x, FALSE, FALSE, OBJ_FOUND_RUBBLE); + + /* Observe new object */ + if (player_can_see_bold(y, x)) + { + msg_print("You have found something!"); + } + } + } + + else + { + /* Message, keep digging */ + msg_print(f_ptr->tunnel); + more = TRUE; + } + } + + /* Secret doors */ + else if (c_ptr->feat >= FEAT_SECRET) + { + /* Tunnel */ + skill_req = 30; + skill_req_1pct = 42; + if ((p_ptr->skill_dig > 30 + rand_int(1200)) && twall(y, x, FEAT_FLOOR)) + { + msg_print("You have finished the tunnel."); + c_ptr->mimic = 0; + lite_spot(y, x); + + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + } + + /* Keep trying */ + else + { + int feat; + + if (c_ptr->mimic) feat = c_ptr->mimic; + else + feat = c_ptr->feat; + + /* We may continue tunelling */ + msg_print(f_info[feat].tunnel); + more = TRUE; + + /* Occasional Search XXX XXX */ + if (rand_int(100) < 25) search(); + } + } + + /* Doors */ + else + { + /* Tunnel */ + skill_req = 30; + skill_req_1pct = 42; + if ((p_ptr->skill_dig > 30 + rand_int(1200)) && twall(y, x, FEAT_FLOOR)) + { + msg_print("You have finished the tunnel."); + } + + /* Keep trying */ + else + { + /* We may continue tunelling */ + msg_print(f_ptr->tunnel); + more = TRUE; + } + } + + if (more && magik(2)) + { + if (p_ptr->skill_dig < skill_req) + { + msg_print("You fail to make even the slightest of progress."); + more = FALSE; + } + else if (p_ptr->skill_dig < skill_req_1pct) + { + msg_print("This will take some time."); + } + } + + /* Notice new floor grids */ + if (!cave_floor_bold(y, x)) + { + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_FLOW | PU_MONSTERS | PU_MON_LITE); + } + + /* Result */ + return (more); +} + + +/* + * Tunnels through "walls" (including rubble and closed doors) + * + * Note that you must tunnel in order to hit invisible monsters + * in walls, though moving into walls still takes a turn anyway. + * + * Digging is very difficult without a "digger" weapon, but can be + * accomplished by strong players using heavy weapons. + */ +void do_cmd_tunnel(void) +{ + int y, x, dir; + + cave_type *c_ptr; + + bool_ more = FALSE; + + + if (p_ptr->wild_mode) return; + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a direction to tunnel, or Abort */ + if (get_rep_dir(&dir)) + { + /* Get location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid */ + c_ptr = &cave[y][x]; + + /* No tunnelling through doors */ + if (((c_ptr->feat >= FEAT_DOOR_HEAD) && + (c_ptr->feat <= FEAT_DOOR_TAIL)) || (c_ptr->feat == FEAT_SHOP)) + { + /* Message */ + msg_print("You cannot tunnel through doors."); + } + + /* No tunnelling through air */ + else if (cave_floor_grid(c_ptr)) + { + /* Message */ + msg_print("You cannot tunnel through air."); + } + + /* A monster is in the way */ + else if (c_ptr->m_idx) + { + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + /* Try digging */ + else + { + /* Tunnel through walls */ + more = do_cmd_tunnel_aux(y, x, dir); + } + } + + /* Cancel repetition unless we can continue */ + if (!more) disturb(0); +} + + +/* + * Perform the basic "disarm" command + * + * Assume destination is a visible trap + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_disarm_chest(int y, int x, s16b o_idx) +{ + int i, j; + + bool_ more = FALSE; + + object_type *o_ptr = &o_list[o_idx]; + + trap_type *t_ptr = &t_info[o_ptr->pval]; + + + /* Take a turn */ + energy_use = 100; + + /* Get the "disarm" factor */ + i = p_ptr->skill_dis; + + /* Penalize some conditions */ + if (p_ptr->blind || no_lite()) i = i / 10; + if (p_ptr->confused || p_ptr->image) i = i / 10; + + /* Extract the difficulty */ + j = i - t_ptr->difficulty * 3; + + /* Always have a small chance of success */ + if (j < 2) j = 2; + + /* Must find the trap first. */ + if (!object_known_p(o_ptr)) + { + msg_print("I don't see any traps."); + } + + /* Already disarmed/unlocked */ + else if (o_ptr->pval <= 0) + { + msg_print("The chest is not trapped."); + } + + /* Success (get a lot of experience) */ + else if (rand_int(100) < j) + { + msg_print("You have disarmed the chest."); + gain_exp(t_ptr->difficulty * 3); + o_ptr->pval = (0 - o_ptr->pval); + } + + /* Failure -- Keep trying */ + else if ((i > 5) && (randint(i) > 5)) + { + /* We may keep trying */ + more = TRUE; + if (flush_failure) flush(); + msg_print("You failed to disarm the chest."); + } + + /* Failure -- Set off the trap */ + else + { + msg_print("You set off a trap!"); + sound(SOUND_FAIL); + chest_trap(y, x, o_idx); + } + + /* Result */ + return (more); +} + + +/* + * Perform the basic "disarm" command + * + * Assume destination is a visible trap + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_disarm_aux(int y, int x, int dir, int do_pickup) +{ + int i, j, power; + + cave_type *c_ptr; + + cptr name; + + bool_ more = FALSE; + + + /* Take a turn */ + energy_use = 100; + + /* Get grid and contents */ + c_ptr = &cave[y][x]; + + /* Access trap name */ + if (t_info[c_ptr->t_idx].ident) + { + name = t_info[c_ptr->t_idx].name; + } + else + { + name = "unknown trap"; + } + + /* Get the "disarm" factor */ + i = p_ptr->skill_dis; + + /* Penalize some conditions */ + if (p_ptr->blind || no_lite()) i = i / 10; + if (p_ptr->confused || p_ptr->image) i = i / 10; + + /* XXX XXX XXX Variable power? */ + + /* Extract trap "power" */ + power = t_info[c_ptr->t_idx].difficulty; + + /* Extract the difficulty */ + j = i - power; + + /* Always have a small chance of success */ + if (j < 2) j = 2; + + /* Success */ + if (rand_int(100) < j) + { + /* Message */ + msg_format("You have disarmed the %s.", name); + + /* Reward */ + gain_exp(power); + + /* Forget the trap */ + c_ptr->info &= ~(CAVE_MARK | CAVE_TRDT); + + /* Remove the trap */ + c_ptr->t_idx = 0; + + /* Move the player onto the trap */ + if (!(f_info[c_ptr->feat].flags1 & FF1_DOOR)) + move_player_aux(dir, do_pickup, 0, TRUE); + + /* Remove trap attr from grid */ + note_spot(y, x); + lite_spot(y, x); + } + + /* Failure -- Keep trying */ + else if ((i > 5) && (randint(i) > 5)) + { + /* Failure */ + if (flush_failure) flush(); + + /* Message */ + msg_format("You failed to disarm the %s.", name); + + /* We may keep trying */ + more = TRUE; + } + + /* Failure -- Set off the trap */ + else + { + /* Message */ + msg_format("You set off the %s!", name); + + /* Move the player onto the trap */ + if (!(f_info[c_ptr->feat].flags1 & FF1_DOOR)) + move_player_aux(dir, do_pickup, 0, FALSE); + } + + /* Result */ + return (more); +} + + +/* + * Disamrs the monster traps(no failure) + */ +void do_cmd_disarm_mon_trap(int y, int x) +{ + msg_print("You disarm the monster trap."); + + place_floor_convert_glass(y, x); + cave[p_ptr->py][p_ptr->px].special = cave[p_ptr->py][p_ptr->px].special2 = 0; +} + + +/* + * Disarms a trap, or chest + */ +void do_cmd_disarm(void) +{ + int y, x, dir; + + s16b o_idx; + + cave_type *c_ptr; + + bool_ more = FALSE; + + + /* Pick a direction if there's an obvious choice */ + { + int num_traps, num_chests; + + /* Count visible traps */ + num_traps = count_feats(&y, &x, is_trap, TRUE); + + /* Count chests (trapped) */ + num_chests = count_chests(&y, &x, TRUE); + + /* See if only one target */ + if (num_traps || num_chests) + { + if (num_traps + num_chests <= 1) + command_dir = coords_to_dir(y, x); + } + } + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a direction (or abort) */ + if (get_rep_dir(&dir)) + { + /* Get location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid and contents */ + c_ptr = &cave[y][x]; + + /* Check for chests */ + o_idx = chest_check(y, x); + + /* Disarm a trap */ + if (((c_ptr->t_idx == 0) || (!(c_ptr->info & CAVE_TRDT))) && + !o_idx && (c_ptr->feat != FEAT_MON_TRAP)) + { + /* Message */ + msg_print("You see nothing there to disarm."); + } + + /* Monster in the way */ + else if (c_ptr->m_idx) + { + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + /* Disarm chest */ + else if (o_idx) + { + /* Disarm the chest */ + more = do_cmd_disarm_chest(y, x, o_idx); + } + + /* Disarm trap */ + else + { + /* Disarm the trap */ + if (c_ptr->feat == FEAT_MON_TRAP) + { + do_cmd_disarm_mon_trap(y, x); + more = FALSE; + } + else + more = do_cmd_disarm_aux(y, x, dir, always_pickup); + } + } + + /* Cancel repeat unless told not to */ + if (!more) disturb(0); +} + + +/* + * Perform the basic "bash" command + * + * Assume destination is a closed/locked/jammed door + * + * Assume there is no monster blocking the destination + * + * Returns TRUE if repeated commands may continue + */ +static bool_ do_cmd_bash_aux(int y, int x, int dir) +{ + int bash, temp; + + cave_type *c_ptr; + + bool_ more = FALSE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_BASH_DOOR)) + { + msg_print("You cannot do that."); + + return (FALSE); + } + + /* Take a turn */ + energy_use = 100; + + /* Get grid */ + c_ptr = &cave[y][x]; + + /* Message */ + msg_print("You smash into the door!"); + + /* Hack -- Bash power based on strength */ + /* (Ranges from 3 to 20 to 100 to 200) */ + bash = adj_str_blow[p_ptr->stat_ind[A_STR]]; + + /* Extract door power */ + temp = ((c_ptr->feat - FEAT_DOOR_HEAD) & 0x07); + + /* Compare bash power to door power XXX XXX XXX */ + temp = (bash - (temp * 10)); + + /* Hack -- always have a chance */ + if (temp < 1) temp = 1; + + /* Hack -- attempt to bash down the door */ + if (rand_int(100) < temp) + { + /* Message */ + msg_print("The door crashes open!"); + + /* Break down the door */ + if (rand_int(100) < 50) + { + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + + cave_set_feat(y, x, FEAT_BROKEN); + } + + /* Open the door */ + else + { + /* Set off trap */ + if (c_ptr->t_idx != 0) player_activate_door_trap(y, x); + + cave_set_feat(y, x, FEAT_OPEN); + } + + /* Sound */ + sound(SOUND_OPENDOOR); + + /* Hack -- Fall through the door. Can't disarm while falling. */ + move_player_aux(dir, always_pickup, 0, FALSE); + + /* Update some things */ + p_ptr->update |= (PU_VIEW | PU_MON_LITE); + p_ptr->update |= (PU_DISTANCE); + } + + /* Saving throw against stun */ + else if (rand_int(100) < adj_dex_safe[p_ptr->stat_ind[A_DEX]] + p_ptr->lev) + { + /* Message */ + msg_print("The door holds firm."); + + /* Allow repeated bashing */ + more = TRUE; + } + + /* High dexterity yields coolness */ + else + { + /* Message */ + msg_print("You are off-balance."); + + /* Hack -- Lose balance ala paralysis */ + (void)set_paralyzed(2 + rand_int(2)); + } + + /* Result */ + return (more); +} + + +/* + * Bash open a door, success based on character strength + * + * For a closed door, pval is positive if locked; negative if stuck. + * + * For an open door, pval is positive for a broken door. + * + * A closed door can be opened - harder if locked. Any door might be + * bashed open (and thereby broken). Bashing a door is (potentially) + * faster! You move into the door way. To open a stuck door, it must + * be bashed. A closed door can be jammed (see do_cmd_spike()). + * + * Creatures can also open or bash doors, see elsewhere. + */ +void do_cmd_bash(void) +{ + int y, x, dir; + + cave_type *c_ptr; + + bool_ more = FALSE; + + monster_race *r_ptr = &r_info[p_ptr->body_monster]; + + + if ((p_ptr->body_monster != 0) && !(r_ptr->flags2 & RF2_BASH_DOOR)) + { + msg_print("You cannot do that."); + + return; + } + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Bash location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid */ + c_ptr = &cave[y][x]; + + /* Nothing useful */ + if ((c_ptr->feat < FEAT_DOOR_HEAD || + c_ptr->feat > FEAT_DOOR_TAIL) && + (c_ptr->feat < FEAT_ALTAR_HEAD || + c_ptr->feat > FEAT_ALTAR_TAIL) && (c_ptr->feat != FEAT_FOUNTAIN)) + { + /* Message */ + msg_print("You see nothing there to bash."); + } + + /* Monster in the way */ + else if (c_ptr->m_idx) + { + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + else if (c_ptr->feat >= FEAT_ALTAR_HEAD && + c_ptr->feat <= FEAT_ALTAR_TAIL) + { + more = do_cmd_bash_altar(y, x); + } + /* Bash a closed door */ + else if (c_ptr->feat == FEAT_FOUNTAIN) + { + more = do_cmd_bash_fountain(y, x); + } + else + { + /* Bash the door */ + more = do_cmd_bash_aux(y, x, dir); + } + } + + /* Unless valid action taken, cancel bash */ + if (!more) disturb(0); +} + + + +/* + * Manipulate an adjacent grid in some way + * + * Attack monsters, tunnel through walls, disarm traps, open doors. + * + * Consider confusion XXX XXX XXX + * + * This command must always take a turn, to prevent free detection + * of invisible monsters. + */ +void do_cmd_alter(void) +{ + int y, x, dir; + + cave_type *c_ptr; + + bool_ more = FALSE; + + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a direction */ + if (get_rep_dir(&dir)) + { + /* Get location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid */ + c_ptr = &cave[y][x]; + + /* Take a turn */ + energy_use = 100; + + /* Attack monsters */ + if (c_ptr->m_idx) + { + /* Attack */ + py_attack(y, x, -1); + } + + /* Open closed doors */ + else if ((c_ptr->feat >= FEAT_DOOR_HEAD) && + (c_ptr->feat <= FEAT_DOOR_TAIL)) + { + /* Tunnel */ + more = do_cmd_open_aux(y, x, dir); + } + + /* Tunnel through walls */ + else if (f_info[c_ptr->feat].flags1 & FF1_TUNNELABLE) + { + /* Tunnel */ + more = do_cmd_tunnel_aux(y, x, dir); + } + + /* Disarm traps */ + else if (c_ptr->t_idx != 0) + { + /* Tunnel */ + more = do_cmd_disarm_aux(y, x, dir, always_pickup); + } + + /* Oops */ + else + { + /* Oops */ + msg_print("You attack the empty air."); + } + } + + /* Cancel repetition unless we can continue */ + if (!more) disturb(0); +} + + +/* + * Find the index of some "spikes", if possible. + * + * XXX XXX XXX Let user choose a pile of spikes, perhaps? + */ +static bool_ get_spike(int *ip) +{ + int i; + + + /* Check every item in the pack */ + for (i = 0; i < INVEN_PACK; i++) + { + object_type *o_ptr = &p_ptr->inventory[i]; + + /* Skip non-objects */ + if (!o_ptr->k_idx) continue; + + /* Check the "tval" code */ + if (o_ptr->tval == TV_SPIKE) + { + /* Save the spike index */ + (*ip) = i; + + /* Success */ + return (TRUE); + } + } + + /* Oops */ + return (FALSE); +} + + + +/* + * Jam a closed door with a spike + * + * This command may NOT be repeated + */ +void do_cmd_spike(void) +{ + int y, x, dir, item; + + cave_type *c_ptr; + + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Get location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get grid and contents */ + c_ptr = &cave[y][x]; + + /* Require closed door */ + if (!((c_ptr->feat >= FEAT_DOOR_HEAD) && + (c_ptr->feat <= FEAT_DOOR_TAIL))) + { + /* Message */ + msg_print("You see nothing there to spike."); + } + + /* Get a spike */ + else if (!get_spike(&item)) + { + /* Message */ + msg_print("You have no spikes!"); + } + + /* Is a monster in the way? */ + else if (c_ptr->m_idx) + { + /* Take a turn */ + energy_use = 100; + + /* Message */ + msg_print("There is a monster in the way!"); + + /* Attack */ + py_attack(y, x, -1); + } + + /* Go for it */ + else + { + /* Take a turn */ + energy_use = 100; + + /* Successful jamming */ + msg_print("You jam the door with a spike."); + + /* Convert "locked" to "stuck" XXX XXX XXX */ + if (c_ptr->feat < FEAT_DOOR_HEAD + 0x08) c_ptr->feat += 0x08; + + /* Add one spike to the door */ + if (c_ptr->feat < FEAT_DOOR_TAIL) c_ptr->feat++; + + /* Use up, and describe, a single spike, from the bottom */ + inc_stack_size(item, -1); + } + } +} + + +static void do_cmd_walk_jump(int pickup, bool_ disarm) +{ + int dir; + + bool_ more = FALSE; + + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Take a turn */ + energy_use = 100; + + /* Actually move the character */ + move_player(dir, pickup, disarm); + + /* Allow more walking */ + more = TRUE; + } + + /* Hack -- In small scale wilderness it takes MUCH more time to move */ + energy_use *= (p_ptr->wild_mode) ? ((MAX_HGT + MAX_WID) / 2) : 1; + + /* Hack again -- Is there a special encounter ??? */ + if (p_ptr->wild_mode && + magik(wf_info[wild_map[p_ptr->py][p_ptr->px].feat].level - (p_ptr->lev * 2))) + { + /* Go into large wilderness view */ + p_ptr->wilderness_x = p_ptr->px; + p_ptr->wilderness_y = p_ptr->py; + energy_use = 100; + change_wild_mode(); + + /* HACk -- set the encouter flag for the wilderness generation */ + generate_encounter = TRUE; + p_ptr->oldpx = MAX_WID / 2; + p_ptr->oldpy = MAX_HGT / 2; + + /* Inform the player of his horrible fate :=) */ + msg_print("You are ambushed!"); + } + + /* Cancel repeat unless we may continue */ + if (!more) disturb(0); +} + + +/* + * Try to ``walk'' using phase door. + */ +static void do_cmd_unwalk() +{ + int dir, y, x, feat; + + cave_type *c_ptr; + + bool_ more = FALSE; + + + if (!get_rep_dir(&dir)) return; + + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + c_ptr = &cave[y][x]; + feat = c_ptr->feat; + + /* Must have knowledge to know feature XXX XXX */ + if (!(c_ptr->info & (CAVE_MARK))) feat = FEAT_NONE; + + /* Take a turn */ + energy_use = 100; + energy_use *= (p_ptr->wild_mode) ? (5 * (MAX_HGT + MAX_WID) / 2) : 1; + + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + + /* Attack monsters */ + if (c_ptr->m_idx > 0) + { + /* Attack */ + py_attack(y, x, -1); + } + + /* Exit the area */ + else if ((!dun_level) && (!p_ptr->wild_mode) && + ((x == 0) || (x == cur_wid - 1) || (y == 0) || (y == cur_hgt - 1))) + { + /* Can the player enter the grid? */ + if (player_can_enter(c_ptr->mimic)) + { + /* Hack: move to new area */ + if ((y == 0) && (x == 0)) + { + p_ptr->wilderness_y--; + p_ptr->wilderness_x--; + p_ptr->oldpy = cur_hgt - 2; + p_ptr->oldpx = cur_wid - 2; + ambush_flag = FALSE; + } + + else if ((y == 0) && (x == MAX_WID - 1)) + { + p_ptr->wilderness_y--; + p_ptr->wilderness_x++; + p_ptr->oldpy = cur_hgt - 2; + p_ptr->oldpx = 1; + ambush_flag = FALSE; + } + + else if ((y == MAX_HGT - 1) && (x == 0)) + { + p_ptr->wilderness_y++; + p_ptr->wilderness_x--; + p_ptr->oldpy = 1; + p_ptr->oldpx = cur_wid - 2; + ambush_flag = FALSE; + } + + else if ((y == MAX_HGT - 1) && (x == MAX_WID - 1)) + { + p_ptr->wilderness_y++; + p_ptr->wilderness_x++; + p_ptr->oldpy = 1; + p_ptr->oldpx = 1; + ambush_flag = FALSE; + } + + else if (y == 0) + { + p_ptr->wilderness_y--; + p_ptr->oldpy = cur_hgt - 2; + p_ptr->oldpx = x; + ambush_flag = FALSE; + } + + else if (y == cur_hgt - 1) + { + p_ptr->wilderness_y++; + p_ptr->oldpy = 1; + p_ptr->oldpx = x; + ambush_flag = FALSE; + } + + else if (x == 0) + { + p_ptr->wilderness_x--; + p_ptr->oldpx = cur_wid - 2; + p_ptr->oldpy = y; + ambush_flag = FALSE; + } + + else if (x == cur_wid - 1) + { + p_ptr->wilderness_x++; + p_ptr->oldpx = 1; + p_ptr->oldpy = y; + ambush_flag = FALSE; + } + + p_ptr->leaving = TRUE; + + return; + } + } + + /* Hack -- Ignore weird terrain types. */ + else if (!cave_floor_grid(c_ptr)) + { + teleport_player(10); + } + + /* Enter quests */ + else if (((feat >= FEAT_QUEST_ENTER) && (feat <= FEAT_QUEST_UP)) || + ((feat >= FEAT_LESS) && (feat <= FEAT_MORE))) + { + move_player(dir, always_pickup, TRUE); + more = FALSE; + } + + /* Hack -- Ignore wilderness mofe. */ + else if (p_ptr->wild_mode) + { + /* Chance to not blink right */ + if (magik(15)) + { + do + { + dir = rand_range(1, 9); + } + while (dir == 5); + } + + move_player(dir, always_pickup, TRUE); + } + + /* Walking semantics */ + else + { + teleport_player_directed(10, dir); + } + + /* Cancel repetition unless we can continue */ + if (!more) disturb(0); +} + + +/* + * Support code for the "Walk" and "Jump" commands + */ +void do_cmd_walk(int pickup, bool_ disarm) +{ + /* Move (usually pickup) */ + + if (p_ptr->immovable) + { + do_cmd_unwalk(); + } + else + { + do_cmd_walk_jump(pickup, disarm); + } +} + + +void do_cmd_run_run() +{ + int dir; + + + /* Hack -- no running when confused */ + if (p_ptr->confused) + { + msg_print("You are too confused!"); + return; + } + + /* Get a "repeated" direction */ + if (get_rep_dir(&dir)) + { + /* Hack -- Set the run counter */ + running = (command_arg ? command_arg : 1000); + + /* First step */ + run_step(dir); + } + p_ptr->window |= (PW_OVERHEAD); +} + + +/* + * Start running. + */ +void do_cmd_run(void) +{ + if (p_ptr->immovable) + { + return; + } + else + { + do_cmd_run_run(); + } +} + + + +/* + * Stay still. Search. Enter stores. + * Pick up treasure if "pickup" is true. + */ +void do_cmd_stay(int pickup) +{ + cave_type *c_ptr = &cave[p_ptr->py][p_ptr->px]; + + + /* Allow repeated command */ + if (command_arg) + { + /* Set repeat count */ + command_rep = command_arg - 1; + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Cancel the arg */ + command_arg = 0; + } + + + /* Take a turn */ + energy_use = 100; + + + /* Spontaneous Searching */ + if ((p_ptr->skill_fos >= 50) || (0 == rand_int(50 - p_ptr->skill_fos))) + { + search(); + } + + /* Continuous Searching */ + if (p_ptr->searching) + { + search(); + } + + + /* Handle "objects" */ + carry(pickup); + + + /* Hack -- enter a store if we are on one */ + if (c_ptr->feat == FEAT_SHOP) + { + /* Disturb */ + disturb(0); + + /* Hack -- enter store */ + command_new = '_'; + } +} + +/* + * Resting allows a player to safely restore his hp -RAK- + */ +void do_cmd_rest(void) +{ + /* Can't rest on a Void Jumpgate -- too dangerous */ + if (cave[p_ptr->py][p_ptr->px].feat == FEAT_BETWEEN) + { + /* 'R&\n' is one of our favourite macros, so we have to do this */ + if (flush_failure) flush(); + + /* Tell the player why */ + msg_print(format("Resting on a %s is too dangerous!", + f_info[cave[p_ptr->py][p_ptr->px].feat].name)); + + /* Done */ + return; + } + + /* Can't rest while undead, it would mean dying */ + if (p_ptr->necro_extra & CLASS_UNDEAD) + { + /* 'R&\n' is one of our favourite macros, so we have to do this */ + if (flush_failure) flush(); + + /* Tell the player why */ + msg_print("Resting is impossible while undead!"); + + /* Done */ + return; + } + + /* Prompt for time if needed */ + if (command_arg <= 0) + { + cptr p = "Rest (0-9999, '*' for HP/SP, '&' as needed): "; + + char out_val[80]; + + /* Default */ + strcpy(out_val, "&"); + + /* Ask for duration */ + if (!get_string(p, out_val, 4)) return; + + /* Rest until done */ + if (out_val[0] == '&') + { + command_arg = ( -2); + } + + /* Rest a lot */ + else if (out_val[0] == '*') + { + command_arg = ( -1); + } + + /* Rest some */ + else + { + command_arg = atoi(out_val); + if (command_arg <= 0) return; + } + } + + + /* Paranoia */ + if (command_arg > 9999) command_arg = 9999; + + + /* Take a turn XXX XXX XXX (?) */ + energy_use = 100; + + /* Save the rest code */ + resting = command_arg; + + /* Cancel searching */ + p_ptr->searching = FALSE; + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + + /* Refresh */ + Term_fresh(); +} + + + + + + +/* + * Determines the odds of an object breaking when thrown at a monster + * + * Note that artifacts never break, see the "drop_near()" function. + */ +int breakage_chance(object_type *o_ptr) +{ + int reducer = + 1 + ((get_skill(SKILL_ARCHERY)) ? (get_skill_scale(SKILL_ARCHERY, 10)) : 0); + + /* Examine the item type */ + switch (o_ptr->tval) + { + /* Always break */ + case TV_FLASK: + case TV_POTION: + case TV_POTION2: + case TV_BOTTLE: + case TV_FOOD: + { + return (100); + } + + /* Often break */ + case TV_LITE: + case TV_SCROLL: + case TV_SKELETON: + { + return (50); + } + + case TV_ARROW: + { + return (50 / reducer); + } + + /* Sometimes break */ + case TV_WAND: + case TV_SPIKE: + { + return (25); + } + + case TV_SHOT: + case TV_BOLT: + { + return (25 / reducer); + } + case TV_BOOMERANG: + { + return 1; + } + } + + /* Rarely break */ + return (10); +} + +/* + * Return multiplier of an object + */ +int get_shooter_mult(object_type *o_ptr) +{ + /* Assume a base multiplier */ + int tmul = 1; + + /* Analyze the launcher */ + switch (o_ptr->sval) + { + case SV_SLING: + { + /* Sling and ammo */ + tmul = 2; + break; + } + + case SV_SHORT_BOW: + { + /* Short Bow and Arrow */ + tmul = 2; + break; + } + + case SV_LONG_BOW: + { + /* Long Bow and Arrow */ + tmul = 3; + break; + } + + /* Light Crossbow and Bolt */ + case SV_LIGHT_XBOW: + { + tmul = 3; + break; + } + + /* Heavy Crossbow and Bolt */ + case SV_HEAVY_XBOW: + { + tmul = 4; + break; + } + } + return tmul; +} + + +/* + * Fire an object from the pack or floor. + * + * You may only fire items that "match" your missile launcher. + * + * You must use slings + pebbles/shots, bows + arrows, xbows + bolts. + * + * See "calc_bonuses()" for more calculations and such. + * + * Note that "firing" a missile is MUCH better than "throwing" it. + * + * Note: "unseen" monsters are very hard to hit. + * + * Objects are more likely to break if they "attempt" to hit a monster. + * + * Rangers (with Bows) and Anyone (with "Extra Shots") get extra shots. + * + * The "extra shot" code works by decreasing the amount of energy + * required to make each shot, spreading the shots out over time. + * + * Note that when firing missiles, the launcher multiplier is applied + * after all the bonuses are added in, making multipliers very useful. + * + * Note that Bows of "Extra Might" get extra range and an extra bonus + * for the damage multiplier. + * + * Note that Bows of "Extra Shots" give an extra shot. + */ +void do_cmd_fire(void) +{ + int dir, item; + + int j, y, x, ny, nx, ty, tx, by, bx; + + int oldtdam, tdam, tdis, thits, tmul; + + int bonus, chance; + + int cur_dis, visible; + + int breakage = -1, num_pierce = 0; + + s32b special = 0; + + object_type forge; + + object_type *q_ptr; + + object_type *o_ptr; + + object_type *j_ptr; + + bool_ hit_body = FALSE; + + byte missile_attr; + + char missile_char; + + char o_name[80]; + + int msec = delay_factor * delay_factor * delay_factor; + + + /* Get the "bow" (if any) */ + j_ptr = &p_ptr->inventory[INVEN_BOW]; + + /* Require a launcher */ + if (!j_ptr->tval) + { + msg_print("You have nothing with which to fire."); + return; + } + + /* XXX HACK */ + if (j_ptr->tval == TV_INSTRUMENT) + { + msg_print("You cannot fire with an instrument."); + return; + } + + /* Get the "ammo" (if any) */ + o_ptr = &p_ptr->inventory[INVEN_AMMO]; + + item = INVEN_AMMO; + + /* If nothing correct try to choose from the backpack */ + if ((p_ptr->tval_ammo != o_ptr->tval) || (!o_ptr->k_idx)) + { + /* Get an item */ + if (!get_item(&item, + "Your quiver is empty. Fire which item? ", + "You have nothing to fire.", + (USE_INVEN | USE_FLOOR), + object_filter::TVal(p_ptr->tval_ammo))) + { + return; + } + + /* Access the item */ + o_ptr = get_object(item); + } + + + /* Get a direction (or cancel) */ + if (!get_aim_dir(&dir)) return; + + + /* Get local object */ + q_ptr = &forge; + + /* Obtain a local object */ + object_copy(q_ptr, o_ptr); + + /* Single object */ + q_ptr->number = 1; + + /* Reduce stack and describe */ + inc_stack_size(item, -1); + + /* Break goi/manashield */ + if (p_ptr->invuln) + { + set_invuln(0); + } + if (p_ptr->disrupt_shield) + { + set_disrupt_shield(0); + } + + + /* Sound */ + sound(SOUND_SHOOT); + + + /* Describe the object */ + object_desc(o_name, q_ptr, FALSE, 3); + + /* Find the color and symbol for the object for throwing */ + missile_attr = object_attr(q_ptr); + missile_char = object_char(q_ptr); + + + /* Use the proper number of shots */ + thits = p_ptr->num_fire; + + /* Use a base distance */ + tdis = 10; + + /* Base damage from thrown object plus launcher bonus */ + tdam = damroll(q_ptr->dd, q_ptr->ds) + q_ptr->to_d + j_ptr->to_d; + + /* Actually "fire" the object */ + bonus = (p_ptr->to_h + p_ptr->to_h_ranged + q_ptr->to_h + j_ptr->to_h); + + chance = (p_ptr->skill_thb + (bonus * BTH_PLUS_ADJ)); + if (chance < 5) chance = 5; + + tmul = get_shooter_mult(j_ptr); + + /* Get extra "power" from "extra might" */ + tmul += p_ptr->xtra_might; + + /* Boost the damage */ + tdam *= tmul; + + /* Add in the player damage */ + tdam += p_ptr->to_d_ranged; + + /* Base range */ + tdis = 10 + 5 * tmul; + + + /* Take a (partial) turn */ + energy_use = (100 / thits); + + /* piercing shots ? */ + if (p_ptr->use_piercing_shots) + { + num_pierce = (get_skill(SKILL_COMBAT) / 10) - 1; + num_pierce = (num_pierce < 0) ? 0 : num_pierce; + } + + /* Start at the player */ + by = p_ptr->py; + bx = p_ptr->px; + y = p_ptr->py; + x = p_ptr->px; + + /* Predict the "target" location */ + tx = p_ptr->px + 99 * ddx[dir]; + ty = p_ptr->py + 99 * ddy[dir]; + + /* Check for "target request" */ + if ((dir == 5) && target_okay()) + { + tx = target_col; + ty = target_row; + } + + + /* Hack -- Handle stuff */ + handle_stuff(); + + oldtdam = tdam; + while (TRUE) + { + /* Reset after a piercing shot */ + tdam = oldtdam; + + /* Travel until stopped */ + for (cur_dis = 0; cur_dis <= tdis; ) + { + /* Hack -- Stop at the target */ + if ((y == ty) && (x == tx)) break; + + /* Calculate the new location (see "project()") */ + ny = y; + nx = x; + mmove2(&ny, &nx, by, bx, ty, tx); + + /* Stopped by walls/doors */ + if (!cave_floor_bold(ny, nx)) break; + + /* Advance the distance */ + cur_dis++; + + /* Save the new location */ + x = nx; + y = ny; + + + /* The player can see the (on screen) missile */ + if (panel_contains(y, x) && player_can_see_bold(y, x)) + { + /* Draw, Hilite, Fresh, Pause, Erase */ + print_rel(missile_char, missile_attr, y, x); + move_cursor_relative(y, x); + Term_fresh(); + sleep_for(milliseconds(msec)); + lite_spot(y, x); + Term_fresh(); + } + + /* The player cannot see the missile */ + else + { + /* Pause anyway, for consistancy */ + sleep_for(milliseconds(msec)); + } + + + /* Monster here, Try to hit it */ + if (cave[y][x].m_idx) + { + cave_type *c_ptr = &cave[y][x]; + + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + monster_race *r_ptr = race_inf(m_ptr); + + /* Check the visibility */ + visible = m_ptr->ml; + + /* Note the collision */ + hit_body = TRUE; + + /* Did we hit it (penalize range) */ + if (test_hit_fire(chance - cur_dis, m_ptr->ac, m_ptr->ml)) + { + bool_ fear = FALSE; + + /* Assume a default death */ + cptr note_dies = " dies."; + + /* Some monsters get "destroyed" */ + if ((r_ptr->flags3 & (RF3_DEMON)) || + (r_ptr->flags3 & (RF3_UNDEAD)) || + (r_ptr->flags2 & (RF2_STUPID)) || + (strchr("Evg", r_ptr->d_char))) + { + /* Special note at death */ + note_dies = " is destroyed."; + } + + + /* Handle unseen monster */ + if (!visible) + { + /* Invisible monster */ + msg_format("The %s finds a mark.", o_name); + } + + /* Handle visible monster */ + else + { + char m_name[80]; + + /* Get "the monster" or "it" */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("The %s hits %s.", o_name, m_name); + + /* Hack -- Track this monster race */ + if (m_ptr->ml) monster_race_track(m_ptr->r_idx, + m_ptr->ego); + + /* Hack -- Track this monster */ + if (m_ptr->ml) health_track(c_ptr->m_idx); + + /* Anger friends */ + { + char m_name[80]; + monster_desc(m_name, m_ptr, 0); + switch (is_friend(m_ptr)) + { + case 1: + { + msg_format("%^s gets angry!", m_name); + change_side(m_ptr); + break; + } + case 0: + { + msg_format("%^s gets angry!", m_name); + m_ptr->status = MSTATUS_NEUTRAL_M; + break; + } + } + } + } + + /* Apply special damage XXX XXX XXX */ + tdam = tot_dam_aux(q_ptr, tdam, m_ptr, &special); + tdam = critical_shot(q_ptr->weight, q_ptr->to_h, tdam, SKILL_ARCHERY); + + /* No negative damage */ + if (tdam < 0) tdam = 0; + + /* Complex message */ + if (wizard) + { + msg_format("You do %d (out of %d) damage.", + tdam, m_ptr->hp); + } + + /* Hit the monster, check for death */ + if (mon_take_hit(c_ptr->m_idx, tdam, &fear, note_dies)) + { + /* Dead monster */ + } + + /* No death */ + else + { + /* Message */ + message_pain(c_ptr->m_idx, tdam); + + if (special) attack_special(m_ptr, special, tdam); + + /* Take note */ + if (fear && m_ptr->ml) + { + char m_name[80]; + + /* Sound */ + sound(SOUND_FLEE); + + /* Get the monster name (or "it") */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("%^s flees in terror!", m_name); + } + } + } + + /* Stop looking */ + break; + } + } + + /* Exploding arrow ? */ + if (q_ptr->pval2 != 0) + { + int rad = 0, dam = + (damroll(q_ptr->dd, q_ptr->ds) + q_ptr->to_d) * 2; + int flag = + PROJECT_STOP | PROJECT_GRID | PROJECT_ITEM | PROJECT_KILL | + PROJECT_JUMP; + switch (q_ptr->sval) + { + case SV_AMMO_LIGHT: + rad = 2; + dam /= 2; + break; + case SV_AMMO_NORMAL: + rad = 3; + break; + case SV_AMMO_HEAVY: + rad = 4; + dam *= 2; + break; + } + + project(0, rad, y, x, dam, q_ptr->pval2, flag); + } + + /* Chance of breakage (during attacks) */ + j = (hit_body ? breakage_chance(q_ptr) : 0); + + /* Break ? */ + if ((q_ptr->pval2 != 0) || (rand_int(100) < j)) + { + breakage = 100; + break; + } + + /* If the ammo doesn't break, it can pierce through */ + if ((num_pierce) && (hit_body) && + (magik(45 + get_skill(SKILL_ARCHERY)))) + { + num_pierce--; + hit_body = FALSE; + + /* If target isn't reached, continue moving to target */ + if ( !((tx < x && x < bx) || (bx < x && x < tx)) && + !((ty < y && y < by) || (by < y && y < ty))) + { + /* Continue moving in same direction if we reached the target */ + int dx = tx - bx; + int dy = ty - by; + tx = x + 99 * dx; + ty = y + 99 * dy; + + /* New base location */ + by = y; + bx = x; + } + + msg_format("The %s pierces through!", o_name); + } + else + break; + } + + /* Drop (or break) near that location */ + drop_near(q_ptr, breakage, y, x); +} + + +/* + * Why is this here? even if it's temporary boost... + * Moved into player_type, hoping it might be useful in future extensions + * -- pelpel + */ +/* int throw_mult = 1; */ + +/* + * Throw an object from the pack or floor. + * + * Note: "unseen" monsters are very hard to hit. + * + * Should throwing a weapon do full damage? Should it allow the magic + * to hit bonus of the weapon to have an effect? Should it ever cause + * the item to be destroyed? Should it do any damage at all? + */ +void do_cmd_throw(void) +{ + int dir; + + s32b special = 0; + + int j, y, x, ny, nx, ty, tx; + + int chance, tdam, tdis; + + int mul, div; + + int boulder_add = 0; + int boulder_mult = 0; + + int cur_dis, visible; + + object_type forge; + + object_type *q_ptr; + + bool_ hit_body = FALSE; + + bool_ hit_wall = FALSE; + + byte missile_attr; + + char missile_char; + + char o_name[80]; + + int msec = delay_factor * delay_factor * delay_factor; + + /* Get an item */ + int item; + if (!get_item(&item, + "Throw which item? ", + "You have nothing to throw.", + (USE_INVEN | USE_FLOOR))) + { + return; + } + + /* Access the item */ + object_type *o_ptr = get_object(item); + + u32b f1, f2, f3, f4, f5, esp; + object_flags(o_ptr, &f1, &f2, &f3, &f4, &f5, &esp); + + /* Hack - Cannot throw away 'no drop' cursed items */ + if (cursed_p(o_ptr) && (f4 & TR4_CURSE_NO_DROP)) + { + /* Oops */ + msg_print("Hmmm, you seem to be unable to throw it."); + + /* Nope */ + return; + } + + /* Boulder throwing */ + if ((o_ptr->tval == TV_JUNK) && (o_ptr->sval == SV_BOULDER) && (get_skill(SKILL_BOULDER))) + { + boulder_add = get_skill_scale(SKILL_BOULDER, 80); + boulder_mult = get_skill_scale(SKILL_BOULDER, 6); + } + + /* Get a direction (or cancel) */ + if (!get_aim_dir(&dir)) return; + + /* Break goi/manashield */ + if (p_ptr->invuln) + { + set_invuln(0); + } + if (p_ptr->disrupt_shield) + { + set_disrupt_shield(0); + } + + /* Get local object */ + q_ptr = &forge; + + /* Obtain a local object */ + object_copy(q_ptr, o_ptr); + + /* + * Hack -- If rods or wands are thrown, the total maximum timeout or + * charges need to be allocated between the two stacks. + */ + if (o_ptr->tval == TV_WAND) + { + q_ptr->pval = o_ptr->pval / o_ptr->number; + + if (o_ptr->number > 1) o_ptr->pval -= q_ptr->pval; + } + + /* Single object */ + q_ptr->number = 1; + + /* Reduce stack and describe */ + inc_stack_size(item, -1); + + /* Description */ + object_desc(o_name, q_ptr, FALSE, 3); + + /* Find the color and symbol for the object for throwing */ + missile_attr = object_attr(q_ptr); + missile_char = object_char(q_ptr); + + /* Extract a "distance multiplier" */ + /* Changed for 'launcher' corruption */ + mul = 10 + (2 * (p_ptr->throw_mult - 1)) + (2 * boulder_mult); + + /* Enforce a minimum "weight" of one pound */ + div = ((q_ptr->weight > 10) ? q_ptr->weight : 10); + + /* Hack -- Distance -- Reward strength, penalize weight */ + tdis = (adj_str_blow[p_ptr->stat_ind[A_STR]] + 20) * mul / div; + + /* Max distance of 10-18 */ + if (tdis > mul) tdis = mul; + + /* Hack -- Base damage from thrown object */ + tdam = damroll(q_ptr->dd, q_ptr->ds) + q_ptr->to_d + boulder_add; + tdam *= p_ptr->throw_mult + boulder_mult; + + /* Chance of hitting - adjusted for Weaponmasters -- Gumby */ + chance = (p_ptr->skill_tht + (p_ptr->to_h * BTH_PLUS_ADJ)); + + /* Take a turn */ + energy_use = 100; + + + /* Start at the player */ + y = p_ptr->py; + x = p_ptr->px; + + /* Predict the "target" location */ + tx = p_ptr->px + 99 * ddx[dir]; + ty = p_ptr->py + 99 * ddy[dir]; + + /* Check for "target request" */ + if ((dir == 5) && target_okay()) + { + tx = target_col; + ty = target_row; + } + + + /* Hack -- Handle stuff */ + handle_stuff(); + + + /* Travel until stopped */ + for (cur_dis = 0; cur_dis <= tdis; ) + { + /* Hack -- Stop at the target */ + if ((y == ty) && (x == tx)) break; + + /* Calculate the new location (see "project()") */ + ny = y; + nx = x; + mmove2(&ny, &nx, p_ptr->py, p_ptr->px, ty, tx); + + /* Stopped by walls/doors */ + if (!cave_floor_bold(ny, nx)) + { + hit_wall = TRUE; + break; + } + + /* Advance the distance */ + cur_dis++; + + /* Save the new location */ + x = nx; + y = ny; + + + /* The player can see the (on screen) missile */ + if (panel_contains(y, x) && player_can_see_bold(y, x)) + { + /* Draw, Hilite, Fresh, Pause, Erase */ + print_rel(missile_char, missile_attr, y, x); + move_cursor_relative(y, x); + Term_fresh(); + sleep_for(milliseconds(msec)); + lite_spot(y, x); + Term_fresh(); + } + + /* The player cannot see the missile */ + else + { + /* Pause anyway, for consistancy */ + sleep_for(milliseconds(msec)); + } + + + /* Monster here, Try to hit it */ + if (cave[y][x].m_idx) + { + cave_type *c_ptr = &cave[y][x]; + + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + monster_race *r_ptr = race_inf(m_ptr); + + /* Check the visibility */ + visible = m_ptr->ml; + + /* Note the collision */ + hit_body = TRUE; + + /* Did we hit it (penalize range) */ + if (test_hit_fire(chance - cur_dis, m_ptr->ac, m_ptr->ml)) + { + bool_ fear = FALSE; + + /* Assume a default death */ + cptr note_dies = " dies."; + + /* Some monsters get "destroyed" */ + if ((r_ptr->flags3 & (RF3_DEMON)) || + (r_ptr->flags3 & (RF3_UNDEAD)) || + (r_ptr->flags2 & (RF2_STUPID)) || + (strchr("Evg", r_ptr->d_char))) + { + /* Special note at death */ + note_dies = " is destroyed."; + } + + + /* Handle unseen monster */ + if (!visible) + { + /* Invisible monster */ + msg_format("The %s finds a mark.", o_name); + } + + /* Handle visible monster */ + else + { + char m_name[80]; + + /* Get "the monster" or "it" */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("The %s hits %s.", o_name, m_name); + + /* Hack -- Track this monster race */ + if (m_ptr->ml) monster_race_track(m_ptr->r_idx, m_ptr->ego); + + /* Hack -- Track this monster */ + if (m_ptr->ml) health_track(c_ptr->m_idx); + } + + /* Apply special damage XXX XXX XXX */ + tdam = tot_dam_aux(q_ptr, tdam, m_ptr, &special); + tdam = critical_shot(q_ptr->weight, q_ptr->to_h, tdam, o_ptr->sval == SV_BOULDER ? SKILL_BOULDER : SKILL_ARCHERY); + + /* No negative damage */ + if (tdam < 0) tdam = 0; + + /* Complex message */ + if (wizard) + { + msg_format("You do %d (out of %d) damage.", + tdam, m_ptr->hp); + } + + /* Hit the monster, check for death */ + if (mon_take_hit(c_ptr->m_idx, tdam, &fear, note_dies)) + { + /* Dead monster */ + } + + /* No death */ + else + { + /* Message */ + message_pain(c_ptr->m_idx, tdam); + + if (special) attack_special(m_ptr, special, tdam); + + /* Anger friends */ + if (!(k_info[q_ptr->k_idx].tval == TV_POTION)) + { + char m_name[80]; + monster_desc(m_name, m_ptr, 0); + switch (is_friend(m_ptr)) + { + case 1: + msg_format("%^s gets angry!", m_name); + change_side(m_ptr); + break; + case 0: + msg_format("%^s gets angry!", m_name); + m_ptr->status = MSTATUS_NEUTRAL_M; + break; + } + } + + /* Take note */ + if (fear && m_ptr->ml) + { + char m_name[80]; + + /* Sound */ + sound(SOUND_FLEE); + + /* Get the monster name (or "it") */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("%^s flees in terror!", m_name); + } + } + } + + /* Stop looking */ + break; + } + } + + /* Chance of breakage (during attacks) */ + j = (hit_body ? breakage_chance(q_ptr) : 0); + + /* Potions smash open */ + if (k_info[q_ptr->k_idx].tval == TV_POTION) + { + if ((hit_body) || (hit_wall) || (randint(100) < j)) + { + /* Message */ + msg_format("The %s shatters!", o_name); + + if (potion_smash_effect(0, y, x, q_ptr->sval)) + { + if (cave[y][x].m_idx) + { + char m_name[80]; + monster_desc(m_name, &m_list[cave[y][x].m_idx], 0); + switch (is_friend(&m_list[cave[y][x].m_idx])) + { + case 1: + msg_format("%^s gets angry!", m_name); + change_side(&m_list[cave[y][x].m_idx]); + break; + case 0: + msg_format("%^s gets angry!", m_name); + m_list[cave[y][x].m_idx].status = MSTATUS_NEUTRAL_M; + break; + } + } + } + + return; + } + else + { + j = 0; + } + } + + /* Drop (or break) near that location */ + drop_near(q_ptr, j, y, x); +} + + +/* + * Throw a boomerang object from the equipement(bow). + * + * Note: "unseen" monsters are very hard to hit. + * + * Should throwing a weapon do full damage? Should it allow the magic + * to hit bonus of the weapon to have an effect? Should it ever cause + * the item to be destroyed? Should it do any damage at all? + */ +void do_cmd_boomerang(void) +{ + int dir; + + int j, y, x, ny, nx, ty, tx; + + int chance, tdam, tdis; + + int mul, div; + + int cur_dis, visible; + + object_type forge; + + object_type *q_ptr; + + object_type *o_ptr; + + bool_ hit_body = FALSE; + + byte missile_attr; + + char missile_char; + + char o_name[80]; + + s32b special = 0; + + int msec = delay_factor * delay_factor * delay_factor; + + + /* Get the "bow" (if any) */ + o_ptr = &p_ptr->inventory[INVEN_BOW]; + + + /* Get a direction (or cancel) */ + if (!get_aim_dir(&dir)) return; + + + /* Get local object */ + q_ptr = &forge; + + /* Obtain a local object */ + object_copy(q_ptr, o_ptr); + + /* Single object */ + q_ptr->number = 1; + + /* Description */ + object_desc(o_name, q_ptr, FALSE, 3); + + /* Find the color and symbol for the object for throwing */ + missile_attr = object_attr(q_ptr); + missile_char = object_char(q_ptr); + + /* Extract a "distance multiplier" */ + /* Changed for 'launcher' corruption */ + mul = 10 + 2 * (p_ptr->throw_mult - 1); + + /* Enforce a minimum "weight" of one pound */ + div = ((q_ptr->weight > 10) ? q_ptr->weight : 10); + + /* Hack -- Distance -- Reward strength, penalize weight */ + tdis = (adj_str_blow[p_ptr->stat_ind[A_STR]] + 20) * mul / div; + + /* Max distance of 10-18 */ + if (tdis > mul) tdis = mul; + + /* Hack -- Base damage from thrown object */ + tdam = damroll(q_ptr->dd, q_ptr->ds) + q_ptr->to_d; + tdam *= p_ptr->throw_mult; + + /* Chance of hitting */ + chance = + (p_ptr->skill_tht + + ((p_ptr->to_h + p_ptr->to_h_ranged) * BTH_PLUS_ADJ)); + + chance += get_skill(SKILL_BOOMERANG); + + /* Take a turn */ + energy_use = 100; + + + /* Start at the player */ + y = p_ptr->py; + x = p_ptr->px; + + /* Predict the "target" location */ + tx = p_ptr->px + 99 * ddx[dir]; + ty = p_ptr->py + 99 * ddy[dir]; + + /* Check for "target request" */ + if ((dir == 5) && target_okay()) + { + tx = target_col; + ty = target_row; + } + + + /* Hack -- Handle stuff */ + handle_stuff(); + + + /* Travel until stopped */ + for (cur_dis = 0; cur_dis <= tdis; ) + { + /* Hack -- Stop at the target */ + if ((y == ty) && (x == tx)) break; + + /* Calculate the new location (see "project()") */ + ny = y; + nx = x; + mmove2(&ny, &nx, p_ptr->py, p_ptr->px, ty, tx); + + /* Stopped by walls/doors */ + if (!cave_floor_bold(ny, nx)) + { + break; + } + + /* Advance the distance */ + cur_dis++; + + /* Save the new location */ + x = nx; + y = ny; + + + /* The player can see the (on screen) missile */ + if (panel_contains(y, x) && player_can_see_bold(y, x)) + { + /* Draw, Hilite, Fresh, Pause, Erase */ + print_rel(missile_char, missile_attr, y, x); + move_cursor_relative(y, x); + Term_fresh(); + sleep_for(milliseconds(msec)); + lite_spot(y, x); + Term_fresh(); + } + + /* The player cannot see the missile */ + else + { + /* Pause anyway, for consistancy */ + sleep_for(milliseconds(msec)); + } + + + /* Monster here, Try to hit it */ + if (cave[y][x].m_idx) + { + cave_type *c_ptr = &cave[y][x]; + + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + monster_race *r_ptr = race_inf(m_ptr); + + /* Check the visibility */ + visible = m_ptr->ml; + + /* Note the collision */ + hit_body = TRUE; + + /* Did we hit it (penalize range) */ + if (test_hit_fire(chance - cur_dis, m_ptr->ac, m_ptr->ml)) + { + bool_ fear = FALSE; + + /* Assume a default death */ + cptr note_dies = " dies."; + + /* Some monsters get "destroyed" */ + if ((r_ptr->flags3 & (RF3_DEMON)) || + (r_ptr->flags3 & (RF3_UNDEAD)) || + (r_ptr->flags2 & (RF2_STUPID)) || + (strchr("Evg", r_ptr->d_char))) + { + /* Special note at death */ + note_dies = " is destroyed."; + } + + + /* Handle unseen monster */ + if (!visible) + { + /* Invisible monster */ + msg_format("The %s finds a mark.", o_name); + } + + /* Handle visible monster */ + else + { + char m_name[80]; + + /* Get "the monster" or "it" */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("The %s hits %s.", o_name, m_name); + + /* Hack -- Track this monster race */ + if (m_ptr->ml) monster_race_track(m_ptr->r_idx, m_ptr->ego); + + /* Hack -- Track this monster */ + if (m_ptr->ml) health_track(c_ptr->m_idx); + } + + /* Apply special damage XXX XXX XXX */ + tdam = tot_dam_aux(q_ptr, tdam, m_ptr, &special); + tdam = critical_shot(q_ptr->weight, q_ptr->to_h, tdam, SKILL_ARCHERY); + + /* No negative damage */ + if (tdam < 0) tdam = 0; + + /* Complex message */ + if (wizard) + { + msg_format("You do %d (out of %d) damage.", + tdam, m_ptr->hp); + } + + /* Hit the monster, check for death */ + if (mon_take_hit(c_ptr->m_idx, tdam, &fear, note_dies)) + { + /* Dead monster */ + } + + /* No death */ + else + { + /* Message */ + message_pain(c_ptr->m_idx, tdam); + + if (special) attack_special(m_ptr, special, tdam); + + /* Anger friends */ + if (!(k_info[q_ptr->k_idx].tval == TV_POTION)) + { + char m_name[80]; + monster_desc(m_name, m_ptr, 0); + switch (is_friend(m_ptr)) + { + case 1: + msg_format("%^s gets angry!", m_name); + change_side(m_ptr); + break; + case 0: + msg_format("%^s gets angry!", m_name); + m_ptr->status = MSTATUS_NEUTRAL_M; + break; + } + } + + /* Take note */ + if (fear && m_ptr->ml) + { + char m_name[80]; + + /* Sound */ + sound(SOUND_FLEE); + + /* Get the monster name (or "it") */ + monster_desc(m_name, m_ptr, 0); + + /* Message */ + msg_format("%^s flees in terror!", m_name); + } + } + + /* Chance of breakage (during attacks) */ + j = (hit_body ? breakage_chance(o_ptr) : 0); + + /* Break the boomerang */ + if (!(o_ptr->art_name || artifact_p(o_ptr)) && + (rand_int(100) < j)) + { + msg_print(format("Your %s is destroyed.", o_name)); + inc_stack_size_ex(INVEN_BOW, -1, OPTIMIZE, NO_DESCRIBE); + } + } + + /* Stop looking */ + break; + } + } + + /* Travel back to the player */ + for (cur_dis = 0; cur_dis <= tdis; ) + { + /* Hack -- Stop at the target */ + if ((y == p_ptr->py) && (x == p_ptr->px)) break; + + /* Calculate the new location (see "project()") */ + ny = y; + nx = x; + mmove2(&ny, &nx, ty, tx, p_ptr->py, p_ptr->px); + + /* Advance the distance */ + cur_dis++; + + /* Save the new location */ + x = nx; + y = ny; + + + /* The player can see the (on screen) missile */ + if (panel_contains(y, x) && player_can_see_bold(y, x)) + { + /* Draw, Hilite, Fresh, Pause, Erase */ + print_rel(missile_char, missile_attr, y, x); + move_cursor_relative(y, x); + Term_fresh(); + sleep_for(milliseconds(msec)); + lite_spot(y, x); + Term_fresh(); + } + + /* The player cannot see the missile */ + else + { + /* Pause anyway, for consistancy */ + sleep_for(milliseconds(msec)); + } + } +} + + +static bool_ tport_vertically(bool_ how) +{ + /* quest? */ + if (p_ptr->inside_quest) + { + msg_print("There is no effect."); + return (FALSE); + } + + if (dungeon_flags2 & DF2_NO_EASY_MOVE) + { + msg_print("Some powerful force prevents you from teleporting."); + return FALSE; + } + + /* Go down */ + if (how) + { + if (dun_level >= d_info[dungeon_type].maxdepth) + { + msg_print("The floor is impermeable."); + return (FALSE); + } + + msg_print("You sink through the floor."); + dun_level++; + p_ptr->leaving = TRUE; + } + else + { + if (dun_level < d_info[dungeon_type].mindepth) + { + msg_print("There is nothing above you but air."); + return (FALSE); + } + + msg_print("You rise through the ceiling."); + dun_level--; + p_ptr->leaving = TRUE; + } + + return (TRUE); +} + + +/* + * Do a special ``movement'' action. Meant to be used for ``immovable'' + * characters. + */ +void do_cmd_immovable_special(void) +{ + int i, ii, ij, dir; + + int foo = p_ptr->immov_cntr; + + int lose_sp = 0; + + int lose_hp = 0; + + bool_ did_act = FALSE; + + bool_ did_load = FALSE; + + + if (foo > 1) + { + if (p_ptr->csp > foo / 2) + { + + msg_format("This will drain %d mana points!", foo / 2); + if (!get_check("Proceed? ")) return; + + lose_sp = foo / 2; + + } + else if (p_ptr->chp > foo / 2) + { + + msg_format("Warning: This will drain %d hit points!", foo / 2); + if (!get_check("Proceed? ")) return; + + lose_hp = foo / 2; + + } + else + { + msg_print("You can't use your powers yet."); + return; + } + } + + /* Enter "icky" mode */ + character_icky = TRUE; + + /* Save the screen */ + Term_save(); + + + /* Interact until done */ + while (1) + { + /* Clear screen */ + Term_clear(); + + /* Ask for a choice */ + prt("Do what special action:", 2, 0); + + /* Give some choices */ + prt("(a) Teleport to a specific place.", 4, 5); + prt("(b) Fetch an item.", 5, 5); + prt("(c) Go up 50'", 6, 5); + prt("(d) Go down 50'", 7, 5); + + /* Prompt */ + prt("Command: ", 9, 0); + + /* Prompt */ + i = inkey(); + + /* Done */ + if (i == ESCAPE) break; + + /* Tele-to */ + if (i == 'a') + { + Term_load(); + character_icky = FALSE; + did_load = TRUE; + + if (!tgt_pt(&ii, &ij)) break; + + /* Teleport to the target */ + teleport_player_to(ij, ii); + + did_act = TRUE; + break; + } + + /* Fetch item */ + else if (i == 'b') + { + Term_load(); + character_icky = FALSE; + did_load = TRUE; + + if (!get_aim_dir(&dir)) return; + fetch(dir, p_ptr->lev * 15, FALSE); + py_pickup_floor(always_pickup); + + did_act = TRUE; + break; + } + + /* Move up */ + else if (i == 'c') + { + Term_load(); + character_icky = FALSE; + did_load = TRUE; + + if (!tport_vertically(FALSE)) return; + + did_act = TRUE; + break; + } + + /* Move down */ + else if (i == 'd') + { + Term_load(); + character_icky = FALSE; + did_load = TRUE; + + if (!tport_vertically(TRUE)) return; + + did_act = TRUE; + break; + } + + /* Unknown option */ + else + { + bell(); + } + + } + + /* Check if screen was restored before */ + if (!did_load) + { + /* Restore the screen */ + Term_load(); + + /* Leave "icky" mode */ + character_icky = FALSE; + } + + /* Apply stat losses if something was done */ + if (did_act) + { + p_ptr->immov_cntr += 101 - (p_ptr->lev * 2); + + if (lose_sp) + { + p_ptr->csp -= lose_sp; + p_ptr->redraw |= (PR_FRAME); + } + + if (lose_hp) + { + p_ptr->chp -= lose_hp; + p_ptr->redraw |= (PR_FRAME); + } + + energy_use = 100; + } +} + +/* Can we sacrifice it ? */ +static bool item_tester_hook_sacrificable(object_type const *o_ptr) +{ + if (p_ptr->pgod == GOD_MELKOR) + { + /* Corpses are */ + if (o_ptr->tval == TV_CORPSE && o_ptr->sval == SV_CORPSE_CORPSE) + return (TRUE); + + /* Books without any udun spells */ + if ((o_ptr->tval == TV_BOOK) && udun_in_book(o_ptr->sval, o_ptr->pval) <= 0) + { + return TRUE; + } + } + + /* Assume not */ + return (FALSE); +} + +/* + * Is item eligible for sacrifice to Aule? + */ +static bool item_tester_hook_sacrifice_aule(object_type const *o_ptr) +{ + /* perhaps restrict this only to metal armour and weapons */ + return (o_ptr->found == OBJ_FOUND_SELFMADE); +} + +/* + * Handle sacrifices to Aule + */ +static void do_cmd_sacrifice_aule() +{ + int item; + + if (!get_item(&item, + "Sacrifice which item? ", + "You have nothing to sacrifice.", + USE_INVEN, + item_tester_hook_sacrifice_aule)) + { + return; + } + + /* Increase piety by the value of the item / 10. */ + { + object_type *o_ptr = get_object(item); + s32b delta = object_value(o_ptr) / 10; + + inc_piety(GOD_ALL, delta); + } + + /* Destroy the object */ + inc_stack_size(item, -1); +} + +/* + * Handle sacrifices. + * Grace is increased by value of sacrifice. + */ +void do_cmd_sacrifice(void) +{ + byte on_what = cave[p_ptr->py][p_ptr->px].feat; + + /* Check valididty */ + if ((on_what < FEAT_ALTAR_HEAD) || (on_what > FEAT_ALTAR_TAIL)) + { + show_god_info(); + return; + } + else + { + int agod = on_what - FEAT_ALTAR_HEAD + 1; + + /* Not worshipping a god ? ahhhh! */ + if (p_ptr->pgod == GOD_NONE) + { + int i; + + for (i = 0; i < 10; i++) + { + if (deity_info[agod].desc[i] != NULL) + msg_print(deity_info[agod].desc[i]); + } + if (get_check(format("Do you want to worship %s? ", deity_info[agod].name))) + { + follow_god(agod, FALSE); + p_ptr->grace = -200; + inc_piety(p_ptr->pgod, 0); + } + } + else if (p_ptr->pgod == agod) + { + if (p_ptr->pgod == GOD_MELKOR) + { + /* One can sacrifice some HP for piety or damage */ + if ((p_ptr->mhp > 10) && (p_ptr->chp > 10) && get_check("Do you want to sacrifice a part of yourself? ")) + { + /* 10 HP = 300 * wis piety */ + if (get_check("Do you want to sacrifice for more piety instead of damage? ")) + { + int x = wisdom_scale(6); + if (x < 1) x = 1; + + p_ptr->hp_mod -= 10; + take_hit(10, "self sacrifice to Melkor"); + msg_print("Your life slips away, and Melkor seems happier."); + inc_piety(GOD_MELKOR, x * 300); + p_ptr->update |= (PU_HP); + } + /* 10 HP = +wis damage */ + else + { + take_hit(10, "self sacrifice to Melkor"); + msg_print("Your life slips away, and your arms grow stronger."); + p_ptr->melkor_sacrifice++; + p_ptr->update |= (PU_BONUS | PU_HP); + } + } + else + { + /* Get an item */ + int item; + if (!get_item(&item, + "Sacrifice which item? ", + "You have nothing to sacrifice.", + (USE_INVEN), + item_tester_hook_sacrificable)) + { + return; + } + + object_type *o_ptr = get_object(item); + + /* Piety for corpses is based on monster level */ + if (o_ptr->tval == TV_CORPSE) + { + inc_piety(GOD_MELKOR, 2 * r_info[o_ptr->pval2].level); + } + + /* In books it depends of the spell levels*/ + if (o_ptr->tval == TV_BOOK) + { + int x = levels_in_book(o_ptr->sval, o_ptr->pval); + + inc_piety(GOD_MELKOR, 2 * x); + } + + /* Remove the item */ + inc_stack_size(item, -1); + } + } + + if (p_ptr->pgod == GOD_AULE) + { + do_cmd_sacrifice_aule(); + } + + } + } +} + + +/* + * scan_monst -- + * + * Return a list of o_list[] indexes of items of the given monster + */ +std::vector<s16b> scan_monst(int m_idx) +{ + constexpr std::size_t max_size = 23; + + /* Create output vector. */ + std::vector<s16b> objects; + objects.reserve(std::min(max_size, m_list[m_idx].hold_o_idxs.size())); + + /* Scan all objects in the grid */ + for (auto const this_o_idx: m_list[m_idx].hold_o_idxs) + { + objects.push_back(this_o_idx); + if (objects.size() == max_size) break; + } + + /* Result */ + return objects; +} + + +/* + * Display a list of the items that the given monster carries. + * Returns the list of objects. + */ +std::vector<s16b> show_monster_inven(int m_idx) +{ + byte out_color[23]; + char out_desc[23][80]; + + /* Default length */ + int len = 79 - 50; + + /* Maximum space allowed for descriptions */ + int lim = 79 - 3; + + /* Require space for weight */ + lim -= 9; + + /* Scan for objects on the monster */ + std::vector<s16b> objects = scan_monst(m_idx); + assert(objects.size() <= 23); + + /* Calculate width of object names */ + for (std::size_t i = 0; i < objects.size(); i++) + { + object_type *o_ptr = &o_list[objects.at(i)]; + + /* Describe the object */ + char o_name[80]; + object_desc(o_name, o_ptr, TRUE, 3); + + /* Hack -- enforce max length */ + o_name[lim] = '\0'; + + /* Acquire p_ptr->inventory color */ + out_color[i] = tval_to_attr[o_ptr->tval & 0x7F]; + + /* Save the object description */ + strcpy(out_desc[i], o_name); + + /* Find the predicted "line length" */ + int l = strlen(out_desc[i]) + 5; + + /* Account for the weight */ + l += 9; + + /* Maintain the maximum length */ + if (l > len) len = l; + } + + /* Find the column to start in */ + int col = (len > 76) ? 0 : (79 - len); + + /* Output each entry */ + std::size_t i = 0; + for (i = 0; i < objects.size(); i++) + { + /* Get the item */ + object_type *o_ptr = &o_list[objects.at(i)]; + + /* Clear the line */ + prt("", i + 1, col ? col - 2 : col); + + /* Prepare an index --(-- */ + char tmp_val[80]; + strnfmt(tmp_val, 80, "%c)", index_to_label(i)); + + /* Clear the line with the (possibly indented) index */ + put_str(tmp_val, i + 1, col); + + /* Display the entry itself */ + c_put_str(out_color[i], out_desc[i], i + 1, col + 3); + + /* Display the weight if needed */ + { + int wgt = o_ptr->weight * o_ptr->number; + strnfmt(tmp_val, 80, "%3d.%1d lb", wgt / 10, wgt % 10); + put_str(tmp_val, i + 1, 71); + } + } + + /* Make a "shadow" below the list (only if needed) */ + if (i && (i < 23)) + { + prt("", i + 1, col ? col - 2 : col); + } + + return objects; +} + + +/* + * Steal an object from a monster + */ +void do_cmd_steal() +{ + int dir = 0, item = -1, k = -1; + + bool_ done = FALSE; + + /* Only works on adjacent monsters */ + if (!get_rep_dir(&dir)) return; + int y = p_ptr->py + ddy[dir]; + int x = p_ptr->px + ddx[dir]; + + cave_type const *c_ptr = &cave[y][x]; + + if (!(c_ptr->m_idx)) + { + msg_print("There is no monster there!"); + return; + } + + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + + /* There were no non-gold items */ + if (m_ptr->hold_o_idxs.empty()) + { + msg_print("That monster has no objects!"); + return; + } + + /* The monster is immune */ + if (r_info[m_ptr->r_idx].flags7 & (RF7_NO_THEFT)) + { + msg_print("The monster is guarding the treasures."); + return; + } + + screen_save(); + + std::vector<s16b> objects = show_monster_inven(c_ptr->m_idx); + + /* Repeat until done */ + while (!done) + { + char tmp_val[80]; + char which = ' '; + + /* Build the prompt */ + strnfmt(tmp_val, 80, "Choose an item to steal (a-%c) or ESC:", + 'a' - 1 + objects.size()); + + /* Show the prompt */ + prt(tmp_val, 0, 0); + + /* Get a key */ + which = inkey(); + + /* Parse it */ + switch (which) + { + case ESCAPE: + { + done = TRUE; + + break; + } + + default: + { + int ver; + + /* Extract "query" setting */ + ver = isupper(which); + which = tolower(which); + + k = islower(which) ? A2I(which) : -1; + if ((k < 0) || (static_cast<std::size_t>(k) >= objects.size())) + { + bell(); + + break; + } + + /* Verify the item */ + if (ver && !verify("Try", -objects[k])) + { + done = TRUE; + + break; + } + + /* Accept that choice */ + item = objects[k]; + done = TRUE; + + break; + } + } + } + + if (item != -1) + { + int chance; + + chance = 40 - p_ptr->stat_ind[A_DEX]; + chance += + o_list[item].weight / (get_skill_scale(SKILL_STEALING, 19) + 1); + chance += get_skill_scale(SKILL_STEALING, 29) + 1; + chance -= (m_ptr->csleep) ? 10 : 0; + chance += m_ptr->level; + + /* Failure check */ + if (rand_int(chance) > 1 + get_skill_scale(SKILL_STEALING, 25)) + { + /* Take a turn */ + energy_use = 100; + + /* Wake up */ + m_ptr->csleep = 0; + + /* Speed up because monsters are ANGRY when you try to thief them */ + m_ptr->mspeed += 5; + + screen_load(); + + msg_print("Oops! The monster is now really *ANGRY*!"); + + return; + } + + /* Remove from the monster's list of objects */ + m_ptr->hold_o_idxs.erase(m_ptr->hold_o_idxs.begin() + k); + + /* Rogues gain some xp */ + if (race_flags1_p(PR1_EASE_STEAL)) + { + s32b max_point; + + /* Max XP gained from stealing */ + max_point = (o_list[item].weight / 2) + (m_ptr->level * 10); + + /* Randomise it a bit, with half a max guaranteed */ + gain_exp((max_point / 2) + (randint(max_point) / 2)); + + /* Allow escape */ + if (get_check("Phase door?")) teleport_player(10); + } + + /* Create the object we're going to copy into */ + object_type forge; + object_type *o_ptr = &forge; + + /* Special handling for gold */ + if (o_list[item].tval == TV_GOLD) + { + /* Collect the gold */ + p_ptr->au += o_list[item].pval; + + /* Redraw gold */ + p_ptr->redraw |= (PR_FRAME); + + /* Window stuff */ + p_ptr->window |= (PW_PLAYER); + } + else + { + object_copy(o_ptr, &o_list[item]); + + inven_carry(o_ptr, FALSE); + } + + /* Delete source item */ + o_list[item].k_idx = 0; + } + + screen_load(); + + /* Take a turn */ + energy_use = 100; +} + + +/* + * Give an item to a monster + */ +void do_cmd_give() +{ + /* Get a "repeated" direction */ + int dir; + if (!get_rep_dir(&dir)) return; + + /* Get requested location */ + int y = p_ptr->py + ddy[dir]; + int x = p_ptr->px + ddx[dir]; + + /* Get requested grid */ + cave_type *c_ptr = &cave[y][x]; + + /* No monster in the way */ + if (c_ptr->m_idx == 0) + { + msg_print("There is no monster there."); + return; + } + + /* Get an item */ + int item; + if (!get_item(&item, + "What item do you want to offer? ", + "You have nothing to offer.", + USE_INVEN)) + { + return; + } + + /* Process hooks if there are any */ + hook_give_in in = { c_ptr->m_idx, item }; + if (!process_hooks_new(HOOK_GIVE, &in, NULL)) + { + msg_print("The monster does not want your item."); + } + + /* Take a turn, even if the offer is declined */ + energy_use = 100; +} + + +/* + * Chat with a monster + */ +void do_cmd_chat() +{ + int dir, x, y; + + cave_type *c_ptr; + + + /* Get a "repeated" direction */ + if (!get_rep_dir(&dir)) return; + + /* Get requested location */ + y = p_ptr->py + ddy[dir]; + x = p_ptr->px + ddx[dir]; + + /* Get requested grid */ + c_ptr = &cave[y][x]; + + /* No monster in the way */ + if (c_ptr->m_idx == 0) + { + msg_print("There is no monster there."); + return; + } + + /* Process hook if there are any */ + struct hook_chat_in in = { c_ptr->m_idx }; + if (!process_hooks_new(HOOK_CHAT, &in, NULL)) + { + msg_print("The monster does not want to chat."); + } + + /* No energy spent */ +} |