Add "active stealth-training" (FR 1833916):
[crawl:crawl.git] / crawl-ref / source / traps.cc
1 /*
2  *  File:       traps.cc
3  *  Summary:    Traps related functions.
4  *  Written by: Linley Henzell
5  *
6  *  Modified for Crawl Reference by $Author$ on $Date$
7  *
8  *  Change History (most recent first):
9  *
10  *               <1>     9/11/07        MPC             Split from misc.cc
11  */
12
13 #include "AppHdr.h"
14
15 #include "externs.h"
16 #include "traps.h"
17
18 #include "beam.h"
19 #include "branch.h"
20 #include "direct.h"
21 #include "it_use2.h"
22 #include "items.h"
23 #include "itemprop.h"
24 #include "makeitem.h"
25 #include "misc.h"
26 #include "mon-util.h"
27 #include "monstuff.h"
28 #include "mtransit.h"
29 #include "ouch.h"
30 #include "place.h"
31 #include "player.h"
32 #include "randart.h"
33 #include "skills.h"
34 #include "spells3.h"
35 #include "spl-cast.h"
36 #include "spl-util.h"
37 #include "terrain.h"
38 #include "transfor.h"
39 #include "tutorial.h"
40 #include "view.h"
41
42 static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison);
43
44 // returns the number of a net on a given square
45 // if trapped only stationary ones are counted
46 // otherwise the first net found is returned
47 int get_trapping_net(int x, int y, bool trapped)
48 {
49     int net, next;
50
51     for (net = igrd[x][y]; net != NON_ITEM; net = next)
52     {
53          next = mitm[net].link;
54
55          if (mitm[net].base_type == OBJ_MISSILES
56              && mitm[net].sub_type == MI_THROWING_NET
57              && (!trapped || item_is_stationary(mitm[net])))
58          {
59              return (net);
60          }
61     }
62     return (NON_ITEM);
63 }
64
65 // if there are more than one net on this square
66 // split off one of them for checking/setting values
67 static void maybe_split_nets(item_def &item, int x, int y)
68 {
69     if (item.quantity == 1)
70     {
71         set_item_stationary(item);
72         return;
73     }
74
75     item_def it;
76
77     it.base_type = item.base_type;
78     it.sub_type = item.sub_type;
79     it.plus = item.plus;
80     it.plus2 = item.plus2;
81     it.flags = item.flags;
82     it.special = item.special;
83     it.quantity = --item.quantity;
84     item_colour(it);
85
86     item.quantity = 1;
87     set_item_stationary(item);
88
89     copy_item_to_grid( it, x, y );
90 }
91
92 void mark_net_trapping(int x, int y)
93 {
94     int net = get_trapping_net(x,y);
95     if (net == NON_ITEM)
96     {
97         net = get_trapping_net(x,y, false);
98         if (net != NON_ITEM)
99             maybe_split_nets(mitm[net], x, y);
100     }
101 }
102
103 void monster_caught_in_net(monsters *mon, bolt &pbolt)
104 {
105     if (mon->body_size(PSIZE_BODY) >= SIZE_GIANT)
106         return;
107
108     if (mons_is_insubstantial(mon->type))
109     {
110         if (mons_near(mon) && player_monster_visible(mon))
111             mprf("The net passes right through %s!", mon->name(DESC_NOCAP_THE).c_str());
112         return;
113     }
114
115     const monsters* mons = static_cast<const monsters*>(mon);
116     bool mon_flies = mons->flight_mode() == FL_FLY;
117     if (mon_flies && (!mons_is_confused(mons) || one_chance_in(3)))
118     {
119         simple_monster_message(mon, " darts out from under the net!");
120         return;
121     }
122
123     if (mons->type == MONS_OOZE || mons->type == MONS_PULSATING_LUMP)
124     {
125         simple_monster_message(mon, " oozes right through the net!");
126         return;
127     }
128
129     if (!mons_is_caught(mon) && mon->add_ench(ENCH_HELD))
130     {
131         if (mons_near(mon) && !player_monster_visible(mon))
132             mpr("Something gets caught in the net!");
133         else
134             simple_monster_message(mon, " is caught in the net!");
135
136         if (mon_flies)
137         {
138             simple_monster_message(mon, " falls like a stone!");
139             mons_check_pool(mon, pbolt.killer(), pbolt.beam_source);
140         }
141     }
142 }
143
144 void player_caught_in_net()
145 {
146     if (you.body_size(PSIZE_BODY) >= SIZE_GIANT)
147         return;
148
149     if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR)
150     {
151         mpr("The net passes right through you!");
152         return;
153     }
154
155     if (you.flight_mode() == FL_FLY && (!you.confused() || one_chance_in(3)))
156     {
157         mpr("You dart out from under the net!");
158         return;
159     }
160
161     if (!you.attribute[ATTR_HELD])
162     {
163         you.attribute[ATTR_HELD] = 10;
164         mpr("You become entangled in the net!");
165         stop_running();
166
167         // I guess levitation works differently, keeping both you
168         // and the net hovering above the floor
169         if (you.flight_mode() == FL_FLY)
170         {
171             mpr("You fall like a stone!");
172             fall_into_a_pool(you.x_pos, you.y_pos, false, grd(you.pos()));
173         }
174     }
175 }
176
177 static void dart_trap(bool trap_known, int trapped, bolt &pbolt, bool poison)
178 {
179     int damage_taken = 0;
180     int trap_hit, your_dodge;
181
182     if (one_chance_in(5) || (trap_known && !one_chance_in(4)))
183     {
184         mprf( "You avoid triggering a%s trap.", pbolt.name.c_str() );
185         return;
186     }
187
188     if (you.equip[EQ_SHIELD] != -1 && one_chance_in(3))
189         exercise( SK_SHIELDS, 1 );
190
191     std::string msg = "A" + pbolt.name + " shoots out and ";
192
193     if (random2( 20 + 5 * you.shield_blocks * you.shield_blocks ) 
194                                                 < player_shield_class())
195     {
196         you.shield_blocks++;
197         msg += "hits your shield.";
198         mpr(msg.c_str());
199     }
200     else
201     {
202         // note that this uses full ( not random2limit(foo,40) )
203         // player_evasion.
204         trap_hit = (20 + (you.your_level * 2)) * random2(200) / 100;
205
206         your_dodge = player_evasion() + random2(you.dex) / 3
207             - 2 + (you.duration[DUR_REPEL_MISSILES] * 10);
208         
209         if (trap_hit >= your_dodge && you.duration[DUR_DEFLECT_MISSILES] == 0)
210         {
211             msg += "hits you!";
212             mpr(msg.c_str());
213             
214             if (poison && random2(100) < 50 - (3 * player_AC()) / 2
215                 && !player_res_poison())
216             {
217                 poison_player( 1 + random2(3) );
218             }
219             
220             damage_taken = roll_dice( pbolt.damage );
221             damage_taken -= random2( player_AC() + 1 );
222             
223             if (damage_taken > 0)
224                 ouch( damage_taken, 0, KILLED_BY_TRAP, pbolt.name.c_str() );
225         }
226         else
227         {
228             msg += "misses you.";
229             mpr(msg.c_str());
230         }
231
232         if (player_light_armour(true) && coinflip())
233             exercise( SK_DODGING, 1 );
234     }
235
236     pbolt.target_x = you.x_pos;
237     pbolt.target_y = you.y_pos;
238
239     if (coinflip())
240         itrap( pbolt, trapped );
241 }                               // end dart_trap()
242
243 //
244 // itrap takes location from target_x, target_y of bolt strcture.
245 //
246
247 void itrap( struct bolt &pbolt, int trapped )
248 {
249     object_class_type base_type = OBJ_MISSILES;
250     int sub_type = MI_DART;
251
252     switch (env.trap[trapped].type)
253     {
254     case TRAP_DART:
255         base_type = OBJ_MISSILES;
256         sub_type = MI_DART;
257         break;
258     case TRAP_ARROW:
259         base_type = OBJ_MISSILES;
260         sub_type = MI_ARROW;
261         break;
262     case TRAP_BOLT:
263         base_type = OBJ_MISSILES;
264         sub_type = MI_BOLT;
265         break;
266     case TRAP_SPEAR:
267         base_type = OBJ_WEAPONS;
268         sub_type = WPN_SPEAR;
269         break;
270     case TRAP_AXE:
271         base_type = OBJ_WEAPONS;
272         sub_type = WPN_HAND_AXE;
273         break;
274     case TRAP_NEEDLE:
275         base_type = OBJ_MISSILES;
276         sub_type = MI_NEEDLE;
277         break;
278     case TRAP_NET:
279         base_type = OBJ_MISSILES;
280         sub_type = MI_THROWING_NET;
281         break;
282     default:
283         return;
284     }
285
286     trap_item( base_type, sub_type, pbolt.target_x, pbolt.target_y );
287
288     return;
289 }                               // end itrap()
290
291 void handle_traps(trap_type trt, int i, bool trap_known)
292 {
293     struct bolt beam;
294     
295     bool branchtype = false;
296     if (trap_category(trt) == DNGN_TRAP_MECHANICAL && trt != TRAP_NET
297         && trt != TRAP_BLADE)
298     {
299         if (you.where_are_you == BRANCH_ORCISH_MINES)
300         {
301             beam.name = "n orcish";
302             branchtype = true;
303         }
304         else if (you.where_are_you == BRANCH_ELVEN_HALLS)
305         {
306             beam.name = "n elven";
307             branchtype = true;
308         }
309         else
310             beam.name = "";
311     }
312
313     switch (trt)
314     {
315     case TRAP_DART:
316         beam.name += " dart";
317         beam.damage = dice_def( 1, 4 + (you.your_level / 2) );
318         dart_trap(trap_known, i, beam, false);
319         break;
320
321     case TRAP_NEEDLE:
322         beam.name += " needle";
323         beam.damage = dice_def( 1, 0 );
324         dart_trap(trap_known, i, beam, true);
325         break;
326
327     case TRAP_ARROW:
328         beam.name += (branchtype? "" : "n");
329         beam.name += " arrow";
330         beam.damage = dice_def( 1, 7 + you.your_level );
331         dart_trap(trap_known, i, beam, false);
332         break;
333
334     case TRAP_BOLT:
335         beam.name += " bolt";
336         beam.damage = dice_def( 1, 13 + you.your_level );
337         dart_trap(trap_known, i, beam, false);
338         break;
339
340     case TRAP_SPEAR:
341         beam.name += " spear";
342         beam.damage = dice_def( 1, 10 + you.your_level );
343         dart_trap(trap_known, i, beam, false);
344         break;
345
346     case TRAP_AXE:
347         beam.name += (branchtype? "" : "n");
348         beam.name += " axe";
349         beam.damage = dice_def( 1, 15 + you.your_level );
350         dart_trap(trap_known, i, beam, false);
351         break;
352
353     case TRAP_TELEPORT:
354         mpr("You enter a teleport trap!");
355
356         if (scan_randarts(RAP_PREVENT_TELEPORTATION))
357             mpr("You feel a weird sense of stasis.");
358         else
359             you_teleport_now( true );
360         break;
361
362     case TRAP_ALARM:
363         if (silenced(you.x_pos, you.y_pos))
364         {
365             if (trap_known)
366                 mpr("The alarm is silenced.");
367             else
368                 grd[you.x_pos][you.y_pos] = DNGN_UNDISCOVERED_TRAP;
369             return;
370         }
371
372         noisy(12, you.x_pos, you.y_pos, "An alarm trap emits a blaring wail!");
373
374         break;
375
376     case TRAP_BLADE:
377         if (trap_known && one_chance_in(3))
378             mpr("You avoid triggering a blade trap.");
379         else if (random2limit(player_evasion(), 40)
380                         + (random2(you.dex) / 3) + (trap_known ? 3 : 0) > 8)
381         {
382             mpr("A huge blade swings just past you!");
383         }
384         else
385         {
386             mpr("A huge blade swings out and slices into you!");
387             int damage = (you.your_level * 2) + random2avg(29, 2)
388                           - random2(1 + player_AC());
389             ouch( damage, 0, KILLED_BY_TRAP, " blade" );
390             bleed_onto_floor(you.x_pos, you.y_pos, -1, damage, true);
391         }
392         break;
393
394     case TRAP_NET:
395         if (trap_known && one_chance_in(3))
396             mpr("A net swings high above you.");
397         else
398         {
399             if (random2limit(player_evasion(), 40)
400                         + (random2(you.dex) / 3) + (trap_known ? 3 : 0) > 12)
401             {
402                 mpr("A net drops to the ground!");
403             }
404             else
405             {
406                 mpr("A large net falls onto you!");
407                 player_caught_in_net();
408             }
409
410             trap_item( OBJ_MISSILES, MI_THROWING_NET, env.trap[i].x, env.trap[i].y );
411             if (you.attribute[ATTR_HELD])
412                 mark_net_trapping(you.x_pos, you.y_pos);
413
414             grd[env.trap[i].x][env.trap[i].y] = DNGN_FLOOR;
415             env.trap[i].type = TRAP_UNASSIGNED;
416         }
417         break;
418
419     // If we don't trigger the shaft, and the player doesn't
420     // already know about it, don't let him/her notice it.
421     case TRAP_SHAFT:
422     {
423         // Paranoia
424         if (!is_valid_shaft_level())
425         {
426             if (trap_known)
427                 mpr("The shaft disappears in a puff of logic!");
428
429             grd[env.trap[i].x][env.trap[i].y] = DNGN_FLOOR;
430             env.trap[i].type = TRAP_UNASSIGNED;
431             return;
432         }
433
434         if (!you.will_trigger_shaft())
435         {
436             if (trap_known && !you.airborne())
437                 mpr("You don't fall through the shaft..");
438
439             if (!trap_known)
440                 grd[you.x_pos][you.y_pos] = DNGN_UNDISCOVERED_TRAP;
441
442             return;
443         }
444
445         if (!you.do_shaft())
446             if (!trap_known)
447             {
448                 grd[you.x_pos][you.y_pos] = DNGN_UNDISCOVERED_TRAP;
449                 return;
450             }
451
452         break;
453     }
454         
455     case TRAP_ZOT:
456     default:
457         mpr((trap_known) ? "You enter the Zot trap."
458                          : "Oh no! You have blundered into a Zot trap!");
459         miscast_effect( SPTYP_RANDOM, random2(30) + you.your_level,
460                         75 + random2(100), 3, "a Zot trap" );
461         break;
462     }
463     learned_something_new(TUT_SEEN_TRAP, you.x_pos, you.y_pos);
464     
465     if (!trap_known) // Now you know...
466         exercise(SK_TRAPS_DOORS, ((coinflip()) ? 2 : 1));
467 }                               // end handle_traps()
468
469 void disarm_trap( struct dist &disa )
470 {
471     if (you.duration[DUR_BERSERKER])
472     {
473         canned_msg(MSG_TOO_BERSERK);
474         return;
475     }
476
477     int i, j;
478
479     for (i = 0; i < MAX_TRAPS; i++)
480     {
481         if (env.trap[i].x == you.x_pos + disa.dx
482             && env.trap[i].y == you.y_pos + disa.dy)
483         {
484             break;
485         }
486
487         if (i == MAX_TRAPS - 1)
488         {
489             mpr("Error - couldn't find that trap.");
490             return;
491         }
492     }
493
494     if (trap_category(env.trap[i].type) == DNGN_TRAP_MAGICAL)
495     {
496         mpr("You can't disarm that trap.");
497         return;
498     }
499
500     if (random2(you.skills[SK_TRAPS_DOORS] + 2) <= random2(you.your_level + 5))
501     {
502         mpr("You failed to disarm the trap.");
503
504         you.turn_is_over = true;
505
506         if (random2(you.dex) > 5 + random2(5 + you.your_level))
507             exercise(SK_TRAPS_DOORS, 1 + random2(you.your_level / 5));
508         else
509         {
510             if (env.trap[i].type == TRAP_NET &&
511                 (env.trap[i].x != you.x_pos || env.trap[i].y != you.y_pos))
512             {
513                 if (coinflip())
514                     return;
515
516                 mpr("You stumble into the trap!");
517                 move_player_to_grid( env.trap[i].x, env.trap[i].y, true, false, true);
518             }
519             else
520                 handle_traps(env.trap[i].type, i, false);
521
522             if (coinflip())
523                 exercise(SK_TRAPS_DOORS, 1);
524         }
525
526         return;
527     }
528
529     mpr("You have disarmed the trap.");
530
531     struct bolt beam;
532
533     beam.target_x = you.x_pos + disa.dx;
534     beam.target_y = you.y_pos + disa.dy;
535
536     if (env.trap[i].type == TRAP_NET)
537         trap_item( OBJ_MISSILES, MI_THROWING_NET, beam.target_x, beam.target_y );
538     else if (env.trap[i].type != TRAP_BLADE
539         && trap_category(env.trap[i].type) == DNGN_TRAP_MECHANICAL)
540     {
541         const int num_to_make = 10 + random2(you.skills[SK_TRAPS_DOORS]);
542         for (j = 0; j < num_to_make; j++)
543         {           
544             // places items (eg darts), which will automatically stack
545             itrap(beam, i);
546         }
547     }
548
549     grd[you.x_pos + disa.dx][you.y_pos + disa.dy] = DNGN_FLOOR;
550     env.trap[i].type = TRAP_UNASSIGNED;
551     you.turn_is_over = true;
552
553     // reduced from 5 + random2(5)
554     exercise(SK_TRAPS_DOORS, 1 + random2(5) + (you.your_level / 5));
555 }                               // end disarm_trap()
556
557 // attempts to take a net off a given monster
558 // Do not expect gratitude for this!
559 // ----------------------------------
560 void remove_net_from(monsters *mon)
561 {
562     you.turn_is_over = true;
563     
564     int net = get_trapping_net(mon->x, mon->y);
565
566     if (net == NON_ITEM)
567     {
568         mon->del_ench(ENCH_HELD, true);
569         return;
570     }
571
572     // factor in whether monster is paralysed or invisible
573     int paralys = 0;
574     if (mons_is_paralysed(mon)) // makes this easier
575         paralys = random2(5);
576         
577     int invis = 0;
578     if (!player_monster_visible(mon)) // makes this harder
579         invis = 3 + random2(5);
580
581     bool net_destroyed = false;
582     if ( random2(you.skills[SK_TRAPS_DOORS] + 2) + paralys
583            <= random2( 2*mon->body_size(PSIZE_BODY) + 3 ) + invis)
584     {
585         if (one_chance_in(you.skills[SK_TRAPS_DOORS] + you.dex/2))
586         {
587             mitm[net].plus--;
588             mpr("You tear at the net.");
589             if (mitm[net].plus < -7)
590             {
591                 mpr("Whoops! The net comes apart in your hands!");
592                 mon->del_ench(ENCH_HELD, true);
593                 destroy_item(net);
594                 net_destroyed = true;
595             }
596         }
597
598         if (!net_destroyed)
599         {
600             if (player_monster_visible(mon))
601             {
602                 mprf("You fail to remove the net from %s.",
603                      mon->name(DESC_NOCAP_THE).c_str());
604             }
605             else
606                 mpr("You fail to remove the net.");
607         }
608
609         if (random2(you.dex) > 5 + random2( 2*mon->body_size(PSIZE_BODY) ))
610             exercise(SK_TRAPS_DOORS, 1 + random2(mon->body_size(PSIZE_BODY)/2));
611         return;
612     }
613      
614     mon->del_ench(ENCH_HELD, true);
615     remove_item_stationary(mitm[net]);
616     
617     if (player_monster_visible(mon))
618         mprf("You free %s.", mon->name(DESC_NOCAP_THE).c_str());
619     else
620         mpr("You loosen the net.");
621
622 }
623
624 // decides whether you will try to tear the net (result <= 0)
625 // or try to slip out of it (result > 0)
626 // both damage and escape could be 9 (more likely for damage)
627 // but are capped at 5 (damage) and 4 (escape)
628 static int damage_or_escape_net(int hold)
629 {
630     // Spriggan: little (+2)
631     // Halfling, Kobold, Gnome: small (+1)
632     // Human, Elf, ...: medium (0)
633     // Ogre, Troll, Centaur, Naga: large (-1)
634     // transformations: spider, bat: tiny (+3); ice beast: large (-1)
635     int escape = SIZE_MEDIUM - you.body_size(PSIZE_BODY);
636     
637     int damage = -escape;
638
639     // your weapon may damage the net, max. bonus of 2
640     if (you.equip[EQ_WEAPON] != -1)
641     {
642         if (can_cut_meat(you.inv[you.equip[EQ_WEAPON]]))
643             damage++;
644             
645         int brand = get_weapon_brand( you.inv[you.equip[EQ_WEAPON]] );
646         if (brand == SPWPN_FLAMING || brand == SPWPN_VORPAL)
647             damage++;
648     }
649     else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
650         damage += 2;
651     else if (you.has_usable_claws())
652     {
653         int level = you.has_claws();
654         if (level == 1)
655             damage += coinflip();
656         else
657             damage += level - 1;
658     }
659     
660     // Berserkers get a fighting bonus
661     if (you.duration[DUR_BERSERKER])
662         damage += 2;
663
664     // check stats
665     if (you.strength > random2(18))
666         damage++;
667     if (you.dex > random2(12))
668         escape++;
669     if (player_evasion() > random2(20))
670         escape++;
671
672     // monsters around you add urgency
673     if (!i_feel_safe())
674     {
675         damage++;
676         escape++;
677     }
678
679     // confusion makes the whole thing somewhat harder
680     // (less so for trying to escape)
681     if (you.duration[DUR_CONF])
682     {
683         if (escape > 1)
684             escape--;
685         else if (damage >= 2)
686             damage -= 2;
687     }
688     
689     // damaged nets are easier to destroy
690     if (hold < 0)
691     {
692         damage += random2(-hold/3 + 1); 
693         
694         // ... and easier to slip out of (but only if escape looks feasible)
695         if (you.attribute[ATTR_HELD] < 5 || escape >= damage)
696             escape += random2(-hold/2) + 1;
697     }
698
699     // if undecided, choose damaging approach (it's quicker)
700     if (damage >= escape)
701         return (-damage); // negate value
702
703     return (escape);
704 }
705
706 // calls the above function to decide on how to get free
707 // note that usually the net will be damaged until trying to slip out
708 // becomes feasible (for size etc.), so it may take even longer
709 void free_self_from_net()
710 {
711     int net = get_trapping_net(you.x_pos, you.y_pos);
712
713     if (net == NON_ITEM) // really shouldn't happen!
714     {
715         you.attribute[ATTR_HELD] = 0;
716         return;
717     }
718
719     int hold = mitm[net].plus;
720     int do_what = damage_or_escape_net(hold);
721 #ifdef DEBUG_DIAGNOSTICS
722     mprf(MSGCH_DIAGNOSTICS, "net.plus: %d, ATTR_HELD: %d, do_what: %d",
723          hold, you.attribute[ATTR_HELD], do_what);
724 #endif
725
726     if (do_what <= 0) // you try to destroy the net
727     {                 // for previously undamaged nets this takes at least 2 
728                       // and at most 8 turns
729         bool can_slice = you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS
730                          || you.equip[EQ_WEAPON] != -1
731                             && can_cut_meat(you.inv[you.equip[EQ_WEAPON]]);
732
733         int damage = -do_what;
734         
735         if (damage < 1)
736             damage = 1;
737
738         if (you.duration[DUR_BERSERKER])
739             damage *= 2;
740
741         // medium sized characters are at disadvantage and sometimes get a bonus
742         if (you.body_size(PSIZE_BODY) == SIZE_MEDIUM)
743             damage += coinflip();
744
745         if (damage > 5)
746             damage = 5;
747                     
748         hold -= damage;
749         mitm[net].plus = hold;
750
751         if (hold < -7)
752         {
753             mprf("You %s the net and break free!",
754                  can_slice ? (damage >= 4? "slice" : "cut") :
755                              (damage >= 4? "shred" : "rip"));
756                   
757             destroy_item(net);
758
759             you.attribute[ATTR_HELD] = 0;
760             return;
761         }
762         
763         if (damage >= 4)
764         {
765             mprf("You %s into the net.",
766                  can_slice? "slice" : "tear a large gash");
767         }
768         else
769             mpr("You struggle against the net.");
770
771         // occasionally decrease duration a bit
772         // (this is so switching from damage to escape does not hurt as much)
773         if (you.attribute[ATTR_HELD] > 1 && coinflip())
774         {
775             you.attribute[ATTR_HELD]--;
776             
777             if (you.attribute[ATTR_HELD] > 1 && hold < -random2(5))
778                 you.attribute[ATTR_HELD]--;
779         }
780    }
781    else // you try to escape (takes at least 3 turns, and at most 10)
782    {
783         int escape = do_what;
784
785         if (you.duration[DUR_HASTE]) // extra bonus, also Berserk
786             escape++;
787             
788         // medium sized characters are at disadvantage and sometimes get a bonus
789         if (you.body_size(PSIZE_BODY) == SIZE_MEDIUM)
790             escape += coinflip();
791
792         if (escape > 4)
793             escape = 4;
794             
795         if (escape >= you.attribute[ATTR_HELD])
796         {
797             if (escape >= 3)
798                 mpr("You slip out of the net!");
799             else
800                 mpr("You break free from the net!");
801                 
802             you.attribute[ATTR_HELD] = 0;
803             remove_item_stationary(mitm[net]);
804             return;
805         }
806         
807         if (escape >= 3)
808             mpr("You try to slip out of the net.");
809         else
810             mpr("You struggle to escape the net.");
811
812         you.attribute[ATTR_HELD] -= escape;
813    }
814 }
815
816 void clear_trapping_net()
817 {
818     if (!you.attribute[ATTR_HELD])
819         return;
820
821     const int net = get_trapping_net(you.x_pos, you.y_pos);
822     if (net != NON_ITEM)
823         remove_item_stationary(mitm[net]);
824
825     you.attribute[ATTR_HELD] = 0;
826 }
827
828 bool trap_item(object_class_type base_type, char sub_type,
829                char beam_x, char beam_y)
830 {
831     item_def  item;
832     item.base_type = base_type;
833     item.sub_type = sub_type;
834     item.plus = 0;
835     item.plus2 = 0;
836     item.flags = 0;
837     item.special = 0;
838     item.quantity = 1;
839
840     if (base_type == OBJ_MISSILES)
841     {
842         if (sub_type == MI_NEEDLE)
843             set_item_ego_type( item, OBJ_MISSILES, SPMSL_POISONED );
844         else
845             set_item_ego_type( item, OBJ_MISSILES, SPMSL_NORMAL );
846     }
847     else
848     {
849         set_item_ego_type( item, OBJ_WEAPONS, SPWPN_NORMAL );
850     }
851
852     item_colour(item);
853
854     if (igrd[beam_x][beam_y] != NON_ITEM)
855     {
856         if (items_stack( item, mitm[ igrd[beam_x][beam_y] ] ))
857         {
858             inc_mitm_item_quantity( igrd[beam_x][beam_y], 1 );
859             return (false);
860         }
861
862         // don't want to go overboard here. Will only generate up to three
863         // separate trap items, or less if there are other items present.
864         if (mitm[ igrd[beam_x][beam_y] ].link != NON_ITEM
865             && (item.base_type != OBJ_MISSILES
866                 || item.sub_type != MI_THROWING_NET))
867         {
868             if (mitm[ mitm[ igrd[beam_x][beam_y] ].link ].link != NON_ITEM)
869                 return (false);
870         }
871     }                           // end of if igrd != NON_ITEM
872
873     // give appropriate racial flag for Orcish Mines and Elven Halls
874     // should we ever allow properties of dungeon features, we could use that
875     if (you.where_are_you == BRANCH_ORCISH_MINES)
876         set_equip_race( item, ISFLAG_ORCISH );
877     else if (you.where_are_you == BRANCH_ELVEN_HALLS)
878         set_equip_race( item, ISFLAG_ELVEN );
879
880     return (!copy_item_to_grid( item, beam_x, beam_y, 1 ));
881 }                               // end trap_item()
882
883 // returns appropriate trap symbol for a given trap type {dlb}
884 dungeon_feature_type trap_category(trap_type type)
885 {
886     switch (type)
887     {
888     case TRAP_SHAFT:
889         return (DNGN_TRAP_NATURAL);
890
891     case TRAP_TELEPORT:
892     case TRAP_ALARM:
893     case TRAP_ZOT:
894         return (DNGN_TRAP_MAGICAL);
895
896     case TRAP_DART:
897     case TRAP_ARROW:
898     case TRAP_SPEAR:
899     case TRAP_AXE:
900     case TRAP_BLADE:
901     case TRAP_BOLT:
902     case TRAP_NEEDLE:
903     case TRAP_NET:
904     default:                    // what *would* be the default? {dlb}
905         return (DNGN_TRAP_MECHANICAL);
906     }
907 }                               // end trap_category()
908
909 // returns index of the trap for a given (x,y) coordinate pair {dlb}
910 int trap_at_xy(int which_x, int which_y)
911 {
912
913     for (int which_trap = 0; which_trap < MAX_TRAPS; which_trap++)
914     {
915         if (env.trap[which_trap].x == which_x &&
916             env.trap[which_trap].y == which_y &&
917             env.trap[which_trap].type != TRAP_UNASSIGNED)
918         {
919             return (which_trap);
920         }
921     }
922
923     // no idea how well this will be handled elsewhere: {dlb}
924     return (-1);
925 }                               // end trap_at_xy()
926
927 trap_type trap_type_at_xy(int x, int y)
928 {
929     const int idx = trap_at_xy(x, y);
930     return (idx == -1? NUM_TRAPS : env.trap[idx].type);
931 }
932
933 bool is_valid_shaft_level(const level_id &place)
934 {
935     if (place.level_type != LEVEL_DUNGEON)
936         return (false);
937
938     // disallow shafts on the first two levels
939     if (place.branch == BRANCH_MAIN_DUNGEON
940         && you.your_level < 2)
941     {
942         return (false);
943     }
944
945     // Don't generate shafts in branches where teleport control
946     // is prevented.  Prevents player from going down levels without
947     // reaching stairs, and also keeps player from getting stuck
948     // on lower levels with the innability to use teleport control to
949     // get back up.
950     if (testbits(get_branch_flags(place.branch), LFLAG_NO_TELE_CONTROL))
951     {
952         return (false);
953     }
954
955     const Branch &branch = branches[place.branch];
956
957     // When generating levels, don't place a shaft on the level
958     // immediately above the bottom of a branch if that branch is
959     // significantly more dangerous than normal.
960     int min_delta = 1;
961     if (env.turns_on_level == -1 && branch.dangerous_bottom_level)
962         min_delta = 2;
963
964     return ((branch.depth - place.depth) >= min_delta);
965 }
966
967 level_id generic_shaft_dest(level_pos lpos)
968 {
969     level_id  lid = lpos.id;
970     coord_def pos = lpos.pos;
971
972     if (lid.level_type != LEVEL_DUNGEON)
973         return lid;
974
975     int      curr_depth = lid.depth;
976     Branch   &branch    = branches[lid.branch];
977
978     lid.depth += ((pos.x + pos.y) % 3) + 1;
979
980     if (lid.depth > branch.depth)
981         lid.depth = branch.depth;
982
983     if (lid.depth == curr_depth)
984         return lid;
985
986     // Only shafts on the level immediately above a dangerous branch
987     // bottom will take you to that dangerous bottom, and shafts can't
988     // be created during level generation time.
989     if (branch.dangerous_bottom_level
990         && lid.depth == branch.depth
991         && (branch.depth - curr_depth) > 1)
992     {
993         lid.depth--;
994     }
995
996     return lid;
997 }
998
999 level_id generic_shaft_dest(coord_def pos)
1000 {
1001     return generic_shaft_dest(level_pos(level_id::current(), pos));
1002 }
1003
1004 void handle_items_on_shaft(int x, int y, bool open_shaft)
1005 {
1006     if (!is_valid_shaft_level())
1007         return;
1008
1009     coord_def pos(x, y);
1010     level_id  dest = generic_shaft_dest(pos);
1011
1012     if (dest == level_id::current())
1013         return;
1014
1015     int o = igrd(pos);
1016
1017     if (o == NON_ITEM)
1018         return;
1019
1020     igrd(pos) = NON_ITEM;
1021
1022     if (is_terrain_seen(pos) && open_shaft)
1023     {
1024         mpr("A shaft opens up in the floor!");
1025         grd(pos) = DNGN_TRAP_NATURAL;
1026     }
1027
1028     while (o != NON_ITEM)
1029     {
1030         int next = mitm[o].link;
1031
1032         if (is_valid_item( mitm[o] ))
1033         {
1034             if (is_terrain_seen(pos))
1035             {
1036                 mprf("%s falls through the shaft.",
1037                      mitm[o].name(DESC_INVENTORY).c_str());
1038             }
1039             add_item_to_transit(dest, mitm[o]);
1040
1041             mitm[o].base_type = OBJ_UNASSIGNED;
1042             mitm[o].quantity = 0;
1043             mitm[o].props.clear();
1044         }
1045
1046         o = next;
1047     }
1048 }
1049
1050 static int num_traps_default(int level_number, const level_id &place)
1051 {
1052     return random2avg(9, 2);
1053 }
1054
1055 int num_traps_for_place(int level_number, const level_id &place)
1056 {
1057     if (level_number == -1)
1058     {
1059         switch(place.level_type)
1060         {
1061         case LEVEL_DUNGEON:
1062             level_number = absdungeon_depth(place.branch, place.depth);
1063             break;
1064         case LEVEL_ABYSS:
1065             level_number = 51;
1066             break;
1067         case LEVEL_PANDEMONIUM:
1068             level_number = 52;
1069             break;
1070         default:
1071             level_number = you.your_level;
1072         }
1073     }
1074
1075     switch(place.level_type)
1076     {
1077     case LEVEL_DUNGEON:
1078         if (branches[place.branch].num_traps_function != NULL)
1079             return branches[place.branch].num_traps_function(level_number);
1080         else
1081             return num_traps_default(level_number, place);
1082     case LEVEL_ABYSS:
1083         return traps_abyss_number(level_number);
1084     case LEVEL_PANDEMONIUM:
1085         return traps_pan_number(level_number);
1086     case LEVEL_LABYRINTH:
1087     case LEVEL_PORTAL_VAULT:
1088         ASSERT(false);
1089         break;
1090     default:
1091         return 0;
1092     }
1093
1094     return 0;
1095 }
1096
1097 static trap_type random_trap_default(int level_number, const level_id &place)
1098 {
1099     trap_type type = TRAP_DART;
1100
1101     if ((random2(1 + level_number) > 1) && one_chance_in(4))
1102         type = TRAP_NEEDLE;
1103     if (random2(1 + level_number) > 3)
1104         type = TRAP_SPEAR;
1105     if (random2(1 + level_number) > 5)
1106         type = TRAP_AXE;
1107
1108     // Note we're boosting arrow trap numbers by moving it
1109     // down the list, and making spear and axe traps rarer.
1110     if (type == TRAP_DART?
1111         random2(1 + level_number) > 2
1112         : one_chance_in(7))
1113         type = TRAP_ARROW;
1114         
1115     if ((type == TRAP_DART || type == TRAP_ARROW) && one_chance_in(15))
1116         type = TRAP_NET;
1117
1118     if (random2(1 + level_number) > 7)
1119         type = TRAP_BOLT;
1120     if (random2(1 + level_number) > 11)
1121         type = TRAP_BLADE;
1122
1123     if ((random2(1 + level_number) > 14 && one_chance_in(3))
1124         || (place.branch == BRANCH_HALL_OF_ZOT &&
1125             place.level_type == LEVEL_DUNGEON && coinflip()))
1126     {
1127         type = TRAP_ZOT;
1128     }
1129
1130     if (one_chance_in(50) && is_valid_shaft_level(place))
1131         type = TRAP_SHAFT;
1132     if (one_chance_in(20))
1133         type = TRAP_TELEPORT;
1134     if (one_chance_in(40))
1135         type = TRAP_ALARM;
1136
1137     return (type);
1138 }
1139
1140 trap_type random_trap_for_place(int level_number, const level_id &place)
1141 {
1142     if (level_number == -1)
1143     {
1144         switch(place.level_type)
1145         {
1146         case LEVEL_DUNGEON:
1147             level_number = absdungeon_depth(place.branch, place.depth);
1148             break;
1149         case LEVEL_ABYSS:
1150             level_number = 51;
1151             break;
1152         case LEVEL_PANDEMONIUM:
1153             level_number = 52;
1154             break;
1155         default:
1156             level_number = you.your_level;
1157         }
1158     }
1159
1160     switch(place.level_type)
1161     {
1162     case LEVEL_DUNGEON:
1163         if (branches[place.branch].rand_trap_function != NULL)
1164             return branches[place.branch].rand_trap_function(level_number);
1165         else
1166             return random_trap_default(level_number, place);
1167     case LEVEL_ABYSS:
1168         return traps_abyss_type(level_number);
1169     case LEVEL_PANDEMONIUM:
1170         return traps_pan_type(level_number);
1171     case LEVEL_LABYRINTH:
1172     case LEVEL_PORTAL_VAULT:
1173         ASSERT(false);
1174         break;
1175     default:
1176         return random_trap_default(level_number, place);
1177     }
1178     return NUM_TRAPS;
1179 }
1180
1181 int traps_zero_number(int level_number)
1182 {
1183     return 0;
1184 }
1185
1186 int traps_pan_number(int level_number)
1187 {
1188     return num_traps_default(level_number, level_id(LEVEL_PANDEMONIUM));
1189 }
1190
1191 trap_type traps_pan_type(int level_number)
1192 {
1193     return random_trap_default(level_number, level_id(LEVEL_PANDEMONIUM));
1194 }
1195
1196 int traps_abyss_number(int level_number)
1197 {
1198     return num_traps_default(level_number, level_id(LEVEL_ABYSS));
1199 }
1200
1201 trap_type traps_abyss_type(int level_number)
1202 {
1203     return random_trap_default(level_number, level_id(LEVEL_ABYSS));
1204 }
1205
1206 int traps_lab_number(int level_number)
1207 {
1208     return num_traps_default(level_number, level_id(LEVEL_LABYRINTH));
1209 }
1210
1211 trap_type traps_lab_type(int level_number)
1212 {
1213     return random_trap_default(level_number, level_id(LEVEL_LABYRINTH));
1214 }