1 /**
2  * @file
3  * @brief Traps related functions.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "traps.h"
9 #include "trap-def.h"
10 
11 #include <algorithm>
12 #include <cmath>
13 
14 #include "areas.h"
15 #include "bloodspatter.h"
16 #include "branch.h"
17 #include "cloud.h"
18 #include "coordit.h"
19 #include "delay.h"
20 #include "describe.h"
21 #include "dungeon.h"
22 #include "english.h"
23 #include "god-passive.h" // passive_t::avoid_traps
24 #include "hints.h"
25 #include "item-prop.h"
26 #include "items.h"
27 #include "libutil.h"
28 #include "mapmark.h"
29 #include "mon-cast.h" // recall for zot traps
30 #include "mon-enum.h"
31 #include "mon-tentacle.h"
32 #include "mon-util.h"
33 #include "message.h"
34 #include "mon-place.h"
35 #include "nearby-danger.h"
36 #include "orb.h"
37 #include "player-stats.h" // lose_stat for zot traps
38 #include "random.h"
39 #include "religion.h"
40 #include "shout.h"
41 #include "spl-damage.h" // cancel_polar_vortex
42 #include "spl-transloc.h"
43 #include "spl-summoning.h"
44 #include "stash.h"
45 #include "state.h"
46 #include "stringutil.h"
47 #include "tag-version.h"
48 #include "teleport.h"
49 #include "terrain.h"
50 #include "travel.h"
51 #include "xom.h"
52 
53 static const string TRAP_PROJECTILE_KEY = "trap_projectile";
54 
active() const55 bool trap_def::active() const
56 {
57     return type != TRAP_UNASSIGNED;
58 }
59 
type_has_ammo() const60 bool trap_def::type_has_ammo() const
61 {
62     switch (type)
63     {
64 #if TAG_MAJOR_VERSION == 34
65     case TRAP_NEEDLE:
66 #endif
67     case TRAP_ARROW:  case TRAP_BOLT:
68     case TRAP_DART: case TRAP_SPEAR:
69         return true;
70     default:
71         break;
72     }
73     return false;
74 }
75 
destroy(bool known)76 void trap_def::destroy(bool known)
77 {
78     if (!in_bounds(pos))
79         die("Trap position out of bounds!");
80 
81     env.grid(pos) = DNGN_FLOOR;
82     if (known)
83     {
84         env.map_knowledge(pos).set_feature(DNGN_FLOOR);
85         StashTrack.update_stash(pos);
86     }
87     env.trap.erase(pos);
88 }
89 
prepare_ammo(int charges)90 void trap_def::prepare_ammo(int charges)
91 {
92     if (charges)
93     {
94         ammo_qty = charges;
95         return;
96     }
97     switch (type)
98     {
99     case TRAP_ARROW:
100     case TRAP_BOLT:
101     case TRAP_DART:
102         ammo_qty = 3 + random2avg(9, 3);
103         break;
104     case TRAP_SPEAR:
105         ammo_qty = 2 + random2avg(6, 3);
106         break;
107     case TRAP_GOLUBRIA:
108         // really, time until it vanishes
109         ammo_qty = (orb_limits_translocation() ? 10 + random2(10)
110                                                : 30 + random2(20));
111         break;
112     case TRAP_TELEPORT:
113         ammo_qty = 1;
114         break;
115     default:
116         ammo_qty = 0;
117         break;
118     }
119 }
120 
reveal()121 void trap_def::reveal()
122 {
123     env.grid(pos) = feature();
124 }
125 
name(description_level_type desc) const126 string trap_def::name(description_level_type desc) const
127 {
128     if (type >= NUM_TRAPS)
129         return "buggy";
130 
131     string basename = full_trap_name(type);
132     if (desc == DESC_A)
133     {
134         string prefix = "a";
135         if (is_vowel(basename[0]))
136             prefix += 'n';
137         prefix += ' ';
138         return prefix + basename;
139     }
140     else if (desc == DESC_THE)
141         return string("the ") + basename;
142     else                        // everything else
143         return basename;
144 }
145 
is_bad_for_player() const146 bool trap_def::is_bad_for_player() const
147 {
148     return type == TRAP_ALARM
149            || type == TRAP_DISPERSAL
150            || type == TRAP_ZOT
151            || type == TRAP_NET;
152 }
153 
is_safe(actor * act) const154 bool trap_def::is_safe(actor* act) const
155 {
156     if (!act)
157         act = &you;
158 
159     // TODO: For now, just assume they're safe; they don't damage outright,
160     // and the messages get old very quickly
161     if (type == TRAP_WEB) // && act->is_web_immune()
162         return true;
163 
164 #if TAG_MAJOR_VERSION == 34
165     if (type == TRAP_SHADOW_DORMANT || type == TRAP_SHADOW)
166         return true;
167 #endif
168 
169     if (!act->is_player())
170         return is_bad_for_player();
171 
172     // No prompt (teleport traps are ineffective if wearing a -Tele item)
173     if ((type == TRAP_TELEPORT || type == TRAP_TELEPORT_PERMANENT)
174         && you.no_tele(false))
175     {
176         return true;
177     }
178 
179     if (type == TRAP_GOLUBRIA || type == TRAP_SHAFT)
180         return true;
181 
182     // Let players specify traps as safe via lua.
183     if (clua.callbooleanfn(false, "c_trap_is_safe", "s", trap_name(type).c_str()))
184         return true;
185 
186     if (type == TRAP_DART)
187         return you.hp > 15;
188     else if (type == TRAP_ARROW)
189         return you.hp > 35;
190     else if (type == TRAP_BOLT)
191         return you.hp > 45;
192     else if (type == TRAP_SPEAR)
193         return you.hp > 40;
194     else if (type == TRAP_BLADE)
195         return you.hp > 95;
196 
197     return false;
198 }
199 
200 /**
201  * Get the item index of the first net on the square.
202  *
203  * @param where The location.
204  * @param trapped If true, the index of the stationary net (trapping a victim)
205  *                is returned.
206  * @return  The item index of the net.
207 */
get_trapping_net(const coord_def & where,bool trapped)208 int get_trapping_net(const coord_def& where, bool trapped)
209 {
210     for (stack_iterator si(where); si; ++si)
211     {
212         if (si->is_type(OBJ_MISSILES, MI_THROWING_NET)
213             && (!trapped || item_is_stationary_net(*si)))
214         {
215             return si->index();
216         }
217     }
218     return NON_ITEM;
219 }
220 
221 /**
222  * Return a string describing the reason a given actor is ensnared. (Since nets
223  * & webs use the same status.
224  *
225  * @param actor     The ensnared actor.
226  * @return          Either 'held in a net' or 'caught in a web'.
227  */
held_status(actor * act)228 const char* held_status(actor *act)
229 {
230     act = act ? act : &you;
231 
232     if (get_trapping_net(act->pos(), true) != NON_ITEM)
233         return "held in a net";
234     else
235         return "caught in a web";
236 }
237 
238 // If there are more than one net on this square
239 // split off one of them for checking/setting values.
_maybe_split_nets(item_def & item,const coord_def & where)240 static void _maybe_split_nets(item_def &item, const coord_def& where)
241 {
242     if (item.quantity == 1)
243     {
244         set_net_stationary(item);
245         return;
246     }
247 
248     item_def it;
249 
250     it.base_type = item.base_type;
251     it.sub_type  = item.sub_type;
252     it.net_durability      = item.net_durability;
253     it.net_placed  = item.net_placed;
254     it.flags     = item.flags;
255     it.special   = item.special;
256     it.quantity  = --item.quantity;
257     item_colour(it);
258 
259     item.quantity = 1;
260     set_net_stationary(item);
261 
262     copy_item_to_grid(it, where);
263 }
264 
_mark_net_trapping(const coord_def & where)265 static void _mark_net_trapping(const coord_def& where)
266 {
267     int net = get_trapping_net(where);
268     if (net == NON_ITEM)
269     {
270         net = get_trapping_net(where, false);
271         if (net != NON_ITEM)
272             _maybe_split_nets(env.item[net], where);
273     }
274 }
275 
276 /**
277  * Attempt to trap a monster in a net.
278  *
279  * @param mon       The monster being trapped.
280  * @return          Whether the monster was successfully trapped.
281  */
monster_caught_in_net(monster * mon)282 bool monster_caught_in_net(monster* mon)
283 {
284     if (mon->body_size(PSIZE_BODY) >= SIZE_GIANT)
285     {
286         if (you.see_cell(mon->pos()))
287         {
288             if (!mon->visible_to(&you))
289                 mpr("The net bounces off something gigantic!");
290             else
291                 simple_monster_message(*mon, " is too large for the net to hold!");
292         }
293         return false;
294     }
295 
296     if (mons_class_is_stationary(mon->type))
297     {
298         if (you.see_cell(mon->pos()))
299         {
300             if (mon->visible_to(&you))
301             {
302                 mprf("The net is caught on %s!",
303                      mon->name(DESC_THE).c_str());
304             }
305             else
306                 mpr("The net is caught on something unseen!");
307         }
308         return false;
309     }
310 
311     if (mon->is_insubstantial())
312     {
313         if (you.can_see(*mon))
314         {
315             mprf("The net passes right through %s!",
316                  mon->name(DESC_THE).c_str());
317         }
318         return false;
319     }
320 
321     if (!mon->caught() && mon->add_ench(ENCH_HELD))
322     {
323         if (you.see_cell(mon->pos()))
324         {
325             if (!mon->visible_to(&you))
326                 mpr("Something gets caught in the net!");
327             else
328                 simple_monster_message(*mon, " is caught in the net!");
329         }
330         return true;
331     }
332 
333     return false;
334 }
335 
player_caught_in_net()336 bool player_caught_in_net()
337 {
338     if (you.body_size(PSIZE_BODY) >= SIZE_GIANT)
339         return false;
340 
341     if (!you.attribute[ATTR_HELD])
342     {
343         mpr("You become entangled in the net!");
344         stop_running();
345 
346         // Set the attribute after the mpr, otherwise the screen updates
347         // and we get a glimpse of a web because there isn't a trapping net
348         // item yet
349         you.attribute[ATTR_HELD] = 1;
350 
351         stop_delay(true); // even stair delays
352         return true;
353     }
354     return false;
355 }
356 
check_net_will_hold_monster(monster * mons)357 void check_net_will_hold_monster(monster* mons)
358 {
359     ASSERT(mons); // XXX: should be monster &mons
360     if (mons->body_size(PSIZE_BODY) >= SIZE_GIANT)
361     {
362         int net = get_trapping_net(mons->pos());
363         if (net != NON_ITEM)
364             destroy_item(net);
365 
366         if (you.see_cell(mons->pos()))
367         {
368             if (mons->visible_to(&you))
369             {
370                 mprf("The net rips apart, and %s comes free!",
371                      mons->name(DESC_THE).c_str());
372             }
373             else
374                 mpr("All of a sudden the net rips apart!");
375         }
376     }
377     else if (mons->is_insubstantial())
378     {
379         const int net = get_trapping_net(mons->pos());
380         if (net != NON_ITEM)
381             free_stationary_net(net);
382 
383         simple_monster_message(*mons,
384                                " drifts right through the net!");
385     }
386     else
387         mons->add_ench(ENCH_HELD);
388 }
389 
_player_caught_in_web()390 static bool _player_caught_in_web()
391 {
392     if (you.attribute[ATTR_HELD])
393         return false;
394 
395     you.attribute[ATTR_HELD] = 1;
396 
397     you.redraw_armour_class = true;
398     you.redraw_evasion      = true;
399     quiver::set_needs_redraw();
400 
401     // No longer stop_running() and stop_delay().
402     return true;
403 }
404 
find_golubria_on_level()405 vector<coord_def> find_golubria_on_level()
406 {
407     vector<coord_def> ret;
408     for (rectangle_iterator ri(coord_def(0, 0), coord_def(GXM-1, GYM-1)); ri; ++ri)
409     {
410         trap_def *trap = trap_at(*ri);
411         if (trap && trap->type == TRAP_GOLUBRIA)
412             ret.push_back(*ri);
413     }
414     return ret;
415 }
416 
417 enum class passage_type
418 {
419     free,
420     blocked,
421     none,
422 };
423 
_find_other_passage_side(coord_def & to)424 static passage_type _find_other_passage_side(coord_def& to)
425 {
426     vector<coord_def> clear_passages;
427     bool has_blocks = false;
428     for (coord_def passage : find_golubria_on_level())
429     {
430         if (passage != to)
431         {
432             if (!actor_at(passage))
433                 clear_passages.push_back(passage);
434             else
435                 has_blocks = true;
436         }
437     }
438     const int choices = clear_passages.size();
439     if (choices < 1)
440         return has_blocks ? passage_type::blocked : passage_type::none;
441     to = clear_passages[random2(choices)];
442     return passage_type::free;
443 }
444 
445 // Table of possible Zot trap effects as pairs with weights.
446 // 2/3 are "evil magic", 1/3 are "summons"
447 static const vector<pair<function<void ()>, int>> zot_effects = {
__anon71a4a9f00102null448     { [] { lose_stat(STAT_RANDOM, 1 + random2avg(5, 2)); }, 4 },
__anon71a4a9f00202null449     { [] { contaminate_player(7000 + random2avg(13000, 2), false); }, 4 },
__anon71a4a9f00302null450     { [] { you.paralyse(nullptr, 2 + random2(4), "a Zot trap"); }, 1 },
__anon71a4a9f00402null451     { [] { drain_mp(you.magic_points); canned_msg(MSG_MAGIC_DRAIN); }, 2 },
__anon71a4a9f00502null452     { [] { you.petrify(nullptr); }, 1 },
__anon71a4a9f00602null453     { [] { you.increase_duration(DUR_LOWERED_WL, 5 + random2(15), 20,
454                 "Your willpower is stripped away!"); }, 4 },
__anon71a4a9f00702null455     { [] { mons_word_of_recall(nullptr, 2 + random2(3)); }, 3 },
__anon71a4a9f00802null456     { [] {
457               mgen_data mg = mgen_data::hostile_at(RANDOM_DEMON_GREATER,
458                                                    true, you.pos());
459               mg.set_summoned(nullptr, 0, SPELL_NO_SPELL, GOD_NO_GOD);
460               mg.set_non_actor_summoner("a Zot trap");
461               mg.extra_flags |= (MF_NO_REWARD | MF_HARD_RESET);
462               if (create_monster(mg))
463                   mpr("You sense a hostile presence.");
464          }, 3 },
__anon71a4a9f00902null465     { [] {
466              coord_def pt = find_gateway_location(&you);
467              if (pt != coord_def(0, 0))
468                  create_malign_gateway(pt, BEH_HOSTILE, "a Zot trap", 150);
469          }, 1 },
__anon71a4a9f00a02null470     { [] {
471               mgen_data mg = mgen_data::hostile_at(MONS_TWISTER,
472                                                    false, you.pos());
473               mg.set_summoned(nullptr, 2, SPELL_NO_SPELL, GOD_NO_GOD);
474               mg.set_non_actor_summoner("a Zot trap");
475               mg.extra_flags |= (MF_NO_REWARD | MF_HARD_RESET);
476               if (create_monster(mg))
477                   mpr("A huge vortex of air appears!");
478          }, 1 },
479 };
480 
481 // Zot traps only target the player. This rolls their effect.
_zot_trap()482 static void _zot_trap()
483 {
484     mpr("The power of Zot is invoked against you!");
485     (*random_choose_weighted(zot_effects))();
486 }
487 
trigger(actor & triggerer)488 void trap_def::trigger(actor& triggerer)
489 {
490     const bool you_trigger = triggerer.is_player();
491 
492     // Traps require line of sight without blocking translocation effects.
493     // Requiring LOS prevents monsters from dispersing out of vaults that have
494     // teleport traps available for the player to utilize. Additionally
495     // requiring LOS_NO_TRANS prevents vaults that feature monsters with trap
496     // behind glass from spamming the message log with irrelevant events.
497     if (!you.see_cell_no_trans(pos))
498         return;
499 
500     // If set, the trap will be removed at the end of the
501     // triggering process.
502     bool trap_destroyed = false, know_trap_destroyed = false;
503 
504     monster* m = triggerer.as_monster();
505 
506     // Intelligent monsters native to a branch get a bonus avoiding traps
507     const bool trig_smart = m
508         && mons_is_native_in_branch(*m)
509         && mons_intel(*m) >= I_HUMAN;
510 
511     // Smarter monsters and those native to the level will simply
512     // side-step shafts. Unless they are already looking for
513     // an exit, of course.
514     if (type == TRAP_SHAFT
515         && m
516         && (!m->will_trigger_shaft()
517             || trig_smart && !mons_is_fleeing(*m) && !m->pacified()))
518     {
519         return;
520     }
521 
522     // Tentacles aren't real monsters, and shouldn't invoke magic traps.
523     if (m && mons_is_tentacle_or_tentacle_segment(m->type)
524         && !is_mechanical())
525     {
526         return;
527     }
528 
529     // Store the position now in case it gets cleared in between.
530     const coord_def p(pos);
531 
532     if (type_has_ammo())
533         shoot_ammo(triggerer, trig_smart || you_trigger);
534     else switch (type)
535     {
536     case TRAP_GOLUBRIA:
537     {
538         coord_def to = p;
539         passage_type search_result = _find_other_passage_side(to);
540         if (search_result == passage_type::free)
541         {
542             if (you_trigger)
543             {
544                 mpr("You enter the passage of Golubria.");
545                 cancel_polar_vortex();
546             }
547             else
548                 simple_monster_message(*m, " enters the passage of Golubria.");
549 
550             // Should always be true.
551             bool moved = triggerer.move_to_pos(to);
552             ASSERT(moved);
553 
554             place_cloud(CLOUD_TLOC_ENERGY, p, 1 + random2(3), &triggerer);
555             trap_destroyed = true;
556             know_trap_destroyed = you_trigger;
557         }
558         else if (you_trigger)
559         {
560             mprf("This passage %s!", search_result == passage_type::blocked ?
561                  "seems to be blocked by something" : "doesn't lead anywhere");
562         }
563         break;
564     }
565     case TRAP_DISPERSAL:
566         dprf("Triggered dispersal.");
567         if (you_trigger)
568             mprf("You enter %s!", name(DESC_A).c_str());
569         else
570             mprf("%s enters %s!", triggerer.name(DESC_THE).c_str(),
571                     name(DESC_A).c_str());
572         apply_visible_monsters([] (monster& mons) {
573                 return !mons.no_tele() && monster_blink(&mons);
574             }, pos);
575         if (!you_trigger && you.see_cell_no_trans(pos))
576         {
577             you.blink();
578             interrupt_activity(activity_interrupt::teleport);
579         }
580         // Don't chain disperse
581         triggerer.blink();
582         break;
583     case TRAP_TELEPORT:
584     case TRAP_TELEPORT_PERMANENT:
585         if (you_trigger)
586             mprf("You enter %s!", name(DESC_A).c_str());
587         if (ammo_qty > 0 && !--ammo_qty)
588         {
589             // can't use trap_destroyed, as we might recurse into a shaft
590             // or be banished by a Zot trap
591             env.map_knowledge(pos).set_feature(DNGN_FLOOR);
592             mprf("%s disappears.", name(DESC_THE).c_str());
593             destroy();
594         }
595         if (!triggerer.no_tele(true, you_trigger))
596             triggerer.teleport(true);
597         break;
598 
599     case TRAP_ALARM:
600         // Alarms always mark the player, but not through glass
601         // The trap gets destroyed to prevent the player from abusing an alarm
602         // trap found in favourable terrain.
603         if (!you.see_cell_no_trans(pos))
604             break;
605         trap_destroyed = true;
606         if (you_trigger)
607             mprf("You set off the alarm!");
608         else
609             mprf("%s %s the alarm!", triggerer.name(DESC_THE).c_str(),
610                  mons_intel(*m) >= I_HUMAN ? "pulls" : "sets off");
611 
612         if (silenced(pos))
613         {
614             mprf("%s vibrates slightly, failing to make a sound.",
615                  name(DESC_THE).c_str());
616         }
617         else
618         {
619             string msg = make_stringf("%s emits a blaring wail!",
620                                name(DESC_THE).c_str());
621             noisy(40, pos, msg.c_str(), triggerer.mid);
622         }
623 
624         you.sentinel_mark(true);
625         break;
626 
627     case TRAP_BLADE:
628         if (you_trigger)
629         {
630             const int narrow_miss_rnd = random2(6) + 3;
631             if (one_chance_in(3))
632                 mpr("You avoid triggering a blade trap.");
633             else if (random2limit(you.evasion(), 40) + narrow_miss_rnd > 8)
634                 mpr("A huge blade swings just past you!");
635             else
636             {
637                 mpr("A huge blade swings out and slices into you!");
638                 const int damage = you.apply_ac(48 + random2avg(29, 2));
639                 string n = name(DESC_A);
640                 ouch(damage, KILLED_BY_TRAP, MID_NOBODY, n.c_str());
641                 bleed_onto_floor(you.pos(), MONS_PLAYER, damage, true);
642             }
643         }
644         else if (m)
645         {
646             if (one_chance_in(5) || (trig_smart && coinflip()))
647             {
648                 // Trap doesn't trigger.
649                 simple_monster_message(*m, " fails to trigger a blade trap.");
650             }
651             else if (random2(m->evasion()) > 8
652                      || (trig_smart && random2(m->evasion()) > 8))
653             {
654                 if (!simple_monster_message(*m,
655                                             " avoids a huge, swinging blade."))
656                 {
657                     mpr("A huge blade swings out!");
658                 }
659             }
660             else
661             {
662                 string msg = "A huge blade swings out";
663                 if (m->visible_to(&you))
664                 {
665                     msg += " and slices into ";
666                     msg += m->name(DESC_THE);
667                 }
668                 msg += "!";
669                 mpr(msg);
670 
671                 int damage_taken = m->apply_ac(10 + random2avg(29, 2));
672 
673                 if (!m->is_summoned())
674                     bleed_onto_floor(m->pos(), m->type, damage_taken, true);
675 
676                 m->hurt(nullptr, damage_taken);
677                 if (m->alive())
678                     print_wounds(*m);
679             }
680         }
681         break;
682 
683     case TRAP_NET:
684         {
685         // Nets need LOF to hit the player, no netting through glass.
686         if (!you.see_cell_no_trans(pos))
687             break;
688         // Don't try to re-net the player when they're already netted/webbed.
689         if (you.attribute[ATTR_HELD])
690             break;
691         // Reduce brutality of traps.
692         if (!you_trigger && !one_chance_in(3))
693             break;
694 
695         bool triggered = you_trigger;
696         if (m)
697         {
698             if (mons_intel(*m) < I_HUMAN)
699             {
700                 // Not triggered, trap stays.
701                 simple_monster_message(*m, " fails to trigger a net trap.");
702             }
703             else
704             {
705                 // Triggered, net the player.
706                 triggered = true;
707 
708                 if (!simple_monster_message(*m,
709                                             " drops a net on you."))
710                 {
711                     mpr("Something launches a net on you.");
712                 }
713             }
714         }
715 
716         if (!triggered)
717             break;
718 
719         if (random2avg(2 * you.evasion(), 2) > 18 + env.absdepth0 / 2)
720         {
721             mpr("You avoid being caught in a net.");
722             break;
723         }
724 
725         if (!player_caught_in_net())
726         {
727             mpr("The net is torn apart by your bulk.");
728             break;
729         }
730 
731         item_def item = generate_trap_item();
732         copy_item_to_grid(item, you.pos());
733         if (player_in_a_dangerous_place())
734             xom_is_stimulated(50);
735 
736         // Mark the item as trapping; after this it's
737         // safe to update the view.
738         _mark_net_trapping(you.pos());
739         break;
740         }
741 
742     case TRAP_WEB:
743         if (triggerer.is_web_immune())
744         {
745             if (m)
746             {
747                 if (m->is_insubstantial())
748                     simple_monster_message(*m, " passes through a web.");
749                 else if (mons_genus(m->type) == MONS_JELLY)
750                     simple_monster_message(*m, " oozes through a web.");
751                 // too spammy for spiders, and expected
752             }
753             break;
754         }
755 
756         if (you_trigger)
757         {
758             if (one_chance_in(3))
759                 mpr("You pick your way through the web.");
760             else
761             {
762                 mpr("You are caught in the web!");
763 
764                 if (_player_caught_in_web() && player_in_a_dangerous_place())
765                     xom_is_stimulated(50);
766             }
767         }
768         else if (m)
769         {
770             if (one_chance_in(3) || (trig_smart && coinflip()))
771                 simple_monster_message(*m, " evades a web.");
772             else
773             {
774                 if (m->visible_to(&you))
775                     simple_monster_message(*m, " is caught in a web!");
776                 else
777                     mpr("A web moves frantically as something is caught in it!");
778 
779                 // If somehow already caught, make it worse.
780                 m->add_ench(ENCH_HELD);
781 
782                 // Don't try to escape the web in the same turn
783                 m->props[NEWLY_TRAPPED_KEY] = true;
784             }
785         }
786         break;
787 
788     case TRAP_ZOT:
789         if (you_trigger)
790         {
791             mpr("You enter the Zot trap.");
792             _zot_trap();
793         }
794         else if (m)
795         {
796             // Zot traps are out to get *the player*! Hostile monsters
797             // benefit and friendly monsters bring effects down on
798             // the player. Such is life.
799 
800             // Give the player a chance to figure out what happened
801             if (player_can_hear(pos))
802                 mprf(MSGCH_SOUND, "You hear a loud \"Zot\"!");
803 
804             if (you.see_cell_no_trans(pos) && one_chance_in(5))
805                 _zot_trap();
806         }
807         break;
808 
809     case TRAP_SHAFT:
810         // Known shafts don't trigger as traps.
811         // Allies don't fall through shafts (no herding!)
812         if (trig_smart || (m && m->wont_attack()) || you_trigger)
813             break;
814 
815         // A chance to escape.
816         if (one_chance_in(4))
817             break;
818 
819         {
820         // keep this for messaging purposes
821         const bool triggerer_seen = you.can_see(triggerer);
822 
823         // Fire away!
824         triggerer.do_shaft();
825 
826         // Player-used shafts are destroyed
827         // after one use in down_stairs()
828         if (!you_trigger)
829         {
830             mprf("%s shaft crumbles and collapses.",
831                  triggerer_seen ? "The" : "A");
832             know_trap_destroyed = true;
833             trap_destroyed = true;
834         }
835         }
836         break;
837 
838 #if TAG_MAJOR_VERSION == 34
839     case TRAP_GAS:
840         mpr("The gas trap seems to be inoperative.");
841         trap_destroyed = true;
842         break;
843 #endif
844 
845     case TRAP_PLATE:
846         dungeon_events.fire_position_event(DET_PRESSURE_PLATE, pos);
847         break;
848 
849 #if TAG_MAJOR_VERSION == 34
850     case TRAP_SHADOW:
851     case TRAP_SHADOW_DORMANT:
852 #endif
853     default:
854         break;
855     }
856 
857     if (you_trigger)
858         learned_something_new(HINT_SEEN_TRAP, p);
859 
860     if (trap_destroyed)
861         destroy(know_trap_destroyed);
862 }
863 
max_damage(const actor & act)864 int trap_def::max_damage(const actor& act)
865 {
866     // Trap damage to monsters is a lot smaller, because they are fairly
867     // stupid and tend to have fewer hp than players -- this choice prevents
868     // traps from easily killing large monsters.
869     bool mon = act.is_monster();
870 
871     switch (type)
872     {
873         case TRAP_DART: return 0;
874         case TRAP_ARROW:  return mon ?  7 : 15;
875         case TRAP_SPEAR:  return mon ? 10 : 26;
876         case TRAP_BOLT:   return mon ? 18 : 40;
877         case TRAP_BLADE:  return mon ? 38 : 76;
878         default:          return 0;
879     }
880 
881     return 0;
882 }
883 
shot_damage(actor & act)884 int trap_def::shot_damage(actor& act)
885 {
886     const int dam = max_damage(act);
887 
888     if (!dam)
889         return 0;
890     return random2(dam) + 1;
891 }
892 
to_hit_bonus()893 int trap_def::to_hit_bonus()
894 {
895     switch (type)
896     {
897     // To-hit:
898     case TRAP_ARROW:
899         return 7;
900     case TRAP_SPEAR:
901         return 10;
902     case TRAP_BOLT:
903         return 15;
904     case TRAP_NET:
905         return 5;
906     case TRAP_DART:
907         return 8;
908     // Irrelevant:
909     default:
910         return 0;
911     }
912 }
913 
destroy_trap(const coord_def & pos)914 void destroy_trap(const coord_def& pos)
915 {
916     if (trap_def* ptrap = trap_at(pos))
917         ptrap->destroy();
918 }
919 
trap_at(const coord_def & pos)920 trap_def* trap_at(const coord_def& pos)
921 {
922     if (!feat_is_trap(env.grid(pos)))
923         return nullptr;
924 
925     auto it = env.trap.find(pos);
926     ASSERT(it != env.trap.end());
927     ASSERT(it->second.pos == pos);
928     ASSERT(it->second.type != TRAP_UNASSIGNED);
929 
930     return &it->second;
931 }
932 
get_trap_type(const coord_def & pos)933 trap_type get_trap_type(const coord_def& pos)
934 {
935     if (trap_def* ptrap = trap_at(pos))
936         return ptrap->type;
937 
938     return TRAP_UNASSIGNED;
939 }
940 
941 /**
942  * End the ATTR_HELD state & redraw appropriate UI.
943  *
944  * Do NOT call without clearing up nets, webs, etc first!
945  */
stop_being_held()946 void stop_being_held()
947 {
948     you.attribute[ATTR_HELD] = 0;
949     quiver::set_needs_redraw();
950     you.redraw_evasion = true;
951 }
952 
953 /**
954  * Exit a web that's currently holding you.
955  *
956  * @param quiet     Whether to squash messages.
957  */
leave_web(bool quiet)958 void leave_web(bool quiet)
959 {
960     const trap_def *trap = trap_at(you.pos());
961     if (!trap || trap->type != TRAP_WEB)
962         return;
963 
964     if (trap->ammo_qty == 1) // temp web from e.g. jumpspider/spidersack
965     {
966         if (!quiet)
967             mpr("The web tears apart.");
968         destroy_trap(you.pos());
969     }
970     else if (!quiet)
971         mpr("You disentangle yourself.");
972 
973     stop_being_held();
974 }
975 
976 /**
977  * Let the player attempt to unstick themself from a web.
978  */
_free_self_from_web()979 static void _free_self_from_web()
980 {
981     // Check if there's actually a web trap in your tile.
982     trap_def *trap = trap_at(you.pos());
983     if (trap && trap->type == TRAP_WEB)
984     {
985         // if so, roll a chance to escape the web.
986         if (x_chance_in_y(3, 10))
987         {
988             mpr("You struggle to detach yourself from the web.");
989             // but you actually accomplished nothing!
990             return;
991         }
992 
993         leave_web();
994     }
995 
996     // whether or not there was a web trap there, you're free now.
997     stop_being_held();
998 }
999 
free_self_from_net()1000 void free_self_from_net()
1001 {
1002     const int net = get_trapping_net(you.pos());
1003 
1004     if (net == NON_ITEM)
1005     {
1006         // If there's no net, it must be a web.
1007         _free_self_from_web();
1008         return;
1009     }
1010 
1011     int hold = env.item[net].net_durability;
1012     dprf("net.net_durability: %d", hold);
1013 
1014     const int damage = 1 + random2(4);
1015 
1016     hold -= damage;
1017     env.item[net].net_durability = hold;
1018 
1019     if (hold < NET_MIN_DURABILITY)
1020     {
1021         mprf("You %s the net and break free!", damage > 3 ? "shred" : "rip");
1022 
1023         destroy_item(net);
1024         stop_being_held();
1025         return;
1026     }
1027 
1028     if (damage > 3)
1029         mpr("You tear a large gash into the net.");
1030     else
1031         mpr("You struggle against the net.");
1032 }
1033 
1034 /**
1035  * Deals with messaging & cleanup for temporary web traps. Does not actually
1036  * delete ENCH_HELD!
1037  *
1038  * @param mons      The monster leaving a web.
1039  * @param quiet     Whether to suppress messages.
1040  */
monster_web_cleanup(const monster & mons,bool quiet)1041 void monster_web_cleanup(const monster &mons, bool quiet)
1042 {
1043     trap_def *trap = trap_at(mons.pos());
1044     if (trap && trap->type == TRAP_WEB)
1045     {
1046         if (trap->ammo_qty == 1)
1047         {
1048             // temp web from e.g. jumpspider/spidersack
1049             if (!quiet)
1050                 simple_monster_message(mons, " tears the web.");
1051             destroy_trap(mons.pos());
1052         }
1053         else if (!quiet)
1054             simple_monster_message(mons, " pulls away from the web.");
1055     }
1056 }
1057 
mons_clear_trapping_net(monster * mon)1058 void mons_clear_trapping_net(monster* mon)
1059 {
1060     if (!mon->caught())
1061         return;
1062 
1063     const int net = get_trapping_net(mon->pos());
1064     if (net != NON_ITEM)
1065         free_stationary_net(net);
1066 
1067     mon->del_ench(ENCH_HELD, true);
1068 }
1069 
free_stationary_net(int item_index)1070 void free_stationary_net(int item_index)
1071 {
1072     item_def &item = env.item[item_index];
1073     if (!item.is_type(OBJ_MISSILES, MI_THROWING_NET))
1074         return;
1075 
1076     const coord_def pos = item.pos;
1077     // Probabilistically mulch net based on damage done, otherwise
1078     // reset damage counter (ie: item.net_durability).
1079     const bool mulch = item.props.exists(TRAP_PROJECTILE_KEY)
1080                     || x_chance_in_y(-item.net_durability, 9);
1081     if (mulch)
1082         destroy_item(item_index);
1083     else
1084     {
1085         item.net_durability = 0;
1086         item.net_placed = false;
1087     }
1088 
1089     // Make sure we don't leave a bad trapping net in the stash
1090     // FIXME: may leak info if a monster escapes an out-of-sight net.
1091     StashTrack.update_stash(pos);
1092     StashTrack.unmark_trapping_nets(pos);
1093 }
1094 
clear_trapping_net()1095 void clear_trapping_net()
1096 {
1097     if (!you.attribute[ATTR_HELD])
1098         return;
1099 
1100     if (!in_bounds(you.pos()))
1101         return;
1102 
1103     const int net = get_trapping_net(you.pos());
1104     if (net == NON_ITEM)
1105         leave_web(true);
1106     else
1107         free_stationary_net(net);
1108 
1109     stop_being_held();
1110 }
1111 
generate_trap_item()1112 item_def trap_def::generate_trap_item()
1113 {
1114     item_def item;
1115     object_class_type base;
1116     int sub;
1117 
1118     switch (type)
1119     {
1120 #if TAG_MAJOR_VERSION == 34
1121     case TRAP_NEEDLE: base = OBJ_MISSILES; sub = MI_NEEDLE;       break;
1122 #endif
1123     case TRAP_ARROW:  base = OBJ_MISSILES; sub = MI_ARROW;        break;
1124     case TRAP_BOLT:   base = OBJ_MISSILES; sub = MI_BOLT;         break;
1125     case TRAP_SPEAR:  base = OBJ_WEAPONS;  sub = WPN_SPEAR;       break;
1126     case TRAP_DART:   base = OBJ_MISSILES; sub = MI_DART;         break;
1127     case TRAP_NET:    base = OBJ_MISSILES; sub = MI_THROWING_NET; break;
1128     default:          return item;
1129     }
1130 
1131     item.base_type = base;
1132     item.sub_type  = sub;
1133     item.quantity  = 1;
1134 
1135     if (base == OBJ_MISSILES)
1136     {
1137         set_item_ego_type(item, base,
1138                           (sub == MI_DART) ? SPMSL_POISONED : SPMSL_NORMAL);
1139     }
1140     else
1141         set_item_ego_type(item, base, SPWPN_NORMAL);
1142 
1143     // Make nets from net traps always mulch.
1144     item.props[TRAP_PROJECTILE_KEY] = true;
1145 
1146     item_colour(item);
1147     return item;
1148 }
1149 
1150 // Shoot a single piece of ammo at the relevant actor.
shoot_ammo(actor & act,bool trig_smart)1151 void trap_def::shoot_ammo(actor& act, bool trig_smart)
1152 {
1153     if (ammo_qty <= 0)
1154     {
1155         if (trig_smart && act.is_player())
1156             mpr("The trap is out of ammunition!");
1157         else if (player_can_hear(pos) && you.see_cell(pos))
1158             mpr("You hear a soft click.");
1159 
1160         destroy();
1161         return;
1162     }
1163 
1164     if (act.is_player())
1165     {
1166         if (one_chance_in(5) || trig_smart && !one_chance_in(4))
1167         {
1168             mprf("You avoid triggering %s.", name(DESC_A).c_str());
1169             return;
1170         }
1171     }
1172     else if (one_chance_in(5))
1173     {
1174         if (trig_smart && you.see_cell(pos) && you.can_see(act))
1175         {
1176             mprf("%s avoids triggering %s.", act.name(DESC_THE).c_str(),
1177                  name(DESC_A).c_str());
1178         }
1179         return;
1180     }
1181 
1182     item_def shot = generate_trap_item();
1183 
1184     int trap_hit = 20 + (to_hit_bonus()*2);
1185     trap_hit *= random2(200);
1186     trap_hit /= 100;
1187     if (act.missile_repulsion())
1188         trap_hit = random2(trap_hit);
1189 
1190     const int con_block = random2(20 + act.shield_block_penalty());
1191     const int pro_block = act.shield_bonus();
1192     dprf("%s: hit %d EV %d, shield hit %d block %d", name(DESC_PLAIN).c_str(),
1193          trap_hit, act.evasion(), con_block, pro_block);
1194 
1195     // Determine whether projectile hits.
1196     if (trap_hit < act.evasion())
1197     {
1198         if (act.is_player())
1199             mprf("%s shoots out and misses you.", shot.name(DESC_A).c_str());
1200         else if (you.see_cell(act.pos()))
1201         {
1202             mprf("%s misses %s!", shot.name(DESC_A).c_str(),
1203                  act.name(DESC_THE).c_str());
1204         }
1205     }
1206     else if (pro_block >= con_block
1207              && you.see_cell(act.pos()))
1208     {
1209         string owner;
1210         if (act.is_player())
1211             owner = "your";
1212         else if (you.can_see(act))
1213             owner = apostrophise(act.name(DESC_THE));
1214         else // "its" sounds abysmal; animals don't use shields
1215             owner = "someone's";
1216         mprf("%s shoots out and hits %s shield.", shot.name(DESC_A).c_str(),
1217              owner.c_str());
1218 
1219         act.shield_block_succeeded();
1220     }
1221     else // OK, we've been hit.
1222     {
1223         bool poison = type == TRAP_DART
1224                        && (x_chance_in_y(50 - (3*act.armour_class()) / 2, 100));
1225 
1226         int damage_taken = act.apply_ac(shot_damage(act));
1227 
1228         if (act.is_player())
1229         {
1230             mprf("%s shoots out and hits you!", shot.name(DESC_A).c_str());
1231 
1232             string n = name(DESC_A);
1233 
1234             // Needle traps can poison.
1235             if (poison)
1236                 poison_player(1 + roll_dice(2, 9), "", n);
1237 
1238             ouch(damage_taken, KILLED_BY_TRAP, MID_NOBODY, n.c_str());
1239         }
1240         else
1241         {
1242             if (you.see_cell(act.pos()))
1243             {
1244                 mprf("%s hits %s%s!",
1245                      shot.name(DESC_A).c_str(),
1246                      act.name(DESC_THE).c_str(),
1247                      (damage_taken == 0 && !poison) ?
1248                          ", but does no damage" : "");
1249             }
1250 
1251             if (poison)
1252                 act.poison(nullptr, 3 + roll_dice(2, 5));
1253             act.hurt(nullptr, damage_taken);
1254         }
1255     }
1256     ammo_qty--;
1257 }
1258 
is_mechanical() const1259 bool trap_def::is_mechanical() const
1260 {
1261     switch (type)
1262     {
1263     case TRAP_ARROW:
1264     case TRAP_SPEAR:
1265     case TRAP_BLADE:
1266     case TRAP_DART:
1267     case TRAP_BOLT:
1268     case TRAP_NET:
1269     case TRAP_PLATE:
1270 #if TAG_MAJOR_VERSION == 34
1271     case TRAP_NEEDLE:
1272     case TRAP_GAS:
1273 #endif
1274         return true;
1275     default:
1276         return false;
1277     }
1278 }
1279 
feature() const1280 dungeon_feature_type trap_def::feature() const
1281 {
1282     return trap_feature(type);
1283 }
1284 
trap_feature(trap_type type)1285 dungeon_feature_type trap_feature(trap_type type)
1286 {
1287     switch (type)
1288     {
1289     case TRAP_WEB:
1290         return DNGN_TRAP_WEB;
1291     case TRAP_SHAFT:
1292         return DNGN_TRAP_SHAFT;
1293     case TRAP_DISPERSAL:
1294         return DNGN_TRAP_DISPERSAL;
1295     case TRAP_TELEPORT:
1296         return DNGN_TRAP_TELEPORT;
1297     case TRAP_TELEPORT_PERMANENT:
1298         return DNGN_TRAP_TELEPORT_PERMANENT;
1299     case TRAP_ALARM:
1300         return DNGN_TRAP_ALARM;
1301     case TRAP_ZOT:
1302         return DNGN_TRAP_ZOT;
1303     case TRAP_GOLUBRIA:
1304         return DNGN_PASSAGE_OF_GOLUBRIA;
1305 #if TAG_MAJOR_VERSION == 34
1306     case TRAP_SHADOW:
1307         return DNGN_TRAP_SHADOW;
1308     case TRAP_SHADOW_DORMANT:
1309         return DNGN_TRAP_SHADOW_DORMANT;
1310 #endif
1311 
1312     case TRAP_ARROW:
1313         return DNGN_TRAP_ARROW;
1314     case TRAP_SPEAR:
1315         return DNGN_TRAP_SPEAR;
1316     case TRAP_BLADE:
1317         return DNGN_TRAP_BLADE;
1318     case TRAP_DART:
1319         return DNGN_TRAP_DART;
1320     case TRAP_BOLT:
1321         return DNGN_TRAP_BOLT;
1322     case TRAP_NET:
1323         return DNGN_TRAP_NET;
1324     case TRAP_PLATE:
1325         return DNGN_TRAP_PLATE;
1326 
1327 #if TAG_MAJOR_VERSION == 34
1328     case TRAP_NEEDLE:
1329     case TRAP_GAS:
1330         return DNGN_TRAP_MECHANICAL;
1331 #endif
1332 
1333     default:
1334         die("placeholder trap type %d used", type);
1335     }
1336 }
1337 
1338 /***
1339  * Can a shaft be placed on the current level?
1340  *
1341  * @returns true if such a shaft can be placed.
1342  */
is_valid_shaft_level()1343 bool is_valid_shaft_level()
1344 {
1345     // Important: We are sometimes called before the level has been loaded
1346     // or generated, so should not depend on properties of the level itself,
1347     // but only on its level_id.
1348     const level_id place = level_id::current();
1349     if (crawl_state.game_is_sprint())
1350         return false;
1351 
1352     if (!is_connected_branch(place))
1353         return false;
1354 
1355     const Branch &branch = branches[place.branch];
1356 
1357     if (branch.branch_flags & brflag::no_shafts)
1358         return false;
1359 
1360     // Don't allow shafts from the bottom of a branch.
1361     return (brdepth[place.branch] - place.depth) >= 1;
1362 }
1363 
1364 /***
1365  * Can we force shaft the player from this level?
1366  *
1367  * @returns true if we can.
1368  */
is_valid_shaft_effect_level()1369 bool is_valid_shaft_effect_level()
1370 {
1371     const level_id place = level_id::current();
1372     const Branch &branch = branches[place.branch];
1373 
1374     // Don't shaft the player when we can't, and also when it would be into a
1375     // dangerous end.
1376     return is_valid_shaft_level()
1377            && !(branch.branch_flags & brflag::dangerous_end
1378                 && brdepth[place.branch] - place.depth == 1);
1379 }
1380 
1381 /***
1382  * The player rolled a new tile, see if they deserve to be trapped.
1383  */
roll_trap_effects()1384 void roll_trap_effects()
1385 {
1386     int trap_rate = trap_rate_for_place();
1387 
1388     you.trapped = you.num_turns && !have_passive(passive_t::avoid_traps)
1389         && env.density > 0 // can happen with builder in debug state
1390         && (you.trapped || x_chance_in_y(trap_rate, 9 * env.density));
1391 }
1392 
1393 /***
1394  * Separate from roll_trap_effects so the trap triggers when crawl is in an
1395  * appropriate state
1396  */
do_trap_effects()1397 void do_trap_effects()
1398 {
1399     // Try to shaft, teleport, or alarm the player.
1400 
1401     // We figure out which possibilities are allowed before picking which happens
1402     // so that the overall chance of being trapped doesn't depend on which
1403     // possibilities are allowed.
1404 
1405     // Teleport effects are allowed everywhere, no need to check
1406     vector<trap_type> available_traps = { TRAP_TELEPORT };
1407     // Don't shaft the player when shafts aren't allowed in the location or when
1408     //  it would be into a dangerous end.
1409     if (is_valid_shaft_effect_level())
1410         available_traps.push_back(TRAP_SHAFT);
1411     // No alarms on the first 3 floors
1412     if (env.absdepth0 > 3)
1413         available_traps.push_back(TRAP_ALARM);
1414 
1415     switch (*random_iterator(available_traps))
1416     {
1417         case TRAP_SHAFT:
1418             dprf("Attempting to shaft player.");
1419             you.do_shaft();
1420             break;
1421 
1422         case TRAP_ALARM:
1423             // Alarm effect alarms are always noisy, even if the player is
1424             // silenced, to avoid "travel only while silenced" behaviour.
1425             // XXX: improve messaging to make it clear there's a wail outside of the
1426             // player's silence
1427             mprf("You set off the alarm!");
1428             fake_noisy(40, you.pos());
1429             you.sentinel_mark(true);
1430             break;
1431 
1432         case TRAP_TELEPORT:
1433             you_teleport_now(false, true, "You stumble into a teleport trap!");
1434             break;
1435 
1436         // Other cases shouldn't be possible, but having a default here quiets
1437         // compiler warnings
1438         default:
1439             break;
1440     }
1441 }
1442 
generic_shaft_dest(level_id place)1443 level_id generic_shaft_dest(level_id place)
1444 {
1445     if (!is_connected_branch(place))
1446         return place;
1447 
1448     int curr_depth = place.depth;
1449     int max_depth = brdepth[place.branch];
1450 
1451     // Shafts drop you 1/2/3 levels with equal chance.
1452     // 33.3% for 1, 2, 3 from D:3, less before
1453     place.depth += 1 + random2(min(place.depth, 3));
1454 
1455     if (place.depth > max_depth)
1456         place.depth = max_depth;
1457 
1458     if (place.depth == curr_depth)
1459         return place;
1460 
1461     // Only shafts on the level immediately above a dangerous branch
1462     // bottom will take you to that dangerous bottom.
1463     if (branches[place.branch].branch_flags & brflag::dangerous_end
1464         && place.depth == max_depth
1465         && (max_depth - curr_depth) > 1)
1466     {
1467         place.depth--;
1468     }
1469 
1470     return place;
1471 }
1472 
1473 /**
1474  * Get the trap effect rate for the current level.
1475  *
1476  * No traps effects occur in either Temple or disconnected branches other than
1477  * Pandemonium. For other branches, this value starts at 1. It is increased for
1478  * deeper levels; by one for every 10 levels of absdepth,
1479  * capping out at max 9.
1480  *
1481  * No traps in tutorial, sprint, and arena.
1482  *
1483  * @return  The trap rate for the current level.
1484 */
trap_rate_for_place()1485 int trap_rate_for_place()
1486 {
1487     if (player_in_branch(BRANCH_TEMPLE)
1488         || (!player_in_connected_branch()
1489             && !player_in_branch(BRANCH_PANDEMONIUM))
1490         || crawl_state.game_is_sprint()
1491         || crawl_state.game_is_tutorial()
1492         || crawl_state.game_is_arena())
1493     {
1494         return 0;
1495     }
1496 
1497     return 1 + env.absdepth0 / 10;
1498 }
1499 
1500 /**
1501  * Choose a weighted random trap type for the currently-generated level.
1502  *
1503  * Odds of generating zot traps vary by depth (and are depth-limited). Alarm
1504  * traps also can't be placed before D:4. All other traps are depth-agnostic.
1505  *
1506  * @return                    A random trap type.
1507  *                            May be NUM_TRAPS, if no traps were valid.
1508  */
1509 
random_trap_for_place(bool dispersal_ok)1510 trap_type random_trap_for_place(bool dispersal_ok)
1511 {
1512     // zot traps are Very Special.
1513     // very common in zot...
1514     if (player_in_branch(BRANCH_ZOT) && coinflip())
1515         return TRAP_ZOT;
1516 
1517     // and elsewhere, increasingly common with depth
1518     // possible starting at depth 15 (end of D, late lair, lair branches)
1519     // XXX: is there a better way to express this?
1520     if (random2(1 + env.absdepth0) > 14 && one_chance_in(3))
1521         return TRAP_ZOT;
1522 
1523     const bool shaft_ok = is_valid_shaft_level();
1524     const bool tele_ok = !crawl_state.game_is_sprint();
1525     const bool alarm_ok = env.absdepth0 > 3;
1526 
1527     const pair<trap_type, int> trap_weights[] =
1528     {
1529         { TRAP_DISPERSAL, dispersal_ok && tele_ok  ? 1 : 0},
1530         { TRAP_TELEPORT,  tele_ok  ? 1 : 0},
1531         { TRAP_SHAFT,    shaft_ok  ? 1 : 0},
1532         { TRAP_ALARM,    alarm_ok  ? 1 : 0},
1533     };
1534 
1535     const trap_type *trap = random_choose_weighted(trap_weights);
1536     return trap ? *trap : NUM_TRAPS;
1537 }
1538 
1539 /**
1540  * Oldstyle trap algorithm, used for vaults. Very bad. Please remove ASAP.
1541  */
random_vault_trap()1542 trap_type random_vault_trap()
1543 {
1544     const int level_number = env.absdepth0;
1545     trap_type type = TRAP_ARROW;
1546 
1547     if ((random2(1 + level_number) > 1) && one_chance_in(4))
1548         type = TRAP_DART;
1549     if (random2(1 + level_number) > 3)
1550         type = TRAP_SPEAR;
1551 
1552     if (type == TRAP_ARROW && one_chance_in(15))
1553         type = TRAP_NET;
1554 
1555     if (random2(1 + level_number) > 7)
1556         type = TRAP_BOLT;
1557     if (random2(1 + level_number) > 14)
1558         type = TRAP_BLADE;
1559 
1560     if (random2(1 + level_number) > 14 && one_chance_in(3)
1561         || (player_in_branch(BRANCH_ZOT) && coinflip()))
1562     {
1563         type = TRAP_ZOT;
1564     }
1565 
1566     if (one_chance_in(20) && is_valid_shaft_level())
1567         type = TRAP_SHAFT;
1568     if (one_chance_in(20) && !crawl_state.game_is_sprint())
1569         type = TRAP_TELEPORT;
1570     if (one_chance_in(40) && level_number > 3)
1571         type = TRAP_ALARM;
1572 
1573     return type;
1574 }
1575 
count_traps(trap_type ttyp)1576 int count_traps(trap_type ttyp)
1577 {
1578     int num = 0;
1579     for (const auto& entry : env.trap)
1580         if (entry.second.type == ttyp)
1581             num++;
1582     return num;
1583 }
1584 
place_webs(int num)1585 void place_webs(int num)
1586 {
1587     trap_def ts;
1588     for (int j = 0; j < num; j++)
1589     {
1590         int tries;
1591         // this is hardly ever enough to place many webs, most of the time
1592         // it will fail prematurely. Which is fine.
1593         for (tries = 0; tries < 200; ++tries)
1594         {
1595             ts.pos.x = random2(GXM);
1596             ts.pos.y = random2(GYM);
1597             if (in_bounds(ts.pos)
1598                 && env.grid(ts.pos) == DNGN_FLOOR
1599                 && !map_masked(ts.pos, MMT_NO_TRAP))
1600             {
1601                 // Calculate weight.
1602                 int weight = 0;
1603                 for (adjacent_iterator ai(ts.pos); ai; ++ai)
1604                 {
1605                     // Solid wall?
1606                     int solid_weight = 0;
1607                     // Orthogonals weight three, diagonals 1.
1608                     if (cell_is_solid(*ai))
1609                     {
1610                         solid_weight = (ai->x == ts.pos.x || ai->y == ts.pos.y)
1611                                         ? 3 : 1;
1612                     }
1613                     weight += solid_weight;
1614                 }
1615 
1616                 // Maximum weight is 4*3+4*1 = 16
1617                 // *But* that would imply completely surrounded by rock (no point there)
1618                 if (weight <= 16 && x_chance_in_y(weight + 2, 34))
1619                     break;
1620             }
1621         }
1622 
1623         if (tries >= 200)
1624             break;
1625 
1626         ts.type = TRAP_WEB;
1627         ts.prepare_ammo();
1628         ts.reveal();
1629         env.trap[ts.pos] = ts;
1630     }
1631 }
1632 
ensnare(actor * fly)1633 bool ensnare(actor *fly)
1634 {
1635     ASSERT(fly); // XXX: change to actor &fly
1636     if (fly->is_web_immune())
1637         return false;
1638 
1639     if (fly->caught())
1640     {
1641         // currently webs are stateless so except for flavour it's a no-op
1642         if (fly->is_player())
1643             mpr("You are even more entangled.");
1644         return false;
1645     }
1646 
1647     if (fly->body_size() >= SIZE_GIANT)
1648     {
1649         if (you.can_see(*fly))
1650             mprf("A web harmlessly splats on %s.", fly->name(DESC_THE).c_str());
1651         return false;
1652     }
1653 
1654     // If we're over water, an open door, shop, portal, etc, the web will
1655     // fail to attach and you'll be released after a single turn.
1656     if (env.grid(fly->pos()) == DNGN_FLOOR)
1657     {
1658         place_specific_trap(fly->pos(), TRAP_WEB, 1); // 1 ammo = destroyed on exit (hackish)
1659         if (you.see_cell(fly->pos()))
1660             env.grid(fly->pos()) = DNGN_TRAP_WEB;
1661     }
1662 
1663     if (fly->is_player())
1664     {
1665         if (_player_caught_in_web()) // no fail, returns false if already held
1666             mpr("You are caught in a web!");
1667     }
1668     else
1669     {
1670         simple_monster_message(*fly->as_monster(), " is caught in a web!");
1671         fly->as_monster()->add_ench(ENCH_HELD);
1672     }
1673 
1674     // Drowned?
1675     if (!fly->alive())
1676         return true;
1677 
1678     return true;
1679 }
1680 
1681 // Whether this trap type can be placed in vaults by the ^ glphy
is_regular_trap(trap_type trap)1682 bool is_regular_trap(trap_type trap)
1683 {
1684 #if TAG_MAJOR_VERSION == 34
1685     return trap <= TRAP_MAX_REGULAR || trap == TRAP_DISPERSAL;
1686 #else
1687     return trap <= TRAP_MAX_REGULAR;
1688 #endif
1689 }
1690