diff options
Diffstat (limited to 'crawl-ref/source/traps.cc')
-rw-r--r-- | crawl-ref/source/traps.cc | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/crawl-ref/source/traps.cc b/crawl-ref/source/traps.cc new file mode 100644 index 0000000000..9168f1e585 --- /dev/null +++ b/crawl-ref/source/traps.cc @@ -0,0 +1,701 @@ +/* + * File: traps.cc + * Summary: Traps related functions. + * Written by: Linley Henzell + * + * Modified for Crawl Reference by $Author: j-p-e-g $ on $Date: 2007-09-03 06:41:30 -0700 (Mon, 03 Sep 2007) $ + * + * Change History (most recent first): + * + * <1> 9/11/07 MPC Split from misc.cc + */ + +#include "externs.h" +#include "traps.h" + +#include "beam.h" +#include "direct.h" +#include "it_use2.h" +#include "items.h" +#include "itemprop.h" +#include "makeitem.h" +#include "mon-util.h" +#include "monstuff.h" +#include "ouch.h" +#include "player.h" +#include "randart.h" +#include "skills.h" +#include "spells3.h" +#include "spl-cast.h" +#include "spl-util.h" +#include "terrain.h" +#include "tutorial.h" +#include "view.h" + +static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison); + +// returns the number of a net on a given square +// if trapped only stationary ones are counted +// otherwise the first net found is returned +int get_trapping_net(int x, int y, bool trapped) +{ + int net, next; + + for (net = igrd[x][y]; net != NON_ITEM; net = next) + { + next = mitm[net].link; + + if (mitm[net].base_type == OBJ_MISSILES + && mitm[net].sub_type == MI_THROWING_NET + && (!trapped || item_is_stationary(mitm[net]))) + { + return (net); + } + } + return (NON_ITEM); +} + +// if there are more than one net on this square +// split off one of them for checking/setting values +static void maybe_split_nets(item_def &item, int x, int y) +{ + if (item.quantity == 1) + { + set_item_stationary(item); + return; + } + + item_def it; + + it.base_type = item.base_type; + it.sub_type = item.sub_type; + it.plus = item.plus; + it.plus2 = item.plus2; + it.flags = item.flags; + it.special = item.special; + it.quantity = --item.quantity; + item_colour(it); + + item.quantity = 1; + set_item_stationary(item); + + copy_item_to_grid( it, x, y ); +} + +void mark_net_trapping(int x, int y) +{ + int net = get_trapping_net(x,y); + if (net == NON_ITEM) + { + net = get_trapping_net(x,y, false); + if (net != NON_ITEM) + maybe_split_nets(mitm[net], x, y); + } +} + +void monster_caught_in_net(monsters *mon, bolt &pbolt) +{ + if (mon->body_size(PSIZE_BODY) >= SIZE_GIANT) + return; + + if (mons_is_insubstantial(mon->type)) + { + if (mons_near(mon) && player_monster_visible(mon)) + mprf("The net passes right through %s!", mon->name(DESC_NOCAP_THE).c_str()); + return; + } + + const monsters* mons = static_cast<const monsters*>(mon); + bool mon_flies = mons->flies(); + if (mon_flies && !mons_is_confused(mons)) + { + simple_monster_message(mon, " darts out from under the net!"); + return; + } + + if (mons->type == MONS_OOZE || mons->type == MONS_PULSATING_LUMP) + { + simple_monster_message(mon, " oozes right through the net!"); + return; + } + + if (!mons_is_caught(mon) && mon->add_ench(ENCH_HELD)) + { + if (mons_near(mon) && !player_monster_visible(mon)) + mpr("Something gets caught in the net!"); + else + simple_monster_message(mon, " is caught in the net!"); + + if (mon_flies) + { + simple_monster_message(mon, " falls like a stone!"); + mons_check_pool(mon, pbolt.killer(), pbolt.beam_source); + } + } +} + +void player_caught_in_net() +{ + if (you.body_size(PSIZE_BODY) >= SIZE_GIANT) + return; + + if (you.flies() && !you.confused()) + { + mpr("You dart out from under the net!"); + return; + } + + if (!you.attribute[ATTR_HELD]) + { + you.attribute[ATTR_HELD] = 10; + mpr("You become entangled in the net!"); + + // I guess levitation works differently, keeping both you + // and the net hovering above the floor + if (you.flies()) + { + mpr("You fall like a stone!"); + fall_into_a_pool(you.x_pos, you.y_pos, false, grd[you.x_pos][you.y_pos]); + } + } + +} + +static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison) +{ + int damage_taken = 0; + int trap_hit, your_dodge; + + if (one_chance_in(5) || (trap_known && !one_chance_in(4))) + { + mprf( "You avoid triggering a%s trap.", pbolt.name.c_str() ); + return; + } + + if (you.equip[EQ_SHIELD] != -1 && one_chance_in(3)) + exercise( SK_SHIELDS, 1 ); + + std::string msg = "A" + pbolt.name + " shoots out and "; + + if (random2( 20 + 5 * you.shield_blocks * you.shield_blocks ) + < player_shield_class()) + { + you.shield_blocks++; + msg += "hits your shield."; + mpr(msg.c_str()); + } + else + { + // note that this uses full ( not random2limit(foo,40) ) + // player_evasion. + trap_hit = (20 + (you.your_level * 2)) * random2(200) / 100; + + your_dodge = player_evasion() + random2(you.dex) / 3 + - 2 + (you.duration[DUR_REPEL_MISSILES] * 10); + + if (trap_hit >= your_dodge && you.duration[DUR_DEFLECT_MISSILES] == 0) + { + msg += "hits you!"; + mpr(msg.c_str()); + + if (poison && random2(100) < 50 - (3 * player_AC()) / 2 + && !player_res_poison()) + { + poison_player( 1 + random2(3) ); + } + + damage_taken = roll_dice( pbolt.damage ); + damage_taken -= random2( player_AC() + 1 ); + + if (damage_taken > 0) + ouch( damage_taken, 0, KILLED_BY_TRAP, pbolt.name.c_str() ); + } + else + { + msg += "misses you."; + mpr(msg.c_str()); + } + + if (player_light_armour(true) && coinflip()) + exercise( SK_DODGING, 1 ); + } + + pbolt.target_x = you.x_pos; + pbolt.target_y = you.y_pos; + + if (coinflip()) + itrap( pbolt, trapped ); +} // end dart_trap() + +// +// itrap takes location from target_x, target_y of bolt strcture. +// + +void itrap( struct bolt &pbolt, int trapped ) +{ + object_class_type base_type = OBJ_MISSILES; + int sub_type = MI_DART; + + switch (env.trap[trapped].type) + { + case TRAP_DART: + base_type = OBJ_MISSILES; + sub_type = MI_DART; + break; + case TRAP_ARROW: + base_type = OBJ_MISSILES; + sub_type = MI_ARROW; + break; + case TRAP_BOLT: + base_type = OBJ_MISSILES; + sub_type = MI_BOLT; + break; + case TRAP_SPEAR: + base_type = OBJ_WEAPONS; + sub_type = WPN_SPEAR; + break; + case TRAP_AXE: + base_type = OBJ_WEAPONS; + sub_type = WPN_HAND_AXE; + break; + case TRAP_NEEDLE: + base_type = OBJ_MISSILES; + sub_type = MI_NEEDLE; + break; + case TRAP_NET: + base_type = OBJ_MISSILES; + sub_type = MI_THROWING_NET; + break; + default: + return; + } + + trap_item( base_type, sub_type, pbolt.target_x, pbolt.target_y ); + + return; +} // end itrap() + +void handle_traps(char trt, int i, bool trap_known) +{ + struct bolt beam; + + switch (trt) + { + case TRAP_DART: + beam.name = " dart"; + beam.damage = dice_def( 1, 4 + (you.your_level / 2) ); + dart_trap(trap_known, i, beam, false); + break; + + case TRAP_NEEDLE: + beam.name = " needle"; + beam.damage = dice_def( 1, 0 ); + dart_trap(trap_known, i, beam, true); + break; + + case TRAP_ARROW: + beam.name = "n arrow"; + beam.damage = dice_def( 1, 7 + you.your_level ); + dart_trap(trap_known, i, beam, false); + break; + + case TRAP_BOLT: + beam.name = " bolt"; + beam.damage = dice_def( 1, 13 + you.your_level ); + dart_trap(trap_known, i, beam, false); + break; + + case TRAP_SPEAR: + beam.name = " spear"; + beam.damage = dice_def( 1, 10 + you.your_level ); + dart_trap(trap_known, i, beam, false); + break; + + case TRAP_AXE: + beam.name = "n axe"; + beam.damage = dice_def( 1, 15 + you.your_level ); + dart_trap(trap_known, i, beam, false); + break; + + case TRAP_TELEPORT: + mpr("You enter a teleport trap!"); + + if (scan_randarts(RAP_PREVENT_TELEPORTATION)) + mpr("You feel a weird sense of stasis."); + else + you_teleport_now( true ); + break; + + case TRAP_AMNESIA: + mpr("You feel momentarily disoriented."); + if (!wearing_amulet(AMU_CLARITY)) + forget_map(random2avg(100, 2)); + break; + + case TRAP_BLADE: + if (trap_known && one_chance_in(3)) + mpr("You avoid triggering a blade trap."); + else if (random2limit(player_evasion(), 40) + + (random2(you.dex) / 3) + (trap_known ? 3 : 0) > 8) + { + mpr("A huge blade swings just past you!"); + } + else + { + mpr("A huge blade swings out and slices into you!"); + ouch( (you.your_level * 2) + random2avg(29, 2) + - random2(1 + player_AC()), 0, KILLED_BY_TRAP, " blade" ); + } + break; + + case TRAP_NET: + + if (trap_known && one_chance_in(3)) + mpr("A net swings high above you."); + else + { + if (random2limit(player_evasion(), 40) + + (random2(you.dex) / 3) + (trap_known ? 3 : 0) > 12) + { + mpr("A net drops to the ground!"); + } + else + { + mpr("A large net falls onto you!"); + player_caught_in_net(); + } + + trap_item( OBJ_MISSILES, MI_THROWING_NET, env.trap[i].x, env.trap[i].y ); + if (you.attribute[ATTR_HELD]) + mark_net_trapping(you.x_pos, you.y_pos); + + grd[env.trap[i].x][env.trap[i].y] = DNGN_FLOOR; + env.trap[i].type = TRAP_UNASSIGNED; + } + break; + + case TRAP_ZOT: + default: + mpr((trap_known) ? "You enter the Zot trap." + : "Oh no! You have blundered into a Zot trap!"); + miscast_effect( SPTYP_RANDOM, random2(30) + you.your_level, + 75 + random2(100), 3, "a Zot trap" ); + break; + } + learned_something_new(TUT_SEEN_TRAP, you.x_pos, you.y_pos); +} // end handle_traps() + +void disarm_trap( struct dist &disa ) +{ + if (you.duration[DUR_BERSERKER]) + { + canned_msg(MSG_TOO_BERSERK); + return; + } + + int i, j; + + for (i = 0; i < MAX_TRAPS; i++) + { + if (env.trap[i].x == you.x_pos + disa.dx + && env.trap[i].y == you.y_pos + disa.dy) + { + break; + } + + if (i == MAX_TRAPS - 1) + { + mpr("Error - couldn't find that trap."); + return; + } + } + + if (trap_category(env.trap[i].type) == DNGN_TRAP_MAGICAL) + { + mpr("You can't disarm that trap."); + return; + } + + if (random2(you.skills[SK_TRAPS_DOORS] + 2) <= random2(you.your_level + 5)) + { + mpr("You failed to disarm the trap."); + + you.turn_is_over = true; + + if (random2(you.dex) > 5 + random2(5 + you.your_level)) + exercise(SK_TRAPS_DOORS, 1 + random2(you.your_level / 5)); + else + { + if (env.trap[i].type == TRAP_NET && + (env.trap[i].x != you.x_pos || env.trap[i].y != you.y_pos)) + { + if (coinflip()) + return; + + mpr("You stumble into the trap!"); + move_player_to_grid( env.trap[i].x, env.trap[i].y, true, false, true); + } + else + handle_traps(env.trap[i].type, i, false); + + if (coinflip()) + exercise(SK_TRAPS_DOORS, 1); + } + + return; + } + + mpr("You have disarmed the trap."); + + struct bolt beam; + + beam.target_x = you.x_pos + disa.dx; + beam.target_y = you.y_pos + disa.dy; + + if (env.trap[i].type == TRAP_NET) + trap_item( OBJ_MISSILES, MI_THROWING_NET, beam.target_x, beam.target_y ); + else if (env.trap[i].type != TRAP_BLADE + && trap_category(env.trap[i].type) == DNGN_TRAP_MECHANICAL) + { + const int num_to_make = 10 + random2(you.skills[SK_TRAPS_DOORS]); + for (j = 0; j < num_to_make; j++) + { + // places items (eg darts), which will automatically stack + itrap(beam, i); + } + } + + grd[you.x_pos + disa.dx][you.y_pos + disa.dy] = DNGN_FLOOR; + env.trap[i].type = TRAP_UNASSIGNED; + you.turn_is_over = true; + + // reduced from 5 + random2(5) + exercise(SK_TRAPS_DOORS, 1 + random2(5) + (you.your_level / 5)); +} // end disarm_trap() + +// attempts to take a net off a given monster +// Do not expect gratitude for this! +// ---------------------------------- +void remove_net_from(monsters *mon) +{ + you.turn_is_over = true; + + int net = get_trapping_net(mon->x, mon->y); + + if (net == NON_ITEM) + { + mon->del_ench(ENCH_HELD, true); + return; + } + + // factor in whether monster is paralysed or invisible + int paralys = 0; + if (mons_is_paralysed(mon)) // makes this easier + paralys = random2(5); + + int invis = 0; + if (!player_monster_visible(mon)) // makes this harder + invis = 3 + random2(5); + + bool net_destroyed = false; + if ( random2(you.skills[SK_TRAPS_DOORS] + 2) + paralys + <= random2( 2*mon->body_size(PSIZE_BODY) + 3 ) + invis) + { + if (one_chance_in(you.skills[SK_TRAPS_DOORS] + you.dex/2)) + { + mitm[net].plus--; + mpr("You tear at the net."); + if (mitm[net].plus < -7) + { + mpr("Whoops! The net comes apart in your hands!"); + mon->del_ench(ENCH_HELD, true); + destroy_item(net); + net_destroyed = true; + } + } + + if (!net_destroyed) + { + if (player_monster_visible(mon)) + { + mprf("You fail to remove the net from %s.", + mon->name(DESC_NOCAP_THE).c_str()); + } + else + mpr("You fail to remove the net."); + } + + if (random2(you.dex) > 5 + random2( 2*mon->body_size(PSIZE_BODY) )) + exercise(SK_TRAPS_DOORS, 1 + random2(mon->body_size(PSIZE_BODY)/2)); + return; + } + + mon->del_ench(ENCH_HELD, true); + remove_item_stationary(mitm[net]); + + if (player_monster_visible(mon)) + mprf("You free %s.", mon->name(DESC_NOCAP_THE).c_str()); + else + mpr("You loosen the net."); + +} + +void free_self_from_net(bool damage_net) +{ + int net = get_trapping_net(you.x_pos, you.y_pos); + + if (net == NON_ITEM) // really shouldn't happen! + { + you.attribute[ATTR_HELD] = 0; + return; + } + int hold = mitm[net].plus; + + if (damage_net) + { + mpr("You struggle against the net."); + int damage = 1; + + // extra damage for cutting weapons + if (you.equip[EQ_WEAPON] != -1 + && can_cut_meat(you.inv[you.equip[EQ_WEAPON]])) + { + damage++; + } + + if (you.body_size(PSIZE_BODY) > SIZE_MEDIUM) + damage++; + + if (hold < 0 && !one_chance_in(-hold/2)) + damage++; + + if (you.duration[DUR_BERSERKER]) + damage *= 2; + + mitm[net].plus -= damage; + + if (mitm[net].plus < -7) + { + mpr("You rip the net and break free!"); + dec_mitm_item_quantity( net, 1 ); + + you.attribute[ATTR_HELD] = 0; + return; + } + } + else // you try to escape + { + mpr("You struggle to escape from the net."); + you.attribute[ATTR_HELD]--; + + if (you.body_size(PSIZE_BODY) < SIZE_MEDIUM) + you.attribute[ATTR_HELD]--; + + if (hold < 0 && !one_chance_in(-hold/2)) + you.attribute[ATTR_HELD]--; + + if (you.attribute[ATTR_HELD] <= 0) + { + mpr("You break free from the net!"); + you.attribute[ATTR_HELD] = 0; + remove_item_stationary(mitm[net]); + return; + } + } +} + +bool trap_item(object_class_type base_type, char sub_type, + char beam_x, char beam_y) +{ + item_def item; + + item.base_type = base_type; + item.sub_type = sub_type; + item.plus = 0; + item.plus2 = 0; + item.flags = 0; + item.special = 0; + item.quantity = 1; + + if (base_type == OBJ_MISSILES) + { + if (sub_type == MI_NEEDLE) + set_item_ego_type( item, OBJ_MISSILES, SPMSL_POISONED ); + else + set_item_ego_type( item, OBJ_MISSILES, SPMSL_NORMAL ); + } + else + { + set_item_ego_type( item, OBJ_WEAPONS, SPWPN_NORMAL ); + } + + item_colour(item); + + if (igrd[beam_x][beam_y] != NON_ITEM) + { + if (items_stack( item, mitm[ igrd[beam_x][beam_y] ] )) + { + inc_mitm_item_quantity( igrd[beam_x][beam_y], 1 ); + return (false); + } + + // don't want to go overboard here. Will only generate up to three + // separate trap items, or less if there are other items present. + if (mitm[ igrd[beam_x][beam_y] ].link != NON_ITEM + && (item.base_type != OBJ_MISSILES || item.sub_type != MI_THROWING_NET)) + { + if (mitm[ mitm[ igrd[beam_x][beam_y] ].link ].link != NON_ITEM) + return (false); + } + } // end of if igrd != NON_ITEM + + return (!copy_item_to_grid( item, beam_x, beam_y, 1 )); +} // end trap_item() + +// returns appropriate trap symbol for a given trap type {dlb} +dungeon_feature_type trap_category(trap_type type) +{ + switch (type) + { + case TRAP_TELEPORT: + case TRAP_AMNESIA: + case TRAP_ZOT: + return (DNGN_TRAP_MAGICAL); + + case TRAP_DART: + case TRAP_ARROW: + case TRAP_SPEAR: + case TRAP_AXE: + case TRAP_BLADE: + case TRAP_BOLT: + case TRAP_NEEDLE: + case TRAP_NET: + default: // what *would* be the default? {dlb} + return (DNGN_TRAP_MECHANICAL); + } +} // end trap_category() + +// returns index of the trap for a given (x,y) coordinate pair {dlb} +int trap_at_xy(int which_x, int which_y) +{ + + for (int which_trap = 0; which_trap < MAX_TRAPS; which_trap++) + { + if (env.trap[which_trap].x == which_x && + env.trap[which_trap].y == which_y && + env.trap[which_trap].type != TRAP_UNASSIGNED) + { + return (which_trap); + } + } + + // no idea how well this will be handled elsewhere: {dlb} + return (-1); +} // end trap_at_xy() + +trap_type trap_type_at_xy(int x, int y) +{ + const int idx = trap_at_xy(x, y); + return (idx == -1? NUM_TRAPS : env.trap[idx].type); +} + |