diff options
author | haranp <haranp@c06c8d41-db1a-0410-9941-cceddc491573> | 2008-10-01 17:44:09 +0000 |
---|---|---|
committer | haranp <haranp@c06c8d41-db1a-0410-9941-cceddc491573> | 2008-10-01 17:44:09 +0000 |
commit | bb183e9257bd99caeab9776b3579acf68cb203b1 (patch) | |
tree | ca8904e3d8db4aa6db1991b0ffebdb5d932ea6fe /crawl-ref/source | |
parent | e32c3272ea58ddf7bf596bbf0ec5c62ec6c42018 (diff) | |
download | crawl-ref-bb183e9257bd99caeab9776b3579acf68cb203b1.tar.gz crawl-ref-bb183e9257bd99caeab9776b3579acf68cb203b1.zip |
Reworked traps: much much cleaner now. There might be bugs, though.
Traps now remember how much ammo they have. The ammo quantities
(from David) are rather tentative.
Breaks savefiles.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7076 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source')
-rw-r--r-- | crawl-ref/source/abyss.cc | 5 | ||||
-rw-r--r-- | crawl-ref/source/acr.cc | 10 | ||||
-rw-r--r-- | crawl-ref/source/debug.cc | 4 | ||||
-rw-r--r-- | crawl-ref/source/decks.cc | 19 | ||||
-rw-r--r-- | crawl-ref/source/directn.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/dungeon.cc | 4 | ||||
-rw-r--r-- | crawl-ref/source/externs.h | 27 | ||||
-rw-r--r-- | crawl-ref/source/hiscores.cc | 6 | ||||
-rw-r--r-- | crawl-ref/source/misc.cc | 10 | ||||
-rw-r--r-- | crawl-ref/source/mon-util.cc | 8 | ||||
-rw-r--r-- | crawl-ref/source/monplace.cc | 50 | ||||
-rw-r--r-- | crawl-ref/source/monstuff.cc | 30 | ||||
-rw-r--r-- | crawl-ref/source/mstuff2.cc | 374 | ||||
-rw-r--r-- | crawl-ref/source/ouch.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/player.cc | 54 | ||||
-rw-r--r-- | crawl-ref/source/spells2.cc | 15 | ||||
-rw-r--r-- | crawl-ref/source/spells3.cc | 27 | ||||
-rw-r--r-- | crawl-ref/source/spells4.cc | 11 | ||||
-rw-r--r-- | crawl-ref/source/stash.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/tags.cc | 9 | ||||
-rw-r--r-- | crawl-ref/source/terrain.cc | 5 | ||||
-rw-r--r-- | crawl-ref/source/terrain.h | 2 | ||||
-rw-r--r-- | crawl-ref/source/traps.cc | 931 | ||||
-rw-r--r-- | crawl-ref/source/traps.h | 14 |
24 files changed, 730 insertions, 891 deletions
diff --git a/crawl-ref/source/abyss.cc b/crawl-ref/source/abyss.cc index 3ab081c559..34a44c16ad 100644 --- a/crawl-ref/source/abyss.cc +++ b/crawl-ref/source/abyss.cc @@ -811,8 +811,11 @@ static void _corrupt_square(const crawl_environment &oenv, const coord_def &c) else feat = oenv.grid(c); - if (is_trap_square(feat) || feat == DNGN_SECRET_DOOR || feat == DNGN_UNSEEN) + if (grid_is_trap(feat, true) + || feat == DNGN_SECRET_DOOR || feat == DNGN_UNSEEN) + { return; + } if (is_traversable(grd(c)) && !is_traversable(feat) && _is_crowded_square(c)) diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index e3629e580a..2a4b67a373 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -1697,10 +1697,9 @@ static void _go_upstairs() static void _go_downstairs() { - bool shaft = (trap_type_at_xy(you.pos()) == TRAP_SHAFT + bool shaft = (get_trap_type(you.pos()) == TRAP_SHAFT && grd(you.pos()) != DNGN_UNDISCOVERED_TRAP); - if (_stairs_check_beheld()) return; @@ -3021,7 +3020,7 @@ static void _check_shafts() { for (int i = 0; i < MAX_TRAPS; i++) { - trap_struct &trap = env.trap[i]; + trap_def &trap = env.trap[i]; if (trap.type != TRAP_SHAFT) continue; @@ -3512,8 +3511,7 @@ static void _open_door(coord_def move, bool check_confused) return; } - if (grd(doorpos) >= DNGN_TRAP_MECHANICAL - && grd(doorpos) <= DNGN_TRAP_NATURAL) + if (find_trap(doorpos) && grd(doorpos) != DNGN_UNDISCOVERED_TRAP) { if (env.cgrid(doorpos) != EMPTY_CLOUD) { @@ -3521,7 +3519,7 @@ static void _open_door(coord_def move, bool check_confused) return; } - disarm_trap(door_move); + disarm_trap(doorpos); return; } } diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index 830503590e..950c21ef1e 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -3844,10 +3844,8 @@ static void _debug_kill_traps() { for (int y = 0; y < GYM; ++y) for (int x = 0; x < GXM; ++x) - { - if (is_trap_square(grd[x][y])) + if (grid_is_trap(grd[x][y], true)) grd[x][y] = DNGN_FLOOR; - } } static int _debug_time_explore() diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index 567f5fb216..5f96a6da07 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -1480,14 +1480,8 @@ static void _warpwright_card(int power, deck_rarity_type rarity) int count = 0; coord_def f; for (adjacent_iterator ai; ai; ++ai) - { - if (grd(*ai) == DNGN_FLOOR - && trap_at_xy(*ai) == -1 - && one_chance_in(++count)) - { + if (grd(*ai) == DNGN_FLOOR && find_trap(*ai) && one_chance_in(++count)) f = *ai; - } - } if (count > 0) // found a spot { @@ -1495,11 +1489,7 @@ static void _warpwright_card(int power, deck_rarity_type rarity) { // Mark it discovered if enough power. if (get_power_level(power, rarity) >= 1) - { - const int i = trap_at_xy(f); - if (i != -1) // should always happen - grd(f) = trap_category(env.trap[i].type); - } + find_trap(f)->reveal(); } } } @@ -1533,8 +1523,7 @@ static void _flight_card(int power, deck_rarity_type rarity) { if (place_specific_trap(you.pos(), TRAP_SHAFT)) { - const int i = trap_at_xy(you.pos()); - grd(you.pos()) = trap_category(env.trap[i].type); + find_trap(you.pos())->reveal(); mpr("A shaft materialises beneath you!"); } } @@ -1554,7 +1543,7 @@ static void _minefield_card(int power, deck_rarity_type rarity) if ( *ri == you.pos() ) continue; - if (grd(*ri) == DNGN_FLOOR && trap_at_xy(*ri) == -1 + if (grd(*ri) == DNGN_FLOOR && !find_trap(*ri) && one_chance_in(4 - power_level)) { if (you.level_type == LEVEL_ABYSS) diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index f6423335f3..6b84395f45 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -2510,7 +2510,7 @@ std::string feature_description(const coord_def& where, bool bloody, case DNGN_TRAP_MECHANICAL: case DNGN_TRAP_MAGICAL: case DNGN_TRAP_NATURAL: - return (feature_description(grid, trap_type_at_xy(where), bloody, + return (feature_description(grid, get_trap_type(where), bloody, dtype, add_stop)); case DNGN_ENTER_SHOP: return (shop_name(where, add_stop)); diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index 6b4bdda1c6..6b024755ea 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -2509,7 +2509,7 @@ static void _place_traps(int level_number) for (int i = 0; i < num_traps; i++) { - trap_struct& ts(env.trap[i]); + trap_def& ts(env.trap[i]); if (ts.type != TRAP_UNASSIGNED) continue; @@ -2540,6 +2540,7 @@ static void _place_traps(int level_number) } } grd(ts.pos) = DNGN_UNDISCOVERED_TRAP; + ts.prepare_ammo(); } } @@ -7296,6 +7297,7 @@ bool place_specific_trap(const coord_def& where, trap_type spec_type) env.trap[tcount].type = spec_type; env.trap[tcount].pos = where; grd(where) = DNGN_UNDISCOVERED_TRAP; + env.trap[tcount].prepare_ammo(); return (true); } diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index bb1be8a97e..a200989c4f 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -363,6 +363,8 @@ public: virtual bool backlit(bool check_haloed = true) const = 0; virtual bool haloed() const = 0; + virtual bool handle_trap(); + virtual void god_conduct(conduct_type thing_done, int level) { } virtual bool incapacitated() const @@ -1372,10 +1374,29 @@ struct shop_struct FixedVector<unsigned char, 3> keeper_name; }; -struct trap_struct +struct trap_def { coord_def pos; - trap_type type; + trap_type type; + int ammo_qty; + + dungeon_feature_type category() const; + std::string name(description_level_type desc = DESC_PLAIN) const; + bool is_known(const actor* act = 0) const; + void trigger(actor& triggerer, bool flat_footed = false); + void disarm(); + void destroy(); + void hide(); + void reveal(); + void prepare_ammo(); + bool type_has_ammo() const; + bool active() const; + +private: + void message_trap_entry(); + void shoot_ammo(actor& act, bool was_known); + item_def generate_trap_item(); + int shot_damage(actor& act); }; struct map_cell @@ -1479,7 +1500,7 @@ public: unsigned char cloud_no; FixedVector< shop_struct, MAX_SHOPS > shop; // shop list - FixedVector< trap_struct, MAX_TRAPS > trap; // trap list + FixedVector< trap_def, MAX_TRAPS > trap; // trap list FixedVector< monster_type, 20 > mons_alloc; map_markers markers; diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc index 0adefdc5b0..ec7a1f4145 100644 --- a/crawl-ref/source/hiscores.cc +++ b/crawl-ref/source/hiscores.cc @@ -1013,8 +1013,10 @@ void scorefile_entry::init() if (calc_item_values) // winners only { - points += (250000 * num_diff_runes) - * ((25000.0 * num_diff_runes) / (1+you.num_turns)); + points += + static_cast<long>( + (250000 * num_diff_runes) + * ((25000.0 * num_diff_runes) / (1+you.num_turns))); } // Players will have a hard time getting 1/10 of this (see XP cap): diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index 04c51de4a5..1e85627792 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -1269,14 +1269,12 @@ void search_around( bool only_adjacent ) else if (grd(*ri) == DNGN_UNDISCOVERED_TRAP && x_chance_in_y(effective + 1, 17)) { - const int i = trap_at_xy(*ri); - - if (i != -1) + trap_def* ptrap = find_trap(*ri); + if (ptrap) { - grd(*ri) = trap_category(env.trap[i].type); + ptrap->reveal(); mpr("You found a trap!"); learned_something_new(TUT_SEEN_TRAP, *ri); - exercise(SK_TRAPS_DOORS, (coinflip() ? 2 : 1)); } else @@ -1720,7 +1718,7 @@ void down_stairs( int old_level, dungeon_feature_type force_stair, branch_type old_where = you.where_are_you; bool shaft = (!force_stair - && trap_type_at_xy(you.pos()) == TRAP_SHAFT + && get_trap_type(you.pos()) == TRAP_SHAFT || force_stair == DNGN_TRAP_NATURAL); level_id shaft_dest; int shaft_level = -1; diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index d78cc0d6d8..d35caa37c7 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -5464,7 +5464,7 @@ static bool _prepare_del_ench(monsters* mon, const mon_enchant &me) { if (mgrd(*ai) == NON_MONSTER && monster_habitable_grid(mon, grd(*ai)) - && trap_type_at_xy(*ai) == NUM_TRAPS) + && !find_trap(*ai)) { if (one_chance_in(++okay_squares)) target_square = *ai; @@ -6537,7 +6537,9 @@ void monsters::apply_location_effects() dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos()); // monsters stepping on traps: - mons_trap(this); + trap_def* ptrap = find_trap(pos()); + if (ptrap) + ptrap->trigger(*this); if (alive()) mons_check_pool(this); @@ -6578,7 +6580,7 @@ bool monsters::do_shaft() // Handle instances of do_shaft() being invoked magically when // the monster isn't standing over a shaft. - if (trap_type_at_xy(this->pos()) != TRAP_SHAFT) + if (get_trap_type(this->pos()) != TRAP_SHAFT) { switch(grd(pos())) { diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index 523c18efaf..a60c628957 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -155,7 +155,7 @@ bool monster_habitable_grid(int monster_class, bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid) { if (mons->type == MONS_TRAPDOOR_SPIDER && grid == DNGN_FLOOR) - return (trap_type_at_xy(mons->pos()) == NUM_TRAPS); + return (!find_trap(mons->pos())); // Zombies of watery critters can not submerge. switch (mons_habitat(mons)) @@ -458,12 +458,9 @@ static monster_type _resolve_monster_type(monster_type mon_type, continue; // Don't generate monsters on top of teleport traps. - int trap = trap_at_xy(pos); - if (trap >= 0) - { - if (!_can_place_on_trap(mon_type, env.trap[trap].type)) - continue; - } + const trap_def* ptrap = find_trap(pos); + if (ptrap && !_can_place_on_trap(mon_type, ptrap->type)) + continue; // Check whether there's a stair // and whether it leads to another branch. @@ -574,12 +571,9 @@ static bool _valid_monster_location(const mgen_data &mg, // Don't generate monsters on top of teleport traps. // (How did they get there?) - int trap = trap_at_xy(mg_pos); - if (trap >= 0) - { - if (!_can_place_on_trap(mg.cls, env.trap[trap].type)) - return (false); - } + const trap_def* ptrap = find_trap(mg_pos); + if (ptrap && !_can_place_on_trap(mg.cls, ptrap->type)) + return (false); return (true); } @@ -2654,14 +2648,16 @@ bool monster_pathfind::traversable(coord_def p) return (false); } - const int trap = trap_at_xy(p); - if (trap >= 0) + const trap_def* ptrap = find_trap(p); + if (ptrap) { - trap_type tt = env.trap[trap].type; - if (tt == TRAP_ZOT && grd(p) != DNGN_UNDISCOVERED_TRAP + const trap_type tt = ptrap->type; + + // Don't allow allies to pass over known (to them) Zot traps. + if (tt == TRAP_ZOT + && ptrap->is_known(mons) && mons_friendly(mons)) { - // Don't allow allies to pass over known Zot traps. return (false); } @@ -2700,19 +2696,11 @@ int monster_pathfind::travel_cost(coord_def npos) } // Try to avoid (known) traps. - const int trap = trap_at_xy(npos); - if (trap >= 0) - { - // A monster can be considered to know a trap if - // a) they're hostile - // b) they're friendly and *you* know about the trap (and told them) - // c) they're friendly and know the terrain - bool knows_trap = (!mons_friendly(mons) - || grd(npos) != DNGN_UNDISCOVERED_TRAP - || mons_intel(mons->type) >= I_NORMAL - && mons_is_native_in_branch(mons)); - - trap_type tt = env.trap[trap].type; + const trap_def* ptrap = find_trap(npos); + if (ptrap) + { + const bool knows_trap = ptrap->is_known(mons); + const trap_type tt = ptrap->type; if (tt == TRAP_ALARM || tt == TRAP_ZOT) { // Your allies take extra precautions to avoid known alarm traps. diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index dc16649615..ede0784ae7 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -2410,7 +2410,7 @@ static void _find_all_level_exits(std::vector<level_exit> &e) e.push_back(level_exit(p, false)); // Teleportation and shaft traps. - const trap_type tt = trap_type_at_xy(p); + const trap_type tt = get_trap_type(p); if (tt == TRAP_TELEPORT || tt == TRAP_SHAFT) e.push_back(level_exit(p, false)); } @@ -6475,7 +6475,10 @@ static bool _is_trap_safe(const monsters *monster, const coord_def& where, if (intel == I_PLANT) return (true); - const trap_struct &trap = env.trap[trap_at_xy(where)]; + const trap_def *ptrap = find_trap(where); + if (!ptrap) + return (true); + const trap_def& trap = *ptrap; if (trap.type == TRAP_SHAFT && monster->will_trigger_shaft()) { @@ -6490,22 +6493,10 @@ static bool _is_trap_safe(const monsters *monster, const coord_def& where, // Monsters are not afraid of non-mechanical traps. XXX: If we add // any non-mechanical traps that can damage monsters, we must add // checks for them here. - const bool mechanical = trap_category(trap.type) == DNGN_TRAP_MECHANICAL; - - const bool player_knows_trap = (grd(where) != DNGN_UNDISCOVERED_TRAP); - - // Smarter trap handling for intelligent monsters - // * monsters native to a branch can be assumed to know the trap - // locations and thus be able to avoid them - // * friendlies and good neutrals can be assumed to have been warned - // by the player about all traps s/he knows about - // * very intelligent monsters can be assumed to have a high T&D - // skill (or have memorised part of the dungeon layout ;) ) - if (intel >= I_NORMAL && mechanical - && (mons_is_native_in_branch(monster) - || mons_wont_attack(monster) - && player_knows_trap - || intel >= I_HIGH && one_chance_in(3))) + const bool mechanical = (trap.category() == DNGN_TRAP_MECHANICAL); + const bool player_knows_trap = (trap.is_known(&you)); + + if (trap.is_known(monster)) { if (just_check) return (false); // Square is blocked. @@ -6776,8 +6767,7 @@ static bool _mon_can_move_to_pos(const monsters *monster, // Wandering through a trap is OK if we're pretty healthy, // really stupid, or immune to the trap. - const int which_trap = trap_at_xy(targ); - if (which_trap >= 0 && !_is_trap_safe(monster, targ, just_check)) + if (!_is_trap_safe(monster, targ, just_check)) return (false); if (targ_cloud_num != EMPTY_CLOUD) diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc index 5eeaaf3c1a..773e4433cf 100644 --- a/crawl-ref/source/mstuff2.cc +++ b/crawl-ref/source/mstuff2.cc @@ -42,380 +42,6 @@ static int _monster_abjuration(const monsters *caster, bool actual); -// NB: only works because grid location already verified -// to be some sort of trap prior to function call: {dlb} -void mons_trap(monsters *monster) -{ - if (!is_trap_square(grd(monster->pos()))) - return; - - // single calculation permissible {dlb} - bool monsterNearby = mons_near(monster); - - const int which_trap = trap_at_xy(monster->pos()); - if (which_trap == -1) - return; - - trap_struct& trap(env.trap[which_trap]); - - bool trapKnown = (grd(monster->pos()) != DNGN_UNDISCOVERED_TRAP); - bool revealTrap = false; // more sophisticated trap uncovering {dlb} - bool projectileFired = false; // <sigh> I had to do it, I swear {dlb} - int damage_taken = -1; // must initialize at -1 for this f(x) {dlb} - - bolt beem; - - - // flying monsters neatly avoid mechanical traps - // and may actually exit this function early: {dlb} - if (mons_flies(monster)) - { - if (trap_category(trap.type) == DNGN_TRAP_MECHANICAL) - { - if (trapKnown) - simple_monster_message(monster, " flies safely over a trap."); - - return; // early return {dlb} - } - } - - // Trap damage to monsters is not a function of level, because they - // are fairly stupid and tend to have fewer hp than players -- this - // choice prevents traps from easily killing large monsters fairly - // deep within the dungeon. - switch (trap.type) - { - case TRAP_DART: - projectileFired = true; - beem.name = " dart"; - beem.damage = dice_def( 1, 4 ); - beem.colour = OBJ_MISSILES; - beem.type = MI_DART; - break; - case TRAP_NEEDLE: - projectileFired = true; - beem.name = " needle"; - beem.damage = dice_def( 1, 0 ); - beem.colour = OBJ_MISSILES; - beem.type = MI_NEEDLE; - break; - case TRAP_ARROW: - projectileFired = true; - beem.name = "n arrow"; - beem.damage = dice_def( 1, 7 ); - beem.colour = OBJ_MISSILES; - beem.type = MI_ARROW; - break; - case TRAP_SPEAR: - projectileFired = true; - beem.name = " spear"; - beem.damage = dice_def( 1, 10 ); - beem.colour = OBJ_WEAPONS; - beem.type = WPN_SPEAR; - break; - case TRAP_BOLT: - projectileFired = true; - beem.name = " bolt"; - beem.damage = dice_def( 1, 13 ); - beem.colour = OBJ_MISSILES; - beem.type = MI_BOLT; - break; - case TRAP_AXE: - projectileFired = true; - beem.name = "n axe"; - beem.damage = dice_def( 1, 15 ); - beem.colour = OBJ_WEAPONS; - beem.type = WPN_HAND_AXE; - break; - // teleport traps are *never* revealed through - // the triggering action of a monster, as any - // number of factors could have been in play: {dlb} - case TRAP_TELEPORT: - monster_teleport(monster, true); - break; - // Alarm traps aren't set off by hostile monsters, because that would - // be way too nasty for the player. - case TRAP_ALARM: - if (!mons_friendly(monster) || silenced(monster->pos())) - { - if (trapKnown && you.can_see(monster) && !silenced(you.pos())) - { - mpr("The alarm trap makes no noise."); - } - return; - } - - noisy(12, monster->pos()); - - if (!silenced(you.pos())) - { - if (monsterNearby) - { - mpr("You hear a blaring wail!", MSGCH_SOUND); - if (you.can_see(monster)) - revealTrap = true; - } - else - mpr("You hear a distant blaring wail!", MSGCH_SOUND); - } - - break; - // blade traps sometimes fail to trigger altogether, - // resulting in an "early return" from this f(x) for - // some - otherwise, blade *always* revealed: {dlb} - case TRAP_BLADE: - if (one_chance_in(5) - || trapKnown && intelligent_ally(monster) && coinflip()) - { - if (trapKnown) - { - simple_monster_message(monster, - " fails to trigger a blade trap."); - } - return; // early return {dlb} - } - - if (random2(monster->ev) > 8 - || trapKnown && intelligent_ally(monster) - && random2(monster->ev) > 8) - { - if (monsterNearby && !simple_monster_message(monster, - " avoids a huge, swinging blade.")) - { - mpr("A huge blade swings out!"); - } - - damage_taken = -1; // just to be certain {dlb} - } - else - { - if (monsterNearby) - { - std::string msg = "A huge blade swings out"; - if (player_monster_visible( monster )) - { - msg += " and slices into "; - msg += monster->name(DESC_NOCAP_THE); - } - msg += "!"; - mpr(msg.c_str()); - } - - damage_taken = 10 + random2avg(29, 2); - damage_taken -= random2(1 + monster->ac); - - if (damage_taken < 0) - damage_taken = 0; - - if (!mons_is_summoned(monster)) - { - bleed_onto_floor(monster->pos(), monster->type, - damage_taken, true); - } - } - - revealTrap = true; - break; - - case TRAP_NET: - { - if (one_chance_in(3) - || trapKnown && intelligent_ally(monster) && coinflip()) - { - if (trapKnown) - { - simple_monster_message(monster, - " fails to trigger a net trap."); - } - return; - } - - if (random2(monster->ev) > 8 - || trapKnown && intelligent_ally(monster) - && random2(monster->ev) > 8) - { - if (monsterNearby && !simple_monster_message(monster, - " nimbly jumps out of the way of a falling net.")) - { - mpr("A large net falls down!"); - } - } - else - { - if (monsterNearby) - { - std::string msg = "A large net falls down"; - if (player_monster_visible( monster )) - { - msg += " onto "; - msg += monster->name(DESC_NOCAP_THE); - } - msg += "!"; - mpr(msg.c_str()); - monster_caught_in_net(monster, beem); - } - } - trap_item( OBJ_MISSILES, MI_THROWING_NET, trap.pos ); - - if (mons_is_caught(monster)) - mark_net_trapping(monster->pos()); - - grd(trap.pos) = DNGN_FLOOR; - trap.type = TRAP_UNASSIGNED; - break; - } - // zot traps are out to get *the player*! Hostile monsters - // benefit and friendly monsters suffer - such is life - on - // rare occasion, the trap affects nearby players, triggering - // an "early return" - zot traps are *never* revealed - instead, - // enchantment messages serve as clues to the trap's presence: {dlb} - case TRAP_ZOT: - if (mons_friendly(monster) || mons_good_neutral(monster)) - { - MiscastEffect( monster, ZOT_TRAP_MISCAST, SPTYP_RANDOM, - 3, "the power of Zot" ); - return; // early return - } - else if (monsterNearby) - { - if (one_chance_in(5)) - { - mpr("The power of Zot is invoked against you!"); - MiscastEffect( &you, ZOT_TRAP_MISCAST, SPTYP_RANDOM, - 3, "the power of Zot" ); - - return; // early return {dlb} - } - } - - // output triggering message to player, where appropriate: {dlb} - if (!silenced(monster->pos()) && !silenced(you.pos())) - { - if (monsterNearby) - mpr("You hear a loud \"Zot\"!", MSGCH_SOUND); - else - mpr("You hear a distant \"Zot\"!", MSGCH_SOUND); - } - - // XXX: It seem that back when a beam's colour determined its - // flavour that Zot traps would heal, haste or make invisible - // hostile monsters. The code has been fixed to work but - // commented out. -#if 0 - if (!mons_friendly(monster) && !mons_good_neutral(monster)) - { - int temp_rand = random2(16); - - beem.thrower = KILL_MON; // probably unnecessary - beem.aux_source.clear(); - beem.flavour = ((temp_rand < 3) ? BEAM_HASTE : // 3 in 16 {dlb} - (temp_rand < 7) ? BEAM_INVISIBILITY //4 in 16 {dlb} - : BEAM_HEALING); // 9 in 16 {dlb} - mons_ench_f2(monster, beem); - } -#endif - - damage_taken = 0; // just to be certain {dlb} - break; - - case TRAP_SHAFT: - // Paranoia - if (!is_valid_shaft_level()) - { - if (trapKnown && monsterNearby) - mpr("The shaft disappears in a puff of logic!"); - - grd(trap.pos) = DNGN_FLOOR; - trap.type = TRAP_UNASSIGNED; - return; - } - - if (!monster->will_trigger_shaft() - || trapKnown && intelligent_ally(monster)) - { - if (trapKnown && !monster->airborne()) - simple_monster_message(monster, - " doesn't fall through the shaft."); - return; - } - - if (monster->do_shaft()) - revealTrap = true; - break; - - default: - break; - } - - - // go back and handle projectile traps: {dlb} - bool apply_poison = false; - - if (projectileFired) - { - // projectile traps *always* revealed after "firing": {dlb} - revealTrap = true; - - // determine whether projectile hits, calculate damage: {dlb} - if (((20 + (you.your_level * 2)) * random2(200)) / 100 - >= monster->ev) - { - damage_taken = roll_dice( beem.damage ); - damage_taken -= random2(1 + monster->ac); - - if (damage_taken < 0) - damage_taken = 0; - - if (beem.colour == OBJ_MISSILES - && beem.type == MI_NEEDLE - && x_chance_in_y(50 - (3 * monster->ac)/ 2, 100)) - { - apply_poison = true; - } - } - else - { - damage_taken = -1; // negative damage marks a miss - } - - if (monsterNearby) - { - mprf("A%s %s %s%s!", - beem.name.c_str(), - (damage_taken >= 0) ? "hits" : "misses", - monster->name(DESC_NOCAP_THE).c_str(), - (damage_taken == 0) ? ", but does no damage" : ""); - } - - if (apply_poison) - poison_monster( monster, KC_OTHER ); - - // Generate "fallen" projectile, where appropriate. {dlb} - if (x_chance_in_y(7, 10)) - { - beem.target = monster->pos(); - itrap(beem, which_trap); - } - } - - - // reveal undiscovered traps, where appropriate: {dlb} - if (monsterNearby && !trapKnown && revealTrap) - grd(trap.pos) = trap_category(trap.type); - - // apply damage and handle death, where appropriate: {dlb} - if (damage_taken > 0) - { - hurt_monster(monster, damage_taken); - - if (monster->hit_points < 1) - { - monster_die(monster, KILL_MISC, NON_MONSTER); - monster->speed_increment = 1; - } - } -} - static bool _mons_abjured(monsters *monster, bool nearby) { if (nearby && _monster_abjuration(monster, false) > 0 diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc index d4e85d8e44..e9ef6a4da9 100644 --- a/crawl-ref/source/ouch.cc +++ b/crawl-ref/source/ouch.cc @@ -858,7 +858,7 @@ void ouch(int dam, int death_source, kill_method_type death_type, _xom_checks_damage(death_type, dam, death_source); // for note taking - std::string damage_desc = ""; + std::string damage_desc; if (!see_source) { snprintf(info, INFO_SIZE, "something (%d)", dam); diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 8cbdb12e3b..b62308d213 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -146,11 +146,10 @@ bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift, + player_mutation_level(MUT_ACUTE_VISION) - 2 * player_mutation_level(MUT_BLURRY_VISION); - if (random2( skill ) > 6) + if (random2(skill) > 6) { - const int id = trap_at_xy( p ); - if (id != -1) - grd(p) = trap_category( env.trap[id].type ); + if (trap_def* ptrap = find_trap(p)) + ptrap->reveal(); viewwindow(true, false); @@ -175,7 +174,7 @@ bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift, #endif || new_grid == DNGN_TRAP_NATURAL) { - const trap_type type = trap_type_at_xy(p); + const trap_type type = get_trap_type(p); if (type == TRAP_ZOT) { if (!yes_or_no("Do you really want to step into the Zot trap")) @@ -301,37 +300,8 @@ bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift, expose_player_to_element( BEAM_LAVA ); // Traps go off. - if (new_grid >= DNGN_TRAP_MECHANICAL && new_grid <= DNGN_UNDISCOVERED_TRAP) - { - int id = trap_at_xy( you.pos() ); - - if (id != -1) - { - bool trap_known = true; - - if (new_grid == DNGN_UNDISCOVERED_TRAP) - { - trap_known = false; - - const dungeon_feature_type type = - trap_category( env.trap[id].type ); - grd(you.pos()) = type; - set_envmap_obj(you.pos(), type); - } - - // It's not easy to blink onto a trap without setting it off. - if (!stepped) - trap_known = false; - - // mechanical traps and shafts cannot be set off if the - // player is flying or levitating - if (!player_is_airborne() - || trap_category( env.trap[id].type ) == DNGN_TRAP_MAGICAL) - { - handle_traps(env.trap[id].type, id, trap_known); - } - } - } + if (trap_def* ptrap = find_trap(you.pos())) + ptrap->trigger(you, !stepped); // blinking makes it hard to evade return (true); } @@ -5410,6 +5380,14 @@ bool actor::can_pass_through(const coord_def &c) const return can_pass_through_feat(grd(c)); } +bool actor::handle_trap() +{ + trap_def* trap = find_trap(pos()); + if (trap) + trap->trigger(*this); + return (trap != NULL); +} + ////////////////////////////////////////////////////////////////////////////// // player @@ -6275,7 +6253,7 @@ int player::armour_class() const int player::melee_evasion(const actor *act) const { return (player_evasion() - - (act->visible()? 0 : 10) + - ((!act || act->visible()) ? 0 : 10) - (you_are_delayed()? 5 : 0)); } @@ -6985,7 +6963,7 @@ bool player::do_shaft() // Handle instances of do_shaft() being invoked magically when // the player isn't standing over a shaft. - if (trap_type_at_xy(this->pos()) != TRAP_SHAFT) + if (get_trap_type(this->pos()) != TRAP_SHAFT) { switch (grd(you.pos())) { diff --git a/crawl-ref/source/spells2.cc b/crawl-ref/source/spells2.cc index a31c75483e..f181ab51b5 100644 --- a/crawl-ref/source/spells2.cc +++ b/crawl-ref/source/spells2.cc @@ -57,16 +57,17 @@ int detect_traps( int pow ) for (int i = 0; i < MAX_TRAPS; i++) { - const coord_def p = env.trap[i].pos; + trap_def& trap = env.trap[i]; - if (grid_distance( you.pos(), p) < range - && grd(p) == DNGN_UNDISCOVERED_TRAP) + if (!trap.active()) + continue; + + if (grid_distance(you.pos(), trap.pos) < range && !trap.is_known()) { traps_found++; - - grd(p) = trap_category( env.trap[i].type ); - set_envmap_obj(p, grd(p)); - set_terrain_mapped(p); + trap.reveal(); + set_envmap_obj(trap.pos, grd(trap.pos)); + set_terrain_mapped(trap.pos); } } diff --git a/crawl-ref/source/spells3.cc b/crawl-ref/source/spells3.cc index dc2f817dd2..85add52f2d 100644 --- a/crawl-ref/source/spells3.cc +++ b/crawl-ref/source/spells3.cc @@ -1432,17 +1432,9 @@ bool entomb(int powc) if (env.cgrid(*ai) != EMPTY_CLOUD) delete_cloud( env.cgrid(*ai) ); - // mechanical traps are destroyed {dlb}: - int which_trap = trap_at_xy(*ai); - if ( which_trap != -1 ) - { - trap_struct& trap(env.trap[which_trap]); - if (trap_category(trap.type) == DNGN_TRAP_MECHANICAL) - { - trap.type = TRAP_UNASSIGNED; - trap.pos.set(1,1); - } - } + // All traps are destroyed + if (trap_def* ptrap = find_trap(*ai)) + ptrap->destroy(); // Finally, place the wall {dlb}: grd(*ai) = DNGN_ROCK_WALL; @@ -1627,14 +1619,13 @@ bool cast_sanctuary(const int power) if (env.map(pos).property == FPROP_BLOODY && see_grid(pos)) blood_count++; - if (trap_type_at_xy(pos) != NUM_TRAPS - && grd(pos) == DNGN_UNDISCOVERED_TRAP) + if (trap_def* ptrap = find_trap(pos)) { - const dungeon_feature_type type = - trap_category( trap_type_at_xy(pos) ); - grd(pos) = type; - set_envmap_obj(pos, type); - trap_count++; + if (!ptrap->is_known()) + { + ptrap->reveal(); + ++trap_count; + } } // forming patterns diff --git a/crawl-ref/source/spells4.cc b/crawl-ref/source/spells4.cc index 5e3c4ad0f6..21052d9ba4 100644 --- a/crawl-ref/source/spells4.cc +++ b/crawl-ref/source/spells4.cc @@ -1701,7 +1701,6 @@ bool cast_fragmentation(int pow, const dist& spd) { bolt beam; int debris = 0; - int trap; bool explode = false; bool hole = true; const char *what = NULL; @@ -1951,9 +1950,9 @@ bool cast_fragmentation(int pow, const dist& spd) case DNGN_UNDISCOVERED_TRAP: case DNGN_TRAP_MECHANICAL: - trap = trap_at_xy(spd.target); - if (trap != -1 - && trap_category(env.trap[trap].type) != DNGN_TRAP_MECHANICAL) + { + trap_def* ptrap = find_trap(spd.target); + if (ptrap && ptrap->category() != DNGN_TRAP_MECHANICAL) { // Non-mechanical traps don't explode with this spell. -- bwr break; @@ -1969,9 +1968,9 @@ bool cast_fragmentation(int pow, const dist& spd) beam.damage.num = 2; // Exploded traps are nonfunctional, ammo is also ruined -- bwr - grd(spd.target) = DNGN_FLOOR; - env.trap[trap].type = TRAP_UNASSIGNED; + ptrap->destroy(); break; + } // // Stone doors and arches diff --git a/crawl-ref/source/stash.cc b/crawl-ref/source/stash.cc index 17104cdddb..1c9dcf83f3 100644 --- a/crawl-ref/source/stash.cc +++ b/crawl-ref/source/stash.cc @@ -271,7 +271,7 @@ void Stash::update() feat = DNGN_FLOOR; if (grid_is_trap(feat)) - trap = trap_type_at_xy(p); + trap = get_trap_type(p); int objl = igrd[x][y]; // If this is your position, you know what's on this square diff --git a/crawl-ref/source/tags.cc b/crawl-ref/source/tags.cc index 9df9fccc26..b531fb8b22 100644 --- a/crawl-ref/source/tags.cc +++ b/crawl-ref/source/tags.cc @@ -1731,8 +1731,8 @@ static void tag_construct_level_items(writer &th) for (int i = 0; i < MAX_TRAPS; ++i) { marshallByte(th, env.trap[i].type); - marshallByte(th, env.trap[i].pos.x); - marshallByte(th, env.trap[i].pos.y); + marshallCoord(th, env.trap[i].pos); + marshallShort(th, env.trap[i].ammo_qty); } // how many items? @@ -2018,9 +2018,8 @@ static void tag_read_level_items(reader &th, char minorVersion) env.trap[i].type = static_cast<trap_type>( static_cast<unsigned char>(unmarshallByte(th)) ); - - env.trap[i].pos.x = unmarshallByte(th); - env.trap[i].pos.y = unmarshallByte(th); + unmarshallCoord(th, env.trap[i].pos); + env.trap[i].ammo_qty = unmarshallShort(th); } // how many items? diff --git a/crawl-ref/source/terrain.cc b/crawl-ref/source/terrain.cc index 67a2fa84b6..ae538be8fb 100644 --- a/crawl-ref/source/terrain.cc +++ b/crawl-ref/source/terrain.cc @@ -181,10 +181,11 @@ bool grid_is_permarock(dungeon_feature_type grid) return (grid == DNGN_PERMAROCK_WALL || grid == DNGN_CLEAR_PERMAROCK_WALL); } -bool grid_is_trap(dungeon_feature_type grid) +bool grid_is_trap(dungeon_feature_type grid, bool undiscovered_too) { return (grid == DNGN_TRAP_MECHANICAL || grid == DNGN_TRAP_MAGICAL - || grid == DNGN_TRAP_NATURAL); + || grid == DNGN_TRAP_NATURAL + || (undiscovered_too && grid == DNGN_UNDISCOVERED_TRAP)); } bool grid_is_water(dungeon_feature_type grid) diff --git a/crawl-ref/source/terrain.h b/crawl-ref/source/terrain.h index 44c3121c8f..2c32be9af7 100644 --- a/crawl-ref/source/terrain.h +++ b/crawl-ref/source/terrain.h @@ -30,7 +30,7 @@ bool grid_is_rock(dungeon_feature_type grid); bool grid_is_permarock(dungeon_feature_type grid); bool grid_is_stone_stair(dungeon_feature_type grid); bool grid_is_escape_hatch(dungeon_feature_type grid); -bool grid_is_trap(dungeon_feature_type grid); +bool grid_is_trap(dungeon_feature_type grid, bool undiscovered_too = false); command_type grid_stair_direction(dungeon_feature_type grid); bool grid_sealable_portal(dungeon_feature_type grid); bool grid_is_portal(dungeon_feature_type grid); diff --git a/crawl-ref/source/traps.cc b/crawl-ref/source/traps.cc index 16aaa0ab87..a49a914852 100644 --- a/crawl-ref/source/traps.cc +++ b/crawl-ref/source/traps.cc @@ -16,11 +16,14 @@ #include "beam.h" #include "branch.h" #include "delay.h" +#include "describe.h" #include "directn.h" #include "it_use2.h" -#include "items.h" +#include "itemname.h" #include "itemprop.h" +#include "items.h" #include "makeitem.h" +#include "message.h" #include "misc.h" #include "mon-util.h" #include "monstuff.h" @@ -38,7 +41,133 @@ #include "tutorial.h" #include "view.h" -static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison); +bool trap_def::active() const +{ + return (this->type != TRAP_UNASSIGNED); +} + +bool trap_def::type_has_ammo() const +{ + bool rc = false; + switch (this->type) + { + case TRAP_DART: case TRAP_ARROW: case TRAP_BOLT: + case TRAP_NEEDLE: case TRAP_SPEAR: case TRAP_AXE: + rc = true; + default: + break; + } + return rc; +} + +void trap_def::message_trap_entry() +{ + if (this->type == TRAP_TELEPORT) + mpr("You enter a teleport trap!"); +} + +void trap_def::disarm() +{ + if (this->type_has_ammo() && this->ammo_qty > 0) + { + item_def trap_item = this->generate_trap_item(); + trap_item.quantity = this->ammo_qty; + copy_item_to_grid(trap_item, this->pos); + } + this->destroy(); +} + +void trap_def::destroy() +{ + grd(this->pos) = DNGN_FLOOR; + this->ammo_qty = 0; + this->pos = coord_def(-1,-1); + this->type = TRAP_UNASSIGNED; +} + +void trap_def::hide() +{ + grd(this->pos) = DNGN_UNDISCOVERED_TRAP; +} + +void trap_def::prepare_ammo() +{ + switch (this->type) + { + case TRAP_DART: + case TRAP_ARROW: + case TRAP_BOLT: + case TRAP_NEEDLE: + this->ammo_qty = 3 + random2avg(9, 3); + break; + case TRAP_SPEAR: + case TRAP_AXE: + this->ammo_qty = 2 + random2avg(6, 3); + break; + default: + this->ammo_qty = 0; + break; + } +} + +void trap_def::reveal() +{ + const dungeon_feature_type cat = this->category(); + grd(this->pos) = cat; + set_envmap_obj(this->pos, cat); +} + +std::string trap_def::name(description_level_type desc) const +{ + if (this->type >= NUM_TRAPS) + return ("buggy"); + + const char* basename = trap_name(this->type); + if (desc == DESC_CAP_A || desc == DESC_NOCAP_A) + { + std::string prefix = (desc == DESC_CAP_A ? "A" : "a"); + if (is_vowel(basename[0])) + prefix += 'n'; + prefix += ' '; + return (prefix + basename); + } + else if (desc == DESC_CAP_THE) + return (std::string("The ") + basename); + else if (desc == DESC_NOCAP_THE) + return (std::string("the ") + basename); + else // everything else + return (basename); +} + +bool trap_def::is_known(const actor* act) const +{ + bool rc = false; + const bool player_knows = (grd(pos) != DNGN_UNDISCOVERED_TRAP); + + if (act == NULL || act->atype() == ACT_PLAYER) + rc = player_knows; + else if (act->atype() == ACT_MONSTER) + { + const monsters* monster = static_cast<const monsters*>(act); + const bool mechanical = (this->category() == DNGN_TRAP_MECHANICAL); + const int intel = mons_intel(monster->type); + + // Smarter trap handling for intelligent monsters + // * monsters native to a branch can be assumed to know the trap + // locations and thus be able to avoid them + // * friendlies and good neutrals can be assumed to have been warned + // by the player about all traps s/he knows about + // * very intelligent monsters can be assumed to have a high T&D + // skill (or have memorised part of the dungeon layout ;) ) + + rc = (intel >= I_NORMAL && mechanical + && (mons_is_native_in_branch(monster) + || (mons_wont_attack(monster) && player_knows) + || (intel >= I_HIGH && one_chance_in(3)))); + } + return rc; +} + // Returns the number of a net on a given square. // If trapped only stationary ones are counted @@ -213,318 +342,370 @@ void check_net_will_hold_monster(monsters *mons) mons->add_ench(ENCH_HELD); } -static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison) +void trap_def::trigger(actor& triggerer, bool flat_footed) { - int damage_taken = 0; - int trap_hit, your_dodge; + const bool you_know = this->is_known(); + const bool trig_knows = !flat_footed && this->is_known(&triggerer); - if (one_chance_in(5) || (trap_known && !one_chance_in(4))) - { - mprf( "You avoid triggering a%s trap.", pbolt.name.c_str() ); - return; - } + const bool you_trigger = (triggerer.atype() == ACT_PLAYER); + const bool in_sight = see_grid(this->pos); - if (you.equip[EQ_SHIELD] != -1 && one_chance_in(3)) - exercise( SK_SHIELDS, 1 ); + monsters* m = NULL; + if (triggerer.atype() == ACT_MONSTER) + m = static_cast<monsters*>(&triggerer); - std::string msg = "A" + pbolt.name + " shoots out and "; + // Anything stepping onto a trap almost always reveals it. + // (We can rehide it later for the exceptions.) + if (in_sight) + this->reveal(); - if (random2( 20 + 5 * you.shield_blocks * you.shield_blocks ) - < player_shield_class()) + // Only magical traps affect flying critters. + if (triggerer.airborne() && this->category() != DNGN_TRAP_MAGICAL) { - you.shield_blocks++; - msg += "hits your shield."; - mpr(msg.c_str()); + if (you_know && m) + simple_monster_message(m, " flies safely over a trap."); + return; } - else + + // OK, something is going to happen. + if (you_trigger) + this->message_trap_entry(); + + if (this->type_has_ammo()) + this->shoot_ammo(triggerer, trig_knows); + else switch (this->type) { - // note that this uses full ( not random2limit(foo,40) ) - // player_evasion. - trap_hit = (20 + (you.your_level * 2)) * random2(200) / 100; + case TRAP_TELEPORT: + // Never revealed. + if (!you_know) + this->hide(); + triggerer.teleport(true); + break; - your_dodge = player_evasion() + random2(you.dex) / 3 - - 2 + (you.duration[DUR_REPEL_MISSILES] * 10); + case TRAP_ALARM: + if (silenced(this->pos)) + { + if (you_know && in_sight) + mpr("The alarm trap is silent."); - if (trap_hit >= your_dodge && you.duration[DUR_DEFLECT_MISSILES] == 0) + // If it's silent, you don't know about it. + if (!you_know) + this->hide(); + } + else if (!(m && mons_friendly(m))) { - msg += "hits you!"; - mpr(msg.c_str()); + // Alarm traps aren't set off by hostile monsters, because + // that would be way too nasty for the player. + const char* message_here = "An alarm trap emits a blaring wail!"; + const char* message_near = "You hear a blaring wail!"; + const char* message_far = "You hear a distant blaring wail!"; + noisy(12, this->pos, (you_trigger ? message_here : + (in_sight ? message_near : message_far))); + } + break; - if (poison && x_chance_in_y(50 - (3 * player_AC()) / 2, 100) - && !player_res_poison()) + case TRAP_BLADE: + if (you_trigger) + { + if (trig_knows && one_chance_in(3)) + mpr("You avoid triggering a blade trap."); + else if (random2limit(player_evasion(), 40) + + (random2(you.dex) / 3) + (trig_knows ? 3 : 0) > 8) { - poison_player( 1 + random2(3) ); + mpr("A huge blade swings just past you!"); } - - damage_taken = roll_dice( pbolt.damage ); - damage_taken -= random2( player_AC() + 1 ); - - if (damage_taken > 0) + else { - ouch(damage_taken, NON_MONSTER, KILLED_BY_TRAP, - pbolt.name.c_str()); + mpr("A huge blade swings out and slices into you!"); + const int damage = (you.your_level * 2) + random2avg(29, 2) + - random2(1 + player_AC()); + ouch(damage, NON_MONSTER, KILLED_BY_TRAP, " blade"); + bleed_onto_floor(you.pos(), -1, damage, true); } } - else + else if (m) { - msg += "misses you."; - mpr(msg.c_str()); + if (one_chance_in(5) || (trig_knows && coinflip())) + { + // Trap doesn't trigger. Don't reveal it. + if (you_know) + { + simple_monster_message(m, + " fails to trigger a blade trap."); + } + else + this->hide(); + } + else if (random2(m->ev) > 8 || (trig_knows && random2(m->ev) > 8)) + { + if (in_sight && + !simple_monster_message(m, + " avoids a huge, swinging blade.")) + { + mpr("A huge blade swings out!"); + } + } + else + { + if (in_sight) + { + std::string msg = "A huge blade swings out"; + if (player_monster_visible( m )) + { + msg += " and slices into "; + msg += m->name(DESC_NOCAP_THE); + } + msg += "!"; + mpr(msg.c_str()); + } + + int damage_taken = 10 + random2avg(29, 2) - random2(1 + m->ac); + + if (damage_taken < 0) + damage_taken = 0; + + if (!mons_is_summoned(m)) + bleed_onto_floor(m->pos(), m->type, damage_taken, true); + + hurt_monster(m, damage_taken); + if (m->hit_points < 1) + monster_die(m, KILL_MISC, NON_MONSTER); + } } - - if (player_light_armour(true) && coinflip()) - exercise( SK_DODGING, 1 ); - } - - pbolt.target = you.pos(); - - if (coinflip()) - itrap( pbolt, trapped ); -} // end dart_trap() - -// -// itrap takes location from target_x, target_y of bolt strcture. -// - -void itrap( 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 ); - - return; -} - -void handle_traps(trap_type trt, int i, bool trap_known) -{ - struct bolt beam; - - bool branchtype = false; - - // Mark traps as racial, if applicable. See the list of racial - // restrictions in _determine_weapon_race() and - // _determine_missile_race() in makeitem.cc. - if (trap_category(trt) == DNGN_TRAP_MECHANICAL && trt != TRAP_BLADE - && trt != TRAP_NET) - { - if (you.where_are_you == BRANCH_ORCISH_MINES) + if (you_trigger) { - beam.name = "n orcish"; - branchtype = true; + if (trig_knows && one_chance_in(3)) + mpr("A net swings high above you."); + else + { + if (random2limit(player_evasion(), 40) + + (random2(you.dex) / 3) + (trig_knows ? 3 : 0) > 12) + { + mpr("A net drops to the ground!"); + } + else + { + mpr("A large net falls onto you!"); + player_caught_in_net(); + } + + item_def item = this->generate_trap_item(); + copy_item_to_grid(item, triggerer.pos()); + + if (you.attribute[ATTR_HELD]) + mark_net_trapping(you.pos()); + + this->destroy(); + } } - else if (you.where_are_you == BRANCH_ELVEN_HALLS - && trt != TRAP_AXE && trt != TRAP_BOLT) + else if (m) { - beam.name = "n elven"; - branchtype = true; - } - else - beam.name = ""; - } - - 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 += (branchtype? "" : "n"); - beam.name += " 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 += (branchtype? "" : "n"); - beam.name += " 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!"); + bool triggered = true; + if (one_chance_in(3) || (trig_knows && coinflip())) + { + triggered = false; + if (you_know) + simple_monster_message(m, " fails to trigger a net trap."); + } + if (random2(m->ev) > 8 + || (trig_knows && random2(m->ev) > 8)) + { + if (in_sight) + { + if (!simple_monster_message(m, + " nimbly jumps out of the way " + "of a falling net.")) + { + mpr("A large net falls down!"); + } + } + else + { + // FIXME: net traps don't trigger unless you can see + // them? Preserving old behaviour here. + if (in_sight) + { + msg::stream << "A large net falls down"; + if (player_monster_visible(m)) + msg::stream << " onto " << m->name(DESC_NOCAP_THE); + msg::stream << "!" << std::endl; + } + // FIXME: Fake a beam for monster_caught_in_net. + bolt beam; + beam.flavour = BEAM_MISSILE; + beam.thrower = KILL_MISC; + beam.beam_source = NON_MONSTER; + monster_caught_in_net(m, beam); + } + } - if (scan_randarts(RAP_PREVENT_TELEPORTATION)) - mpr("You feel a weird sense of stasis."); - else - you_teleport_now( true ); - break; + if (triggered) + { + item_def item = this->generate_trap_item(); + copy_item_to_grid(item, triggerer.pos()); - case TRAP_ALARM: - if (silenced(you.pos())) - { - if (trap_known) - mpr("The alarm is silenced."); - else - grd(you.pos()) = DNGN_UNDISCOVERED_TRAP; - return; + if (mons_is_caught(m)) + mark_net_trapping(m->pos()); + + this->destroy(); + } } - - noisy(12, you.pos(), "An alarm trap emits a blaring wail!"); - 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) + case TRAP_ZOT: + if (you_trigger) { - mpr("A huge blade swings just past you!"); + mpr((trig_knows) ? "You enter the Zot trap." + : "Oh no! You have blundered into a Zot trap!"); + MiscastEffect( &you, ZOT_TRAP_MISCAST, SPTYP_RANDOM, + 3, "a Zot trap" ); } - else + else if (m) { - mpr("A huge blade swings out and slices into you!"); - int damage = (you.your_level * 2) + random2avg(29, 2) - - random2(1 + player_AC()); - ouch(damage, NON_MONSTER, KILLED_BY_TRAP, " blade"); - bleed_onto_floor(you.pos(), -1, damage, true); - } - break; + // Zot traps are out to get *the player*! Hostile monsters + // benefit and friendly monsters suffer. Such is life. - 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) + // Preserving original functionality: don't reveal location. + if (!you_know) + this->hide(); + + if (mons_wont_attack(m)) { - mpr("A net drops to the ground!"); + MiscastEffect( m, ZOT_TRAP_MISCAST, SPTYP_RANDOM, + 3, "the power of Zot" ); } - else + else if (in_sight) { - mpr("A large net falls onto you!"); - player_caught_in_net(); + if (one_chance_in(5)) + { + mpr("The power of Zot is invoked against you!"); + MiscastEffect( &you, ZOT_TRAP_MISCAST, SPTYP_RANDOM, + 3, "the power of Zot" ); + } + } + else if (player_can_hear(this->pos)) + { + mprf(MSGCH_SOUND, "You hear a %s \"Zot\"!", + in_sight ? "loud" : "distant"); } - - trap_item( OBJ_MISSILES, MI_THROWING_NET, env.trap[i].pos); - if (you.attribute[ATTR_HELD]) - mark_net_trapping(you.pos()); - - grd(env.trap[i].pos) = DNGN_FLOOR; - env.trap[i].type = TRAP_UNASSIGNED; } break; - // If we don't trigger the shaft, and the player doesn't - // already know about it, don't let him/her notice it. case TRAP_SHAFT: - { - if (!you.will_trigger_shaft()) - { - if (trap_known && !you.airborne()) - mpr("You don't fall through the shaft."); - - if (!trap_known) - grd(you.pos()) = DNGN_UNDISCOVERED_TRAP; + // Known shafts don't trigger as traps. + if (trig_knows) + break; - return; + if (!triggerer.will_trigger_shaft()) + { + if (!you_know) + this->hide(); + else if (!triggerer.airborne()) + { + if (you_trigger) + { + mpr("You don't fall through the shaft."); + } + else if (m) + { + simple_monster_message(m, + " doesn't fall through the shaft."); + } + } } // Paranoia if (!is_valid_shaft_level()) { - if (trap_known) + if (you_know && in_sight) mpr("The shaft disappears in a puff of logic!"); - grd(env.trap[i].pos) = DNGN_FLOOR; - env.trap[i].type = TRAP_UNASSIGNED; - return; + this->destroy(); } - - if (!you.do_shaft() && !trap_known) + else { - grd(you.pos()) = DNGN_UNDISCOVERED_TRAP; - return; + // Fire away! + const bool revealed = triggerer.do_shaft(); + if (!revealed && !you_know) + this->hide(); } - break; - } - case TRAP_ZOT: default: - mpr((trap_known) ? "You enter the Zot trap." - : "Oh no! You have blundered into a Zot trap!"); - MiscastEffect( &you, ZOT_TRAP_MISCAST, SPTYP_RANDOM, - 3, "a Zot trap" ); break; } - learned_something_new(TUT_SEEN_TRAP, you.pos()); - - if (!trap_known) // Now you know... - exercise(SK_TRAPS_DOORS, ((coinflip()) ? 2 : 1)); + + if (you_trigger) + { + learned_something_new(TUT_SEEN_TRAP, this->pos); + + // Exercise T&D if the trap revealed itself. + if (!you_know && this->is_known()) + exercise(SK_TRAPS_DOORS, ((coinflip()) ? 2 : 1)); + } } -void destroy_trap( const coord_def& pos ) +int trap_def::shot_damage(actor& act) { - for (int i = 0; i < MAX_TRAPS; ++i) + if (act.atype() == ACT_PLAYER) { - if (env.trap[i].pos == pos && env.trap[i].type != TRAP_UNASSIGNED) + switch (this->type) { - grd(pos) = DNGN_FLOOR; - env.trap[i].type = TRAP_UNASSIGNED; - return; + case TRAP_NEEDLE: return 0; + case TRAP_DART: return random2( 4 + you.your_level/2) + 1; + case TRAP_ARROW: return random2( 7 + you.your_level) + 1; + case TRAP_SPEAR: return random2(10 + you.your_level) + 1; + case TRAP_BOLT: return random2(13 + you.your_level) + 1; + case TRAP_AXE: return random2(15 + you.your_level) + 1; + default: return 0; + } + } + else if (act.atype() == ACT_MONSTER) + { + // Trap damage to monsters is not a function of level, because + // they are fairly stupid and tend to have fewer hp than + // players -- this choice prevents traps from easily killing + // large monsters fairly deep within the dungeon. + switch (this->type) + { + case TRAP_NEEDLE: return 0; + case TRAP_DART: return random2( 4) + 1; + case TRAP_ARROW: return random2( 7) + 1; + case TRAP_SPEAR: return random2(10) + 1; + case TRAP_BOLT: return random2(13) + 1; + case TRAP_AXE: return random2(15) + 1; + default: return 0; } } + return (0); +} + +void destroy_trap( const coord_def& pos ) +{ + if (trap_def* ptrap = find_trap(pos)) + ptrap->destroy(); +} + +trap_def* find_trap(const coord_def& pos) +{ + for (int i = 0; i < MAX_TRAPS; ++i) + if (env.trap[i].pos == pos && env.trap[i].type != TRAP_UNASSIGNED) + return (&env.trap[i]); + return (NULL); +} + +trap_type get_trap_type(const coord_def& pos) +{ + if (trap_def* ptrap = find_trap(pos)) + return (ptrap->type); + return (TRAP_UNASSIGNED); } -void disarm_trap( dist &disa ) +// where *must* point to a valid, discovered trap. +void disarm_trap(const coord_def& where) { if (you.duration[DUR_BERSERKER]) { @@ -532,21 +713,9 @@ void disarm_trap( dist &disa ) return; } - int i, j; + trap_def& trap = *find_trap(where); - for (i = 0; i < MAX_TRAPS; i++) - { - if (env.trap[i].pos == you.pos() + disa.delta) - break; - - if (i == MAX_TRAPS - 1) - { - mpr("Error - couldn't find that trap.", MSGCH_ERROR); - return; - } - } - - switch (trap_category(env.trap[i].type)) + switch (trap.category()) { case DNGN_TRAP_MAGICAL: mpr("You can't disarm that trap."); @@ -559,59 +728,36 @@ void disarm_trap( dist &disa ) break; } + // Make the actual attempt + you.turn_is_over = true; 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].pos != you.pos()) + if (trap.type == TRAP_NET && trap.pos != you.pos()) { if (coinflip()) - return; - - mpr("You stumble into the trap!"); - move_player_to_grid( env.trap[i].pos, true, false, true); + { + mpr("You stumble into the trap!"); + move_player_to_grid(trap.pos, true, false, true); + } } else - handle_traps(env.trap[i].type, i, false); + trap.trigger(you, true); if (coinflip()) exercise(SK_TRAPS_DOORS, 1); } - - return; } - - mpr("You have disarmed the trap."); - - bolt beam; - - beam.target = you.pos() + disa.delta; - - if (env.trap[i].type == TRAP_NET) - trap_item( OBJ_MISSILES, MI_THROWING_NET, beam.target ); - else if (env.trap[i].type != TRAP_BLADE - && trap_category(env.trap[i].type) == DNGN_TRAP_MECHANICAL) + else { - 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); - } + mpr("You have disarmed the trap."); + trap.disarm(); + exercise(SK_TRAPS_DOORS, 1 + random2(5) + (you.your_level/5)); } - - grd(you.pos() + disa.delta) = 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)); } // Attempts to take a net off a given monster. @@ -890,65 +1036,177 @@ void clear_trapping_net() you.attribute[ATTR_HELD] = 0; } -bool trap_item(object_class_type base_type, char sub_type, - const coord_def& where) +item_def trap_def::generate_trap_item() { 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; + object_class_type base; + int sub; + + switch (this->type) + { + case TRAP_DART: base = OBJ_MISSILES; sub = MI_DART; break; + case TRAP_ARROW: base = OBJ_MISSILES; sub = MI_ARROW; break; + case TRAP_BOLT: base = OBJ_MISSILES; sub = MI_BOLT; break; + case TRAP_SPEAR: base = OBJ_WEAPONS; sub = WPN_SPEAR; break; + case TRAP_AXE: base = OBJ_WEAPONS; sub = WPN_HAND_AXE; break; + case TRAP_NEEDLE: base = OBJ_MISSILES; sub = MI_NEEDLE; break; + case TRAP_NET: base = OBJ_MISSILES; sub = MI_THROWING_NET; break; + default: return item; + } + + item.base_type = base; + item.sub_type = sub; item.quantity = 1; - if (base_type == OBJ_MISSILES) + if (base == 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 ); + set_item_ego_type(item, base, + (sub == MI_NEEDLE) ? SPMSL_POISONED : SPMSL_NORMAL); } else { - set_item_ego_type( item, OBJ_WEAPONS, SPWPN_NORMAL ); + set_item_ego_type(item, base, SPWPN_NORMAL); } + // give appropriate racial flag for Orcish Mines and Elven Halls + // should we ever allow properties of dungeon features, we could use that + if (you.where_are_you == BRANCH_ORCISH_MINES) + set_equip_race( item, ISFLAG_ORCISH ); + else if (you.where_are_you == BRANCH_ELVEN_HALLS) + set_equip_race( item, ISFLAG_ELVEN ); + item_colour(item); + return item; +} - if (igrd(where) != NON_ITEM) +// Shoot a single piece of ammo at the relevant actor. +void trap_def::shoot_ammo(actor& act, bool was_known) +{ + if (this->ammo_qty <= 0) { - if (items_stack( item, mitm[ igrd(where) ] )) + if (act.atype() == ACT_PLAYER) + mpr("The trap is out of ammunition!"); + else if (player_can_hear(this->pos)) + mpr("You hear a soft click."); + this->disarm(); + } + else + { + item_def shot = this->generate_trap_item(); + bool poison = (this->type == TRAP_NEEDLE); + int damage_taken = + this->shot_damage(act) - random2(act.armour_class()+1); + if (damage_taken < 0) + damage_taken = 0; + + if (act.atype() == ACT_PLAYER) { - inc_mitm_item_quantity( igrd(where), 1 ); - return (false); - } + if (one_chance_in(5) || (was_known && !one_chance_in(4))) + { + mprf( "You avoid triggering %s trap.", + this->name(DESC_NOCAP_A).c_str() ); - // 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(where) ].link != NON_ITEM - && (item.base_type != OBJ_MISSILES - || item.sub_type != MI_THROWING_NET)) + return; // no ammo generated either + } + + // Start constructing the message. + std::string msg = shot.name(DESC_CAP_A) + " shoots out and "; + + // Check for shield blocking. + // Exercise only if the trap was unknown (to prevent scumming.) + if (!was_known && you.shield() && one_chance_in(3)) + exercise(SK_SHIELDS, 1); + + 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. + int trap_hit = (20 + (you.your_level*2)) * random2(200) / 100; + int your_dodge = player_evasion() + random2(you.dex) / 3 + - 2 + (you.duration[DUR_REPEL_MISSILES] * 10); + + // Check if it got past dodging. Deflect Missiles provides + // immunity to such traps. + if (trap_hit >= your_dodge + && you.duration[DUR_DEFLECT_MISSILES] == 0) + { + // OK, we've been hit. + msg += "hits you!"; + mpr(msg.c_str()); + + // Needle traps can poison. + if (poison && !player_res_poison() + && x_chance_in_y(50 - (3*player_AC()) / 2, 100)) + { + poison_player(1 + random2(3)); + } + + ouch(damage_taken, NON_MONSTER, KILLED_BY_TRAP, + shot.name(DESC_PLAIN).c_str()); + } + else // trap dodged + { + msg += "misses you."; + mpr(msg.c_str()); + } + + // Exercise only if the trap was unknown (to prevent scumming.) + if (!was_known && player_light_armour(true) && coinflip()) + exercise(SK_DODGING, 1); + } + } + else if (act.atype() == ACT_MONSTER) { - if (mitm[ mitm[ igrd(where) ].link ].link != NON_ITEM) - return (false); + // XXX reveal the trap XXX + + monsters* monster = static_cast<monsters *>(&act); + + // Determine whether projectile hits. + bool hit = (((20+(you.your_level*2))*random2(200))/100 + >= monster->ev); + + // Check whether to poison. + if (poison) + poison = (hit && x_chance_in_y(50 - (3*monster->ac)/2, 100)); + + if (see_grid(act.pos())) + { + mprf("%s %s %s%s!", + shot.name(DESC_CAP_A).c_str(), + hit ? "hits" : "misses", + monster->name(DESC_NOCAP_THE).c_str(), + (damage_taken == 0 + && !poison) ? ", but does no damage" : ""); + } + + if (poison) + poison_monster(monster, KC_OTHER); + + // Apply damage. + hurt_monster(monster, damage_taken); + if (monster->hit_points < 1) + monster_die(monster, KILL_MISC, NON_MONSTER); } - } - // give appropriate racial flag for Orcish Mines and Elven Halls - // should we ever allow properties of dungeon features, we could use that - if (you.where_are_you == BRANCH_ORCISH_MINES) - set_equip_race( item, ISFLAG_ORCISH ); - else if (you.where_are_you == BRANCH_ELVEN_HALLS) - set_equip_race( item, ISFLAG_ELVEN ); + // Drop the item (sometimes.) + if (coinflip()) + copy_item_to_grid(shot, act.pos()); - return (!copy_item_to_grid( item, where, 1 )); -} // end trap_item() + this->ammo_qty--; + } +} -// returns appropriate trap symbol for a given trap type {dlb} -dungeon_feature_type trap_category(trap_type type) +// returns appropriate trap symbol +dungeon_feature_type trap_def::category() const { - switch (type) + switch (this->type) { case TRAP_SHAFT: return (DNGN_TRAP_NATURAL); @@ -969,7 +1227,8 @@ dungeon_feature_type trap_category(trap_type type) 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(const coord_def& xy) diff --git a/crawl-ref/source/traps.h b/crawl-ref/source/traps.h index d939aa3285..bfa5eaa528 100644 --- a/crawl-ref/source/traps.h +++ b/crawl-ref/source/traps.h @@ -12,18 +12,14 @@ #include "enum.h" #include "travel.h" -struct dist; struct bolt; class monsters; -bool trap_item(object_class_type base_type, char sub_type, - const coord_def& where); - // last updated 12may2000 {dlb} /* *********************************************************************** * called from: acr * *********************************************************************** */ -void disarm_trap(struct dist &disa); +void disarm_trap(const coord_def& where); void remove_net_from( monsters *mon ); void free_self_from_net(void); @@ -39,11 +35,9 @@ void player_caught_in_net(void); void clear_trapping_net(void); void check_net_will_hold_monster(monsters *mon); -void itrap(struct bolt &pbolt, int trapped); -void destroy_trap( const coord_def& pos ); -dungeon_feature_type trap_category(trap_type type); -int trap_at_xy(const coord_def& xy); -trap_type trap_type_at_xy(const coord_def& xy); +void destroy_trap(const coord_def& pos); +trap_def* find_trap(const coord_def& where); +trap_type get_trap_type(const coord_def& where); bool is_valid_shaft_level(const level_id &place = level_id::current()); level_id generic_shaft_dest(coord_def pos); |