summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/traps.cc
diff options
context:
space:
mode:
authorharanp <haranp@c06c8d41-db1a-0410-9941-cceddc491573>2008-10-01 17:44:09 +0000
committerharanp <haranp@c06c8d41-db1a-0410-9941-cceddc491573>2008-10-01 17:44:09 +0000
commitbb183e9257bd99caeab9776b3579acf68cb203b1 (patch)
treeca8904e3d8db4aa6db1991b0ffebdb5d932ea6fe /crawl-ref/source/traps.cc
parente32c3272ea58ddf7bf596bbf0ec5c62ec6c42018 (diff)
downloadcrawl-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/traps.cc')
-rw-r--r--crawl-ref/source/traps.cc931
1 files changed, 595 insertions, 336 deletions
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)