1 /**
2 * @file
3 * @brief Gametime related functions.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "timed-effects.h"
9
10 #include "abyss.h"
11 #include "act-iter.h"
12 #include "areas.h"
13 #include "beam.h"
14 #include "branch.h" // for zot clock key
15 #include "cloud.h"
16 #include "coordit.h"
17 #include "corpse.h"
18 #include "database.h"
19 #include "delay.h"
20 #include "dgn-shoals.h"
21 #include "dgn-event.h"
22 #include "env.h"
23 #include "exercise.h"
24 #include "externs.h"
25 #include "fprop.h"
26 #include "god-passive.h"
27 #include "items.h"
28 #include "libutil.h"
29 #include "mapmark.h"
30 #include "message.h"
31 #include "mgen-data.h"
32 #include "monster.h"
33 #include "mon-behv.h"
34 #include "mon-death.h"
35 #include "mon-pathfind.h"
36 #include "mon-place.h"
37 #include "mon-project.h"
38 #include "mutation.h"
39 #include "notes.h"
40 #include "player.h"
41 #include "player-stats.h"
42 #include "random.h"
43 #include "religion.h"
44 #include "skills.h"
45 #include "shout.h"
46 #include "state.h"
47 #include "spl-clouds.h"
48 #include "spl-miscast.h"
49 #include "stringutil.h"
50 #include "tag-version.h"
51 #include "teleport.h"
52 #include "terrain.h"
53 #include "tileview.h"
54 #include "throw.h"
55 #include "travel.h"
56 #include "viewchar.h"
57 #include "unwind.h"
58
59 /**
60 * Choose a random, spooky hell effect message, print it, and make a loud noise
61 * if appropriate. (1/6 chance of loud noise.)
62 */
_hell_effect_noise()63 static void _hell_effect_noise()
64 {
65 const bool loud = one_chance_in(6) && !silenced(you.pos());
66 string msg = getMiscString(loud ? "hell_effect_noisy"
67 : "hell_effect_quiet");
68 if (msg.empty())
69 msg = "Something hellishly buggy happens.";
70
71 mprf(MSGCH_HELL_EFFECT, "%s", msg.c_str());
72 if (loud)
73 noisy(15, you.pos());
74 }
75
76 /**
77 * Choose a random miscast effect (from a weighted list) & apply it to the
78 * player.
79 */
_random_hell_miscast()80 static void _random_hell_miscast()
81 {
82 const spschool which_miscast
83 = random_choose_weighted(8, spschool::necromancy,
84 4, spschool::summoning,
85 2, spschool::conjuration,
86 2, spschool::hexes);
87
88 miscast_effect(you, nullptr, {miscast_source::hell_effect}, which_miscast,
89 5, random2avg(40, 3), "the effects of Hell");
90 }
91
92 /// The thematically appropriate hell effects for a given hell branch.
93 struct hell_effect_spec
94 {
95 /// The type of greater demon to spawn from hell effects.
96 vector<monster_type> fiend_types;
97 /// The appropriate theme of miscast effects to toss at the player.
98 spschool miscast_type;
99 /// A weighted list of lesser creatures to spawn.
100 vector<pair<monster_type, int>> minor_summons;
101 };
102
103 /// Hell effects for each branch of hell
104 static map<branch_type, hell_effect_spec> hell_effects_by_branch =
105 {
106 { BRANCH_DIS, { {RANDOM_DEMON_GREATER}, spschool::earth, {
107 { RANDOM_MONSTER, 100 }, // TODO
108 }}},
109 { BRANCH_GEHENNA, { {MONS_BRIMSTONE_FIEND}, spschool::fire, {
110 { RANDOM_MONSTER, 100 }, // TODO
111 }}},
112 { BRANCH_COCYTUS, { {MONS_ICE_FIEND, MONS_SHARD_SHRIKE}, spschool::ice, {
113 // total weight 100
114 { MONS_ZOMBIE, 15 },
115 { MONS_SKELETON, 10 },
116 { MONS_SIMULACRUM, 10 },
117 { MONS_FREEZING_WRAITH, 10 },
118 { MONS_FLYING_SKULL, 10 },
119 { MONS_TORMENTOR, 10 },
120 { MONS_REAPER, 10 },
121 { MONS_BONE_DRAGON, 5 },
122 { MONS_ICE_DRAGON, 5 },
123 { MONS_BLIZZARD_DEMON, 5 },
124 { MONS_ICE_DEVIL, 5 },
125 }}},
126 { BRANCH_TARTARUS, { {MONS_TZITZIMITL}, spschool::necromancy, {
127 { RANDOM_MONSTER, 100 }, // TODO
128 }}},
129 };
130
131 /**
132 * Either dump a fiend or a hell-appropriate miscast effect on the player.
133 *
134 * 40% chance of fiend, 60% chance of miscast.
135 */
_themed_hell_summon_or_miscast()136 static void _themed_hell_summon_or_miscast()
137 {
138 const hell_effect_spec *spec = map_find(hell_effects_by_branch,
139 you.where_are_you);
140 if (!spec)
141 die("Attempting to call down a hell effect in a non-hellish branch.");
142
143 if (x_chance_in_y(2, 5))
144 {
145 const monster_type fiend
146 = spec->fiend_types[random2(spec->fiend_types.size())];
147 create_monster(
148 mgen_data::hostile_at(fiend, true, you.pos())
149 .set_non_actor_summoner("the effects of Hell"));
150 }
151 else
152 {
153 miscast_effect(you, nullptr, {miscast_source::hell_effect},
154 spec->miscast_type, 5, random2avg(40, 3),
155 "the effects of Hell");
156 }
157 }
158
159 /**
160 * Try to summon at some number of random spawns from the current branch, to
161 * harass the player & give them easy xp/TSO piety. Occasionally, to kill them.
162 *
163 * Min zero, max five, average 1.67.
164 *
165 * Can and does summon bands as individual spawns.
166 */
_minor_hell_summons()167 static void _minor_hell_summons()
168 {
169 hell_effect_spec *spec = map_find(hell_effects_by_branch,
170 you.where_are_you);
171 if (!spec)
172 die("Attempting to call down a hell effect in a non-hellish branch.");
173
174 // Try to summon at least one and up to five random monsters. {dlb}
175 mgen_data mg;
176 mg.pos = you.pos();
177 mg.foe = MHITYOU;
178 mg.non_actor_summoner = "the effects of Hell";
179 create_monster(mg);
180
181 for (int i = 0; i < 4; ++i)
182 {
183 if (one_chance_in(3))
184 {
185 monster_type *type
186 = random_choose_weighted(spec->minor_summons);
187 ASSERT(type);
188 mg.cls = *type;
189 create_monster(mg);
190 }
191 }
192 }
193
194 /// Nasty things happen to people who spend too long in Hell.
_hell_effects(int)195 static void _hell_effects(int /*time_delta*/)
196 {
197 if (!player_in_hell())
198 return;
199
200 // 50% chance at max piety
201 if (have_passive(passive_t::resist_hell_effects)
202 && x_chance_in_y(you.piety, MAX_PIETY * 2) || is_sanctuary(you.pos()))
203 {
204 simple_god_message("'s power protects you from the chaos of Hell!");
205 return;
206 }
207
208 _hell_effect_noise();
209
210 if (one_chance_in(3))
211 _random_hell_miscast();
212 else if (x_chance_in_y(5, 9))
213 _themed_hell_summon_or_miscast();
214
215 if (one_chance_in(3)) // NB: No "else"
216 _minor_hell_summons();
217 }
218
_apply_contam_over_time()219 static void _apply_contam_over_time()
220 {
221 int added_contamination = 0;
222
223 //Increase contamination each turn while invisible
224 if (you.duration[DUR_INVIS])
225 added_contamination += INVIS_CONTAM_PER_TURN;
226 //If not invisible, normal dissipation
227 else
228 added_contamination -= 75;
229
230 // The Orb halves dissipation (well a bit more, I had to round it),
231 // but won't cause glow on its own -- otherwise it'd spam the player
232 // with messages about contamination oscillating near zero.
233 if (you.magic_contamination && player_has_orb())
234 added_contamination += 38;
235
236 // Scaling to turn length
237 added_contamination = div_rand_round(added_contamination * you.time_taken,
238 BASELINE_DELAY);
239
240 contaminate_player(added_contamination, false);
241 }
242
243 // Bad effects from magic contamination.
_magic_contamination_effects()244 static void _magic_contamination_effects()
245 {
246 mprf(MSGCH_WARN, "Your body shudders with the violent release "
247 "of wild energies!");
248
249 const int contam = you.magic_contamination;
250
251 // For particularly violent releases, make a little boom.
252 if (contam > 10000 && coinflip())
253 {
254 bolt beam;
255
256 beam.flavour = BEAM_RANDOM;
257 beam.glyph = dchar_glyph(DCHAR_FIRED_BURST);
258 beam.damage = dice_def(3, div_rand_round(contam, 2000));
259 beam.target = you.pos();
260 beam.name = "magical storm";
261 //XXX: Should this be MID_PLAYER?
262 beam.source_id = MID_NOBODY;
263 beam.aux_source = "a magical explosion";
264 beam.ex_size = max(1, min(LOS_RADIUS,
265 div_rand_round(contam, 15000)));
266 beam.ench_power = div_rand_round(contam, 200);
267 beam.is_explosion = true;
268
269 beam.explode();
270 }
271
272 const mutation_permanence_class mutclass = MUTCLASS_NORMAL;
273
274 // We want to warp the player, not do good stuff!
275 mutate(one_chance_in(5) ? RANDOM_MUTATION : RANDOM_BAD_MUTATION,
276 "mutagenic glow", true, coinflip(), false, false, mutclass);
277
278 // we're meaner now, what with explosions and whatnot, but
279 // we dial down the contamination a little faster if its actually
280 // mutating you. -- GDL
281 contaminate_player(-(random2(contam / 4) + 1000));
282 }
283 // Checks if the player should be hit with magic contaimination effects,
284 // then actually does it if they should be.
_check_contamination_effects(int)285 static void _check_contamination_effects(int /*time_delta*/)
286 {
287 const bool glow_effect = player_severe_contamination()
288 && x_chance_in_y(you.magic_contamination, 12000);
289
290 if (glow_effect)
291 {
292 if (is_sanctuary(you.pos()))
293 {
294 mprf(MSGCH_GOD, "Your body momentarily shudders from a surge of wild "
295 "energies until Zin's power calms it.");
296 }
297 else
298 _magic_contamination_effects();
299 }
300 }
301
302 // Exercise armour *xor* stealth skill: {dlb}
_wait_practice(int)303 static void _wait_practice(int /*time_delta*/)
304 {
305 practise_waiting();
306 }
307
308 // Update the abyss speed. This place is unstable and the speed can
309 // fluctuate. It's not a constant increase.
_abyss_speed(int)310 static void _abyss_speed(int /*time_delta*/)
311 {
312 if (!player_in_branch(BRANCH_ABYSS))
313 return;
314
315 if (have_passive(passive_t::slow_abyss) && coinflip())
316 ; // Speed change less often for Chei.
317 else if (coinflip() && you.abyss_speed < 100)
318 ++you.abyss_speed;
319 else if (one_chance_in(5) && you.abyss_speed > 0)
320 --you.abyss_speed;
321 }
322
_jiyva_effects(int)323 static void _jiyva_effects(int /*time_delta*/)
324 {
325 if (have_passive(passive_t::jellies_army) && one_chance_in(10))
326 {
327 int total_jellies = 1 + random2(5);
328 bool success = false;
329 for (int num_jellies = total_jellies; num_jellies > 0; num_jellies--)
330 {
331 // Spread jellies around the level.
332 coord_def newpos;
333 do
334 {
335 newpos = random_in_bounds();
336 }
337 while (env.grid(newpos) != DNGN_FLOOR
338 && env.grid(newpos) != DNGN_SHALLOW_WATER
339 || monster_at(newpos)
340 || cloud_at(newpos)
341 || testbits(env.pgrid(newpos), FPROP_NO_JIYVA));
342
343 mgen_data mg(MONS_JELLY, BEH_STRICT_NEUTRAL, newpos);
344 mg.god = GOD_JIYVA;
345 mg.non_actor_summoner = "Jiyva";
346
347 if (create_monster(mg))
348 success = true;
349 }
350
351 if (success && !silenced(you.pos()))
352 {
353 switch (random2(3))
354 {
355 case 0:
356 simple_god_message(" gurgles merrily.");
357 break;
358 case 1:
359 mprf(MSGCH_SOUND, "You hear %s splatter%s.",
360 total_jellies > 1 ? "a series of" : "a",
361 total_jellies > 1 ? "s" : "");
362 break;
363 case 2:
364 simple_god_message(" says: Divide and consume!");
365 break;
366 }
367 }
368 }
369
370 if (have_passive(passive_t::fluid_stats)
371 && x_chance_in_y(you.piety / 4, MAX_PIETY)
372 && !player_under_penance() && one_chance_in(4))
373 {
374 jiyva_stat_action();
375 }
376
377 if (have_passive(passive_t::jelly_eating) && one_chance_in(25))
378 jiyva_eat_offlevel_items();
379 }
380
_evolve(int)381 static void _evolve(int /*time_delta*/)
382 {
383 if (int lev = you.get_mutation_level(MUT_EVOLUTION))
384 if (one_chance_in(2 / lev)
385 && you.attribute[ATTR_EVOL_XP] * (1 + random2(10))
386 > (int)exp_needed(you.experience_level + 1))
387 {
388 you.attribute[ATTR_EVOL_XP] = 0;
389 mpr("You feel a genetic drift.");
390 bool evol = one_chance_in(5) ?
391 delete_mutation(RANDOM_BAD_MUTATION, "evolution", false) :
392 mutate(random_choose(RANDOM_GOOD_MUTATION, RANDOM_MUTATION),
393 "evolution", false, false, false, false, MUTCLASS_NORMAL);
394 // it would kill itself anyway, but let's speed that up
395 if (one_chance_in(10)
396 && (!you.rmut_from_item()
397 || one_chance_in(10)))
398 {
399 const string reason = (you.get_mutation_level(MUT_EVOLUTION) == 1)
400 ? "end of evolution"
401 : "decline of evolution";
402 evol |= delete_mutation(MUT_EVOLUTION, reason, false);
403 }
404 // interrupt the player only if something actually happened
405 if (evol)
406 more();
407 }
408 }
409
410 // Get around C++ dividing integers towards 0.
_div(int num,int denom)411 static int _div(int num, int denom)
412 {
413 div_t res = div(num, denom);
414 return res.rem >= 0 ? res.quot : res.quot - 1;
415 }
416
417 struct timed_effect
418 {
419 void (*trigger)(int);
420 int min_time;
421 int max_time;
422 bool arena;
423 };
424
425 // If you add an entry to this list, remember to add a matching entry
426 // to timed_effect_type in timed-effect-type.h!
427 static struct timed_effect timed_effects[] =
428 {
429 { rot_corpses, 200, 200, true },
430 { _hell_effects, 200, 600, false },
431 #if TAG_MAJOR_VERSION == 34
432 { nullptr, 0, 0, false },
433 #endif
434 { _check_contamination_effects, 70, 200, false },
435 #if TAG_MAJOR_VERSION == 34
436 { nullptr, 0, 0, false },
437 #endif
438 { handle_god_time, 100, 300, false },
439 #if TAG_MAJOR_VERSION == 34
440 { nullptr, 0, 0, false },
441 { nullptr, 0, 0, false },
442 #endif
443 { _wait_practice, 100, 300, false },
444 #if TAG_MAJOR_VERSION == 34
445 { nullptr, 0, 0, false },
446 #endif
447 { _abyss_speed, 100, 300, false },
448 { _jiyva_effects, 100, 300, false },
449 { _evolve, 5000, 15000, false },
450 #if TAG_MAJOR_VERSION == 34
451 { nullptr, 0, 0, false },
452 #endif
453 };
454
455 // Do various time related actions...
handle_time()456 void handle_time()
457 {
458 int base_time = you.elapsed_time % 200;
459 int old_time = base_time - you.time_taken;
460
461 // The checks below assume the function is called at least
462 // once every 50 elapsed time units.
463
464 // Every 5 turns, spawn random monsters
465 if (_div(base_time, 50) > _div(old_time, 50))
466 {
467 spawn_random_monsters();
468 if (player_in_branch(BRANCH_ABYSS))
469 for (int i = 1; i < you.depth; ++i)
470 if (x_chance_in_y(i, 5))
471 spawn_random_monsters();
472 }
473
474 // Abyss maprot.
475 if (player_in_branch(BRANCH_ABYSS))
476 forget_map(true);
477
478 // Magic contamination from spells and Orb.
479 if (!crawl_state.game_is_arena())
480 _apply_contam_over_time();
481
482 for (unsigned int i = 0; i < ARRAYSZ(timed_effects); i++)
483 {
484 if (crawl_state.game_is_arena() && !timed_effects[i].arena)
485 continue;
486
487 if (!timed_effects[i].trigger)
488 {
489 if (you.next_timer_effect[i] < INT_MAX)
490 you.next_timer_effect[i] = INT_MAX;
491 continue;
492 }
493
494 if (you.elapsed_time >= you.next_timer_effect[i])
495 {
496 int time_delta = you.elapsed_time - you.last_timer_effect[i];
497 (timed_effects[i].trigger)(time_delta);
498 you.last_timer_effect[i] = you.next_timer_effect[i];
499 you.next_timer_effect[i] =
500 you.last_timer_effect[i]
501 + random_range(timed_effects[i].min_time,
502 timed_effects[i].max_time);
503 }
504 }
505 }
506
507 /**
508 * Make ranged monsters flee from the player during their time offlevel.
509 *
510 * @param mon The monster in question.
511 */
_monster_flee(monster * mon)512 static void _monster_flee(monster *mon)
513 {
514 mon->behaviour = BEH_FLEE;
515 dprf("backing off...");
516
517 if (mon->pos() != mon->target)
518 return;
519 // If the monster is on the target square, fleeing won't work.
520
521 if (in_bounds(env.old_player_pos) && env.old_player_pos != mon->pos())
522 {
523 // Flee from player's old position if different.
524 mon->target = env.old_player_pos;
525 return;
526 }
527
528 // Randomise the target so we have a direction to flee.
529 coord_def mshift;
530 mshift.x = random2(3) - 1;
531 mshift.y = random2(3) - 1;
532
533 // Bounds check: don't let fleeing monsters try to run off the grid.
534 const coord_def s = mon->target + mshift;
535 if (!in_bounds_x(s.x))
536 mshift.x = 0;
537 if (!in_bounds_y(s.y))
538 mshift.y = 0;
539
540 mon->target.x += mshift.x;
541 mon->target.y += mshift.y;
542
543 return;
544 }
545
546 /**
547 * Make a monster take a number of moves toward (or away from, if fleeing)
548 * their current target, very crudely.
549 *
550 * @param mon The mon in question.
551 * @param moves The number of moves to take.
552 */
_catchup_monster_move(monster * mon,int moves)553 static void _catchup_monster_move(monster* mon, int moves)
554 {
555 coord_def pos(mon->pos());
556
557 // Dirt simple movement.
558 for (int i = 0; i < moves; ++i)
559 {
560 coord_def inc(mon->target - pos);
561 inc = coord_def(sgn(inc.x), sgn(inc.y));
562
563 if (mons_is_retreating(*mon))
564 inc *= -1;
565
566 // Bounds check: don't let shifting monsters try to run off the
567 // grid.
568 const coord_def s = pos + inc;
569 if (!in_bounds_x(s.x))
570 inc.x = 0;
571 if (!in_bounds_y(s.y))
572 inc.y = 0;
573
574 if (inc.origin())
575 break;
576
577 const coord_def next(pos + inc);
578 const dungeon_feature_type feat = env.grid(next);
579 if (feat_is_solid(feat)
580 || monster_at(next)
581 || !monster_habitable_grid(mon, feat))
582 {
583 break;
584 }
585
586 pos = next;
587 }
588
589 if (!mon->shift(pos))
590 mon->shift(mon->pos());
591 }
592
593 /**
594 * Move monsters around to fake them walking around while player was
595 * off-level.
596 *
597 * Does not account for monster move speeds.
598 *
599 * Also make them forget about the player over time.
600 *
601 * @param mon The monster under consideration
602 * @param turns The number of offlevel player turns to simulate.
603 */
_catchup_monster_moves(monster * mon,int turns)604 static void _catchup_monster_moves(monster* mon, int turns)
605 {
606 // Summoned monsters might have disappeared.
607 if (!mon->alive())
608 return;
609
610 // Ball lightning dissapates harmlessly out of LOS
611 if (mon->type == MONS_BALL_LIGHTNING && mon->summoner == MID_PLAYER)
612 {
613 monster_die(*mon, KILL_RESET, NON_MONSTER);
614 return;
615 }
616
617 // Expire friendly summons and temporary allies
618 if (mon->friendly()
619 && (mon->is_summoned() || mon->has_ench(ENCH_FAKE_ABJURATION))
620 && !mon->is_perm_summoned())
621 {
622 // You might still see them disappear if you were quick
623 if (turns > 2)
624 monster_die(*mon, KILL_DISMISSED, NON_MONSTER);
625 else
626 {
627 enchant_type abj_type = mon->has_ench(ENCH_ABJ) ? ENCH_ABJ
628 : ENCH_FAKE_ABJURATION;
629 mon_enchant abj = mon->get_ench(abj_type);
630 abj.duration = 0;
631 mon->update_ench(abj);
632 }
633 return;
634 }
635
636 // Don't move non-land or stationary monsters around.
637 if (mons_primary_habitat(*mon) != HT_LAND
638 || mons_is_zombified(*mon)
639 && mons_class_primary_habitat(mon->base_monster) != HT_LAND
640 || mon->is_stationary())
641 {
642 return;
643 }
644
645 // special movement code for ioods
646 if (mons_is_projectile(*mon))
647 {
648 iood_catchup(mon, turns);
649 return;
650 }
651
652 // Let sleeping monsters lie.
653 if (mon->asleep() || mon->paralysed())
654 return;
655
656
657
658 const int mon_turns = (turns * mon->speed) / 10;
659 const int moves = min(mon_turns, 50);
660
661 // probably too annoying even for DEBUG_DIAGNOSTICS
662 dprf("mon #%d: range %d; "
663 "pos (%d,%d); targ %d(%d,%d); flags %" PRIx64,
664 mon->mindex(), mon_turns, mon->pos().x, mon->pos().y,
665 mon->foe, mon->target.x, mon->target.y, mon->flags.flags);
666
667 if (mon_turns <= 0)
668 return;
669
670 // restore behaviour later if we start fleeing
671 unwind_var<beh_type> saved_beh(mon->behaviour);
672
673 if (mons_has_ranged_attack(*mon))
674 {
675 // If we're doing short time movement and the monster has a
676 // ranged attack (missile or spell), then the monster will
677 // flee to gain distance if it's "too close", else it will
678 // just shift its position rather than charge the player. -- bwr
679 if (grid_distance(mon->pos(), mon->target) >= 3)
680 {
681 mon->shift(mon->pos());
682 dprf("shifted to (%d, %d)", mon->pos().x, mon->pos().y);
683 return;
684 }
685
686 _monster_flee(mon);
687 }
688
689 _catchup_monster_move(mon, moves);
690
691 dprf("moved to (%d, %d)", mon->pos().x, mon->pos().y);
692 }
693
694 /**
695 * Update a monster's enchantments when the player returns
696 * to the level.
697 *
698 * Management for enchantments... problems with this are the oddities
699 * (monster dying from poison several thousands of turns later), and
700 * game balance.
701 *
702 * Consider: Poison/Sticky Flame a monster at range and leave, monster
703 * dies but can't leave level to get to player (implied game balance of
704 * the delayed damage is that the monster could be a danger before
705 * it dies). This could be fixed by keeping some monsters active
706 * off level and allowing them to take stairs (a very serious change).
707 *
708 * Compare this to the current abuse where the player gets
709 * effectively extended duration of these effects (although only
710 * the actual effects only occur on level, the player can leave
711 * and heal up without having the effect disappear).
712 *
713 * This is a simple compromise between the two... the enchantments
714 * go away, but the effects don't happen off level. -- bwr
715 *
716 * @param levels XXX: sometimes the missing aut/10, sometimes aut/100
717 */
timeout_enchantments(int levels)718 void monster::timeout_enchantments(int levels)
719 {
720 if (enchantments.empty())
721 return;
722
723 const mon_enchant_list ec = enchantments;
724 for (auto &entry : ec)
725 {
726 switch (entry.first)
727 {
728 case ENCH_POISON: case ENCH_CORONA:
729 case ENCH_STICKY_FLAME: case ENCH_ABJ: case ENCH_SHORT_LIVED:
730 case ENCH_HASTE: case ENCH_MIGHT: case ENCH_FEAR:
731 case ENCH_CHARM: case ENCH_SLEEP_WARY: case ENCH_SICK:
732 case ENCH_PARALYSIS: case ENCH_PETRIFYING:
733 case ENCH_PETRIFIED: case ENCH_SWIFT: case ENCH_SILENCE:
734 case ENCH_LOWERED_WL: case ENCH_SOUL_RIPE: case ENCH_ANTIMAGIC:
735 case ENCH_REGENERATION: case ENCH_STRONG_WILLED:
736 case ENCH_MIRROR_DAMAGE: case ENCH_LIQUEFYING:
737 case ENCH_SILVER_CORONA: case ENCH_DAZED: case ENCH_FAKE_ABJURATION:
738 case ENCH_BREATH_WEAPON: case ENCH_WRETCHED:
739 case ENCH_SCREAMED: case ENCH_BLIND: case ENCH_WORD_OF_RECALL:
740 case ENCH_INJURY_BOND: case ENCH_FLAYED: case ENCH_BARBS:
741 case ENCH_AGILE: case ENCH_FROZEN:
742 case ENCH_BLACK_MARK: case ENCH_SAP_MAGIC: case ENCH_NEUTRAL_BRIBED:
743 case ENCH_FRIENDLY_BRIBED: case ENCH_CORROSION: case ENCH_GOLD_LUST:
744 case ENCH_RESISTANCE: case ENCH_HEXED: case ENCH_IDEALISED:
745 case ENCH_BOUND_SOUL: case ENCH_STILL_WINDS:
746 lose_ench_levels(entry.second, levels);
747 break;
748
749 case ENCH_SLOW:
750 if (torpor_slowed())
751 {
752 lose_ench_levels(entry.second,
753 min(levels, entry.second.degree - 1));
754 }
755 else
756 {
757 lose_ench_levels(entry.second, levels);
758 if (props.exists(TORPOR_SLOWED_KEY))
759 props.erase(TORPOR_SLOWED_KEY);
760 }
761 break;
762
763 case ENCH_INVIS:
764 if (!mons_class_flag(type, M_INVIS))
765 lose_ench_levels(entry.second, levels);
766 break;
767
768 case ENCH_INSANE:
769 case ENCH_BERSERK:
770 case ENCH_INNER_FLAME:
771 case ENCH_ROLLING:
772 case ENCH_MERFOLK_AVATAR_SONG:
773 case ENCH_INFESTATION:
774 del_ench(entry.first);
775 break;
776
777 case ENCH_FATIGUE:
778 del_ench(entry.first);
779 del_ench(ENCH_SLOW);
780 break;
781
782 case ENCH_TP:
783 teleport(true);
784 del_ench(entry.first);
785 break;
786
787 case ENCH_CONFUSION:
788 if (!mons_class_flag(type, M_CONFUSED))
789 del_ench(entry.first);
790 // That triggered a behaviour_event, which could have made a
791 // pacified monster leave the level.
792 if (alive() && !is_stationary())
793 monster_blink(this, true);
794 break;
795
796 case ENCH_HELD:
797 del_ench(entry.first);
798 break;
799
800 case ENCH_TIDE:
801 {
802 const int actdur = speed_to_duration(speed) * levels;
803 lose_ench_duration(entry.first, actdur);
804 break;
805 }
806
807 case ENCH_SLOWLY_DYING:
808 {
809 const int actdur = speed_to_duration(speed) * levels;
810 if (lose_ench_duration(entry.first, actdur))
811 monster_die(*this, KILL_MISC, NON_MONSTER, true);
812 break;
813 }
814
815 default:
816 break;
817 }
818
819 if (!alive())
820 break;
821 }
822 }
823
824 /**
825 * Update the level upon the player's return.
826 *
827 * @param elapsedTime how long the player was away.
828 */
update_level(int elapsedTime)829 void update_level(int elapsedTime)
830 {
831 ASSERT(!crawl_state.game_is_arena());
832
833 const int turns = elapsedTime / 10;
834
835 #ifdef DEBUG_DIAGNOSTICS
836 int mons_total = 0;
837
838 dprf("turns: %d", turns);
839 #endif
840
841 rot_corpses(elapsedTime);
842 shoals_apply_tides(turns, true);
843 timeout_tombs(turns);
844 timeout_terrain_changes(elapsedTime);
845
846 if (env.sanctuary_time)
847 {
848 if (turns >= env.sanctuary_time)
849 remove_sanctuary();
850 else
851 env.sanctuary_time -= turns;
852 }
853
854 dungeon_events.fire_event(
855 dgn_event(DET_TURN_ELAPSED, coord_def(0, 0), turns * 10));
856
857 for (monster_iterator mi; mi; ++mi)
858 {
859 #ifdef DEBUG_DIAGNOSTICS
860 mons_total++;
861 #endif
862
863 if (!update_monster(**mi, turns))
864 continue;
865 }
866
867 #ifdef DEBUG_DIAGNOSTICS
868 dprf("total monsters on level = %d", mons_total);
869 #endif
870
871 delete_all_clouds();
872 }
873
874 /**
875 * Update the monster upon the player's return
876 *
877 * @param mon The monster to update.
878 * @param turns How many turns (not auts) since the monster left the player
879 * @returns Returns nullptr if monster was destroyed by the update;
880 * Returns the updated monster if it still exists.
881 */
update_monster(monster & mon,int turns)882 monster* update_monster(monster& mon, int turns)
883 {
884 // Pacified monsters often leave the level now.
885 if (mon.pacified() && turns > random2(40) + 21)
886 {
887 make_mons_leave_level(&mon);
888 return nullptr;
889 }
890
891 // Ignore monsters flagged to skip their next action
892 if (mon.flags & MF_JUST_SUMMONED)
893 return &mon;
894
895 // XXX: Allow some spellcasting (like Healing and Teleport)? - bwr
896 // const bool healthy = (mon->hit_points * 2 > mon->max_hit_points);
897
898 mon.heal(div_rand_round(turns * mon.off_level_regen_rate(), 100));
899
900 // Handle nets specially to remove the trapping property of the net.
901 if (mon.caught())
902 mon.del_ench(ENCH_HELD, true);
903
904 _catchup_monster_moves(&mon, turns);
905
906 mon.foe_memory = max(mon.foe_memory - turns, 0);
907
908 // FIXME: Convert literal string 10 to constant to convert to auts
909 if (turns >= 10 && mon.alive())
910 mon.timeout_enchantments(turns / 10);
911
912 return &mon;
913 }
914
_drop_tomb(const coord_def & pos,bool premature,bool zin)915 static void _drop_tomb(const coord_def& pos, bool premature, bool zin)
916 {
917 int count = 0;
918 monster* mon = monster_at(pos);
919
920 // Don't wander on duty!
921 if (mon)
922 mon->behaviour = BEH_SEEK;
923
924 bool seen_change = false;
925 for (adjacent_iterator ai(pos); ai; ++ai)
926 {
927 // "Normal" tomb (card or monster spell)
928 if (!zin && revert_terrain_change(*ai, TERRAIN_CHANGE_TOMB))
929 {
930 count++;
931 if (you.see_cell(*ai))
932 seen_change = true;
933 }
934 // Zin's Imprison.
935 else if (zin && revert_terrain_change(*ai, TERRAIN_CHANGE_IMPRISON))
936 {
937 for (map_marker *mark : env.markers.get_markers_at(*ai))
938 {
939 if (mark->property("feature_description")
940 == "a gleaming silver wall")
941 {
942 env.markers.remove(mark);
943 }
944 }
945
946 env.grid_colours(*ai) = 0;
947 tile_clear_flavour(*ai);
948 tile_init_flavour(*ai);
949 count++;
950 if (you.see_cell(*ai))
951 seen_change = true;
952 }
953 }
954
955 if (count)
956 {
957 if (seen_change && !zin)
958 mprf("The walls disappear%s!", premature ? " prematurely" : "");
959 else if (seen_change && zin)
960 {
961 mprf("Zin %s %s %s.",
962 (mon) ? "releases"
963 : "dismisses",
964 (mon) ? mon->name(DESC_THE).c_str()
965 : "the silver walls,",
966 (mon) ? make_stringf("from %s prison",
967 mon->pronoun(PRONOUN_POSSESSIVE).c_str()).c_str()
968 : "but there is nothing inside them");
969 }
970 else
971 {
972 if (!silenced(you.pos()))
973 mprf(MSGCH_SOUND, "You hear a deep rumble.");
974 else
975 mpr("You feel the ground shudder.");
976 }
977 }
978 }
979
_get_malign_gateways()980 static vector<map_malign_gateway_marker*> _get_malign_gateways()
981 {
982 vector<map_malign_gateway_marker*> mm_markers;
983
984 for (map_marker *mark : env.markers.get_all(MAT_MALIGN))
985 {
986 if (mark->get_type() != MAT_MALIGN)
987 continue;
988
989 map_malign_gateway_marker *mmark = dynamic_cast<map_malign_gateway_marker*>(mark);
990
991 mm_markers.push_back(mmark);
992 }
993
994 return mm_markers;
995 }
996
count_malign_gateways()997 int count_malign_gateways()
998 {
999 return _get_malign_gateways().size();
1000 }
1001
timeout_malign_gateways(int duration)1002 void timeout_malign_gateways(int duration)
1003 {
1004 // Passing 0 should allow us to just touch the gateway and see
1005 // if it should decay. This, in theory, should resolve the one
1006 // turn delay between it timing out and being recastable. -due
1007 for (map_malign_gateway_marker *mmark : _get_malign_gateways())
1008 {
1009 if (duration)
1010 mmark->duration -= duration;
1011
1012 if (mmark->duration > 0)
1013 {
1014 const int pow = 3 + random2(10);
1015 const int size = 2 + random2(5);
1016 big_cloud(CLOUD_TLOC_ENERGY, 0, mmark->pos, pow, size);
1017 }
1018 else
1019 {
1020 monster* mons = monster_at(mmark->pos);
1021 if (mmark->monster_summoned && !mons)
1022 {
1023 // The marker hangs around until later.
1024 if (env.grid(mmark->pos) == DNGN_MALIGN_GATEWAY)
1025 env.grid(mmark->pos) = DNGN_FLOOR;
1026
1027 env.markers.remove(mmark);
1028 }
1029 else if (!mmark->monster_summoned && !mons)
1030 {
1031 bool is_player = mmark->is_player;
1032 actor* caster = 0;
1033 if (is_player)
1034 caster = &you;
1035
1036 mgen_data mg = mgen_data(MONS_ELDRITCH_TENTACLE,
1037 mmark->behaviour,
1038 mmark->pos,
1039 MHITNOT,
1040 MG_FORCE_PLACE);
1041 mg.set_summoned(caster, 0, 0, mmark->god);
1042 if (!is_player)
1043 mg.non_actor_summoner = mmark->summoner_string;
1044
1045 if (monster *tentacle = create_monster(mg))
1046 {
1047 tentacle->flags |= MF_NO_REWARD;
1048 tentacle->add_ench(ENCH_PORTAL_TIMER);
1049 int dur = random2avg(mmark->power, 6);
1050 dur -= random2(4); // sequence point between random calls
1051 dur *= 10;
1052 mon_enchant kduration = mon_enchant(ENCH_PORTAL_PACIFIED, 4,
1053 caster, dur);
1054 tentacle->props["base_position"].get_coord()
1055 = tentacle->pos();
1056 tentacle->add_ench(kduration);
1057
1058 mmark->monster_summoned = true;
1059 }
1060 }
1061 }
1062 }
1063 }
1064
timeout_tombs(int duration)1065 void timeout_tombs(int duration)
1066 {
1067 if (!duration)
1068 return;
1069
1070 for (map_marker *mark : env.markers.get_all(MAT_TOMB))
1071 {
1072 if (mark->get_type() != MAT_TOMB)
1073 continue;
1074
1075 map_tomb_marker *cmark = dynamic_cast<map_tomb_marker*>(mark);
1076 cmark->duration -= duration;
1077
1078 // Empty tombs disappear early.
1079 monster* mon_entombed = monster_at(cmark->pos);
1080 bool empty_tomb = !(mon_entombed || you.pos() == cmark->pos);
1081 bool zin = (cmark->source == -GOD_ZIN);
1082
1083 if (cmark->duration <= 0 || empty_tomb)
1084 {
1085 _drop_tomb(cmark->pos, empty_tomb, zin);
1086
1087 monster* mon_src =
1088 !invalid_monster_index(cmark->source) ? &env.mons[cmark->source]
1089 : nullptr;
1090 // A monster's Tomb of Doroklohe spell.
1091 if (mon_src
1092 && mon_src == mon_entombed)
1093 {
1094 mon_src->lose_energy(EUT_SPELL);
1095 }
1096
1097 env.markers.remove(cmark);
1098 }
1099 }
1100 }
1101
timeout_terrain_changes(int duration,bool force)1102 void timeout_terrain_changes(int duration, bool force)
1103 {
1104 if (!duration && !force)
1105 return;
1106
1107 int num_seen[NUM_TERRAIN_CHANGE_TYPES] = {0};
1108 // n.b. unordered_set doesn't work here because pair isn't hashable
1109 set<pair<coord_def, terrain_change_type>> revert;
1110
1111 for (map_marker *mark : env.markers.get_all(MAT_TERRAIN_CHANGE))
1112 {
1113 map_terrain_change_marker *marker =
1114 dynamic_cast<map_terrain_change_marker*>(mark);
1115
1116 if (marker->duration != INFINITE_DURATION)
1117 marker->duration -= duration;
1118
1119 if (marker->change_type == TERRAIN_CHANGE_DOOR_SEAL
1120 && !feat_is_sealed(env.grid(marker->pos)))
1121 {
1122 // TODO: could this be done inside `revert_terrain_change`? The
1123 // two things to test are corrupting sealed doors, and destroying
1124 // sealed doors. See 7aedcd24e1be3ed58fef9542786c1a194e4c07d0 and
1125 // 6c286a4f22bcba4cfcb36053eb066367874be752.
1126 if (marker->duration <= 0)
1127 env.markers.remove(marker); // deletes `marker`
1128 continue;
1129 }
1130
1131 if (marker->change_type == TERRAIN_CHANGE_BOG
1132 && !you.see_cell(marker->pos))
1133 {
1134 marker->duration = 0;
1135 }
1136
1137 monster* mon_src = monster_by_mid(marker->mon_num);
1138 if (marker->duration <= 0
1139 || (marker->mon_num != 0
1140 && (!mon_src || !mon_src->alive() || mon_src->pacified())))
1141 {
1142 if (you.see_cell(marker->pos))
1143 num_seen[marker->change_type]++;
1144 revert.insert(pair<coord_def, terrain_change_type>(marker->pos,
1145 marker->change_type));
1146 }
1147 }
1148 // finally, revert the changes and delete the markers
1149 for (const auto &m_pos : revert)
1150 revert_terrain_change(m_pos.first, m_pos.second);
1151
1152 if (num_seen[TERRAIN_CHANGE_DOOR_SEAL] > 1)
1153 mpr("The runic seals fade away.");
1154 else if (num_seen[TERRAIN_CHANGE_DOOR_SEAL] > 0)
1155 mpr("The runic seal fades away.");
1156 }
1157
1158 ////////////////////////////////////////////////////////////////////////////
1159 // Living breathing dungeon stuff.
1160 //
1161
1162 static vector<coord_def> sfx_seeds;
1163
setup_environment_effects()1164 void setup_environment_effects()
1165 {
1166 sfx_seeds.clear();
1167
1168 for (int x = X_BOUND_1; x <= X_BOUND_2; ++x)
1169 {
1170 for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y)
1171 {
1172 if (!in_bounds(x, y))
1173 continue;
1174
1175 const int grid = env.grid[x][y];
1176 if (grid == DNGN_LAVA
1177 || (grid == DNGN_SHALLOW_WATER
1178 && player_in_branch(BRANCH_SWAMP)))
1179 {
1180 const coord_def c(x, y);
1181 sfx_seeds.push_back(c);
1182 }
1183 }
1184 }
1185 dprf("%u environment effect seeds", (unsigned int)sfx_seeds.size());
1186 }
1187
apply_environment_effect(const coord_def & c)1188 static void apply_environment_effect(const coord_def &c)
1189 {
1190 const dungeon_feature_type grid = env.grid(c);
1191 // Don't apply if if the feature doesn't want it.
1192 if (testbits(env.pgrid(c), FPROP_NO_CLOUD_GEN))
1193 return;
1194 if (grid == DNGN_LAVA)
1195 check_place_cloud(CLOUD_BLACK_SMOKE, c, random_range(4, 8), 0);
1196 else if (one_chance_in(3) && grid == DNGN_SHALLOW_WATER)
1197 check_place_cloud(CLOUD_MIST, c, random_range(2, 5), 0);
1198 }
1199
1200 static const int Base_Sfx_Chance = 5;
run_environment_effects()1201 void run_environment_effects()
1202 {
1203 if (!you.time_taken)
1204 return;
1205
1206 dungeon_events.fire_event(DET_TURN_ELAPSED);
1207
1208 // Each square in sfx_seeds has this chance of doing something special
1209 // per turn.
1210 const int sfx_chance = Base_Sfx_Chance * you.time_taken / 10;
1211 const int nseeds = sfx_seeds.size();
1212
1213 // If there are a large number of seeds, speed things up by fudging the
1214 // numbers.
1215 if (nseeds > 50)
1216 {
1217 int nsels = div_rand_round(sfx_seeds.size() * sfx_chance, 100);
1218 if (one_chance_in(5))
1219 nsels += random2(nsels * 3);
1220
1221 for (int i = 0; i < nsels; ++i)
1222 apply_environment_effect(sfx_seeds[ random2(nseeds) ]);
1223 }
1224 else
1225 {
1226 for (int i = 0; i < nseeds; ++i)
1227 {
1228 if (random2(100) >= sfx_chance)
1229 continue;
1230
1231 apply_environment_effect(sfx_seeds[i]);
1232 }
1233 }
1234
1235 run_corruption_effects(you.time_taken);
1236 shoals_apply_tides(div_rand_round(you.time_taken, BASELINE_DELAY),
1237 false);
1238 timeout_tombs(you.time_taken);
1239 timeout_malign_gateways(you.time_taken);
1240 timeout_terrain_changes(you.time_taken);
1241 run_cloud_spreaders(you.time_taken);
1242 }
1243
1244 // Converts a movement speed to a duration. i.e., answers the
1245 // question: if the monster is so fast, how much time has it spent in
1246 // its last movement?
1247 //
1248 // If speed is 10 (normal), one movement is a duration of 10.
1249 // If speed is 1 (very slow), each movement is a duration of 100.
1250 // If speed is 15 (50% faster than normal), each movement is a duration of
1251 // 6.6667.
speed_to_duration(int speed)1252 int speed_to_duration(int speed)
1253 {
1254 if (speed < 1)
1255 speed = 10;
1256 else if (speed > 100)
1257 speed = 100;
1258
1259 return div_rand_round(100, speed);
1260 }
1261
1262 #if TAG_MAJOR_VERSION == 34
_old_zot_clock(const string & branch_name)1263 static int _old_zot_clock(const string& branch_name) {
1264 // The old clock was measured in turns (deca-auts), not aut.
1265 static const string OLD_KEY = "ZOT_CLOCK";
1266 if (!you.props.exists(OLD_KEY))
1267 return -1;
1268 CrawlHashTable &branch_clock = you.props[OLD_KEY];
1269 if (!branch_clock.exists(branch_name))
1270 return -1;
1271 return branch_clock[branch_name].get_int();
1272 }
1273 #endif
1274
1275 // Returns -1 if the player hasn't been in this branch before.
_zot_clock_for(branch_type br)1276 static int& _zot_clock_for(branch_type br)
1277 {
1278 CrawlHashTable &branch_clock = you.props["ZOT_AUTS"];
1279 const string branch_name = is_hell_branch(br) ? "Hells" : branches[br].abbrevname;
1280 // When entering a new branch, start with an empty clock.
1281 // (You'll get the usual time when you finish entering.)
1282 if (!branch_clock.exists(branch_name))
1283 {
1284 #if TAG_MAJOR_VERSION == 34
1285 // The old clock was measured in turns (deca-auts), not aut.
1286 const int old_clock = _old_zot_clock(branch_name);
1287 if (old_clock != -1)
1288 branch_clock[branch_name].get_int() = old_clock;
1289 else
1290 #endif
1291 branch_clock[branch_name].get_int() = -1;
1292 }
1293 return branch_clock[branch_name].get_int();
1294 }
1295
_zot_clock()1296 static int& _zot_clock()
1297 {
1298 return _zot_clock_for(you.where_are_you);
1299 }
1300
_zot_clock_active_in(branch_type br)1301 static bool _zot_clock_active_in(branch_type br)
1302 {
1303 return br != BRANCH_ABYSS && !zot_immune() && !crawl_state.game_is_sprint();
1304 }
1305
1306 // Is the zot clock running, or is it paused or stopped altogether?
zot_clock_active()1307 bool zot_clock_active()
1308 {
1309 return _zot_clock_active_in(you.where_are_you);
1310 }
1311
1312 // Has the player stopped the zot clock?
zot_immune()1313 bool zot_immune()
1314 {
1315 return player_has_orb() || you.zigs_completed;
1316 }
1317
turns_until_zot_in(branch_type br)1318 int turns_until_zot_in(branch_type br)
1319 {
1320 const int aut = (MAX_ZOT_CLOCK - _zot_clock_for(br));
1321 if (have_passive(passive_t::slow_zot))
1322 return aut * 3 / (2 * BASELINE_DELAY);
1323 return aut / BASELINE_DELAY;
1324 }
1325
1326 // How many turns (deca-auts) does the player have until Zot finds them?
turns_until_zot()1327 int turns_until_zot()
1328 {
1329 return turns_until_zot_in(you.where_are_you);
1330 }
1331
1332 // A scale from 0 to 4 of how much danger the player is in of
1333 // reaching the end of the zot clock. 0 is no danger, 4 is dead.
_bezotting_level_in(branch_type br)1334 static int _bezotting_level_in(branch_type br)
1335 {
1336 if (!_zot_clock_active_in(br))
1337 return 0;
1338
1339 const int remaining_turns = turns_until_zot_in(br);
1340 if (remaining_turns <= 0)
1341 return 4;
1342 if (remaining_turns < 100)
1343 return 3;
1344 if (remaining_turns < 500)
1345 return 2;
1346 if (remaining_turns < 1000)
1347 return 1;
1348 return 0;
1349 }
1350
1351 // A scale from 0 to 4 of how much danger the player is in of
1352 // reaching the end of the zot clock in their current branch.
bezotting_level()1353 int bezotting_level()
1354 {
1355 return _bezotting_level_in(you.where_are_you);
1356 }
1357
1358 // If the player was in the given branch, would they see warnings for
1359 // nearing the end of the zot clock?
bezotted_in(branch_type br)1360 bool bezotted_in(branch_type br)
1361 {
1362 return _bezotting_level_in(br) > 0;
1363 }
1364
1365 // Is the player seeing warnings about nearing the end of the zot clock?
bezotted()1366 bool bezotted()
1367 {
1368 return bezotted_in(you.where_are_you);
1369 }
1370
1371 // Decrease the zot clock when the player enters a new level.
decr_zot_clock()1372 void decr_zot_clock()
1373 {
1374 if (!zot_clock_active())
1375 return;
1376 int &zot = _zot_clock();
1377 if (zot == -1)
1378 {
1379 // new branch
1380 zot = MAX_ZOT_CLOCK - ZOT_CLOCK_PER_FLOOR;
1381 }
1382 else
1383 {
1384 // old branch, new floor
1385 if (bezotted())
1386 mpr("As you enter the new level, Zot loses track of you.");
1387 zot = max(0, zot - ZOT_CLOCK_PER_FLOOR);
1388 }
1389 }
1390
_added_zot_time()1391 static int _added_zot_time()
1392 {
1393 if (have_passive(passive_t::slow_zot))
1394 return div_rand_round(you.time_taken * 2, 3);
1395 return you.time_taken;
1396 }
1397
incr_zot_clock()1398 void incr_zot_clock()
1399 {
1400 if (!zot_clock_active())
1401 return;
1402
1403 const int old_lvl = bezotting_level();
1404 _zot_clock() += _added_zot_time();
1405 if (!bezotted())
1406 return;
1407
1408 if (_zot_clock() >= MAX_ZOT_CLOCK)
1409 {
1410 mpr("Zot has found you!");
1411 ouch(INSTANT_DEATH, KILLED_BY_ZOT);
1412 return;
1413 }
1414
1415 const int lvl = bezotting_level();
1416 if (old_lvl >= lvl)
1417 return;
1418
1419 switch (lvl)
1420 {
1421 case 1:
1422 mpr("You have lingered too long. Zot senses you. Dive deeper or flee this branch before you perish!");
1423 break;
1424 case 2:
1425 mpr("Zot draws nearer. Dive deeper or flee this branch before you perish!");
1426 break;
1427 case 3:
1428 mpr("Zot has nearly found you. Death is approaching. Descend or flee this branch!");
1429 break;
1430 }
1431
1432 take_note(Note(NOTE_MESSAGE, 0, 0, "Glimpsed the power of Zot."));
1433 interrupt_activity(activity_interrupt::force);
1434 }
1435
set_turns_until_zot(int turns_left)1436 void set_turns_until_zot(int turns_left)
1437 {
1438 if (turns_left < 0 || turns_left > MAX_ZOT_CLOCK / BASELINE_DELAY)
1439 return;
1440
1441 int &clock = _zot_clock();
1442 clock = MAX_ZOT_CLOCK - turns_left * BASELINE_DELAY;
1443 }
1444