#include "AppHdr.h"
#include "externs.h"
#include "traps.h"
#include <algorithm>
#include "artefact.h"
#include "beam.h"
#include "branch.h"
#include "coord.h"
#include "delay.h"
#include "describe.h"
#include "directn.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "los.h"
#include "makeitem.h"
#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "monstuff.h"
#include "mtransit.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 "terrain.h"
#include "transfor.h"
#include "tutorial.h"
#include "view.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()
{
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 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);
rc = (intel >= I_NORMAL
&& (mons_is_native_in_branch(monster)
|| mons_wont_attack(monster) && player_knows
|| intel >= I_HIGH && one_chance_in(3)));
}
return (rc);
}
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);
}
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 (!mons_is_caught(mon) && 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();
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); 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 (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 = see_cell(this->pos);
bool trap_destroyed = false;
monsters* m = NULL;
if (triggerer.atype() == ACT_MONSTER)
m = dynamic_cast<monsters*>(&triggerer);
if (in_sight)
this->reveal();
if (triggerer.airborne() && this->category() != DNGN_TRAP_MAGICAL)
{
if (you_know && m)
simple_monster_message(m, " flies safely over a trap.");
return;
}
if (you_trigger)
this->message_trap_entry();
const coord_def p(this->pos);
if (this->type_has_ammo())
this->shoot_ammo(triggerer, trig_knows);
else switch (this->type)
{
case TRAP_TELEPORT:
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 (!you_know)
this->hide();
}
else if (!(m && mons_friendly(m)))
{
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));
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 + player_AC());
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()))
{
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 (!mons_is_summoned(m))
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()))
{
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 = 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 = 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;
}
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 (mons_is_caught(m))
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)
{
if (!you_know)
this->hide();
if (mons_wont_attack(m) || 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:
if (!is_valid_shaft_level())
{
if (you_know && in_sight)
mpr("The shaft disappears in a puff of logic!");
trap_destroyed = true;
break;
}
if (trig_knows)
{
if (you_trigger)
{
if (triggerer.airborne())
{
if (you.flight_mode() == FL_LEVITATE)
mpr("You float over the shaft.");
else
mpr("You fly over the shaft.");
}
else
mpr("You carefully avoid triggering the shaft.");
}
break;
}
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.");
}
}
}
{
const bool revealed = triggerer.do_shaft();
if (!revealed && !you_know)
this->hide();
}
break;
default:
break;
}
if (you_trigger)
{
learned_something_new(TUT_SEEN_TRAP, p);
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::shot_damage(actor& act)
{
if (act.atype() == ACT_PLAYER)
{
switch (this->type)
{
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)
{
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(const coord_def& where)
{
if (you.duration[DUR_BERSERKER])
{
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:
mpr("You can't disarm a shaft.");
return;
default:
break;
}
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));
}
}
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;
}
int paralys = 0;
if (mons_is_paralysed(mon)) paralys = random2(5);
int invis = 0;
if (!mon->visible_to(&you)) 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.");
}
static int damage_or_escape_net(int hold)
{
int escape = SIZE_MEDIUM - you.body_size(PSIZE_BODY);
int damage = -escape;
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;
}
if (you.duration[DUR_BERSERKER])
damage += 2;
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++;
if (there_are_monsters_nearby(true))
{
damage++;
escape++;
}
if (you.confused())
{
if (escape > 1)
escape--;
else if (damage >= 2)
damage -= 2;
}
if (hold < 0)
{
damage += random2(-hold/3 + 1);
if (you.attribute[ATTR_HELD] < 5 || escape >= damage)
escape += random2(-hold/2) + 1;
}
if (damage >= escape)
return (-damage);
return (escape);
}
void free_self_from_net()
{
int net = get_trapping_net(you.pos());
if (net == NON_ITEM) {
you.attribute[ATTR_HELD] = 0;
return;
}
int hold = mitm[net].plus;
int do_what = damage_or_escape_net(hold);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "net.plus: %d, ATTR_HELD: %d, do_what: %d",
hold, you.attribute[ATTR_HELD], do_what);
#endif
if (do_what <= 0) {
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.duration[DUR_BERSERKER])
damage *= 2;
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.");
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
{
unsigned int escape = do_what;
if (you.duration[DUR_HASTE]) escape++;
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);
}
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;
}
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) && see_cell(this->pos))
mpr("You hear a soft click.");
this->disarm();
}
else
{
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; }
std::string msg = shot.name(DESC_CAP_A) + " shoots out and ";
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)
{
you.shield_blocks++;
msg += "hits your shield.";
mpr(msg.c_str());
}
else
{
int your_dodge = you.melee_evasion(NULL) - 2
+ (random2(you.dex) / 3)
+ (you.duration[DUR_REPEL_MISSILES] * 10);
if (trap_hit >= your_dodge
&& you.duration[DUR_DEFLECT_MISSILES] == 0)
{
msg += "hits you!";
mpr(msg.c_str());
if (poison)
you.poison(NULL, 1 + random2(3));
ouch(damage_taken, NON_MONSTER, KILLED_BY_TRAP,
shot.name(DESC_PLAIN).c_str());
}
else {
msg += "misses you.";
mpr(msg.c_str());
}
if (!was_known && player_light_armour(true) && coinflip())
exercise(SK_DODGING, 1);
}
}
else if (act.atype() == ACT_MONSTER)
{
bool hit = (trap_hit >= act.melee_evasion(NULL));
if (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" : "");
}
if (hit)
{
if (poison)
act.poison(NULL, 1 + random2(3));
act.hurt(NULL, damage_taken);
}
}
if (coinflip())
copy_item_to_grid(shot, apos);
this->ammo_qty--;
}
}
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: return (DNGN_TRAP_MECHANICAL);
}
}
bool is_valid_shaft_level(const level_id &place)
{
if (place.level_type != LEVEL_DUNGEON)
return (false);
if (place == BRANCH_MAIN_DUNGEON && you.your_level < 2)
return (false);
if (testbits(get_branch_flags(place.branch), LFLAG_NO_TELE_CONTROL))
return (false);
const Branch &branch = branches[place.branch];
int min_delta = 1;
if (env.turns_on_level == -1 && branch.dangerous_bottom_level)
min_delta = 2;
return ((branch.depth - place.depth) >= min_delta);
}
level_id generic_shaft_dest(level_pos lpos)
{
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];
lid.depth += 2;
if (pos.x % 2)
lid.depth--;
if (pos.y % 2)
lid.depth++;
if (lid.depth > branch.depth)
lid.depth = branch.depth;
if (lid.depth == curr_depth)
return lid;
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)
{
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 (is_valid_item( mitm[o] ))
{
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;
}
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;
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(50) && 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));
}