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