/*
* File: traps.cc
* Summary: Traps related functions.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "traps.h"
#include "trap_def.h"
#include <algorithm>
#include "artefact.h"
#include "beam.h"
#include "branch.h"
#include "clua.h"
#include "coord.h"
#include "delay.h"
#include "describe.h"
#include "directn.h"
#include "map_knowledge.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "makeitem.h"
#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "mon-stuff.h"
#include "mon-transit.h"
#include "ouch.h"
#include "player.h"
#include "skills.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "travel.h"
#include "env.h"
#include "areas.h"
#include "terrain.h"
#include "transform.h"
#include "tutorial.h"
#include "view.h"
#include "shout.h"
#include "xom.h"
bool trap_def::active() const
{
return (this->type != TRAP_UNASSIGNED);
}
bool trap_def::type_has_ammo() const
{
switch (this->type)
{
case TRAP_DART: case TRAP_ARROW: case TRAP_BOLT:
case TRAP_NEEDLE: case TRAP_SPEAR: case TRAP_AXE:
return (true);
default:
break;
}
return (false);
}
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()
{
if (!in_bounds(this->pos))
ASSERT("trap position out of bounds!");
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;
case TRAP_ALARM:
this->ammo_qty = 1 + random2(3);
break;
default:
this->ammo_qty = 0;
break;
}
}
void trap_def::reveal()
{
grd(this->pos) = this->category();
}
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 = dynamic_cast<const monsters*>(act);
const int intel = mons_intel(monster);
// 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
&& (mons_is_native_in_branch(monster)
|| monster->wont_attack() && 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
// otherwise the first net found is returned.
int get_trapping_net(const coord_def& where, bool trapped)
{
for (stack_iterator si(where); si; ++si)
{
if (si->base_type == OBJ_MISSILES
&& si->sub_type == MI_THROWING_NET
&& (!trapped || item_is_stationary(*si)))
{
return (si->index());
}
}
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, const coord_def& where)
{
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, where );
}
void mark_net_trapping(const coord_def& where)
{
int net = get_trapping_net(where);
if (net == NON_ITEM)
{
net = get_trapping_net(where, false);
if (net != NON_ITEM)
maybe_split_nets(mitm[net], where);
}
}
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 (you.can_see(mon))
{
mprf("The net passes right through %s!",
mon->name(DESC_NOCAP_THE).c_str());
}
return;
}
bool mon_flies = mon->flight_mode() == FL_FLY;
if (mon_flies && (!mons_is_confused(mon) || one_chance_in(3)))
{
simple_monster_message(mon, " darts out from under the net!");
return;
}
if (mon->type == MONS_OOZE || mon->type == MONS_PULSATING_LUMP)
{
simple_monster_message(mon, " oozes right through the net!");
return;
}
if (!mon->caught() && mon->add_ench(ENCH_HELD))
{
if (mons_near(mon) && !mon->visible_to(&you))
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, mon->pos(), pbolt.killer(), pbolt.beam_source);
}
}
}
bool player_caught_in_net()
{
if (you.body_size(PSIZE_BODY) >= SIZE_GIANT)
return (false);
if (you.flight_mode() == FL_FLY && (!you.confused() || one_chance_in(3)))
{
mpr("You dart out from under the net!");
return (false);
}
if (!you.attribute[ATTR_HELD])
{
you.attribute[ATTR_HELD] = 10;
mpr("You become entangled in the net!");
stop_running();
// I guess levitation works differently, keeping both you
// and the net hovering above the floor
if (you.flight_mode() == FL_FLY)
{
mpr("You fall like a stone!");
fall_into_a_pool(you.pos(), false, grd(you.pos()));
}
stop_delay(true); // even stair delays
return (true);
}
return (false);
}
void check_net_will_hold_monster(monsters *mons)
{
if (mons->body_size(PSIZE_BODY) >= SIZE_GIANT)
{
int net = get_trapping_net(mons->pos());
if (net != NON_ITEM)
destroy_item(net);
if (you.see_cell(mons->pos()))
{
if (mons->visible_to(&you))
{
mprf("The net rips apart, and %s comes free!",
mons->name(DESC_NOCAP_THE).c_str());
}
else
mpr("All of a sudden the net rips apart!");
}
}
else if (mons_is_insubstantial(mons->type)
|| mons->type == MONS_OOZE
|| mons->type == MONS_PULSATING_LUMP)
{
const int net = get_trapping_net(mons->pos());
if (net != NON_ITEM)
remove_item_stationary(mitm[net]);
if (mons_is_insubstantial(mons->type))
{
simple_monster_message(mons,
" drifts right through the net!");
}
else
{
simple_monster_message(mons,
" oozes right through the net!");
}
}
else
mons->add_ench(ENCH_HELD);
}
void trap_def::trigger(actor& triggerer, bool flat_footed)
{
const bool you_know = this->is_known();
const bool trig_knows = !flat_footed && this->is_known(&triggerer);
const bool you_trigger = (triggerer.atype() == ACT_PLAYER);
const bool in_sight = you.see_cell(this->pos);
// If set, the trap will be removed at the end of the
// triggering process.
bool trap_destroyed = false;
monsters* m = NULL;
if (triggerer.atype() == ACT_MONSTER)
m = dynamic_cast<monsters*>(&triggerer);
// Anything stepping onto a trap almost always reveals it.
// (We can rehide it later for the exceptions.)
if (in_sight)
this->reveal();
// Only magical traps affect flying critters.
if (triggerer.airborne() && this->category() != DNGN_TRAP_MAGICAL)
{
if (you_know && m)
simple_monster_message(m, " flies safely over a trap.");
return;
}
// OK, something is going to happen.
if (you_trigger)
this->message_trap_entry();
// Store the position now in case it gets cleared inbetween.
const coord_def p(this->pos);
if (this->type_has_ammo())
this->shoot_ammo(triggerer, trig_knows);
else switch (this->type)
{
case TRAP_TELEPORT:
// Never revealed by monsters.
if (!you_trigger && !you_know)
this->hide();
triggerer.teleport(true);
break;
case TRAP_ALARM:
if (!ammo_qty--)
{
if (you_trigger)
mpr("You trigger an alarm trap, but it seems broken.");
else if (in_sight && you_know)
mpr("The alarm trap gives no sound.");
trap_destroyed = true;
}
else if (silenced(this->pos))
{
if (you_know && in_sight)
mpr("The alarm trap is silent.");
// If it's silent, you don't know about it.
if (!you_know)
this->hide();
}
else if (!(m && m->friendly()))
{
// 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!";
const char* msg = (you_trigger ? message_here :
(in_sight ? message_near : message_far));
// Monsters of normal or greater intelligence will realize that
// they were the one to set off the trap.
int source = !m ? you.mindex() :
mons_intel(m) >= I_NORMAL ? m->mindex() : -1;
noisy(12, this->pos, msg, source);
}
break;
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)
{
mpr("A huge blade swings just past you!");
}
else
{
mpr("A huge blade swings out and slices into you!");
const int damage = (you.your_level * 2) + random2avg(29, 2)
- random2(1 + you.armour_class());
ouch(damage, NON_MONSTER, KILLED_BY_TRAP, "blade");
bleed_onto_floor(you.pos(), MONS_PLAYER, damage, true);
}
}
else if (m)
{
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 (m->visible_to(&you))
{
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 (!m->is_summoned())
bleed_onto_floor(m->pos(), m->type, damage_taken, true);
m->hurt(NULL, damage_taken);
if (in_sight && m->alive())
print_wounds(m);
}
}
break;
case TRAP_NET:
if (you_trigger)
{
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!");
if (player_caught_in_net() && player_in_a_dangerous_place())
xom_is_stimulated(64);
}
item_def item = this->generate_trap_item();
copy_item_to_grid(item, triggerer.pos());
if (you.attribute[ATTR_HELD])
mark_net_trapping(you.pos());
trap_destroyed = true;
}
}
else if (m)
{
bool triggered = false;
if (one_chance_in(3) || (trig_knows && coinflip()))
{
// Not triggered, trap stays.
triggered = false;
if (you_know)
simple_monster_message(m, " fails to trigger a net trap.");
else
this->hide();
}
else if (random2(m->ev) > 8 || (trig_knows && random2(m->ev) > 8))
{
// Triggered but evaded.
triggered = true;
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
{
// Triggered and hit.
triggered = true;
if (in_sight)
{
msg::stream << "A large net falls down";
if (m->visible_to(&you))
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 (triggered)
{
item_def item = this->generate_trap_item();
copy_item_to_grid(item, triggerer.pos());
if (m->caught())
mark_net_trapping(m->pos());
trap_destroyed = true;
}
}
break;
case TRAP_ZOT:
if (you_trigger)
{
mpr((trig_knows) ? "You enter the Zot trap."
: "Oh no! You have blundered into a Zot trap!");
if (!trig_knows)
xom_is_stimulated(32);
MiscastEffect( &you, ZOT_TRAP_MISCAST, SPTYP_RANDOM,
3, "a Zot trap" );
}
else if (m)
{
// Zot traps are out to get *the player*! Hostile monsters
// benefit and friendly monsters suffer. Such is life.
// Preserving original functionality: don't reveal location.
if (!you_know)
this->hide();
if (m->wont_attack() || crawl_state.arena)
{
MiscastEffect( m, ZOT_TRAP_MISCAST, SPTYP_RANDOM,
3, "the power of Zot" );
}
else if (in_sight && 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");
}
}
break;
case TRAP_SHAFT:
// Unknown shafts are traps triggered by walking onto them.
// Known shafts are used as escape hatches
// Paranoia
if (!is_valid_shaft_level())
{
if (you_know && in_sight)
mpr("The shaft disappears in a puff of logic!");
trap_destroyed = true;
break;
}
// If the shaft isn't known, don't reveal it.
// The shafting code in downstairs() needs to know
// whether it's undiscovered.
if (!you_know)
this->hide();
// Known shafts don't trigger as traps.
if (trig_knows)
break;
// Fire away!
triggerer.do_shaft();
// Shafts are destroyed
// after one use in down_stairs(), misc.cc
break;
default:
break;
}
if (you_trigger)
{
learned_something_new(TUT_SEEN_TRAP, p);
// Exercise T&D if the trap revealed itself, but not if it ran
// out of ammo.
if (!you_know && this->type != TRAP_UNASSIGNED && this->is_known())
exercise(SK_TRAPS_DOORS, ((coinflip()) ? 2 : 1));
}
if (trap_destroyed)
this->destroy();
}
int trap_def::max_damage(const actor& act)
{
int level = you.your_level;
// 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.
if (act.atype() == ACT_MONSTER)
level = 0;
switch (this->type)
{
case TRAP_NEEDLE: return 0;
case TRAP_DART: return 4 + level/2;
case TRAP_ARROW: return 7 + level;
case TRAP_SPEAR: return 10 + level;
case TRAP_BOLT: return 13 + level;
case TRAP_AXE: return 15 + level;
case TRAP_BLADE: return (level ? 2*level : 10) + 28;
default: return 0;
}
return (0);
}
int trap_def::shot_damage(actor& act)
{
const int dam = max_damage(act);
if (!dam)
return 0;
return random2(dam) + 1;
}
int reveal_traps(const int range)
{
int traps_found = 0;
for (int i = 0; i < MAX_TRAPS; i++)
{
trap_def& trap = env.trap[i];
if (!trap.active())
continue;
if (grid_distance(you.pos(), trap.pos) < range && !trap.is_known())
{
traps_found++;
trap.reveal();
set_map_knowledge_obj(trap.pos, show_type(grd(trap.pos)));
set_terrain_mapped(trap.pos);
}
}
return (traps_found);
}
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);
}
static bool _disarm_is_deadly(trap_def& trap)
{
int dam = trap.max_damage(you);
if (trap.type == TRAP_NEEDLE && you.res_poison() <= 0)
dam += 15; // arbitrary
return (you.hp <= dam);
}
// where *must* point to a valid, discovered trap.
void disarm_trap(const coord_def& where)
{
if (you.berserk())
{
canned_msg(MSG_TOO_BERSERK);
return;
}
trap_def& trap = *find_trap(where);
switch (trap.category())
{
case DNGN_TRAP_MAGICAL:
mpr("You can't disarm that trap.");
return;
case DNGN_TRAP_NATURAL:
// Only shafts for now.
mpr("You can't disarm a shaft.");
return;
default:
break;
}
// Prompt for any trap for which you might not survive setting it off.
if (_disarm_is_deadly(trap))
{
std::string prompt = make_stringf(
"Really try disarming that %s?",
feature_description(trap.category(),
get_trap_type(where),
false, DESC_BASENAME,
false).c_str());
if (!yesno(prompt.c_str(), true, 'n'))
{
canned_msg(MSG_OK);
return;
}
}
// 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.");
if (random2(you.dex) > 5 + random2(5 + you.your_level))
exercise(SK_TRAPS_DOORS, 1 + random2(you.your_level / 5));
else
{
if (trap.type == TRAP_NET && trap.pos != you.pos())
{
if (coinflip())
{
mpr("You stumble into the trap!");
move_player_to_grid(trap.pos, true, false, true);
}
}
else
trap.trigger(you, true);
if (coinflip())
exercise(SK_TRAPS_DOORS, 1);
}
}
else
{
mpr("You have disarmed the trap.");
trap.disarm();
exercise(SK_TRAPS_DOORS, 1 + random2(5) + (you.your_level/5));
}
}
// Attempts to take a net off a given monster.
// This doesn't actually have any effect (yet).
// Do not expect gratitude for this!
// ----------------------------------
void remove_net_from(monsters *mon)
{
you.turn_is_over = true;
int net = get_trapping_net(mon->pos());
if (net == NON_ITEM)
{
mon->del_ench(ENCH_HELD, true);
return;
}
// factor in whether monster is paralysed or invisible
int paralys = 0;
if (mon->paralysed()) // makes this easier
paralys = random2(5);
int invis = 0;
if (!mon->visible_to(&you)) // 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 (mon->visible_to(&you))
{
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 (mon->visible_to(&you))
mprf("You free %s.", mon->name(DESC_NOCAP_THE).c_str());
else
mpr("You loosen the net.");
}
// Decides whether you will try to tear the net (result <= 0)
// or try to slip out of it (result > 0).
// Both damage and escape could be 9 (more likely for damage)
// but are capped at 5 (damage) and 4 (escape).
static int damage_or_escape_net(int hold)
{
// Spriggan: little (+2)
// Halfling, Kobold: small (+1)
// Human, Elf, ...: medium (0)
// Ogre, Troll, Centaur, Naga: large (-1)
// transformations: spider, bat: tiny (+3); ice beast: large (-1)
int escape = SIZE_MEDIUM - you.body_size(PSIZE_BODY);
int damage = -escape;
// your weapon may damage the net, max. bonus of 2
if (you.weapon())
{
if (can_cut_meat(*you.weapon()))
damage++;
int brand = get_weapon_brand(*you.weapon());
if (brand == SPWPN_FLAMING || brand == SPWPN_VORPAL)
damage++;
}
else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
damage += 2;
else if (you.has_usable_claws())
{
int level = you.has_claws();
if (level == 1)
damage += coinflip();
else
damage += level - 1;
}
// Berserkers get a fighting bonus.
if (you.berserk())
damage += 2;
// Check stats.
if (x_chance_in_y(you.strength, 18))
damage++;
if (x_chance_in_y(you.dex, 12))
escape++;
if (x_chance_in_y(player_evasion(), 20))
escape++;
// Dangerous monsters around you add urgency.
if (there_are_monsters_nearby(true))
{
damage++;
escape++;
}
// Confusion makes the whole thing somewhat harder
// (less so for trying to escape).
if (you.confused())
{
if (escape > 1)
escape--;
else if (damage >= 2)
damage -= 2;
}
// Damaged nets are easier to destroy.
if (hold < 0)
{
damage += random2(-hold/3 + 1);
// ... and easier to slip out of (but only if escape looks feasible).
if (you.attribute[ATTR_HELD] < 5 || escape >= damage)
escape += random2(-hold/2) + 1;
}
// If undecided, choose damaging approach (it's quicker).
if (damage >= escape)
return (-damage); // negate value
return (escape);
}
// Calls the above function to decide on how to get free.
// Note that usually the net will be damaged until trying to slip out
// becomes feasible (for size etc.), so it may take even longer.
void free_self_from_net()
{
int net = get_trapping_net(you.pos());
if (net == NON_ITEM) // really shouldn't happen!
{
you.attribute[ATTR_HELD] = 0;
return;
}
int hold = mitm[net].plus;
int do_what = damage_or_escape_net(hold);
dprf("net.plus: %d, ATTR_HELD: %d, do_what: %d",
hold, you.attribute[ATTR_HELD], do_what);
if (do_what <= 0) // You try to destroy the net
{
// For previously undamaged nets this takes at least 2 and at most
// 8 turns.
bool can_slice =
(you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
|| (you.weapon() && can_cut_meat(*you.weapon()));
int damage = -do_what;
if (damage < 1)
damage = 1;
if (you.berserk())
damage *= 2;
// Medium sized characters are at a disadvantage and sometimes
// get a bonus.
if (you.body_size(PSIZE_BODY) == SIZE_MEDIUM)
damage += coinflip();
if (damage > 5)
damage = 5;
hold -= damage;
mitm[net].plus = hold;
if (hold < -7)
{
mprf("You %s the net and break free!",
can_slice ? (damage >= 4? "slice" : "cut") :
(damage >= 4? "shred" : "rip"));
destroy_item(net);
you.attribute[ATTR_HELD] = 0;
return;
}
if (damage >= 4)
{
mprf("You %s into the net.",
can_slice? "slice" : "tear a large gash");
}
else
mpr("You struggle against the net.");
// Occasionally decrease duration a bit
// (this is so switching from damage to escape does not hurt as much).
if (you.attribute[ATTR_HELD] > 1 && coinflip())
{
you.attribute[ATTR_HELD]--;
if (you.attribute[ATTR_HELD] > 1 && hold < -random2(5))
you.attribute[ATTR_HELD]--;
}
}
else
{
// You try to escape (takes at least 3 turns, and at most 10).
unsigned int escape = do_what;
if (you.duration[DUR_HASTE]) // extra bonus, also Berserk
escape++;
// Medium sized characters are at a disadvantage and sometimes
// get a bonus.
if (you.body_size(PSIZE_BODY) == SIZE_MEDIUM)
escape += coinflip();
if (escape > 4)
escape = 4;
if (escape >= you.attribute[ATTR_HELD])
{
if (escape >= 3)
mpr("You slip out of the net!");
else
mpr("You break free from the net!");
you.attribute[ATTR_HELD] = 0;
remove_item_stationary(mitm[net]);
return;
}
if (escape >= 3)
mpr("You try to slip out of the net.");
else
mpr("You struggle to escape the net.");
you.attribute[ATTR_HELD] -= escape;
}
}
void clear_trapping_net()
{
if (!you.attribute[ATTR_HELD])
return;
if (!in_bounds(you.pos()))
return;
const int net = get_trapping_net(you.pos());
if (net != NON_ITEM)
remove_item_stationary(mitm[net]);
you.attribute[ATTR_HELD] = 0;
}
item_def trap_def::generate_trap_item()
{
item_def item;
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 == OBJ_MISSILES)
{
set_item_ego_type(item, base,
(sub == MI_NEEDLE) ? SPMSL_POISONED : SPMSL_NORMAL);
}
else
{
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;
}
// 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 (was_known && act.atype() == ACT_PLAYER)
mpr("The trap is out of ammunition!");
else if (player_can_hear(this->pos) && you.see_cell(this->pos))
mpr("You hear a soft click.");
this->disarm();
}
else
{
// Record position now, in case it's a monster and dies (thus
// resetting its position) before the ammo can be dropped.
const coord_def apos = act.pos();
item_def shot = this->generate_trap_item();
bool poison = (this->type == TRAP_NEEDLE
&& !act.res_poison()
&& x_chance_in_y(50 - (3*act.armour_class()) / 2, 100));
int damage_taken =
std::max(this->shot_damage(act) - random2(act.armour_class()+1),0);
int trap_hit = (20 + (you.your_level*2)) * random2(200) / 100;
if (act.atype() == ACT_PLAYER)
{
if (one_chance_in(5) || (was_known && !one_chance_in(4)))
{
mprf( "You avoid triggering %s trap.",
this->name(DESC_NOCAP_A).c_str() );
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 && player_shield_class() && coinflip())
exercise(SK_SHIELDS, 1);
const int con_block = random2(20 + you.shield_block_penalty());
const int pro_block = you.shield_bonus();
if (pro_block >= con_block)
{
// Note that we don't call shield_block_succeeded()
// because that can exercise Shields skill.
you.shield_blocks++;
msg += "hits your shield.";
mpr(msg.c_str());
}
else
{
int repel_turns = you.duration[DUR_REPEL_MISSILES]
/ BASELINE_DELAY;
// Note that this uses full (not random2limit(foo,40))
// player_evasion.
int your_dodge = you.melee_evasion(NULL) - 2
+ (random2(you.dex) / 3)
+ (repel_turns * 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)
you.poison(NULL, 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)
{
// Determine whether projectile hits.
bool hit = (trap_hit >= act.melee_evasion(NULL));
if (you.see_cell(act.pos()))
{
mprf("%s %s %s%s!",
shot.name(DESC_CAP_A).c_str(),
hit ? "hits" : "misses",
act.name(DESC_NOCAP_THE).c_str(),
(hit && damage_taken == 0
&& !poison) ? ", but does no damage" : "");
}
// Apply damage.
if (hit)
{
if (poison)
act.poison(NULL, 1 + random2(3));
act.hurt(NULL, damage_taken);
}
}
// Drop the item (sometimes.)
if (coinflip())
copy_item_to_grid(shot, apos);
this->ammo_qty--;
}
}
// returns appropriate trap symbol
dungeon_feature_type trap_def::category() const
{
return trap_category(type);
}
dungeon_feature_type trap_category(trap_type type)
{
switch (type)
{
case TRAP_SHAFT:
return (DNGN_TRAP_NATURAL);
case TRAP_TELEPORT:
case TRAP_ALARM:
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);
}
}
bool is_valid_shaft_level(const level_id &place)
{
if (place.level_type != LEVEL_DUNGEON)
return (false);
// Shafts are now allowed on the first two levels,
// as they have a good chance of being detected.
/* if (place == BRANCH_MAIN_DUNGEON && you.your_level < 2)
return (false); */
// Don't generate shafts in branches where teleport control
// is prevented. Prevents player from going down levels without
// reaching stairs, and also keeps player from getting stuck
// on lower levels with the innability to use teleport control to
// get back up.
if (testbits(get_branch_flags(place.branch), BFLAG_NO_TELE_CONTROL))
return (false);
const Branch &branch = branches[place.branch];
// When generating levels, don't place a shaft on the level
// immediately above the bottom of a branch if that branch is
// significantly more dangerous than normal.
int min_delta = 1;
if (env.turns_on_level == -1 && branch.dangerous_bottom_level)
min_delta = 2;
return ((branch.depth - place.depth) >= min_delta);
}
// Shafts can be generated visible.
//
// Starts about 50% of the time and approaches 0% for randomly
// placed traps, and starts at 100% and approaches 50% for
// others (e.g. at end of corridor).
bool shaft_known(int depth, bool randomly_placed)
{
if (randomly_placed)
return (coinflip() && x_chance_in_y(3, depth));
else
return (coinflip() || x_chance_in_y(3, depth));
}
level_id generic_shaft_dest(level_pos lpos, bool known = false)
{
level_id lid = lpos.id;
coord_def pos = lpos.pos;
if (lid.level_type != LEVEL_DUNGEON)
return lid;
int curr_depth = lid.depth;
Branch &branch = branches[lid.branch];
// Shaft traps' behavior depends on whether it is entered intentionally.
// Knowingly entering one is more likely to drop you 1 level.
// Falling in unknowingly can drop you 1/2/3 levels with equal chance.
if (known)
{
// Chances are 5/8s for 1 level, 2/8s for 2 levels, 1/8 for 3 levels
int s = random2(8) + 1;
if (s == 1)
lid.depth += 3;
else if (s <= 3)
lid.depth += 2;
else
lid.depth += 1;
}
else
{
// 33.3% for 1, 2, 3 from D:3, less before
lid.depth += 1 + random2(std::min(lid.depth, 3));
}
if (lid.depth > branch.depth)
lid.depth = branch.depth;
if (lid.depth == curr_depth)
return lid;
// Only shafts on the level immediately above a dangerous branch
// bottom will take you to that dangerous bottom, and shafts can't
// be created during level generation time.
// Include level 27 of the main dungeon here, but don't restrict
// shaft creation (so don't set branch.dangerous_bottom_level).
if (branch.dangerous_bottom_level
&& lid.depth == branch.depth
&& (branch.depth - curr_depth) > 1)
{
lid.depth--;
}
return lid;
}
level_id generic_shaft_dest(coord_def pos, bool known = false)
{
return generic_shaft_dest(level_pos(level_id::current(), pos));
}
void handle_items_on_shaft(const coord_def& pos, bool open_shaft)
{
if (!is_valid_shaft_level())
return;
level_id dest = generic_shaft_dest(pos);
if (dest == level_id::current())
return;
int o = igrd(pos);
if (o == NON_ITEM)
return;
igrd(pos) = NON_ITEM;
if (is_terrain_seen(pos) && open_shaft)
{
mpr("A shaft opens up in the floor!");
grd(pos) = DNGN_TRAP_NATURAL;
}
while (o != NON_ITEM)
{
int next = mitm[o].link;
if (mitm[o].is_valid())
{
if (is_terrain_seen(pos))
{
mprf("%s falls through the shaft.",
mitm[o].name(DESC_INVENTORY).c_str());
}
add_item_to_transit(dest, mitm[o]);
mitm[o].base_type = OBJ_UNASSIGNED;
mitm[o].quantity = 0;
mitm[o].props.clear();
}
o = next;
}
}
static int num_traps_default(int level_number, const level_id &place)
{
return random2avg(9, 2);
}
int num_traps_for_place(int level_number, const level_id &place)
{
if (level_number == -1)
level_number = place.absdepth();
switch (place.level_type)
{
case LEVEL_DUNGEON:
if (branches[place.branch].num_traps_function != NULL)
return branches[place.branch].num_traps_function(level_number);
else
return num_traps_default(level_number, place);
case LEVEL_ABYSS:
return traps_abyss_number(level_number);
case LEVEL_PANDEMONIUM:
return traps_pan_number(level_number);
case LEVEL_LABYRINTH:
case LEVEL_PORTAL_VAULT:
ASSERT(false);
break;
default:
return 0;
}
return 0;
}
trap_type random_trap_slime(int level_number)
{
trap_type type = NUM_TRAPS;
if (random2(1 + level_number) > 14 && one_chance_in(3))
{
type = TRAP_ZOT;
}
if (one_chance_in(5) && is_valid_shaft_level(level_id::current()))
type = TRAP_SHAFT;
if (one_chance_in(5))
type = TRAP_TELEPORT;
if (one_chance_in(10))
type = TRAP_ALARM;
return (type);
}
static trap_type random_trap_default(int level_number, const level_id &place)
{
trap_type type = TRAP_DART;
if ((random2(1 + level_number) > 1) && one_chance_in(4))
type = TRAP_NEEDLE;
if (random2(1 + level_number) > 3)
type = TRAP_SPEAR;
if (random2(1 + level_number) > 5)
type = TRAP_AXE;
// Note we're boosting arrow trap numbers by moving it
// down the list, and making spear and axe traps rarer.
if (type == TRAP_DART ? random2(1 + level_number) > 2
: one_chance_in(7))
{
type = TRAP_ARROW;
}
if ((type == TRAP_DART || type == TRAP_ARROW) && one_chance_in(15))
type = TRAP_NET;
if (random2(1 + level_number) > 7)
type = TRAP_BOLT;
if (random2(1 + level_number) > 11)
type = TRAP_BLADE;
if (random2(1 + level_number) > 14 && one_chance_in(3)
|| (place == BRANCH_HALL_OF_ZOT && coinflip()))
{
type = TRAP_ZOT;
}
if (one_chance_in(20) && is_valid_shaft_level(place))
type = TRAP_SHAFT;
if (one_chance_in(20))
type = TRAP_TELEPORT;
if (one_chance_in(40))
type = TRAP_ALARM;
return (type);
}
trap_type random_trap_for_place(int level_number, const level_id &place)
{
if (level_number == -1)
level_number = place.absdepth();
switch (place.level_type)
{
case LEVEL_DUNGEON:
if (branches[place.branch].rand_trap_function != NULL)
return branches[place.branch].rand_trap_function(level_number);
else
return random_trap_default(level_number, place);
case LEVEL_ABYSS:
return traps_abyss_type(level_number);
case LEVEL_PANDEMONIUM:
return traps_pan_type(level_number);
default:
return random_trap_default(level_number, place);
}
return NUM_TRAPS;
}
int traps_zero_number(int level_number)
{
return 0;
}
int traps_pan_number(int level_number)
{
return num_traps_default(level_number, level_id(LEVEL_PANDEMONIUM));
}
trap_type traps_pan_type(int level_number)
{
return random_trap_default(level_number, level_id(LEVEL_PANDEMONIUM));
}
int traps_abyss_number(int level_number)
{
return num_traps_default(level_number, level_id(LEVEL_ABYSS));
}
trap_type traps_abyss_type(int level_number)
{
return random_trap_default(level_number, level_id(LEVEL_ABYSS));
}
int traps_lab_number(int level_number)
{
return num_traps_default(level_number, level_id(LEVEL_LABYRINTH));
}
trap_type traps_lab_type(int level_number)
{
return random_trap_default(level_number, level_id(LEVEL_LABYRINTH));
}