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