1 /**
2 * @file
3 * @brief Tracking effects that affect areas for durations.
4 * Silence, sanctuary, halos, ...
5 **/
6
7 #include "AppHdr.h"
8
9 #include "areas.h"
10
11 #include "act-iter.h"
12 #include "artefact.h"
13 #include "art-enum.h"
14 #include "cloud.h"
15 #include "coordit.h"
16 #include "env.h"
17 #include "fprop.h"
18 #include "god-abil.h"
19 #include "god-conduct.h"
20 #include "god-passive.h" // passive_t::umbra
21 #include "libutil.h"
22 #include "losglobal.h"
23 #include "message.h"
24 #include "mon-behv.h"
25 #include "mutation.h"
26 #include "religion.h"
27 #include "stepdown.h"
28 #include "terrain.h"
29 #include "traps.h"
30 #include "travel.h"
31
32 /// Bitmasks for area properties
33 enum class areaprop
34 {
35 sanctuary_1 = (1 << 0),
36 sanctuary_2 = (1 << 1),
37 silence = (1 << 2),
38 halo = (1 << 3),
39 liquid = (1 << 4),
40 actual_liquid = (1 << 5),
41 orb = (1 << 6), ///< The glow of the Orb of Zot
42 umbra = (1 << 7),
43 quad = (1 << 8),
44 disjunction = (1 << 9),
45 soul_aura = (1 << 10),
46 };
47 /// Bit field for the area properties
48 DEF_BITFIELD(areaprops, areaprop);
49
50 /// Center of an area effect
51 struct area_centre
52 {
53 area_centre_type type;
54 coord_def centre;
55 int radius;
56
area_centrearea_centre57 explicit area_centre (area_centre_type t, coord_def c, int r) : type(t), centre(c), radius(r) { }
58 };
59
60 typedef FixedArray<areaprops, GXM, GYM> propgrid_t;
61
62 /// The area center cache. Contains centers of all area effects.
63 static vector<area_centre> _agrid_centres;
64
65 static propgrid_t _agrid; ///< The area grid cache
66 /// \brief Is the area grid cache up-to-date?
67 /// \details If false, each check for area effects that affect a coordinate
68 /// would trigger an update of the area grid cache.
69 static bool _agrid_valid = false;
70 /// \brief If true, the level has no area effect
71 static bool no_areas = false;
72
_set_agrid_flag(const coord_def & p,areaprop f)73 static void _set_agrid_flag(const coord_def& p, areaprop f)
74 {
75 _agrid(p) |= f;
76 }
77
_check_agrid_flag(const coord_def & p,areaprop f)78 static bool _check_agrid_flag(const coord_def& p, areaprop f)
79 {
80 return bool(_agrid(p) & f);
81 }
82
83 /// \brief Invalidates the area effect cache
84 /// \details Invalidates the area effect cache, causing the next request for
85 /// area effects to re-calculate which locations are covered by halos, etc.
86 /// If \p recheck_new is false, the cache will only be invalidated if the level
87 /// had existing area effects.
invalidate_agrid(bool recheck_new)88 void invalidate_agrid(bool recheck_new)
89 {
90 _agrid_valid = false;
91 if (recheck_new)
92 no_areas = false;
93 }
94
areas_actor_moved(const actor * act,const coord_def & oldpos)95 void areas_actor_moved(const actor* act, const coord_def& oldpos)
96 {
97 UNUSED(oldpos);
98 if (act->alive() &&
99 (you.entering_level
100 || act->halo_radius() > -1 || act->silence_radius() > -1
101 || act->liquefying_radius() > -1 || act->umbra_radius() > -1
102 || act->demon_silence_radius() > -1))
103 {
104 // Not necessarily new, but certainly potentially interesting.
105 invalidate_agrid(true);
106 }
107 }
108
109 /// \brief Add some of the actor's area effects to the grid and center caches
110 /// \param actor The actor
111 /// \details Adds some but not all of an actor's area effects (e.g. silence)
112 /// to the area grid (\ref _agrid) and center (\ref _agrid_centres) caches.
113 /// Sets \ref no_areas to false if the actor generates those area effects.
_actor_areas(actor * a)114 static void _actor_areas(actor *a)
115 {
116 int r;
117
118 if ((r = a->silence_radius()) >= 0)
119 {
120 _agrid_centres.emplace_back(area_centre_type::silence, a->pos(), r);
121
122 for (radius_iterator ri(a->pos(), r, C_SQUARE); ri; ++ri)
123 _set_agrid_flag(*ri, areaprop::silence);
124 no_areas = false;
125 }
126
127 if ((r = a->demon_silence_radius()) >= 0)
128 {
129 _agrid_centres.emplace_back(area_centre_type::silence, a->pos(), r);
130
131 for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_DEFAULT, true); ri; ++ri)
132 _set_agrid_flag(*ri, areaprop::silence);
133 no_areas = false;
134 }
135
136 if ((r = a->halo_radius()) >= 0)
137 {
138 _agrid_centres.emplace_back(area_centre_type::halo, a->pos(), r);
139
140 for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri)
141 _set_agrid_flag(*ri, areaprop::halo);
142 no_areas = false;
143 }
144
145 if ((r = a->liquefying_radius()) >= 0)
146 {
147 _agrid_centres.emplace_back(area_centre_type::liquid, a->pos(), r);
148
149 for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_SOLID); ri; ++ri)
150 {
151 dungeon_feature_type f = env.grid(*ri);
152
153 _set_agrid_flag(*ri, areaprop::liquid);
154
155 if (feat_has_solid_floor(f) && !feat_is_water(f))
156 _set_agrid_flag(*ri, areaprop::actual_liquid);
157 }
158 no_areas = false;
159 }
160
161 if ((r = a->umbra_radius()) >= 0)
162 {
163 _agrid_centres.emplace_back(area_centre_type::umbra, a->pos(), r);
164
165 for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri)
166 _set_agrid_flag(*ri, areaprop::umbra);
167 no_areas = false;
168 }
169 }
170
171 /**
172 * Update the area grid cache.
173 *
174 * Updates the _agrid FixedArray of grid information flags using the
175 * areaprop types.
176 */
_update_agrid()177 static void _update_agrid()
178 {
179 // sanitize rng in case this gets indirectly called by the builder.
180 rng::generator gameplay(rng::GAMEPLAY);
181
182 if (no_areas)
183 {
184 _agrid_valid = true;
185 return;
186 }
187
188 _agrid.init(areaprops());
189 _agrid_centres.clear();
190
191 no_areas = true;
192
193 _actor_areas(&you);
194 for (monster_iterator mi; mi; ++mi)
195 _actor_areas(*mi);
196
197 if (player_has_orb() && !you.pos().origin())
198 {
199 const int r = 2;
200 _agrid_centres.emplace_back(area_centre_type::orb, you.pos(), r);
201 for (radius_iterator ri(you.pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri)
202 _set_agrid_flag(*ri, areaprop::orb);
203 no_areas = false;
204 }
205
206 if (you.duration[DUR_QUAD_DAMAGE])
207 {
208 const int r = 2;
209 _agrid_centres.emplace_back(area_centre_type::quad, you.pos(), r);
210 for (radius_iterator ri(you.pos(), r, C_SQUARE);
211 ri; ++ri)
212 {
213 if (cell_see_cell(you.pos(), *ri, LOS_DEFAULT))
214 _set_agrid_flag(*ri, areaprop::quad);
215 }
216 no_areas = false;
217 }
218
219 if (you.duration[DUR_DISJUNCTION])
220 {
221 const int r = 4;
222 _agrid_centres.emplace_back(area_centre_type::disjunction,
223 you.pos(), r);
224 for (radius_iterator ri(you.pos(), r, C_SQUARE);
225 ri; ++ri)
226 {
227 if (cell_see_cell(you.pos(), *ri, LOS_DEFAULT))
228 _set_agrid_flag(*ri, areaprop::disjunction);
229 }
230 no_areas = false;
231 }
232
233 // TODO: update sanctuary here.
234
235 _agrid_valid = true;
236 }
237
_get_first_area(const coord_def & f)238 static area_centre_type _get_first_area(const coord_def& f)
239 {
240 areaprops a = _agrid(f);
241 if (a & areaprop::sanctuary_1)
242 return area_centre_type::sanctuary;
243 if (a & areaprop::sanctuary_2)
244 return area_centre_type::sanctuary;
245 if (a & areaprop::silence)
246 return area_centre_type::silence;
247 if (a & areaprop::halo)
248 return area_centre_type::halo;
249 if (a & areaprop::umbra)
250 return area_centre_type::umbra;
251 // liquid is always applied; actual_liquid is on top
252 // of this. If we find the first, we don't care about
253 // the second.
254 if (a & areaprop::liquid)
255 return area_centre_type::liquid;
256
257 return area_centre_type::none;
258 }
259
find_centre_for(const coord_def & f,area_centre_type at)260 coord_def find_centre_for(const coord_def& f, area_centre_type at)
261 {
262 if (!map_bounds(f))
263 return coord_def(-1, -1);
264
265 if (!_agrid_valid)
266 _update_agrid();
267
268 if (!_agrid(f))
269 return coord_def(-1, -1);
270
271 if (_agrid_centres.empty())
272 return coord_def(-1, -1);
273
274 coord_def possible = coord_def(-1, -1);
275 int dist = 0;
276
277 // Unspecified area type; settle for the first valid one.
278 // We checked for no aprop a bit ago.
279 if (at == area_centre_type::none)
280 at = _get_first_area(f);
281
282 // on the off chance that there is an error, assert here
283 ASSERT(at != area_centre_type::none);
284
285 for (const area_centre &a : _agrid_centres)
286 {
287 if (a.type != at)
288 continue;
289
290 if (a.centre == f)
291 return f;
292
293 int d = grid_distance(a.centre, f);
294 if (d <= a.radius && (d <= dist || dist == 0))
295 {
296 possible = a.centre;
297 dist = d;
298 }
299 }
300
301 return possible;
302 }
303
304 ///////////////
305 // Sanctuary
306
_remove_sanctuary_property(const coord_def & where)307 static void _remove_sanctuary_property(const coord_def& where)
308 {
309 env.pgrid(where) &= ~(FPROP_SANCTUARY_1 | FPROP_SANCTUARY_2);
310 }
311
sanctuary_exists()312 bool sanctuary_exists()
313 {
314 return in_bounds(env.sanctuary_pos);
315 }
316
317 /*
318 * Remove any sanctuary from the level.
319 *
320 * @param did_attack If true, the sanctuary removal was the result of a player
321 * attack, so we apply penance. Otherwise the sanctuary is
322 * removed with no penance.
323 * @returns True if we removed an existing sanctuary, false otherwise.
324 */
remove_sanctuary(bool did_attack)325 bool remove_sanctuary(bool did_attack)
326 {
327 if (env.sanctuary_time)
328 env.sanctuary_time = 0;
329
330 if (!sanctuary_exists())
331 return false;
332
333 const int radius = 4;
334 bool seen_change = false;
335 for (rectangle_iterator ri(env.sanctuary_pos, radius, true); ri; ++ri)
336 if (is_sanctuary(*ri))
337 {
338 _remove_sanctuary_property(*ri);
339 if (you.see_cell(*ri))
340 seen_change = true;
341 }
342
343 env.sanctuary_pos.set(-1, -1);
344
345 if (did_attack)
346 {
347 if (seen_change)
348 simple_god_message(" revokes the gift of sanctuary.", GOD_ZIN);
349 did_god_conduct(DID_ATTACK_IN_SANCTUARY, 3);
350 }
351
352 // Now that the sanctuary is gone, monsters aren't afraid of it
353 // anymore.
354 for (monster_iterator mi; mi; ++mi)
355 mons_stop_fleeing_from_sanctuary(**mi);
356
357 if (is_resting())
358 stop_running();
359
360 return true;
361 }
362
363 // For the last (radius) counter turns the sanctuary will slowly shrink.
decrease_sanctuary_radius()364 void decrease_sanctuary_radius()
365 {
366 const int radius = 4;
367
368 // For the last (radius-1) turns 33% chance of not decreasing.
369 if (env.sanctuary_time < radius && one_chance_in(3))
370 return;
371
372 int size = --env.sanctuary_time;
373 if (size >= radius)
374 return;
375
376 if (you.running && is_sanctuary(you.pos()))
377 {
378 mprf(MSGCH_DURATION, "The sanctuary starts shrinking.");
379 stop_running();
380 }
381
382 for (rectangle_iterator ri(env.sanctuary_pos, size+1, true); ri; ++ri)
383 {
384 int dist = grid_distance(*ri, env.sanctuary_pos);
385
386 // If necessary overwrite sanctuary property.
387 if (dist > size)
388 _remove_sanctuary_property(*ri);
389 }
390
391 // Special case for time-out of sanctuary.
392 if (!size)
393 {
394 _remove_sanctuary_property(env.sanctuary_pos);
395 if (you.see_cell(env.sanctuary_pos))
396 mprf(MSGCH_DURATION, "The sanctuary disappears.");
397 }
398 }
399
create_sanctuary(const coord_def & center,int time)400 void create_sanctuary(const coord_def& center, int time)
401 {
402 env.sanctuary_pos = center;
403 env.sanctuary_time = time;
404
405 // radius could also be influenced by Inv
406 // and would then have to be stored globally.
407 const int radius = 4;
408 int blood_count = 0;
409 int scare_count = 0;
410 int cloud_count = 0;
411 monster* seen_mon = nullptr;
412
413 int shape = random2(4);
414 for (radius_iterator ri(center, radius, C_SQUARE); ri; ++ri)
415 {
416 const coord_def pos = *ri;
417 const int dist = grid_distance(center, pos);
418
419 if (testbits(env.pgrid(pos), FPROP_BLOODY) && you.see_cell(pos))
420 blood_count++;
421
422 // forming patterns
423 const int x = pos.x - center.x, y = pos.y - center.y;
424 bool in_yellow = false;
425 switch (shape)
426 {
427 case 0: // outward rays
428 in_yellow = (x == 0 || y == 0 || x == y || x == -y);
429 break;
430 case 1: // circles
431 in_yellow = (dist == radius
432 || dist == radius/2);
433 break;
434 case 2: // latticed
435 in_yellow = (x%2 == 0 || y%2 == 0);
436 break;
437 case 3: // cross-like
438 in_yellow = (abs(x)+abs(y) < 5 && x != y && x != -y);
439 break;
440 default:
441 break;
442 }
443
444 env.pgrid(pos) |= (in_yellow ? FPROP_SANCTUARY_1
445 : FPROP_SANCTUARY_2);
446
447 env.pgrid(pos) &= ~(FPROP_BLOODY);
448
449 // Scare all attacking monsters inside sanctuary, and make
450 // all friendly monsters inside sanctuary stop attacking and
451 // move towards the player.
452 if (monster* mon = monster_at(pos))
453 {
454 if (mon->friendly())
455 {
456 mon->foe = MHITYOU;
457 mon->target = center;
458 mon->behaviour = BEH_SEEK;
459 behaviour_event(mon, ME_EVAL, &you);
460 }
461 else if (!mon->wont_attack() && mons_is_influenced_by_sanctuary(*mon))
462 {
463 mons_start_fleeing_from_sanctuary(*mon);
464
465 // Check to see that monster is actually fleeing.
466 if (mons_is_fleeing(*mon) && you.can_see(*mon))
467 {
468 scare_count++;
469 seen_mon = mon;
470 }
471 }
472 }
473
474 if (!is_harmless_cloud(cloud_type_at(pos)))
475 {
476 delete_cloud(pos);
477 if (you.see_cell(pos))
478 cloud_count++;
479 }
480 } // radius loop
481
482 // Messaging.
483 if (cloud_count == 1)
484 {
485 mprf(MSGCH_GOD, "By Zin's power, the foul cloud within the sanctuary "
486 "is swept away.");
487 }
488 else if (cloud_count > 1)
489 {
490 mprf(MSGCH_GOD, "By Zin's power, all foul fumes within the sanctuary "
491 "are swept away.");
492 }
493
494 if (blood_count > 0)
495 mprf(MSGCH_GOD, "By Zin's power, all blood is cleared from the sanctuary.");
496
497 if (scare_count == 1 && seen_mon != nullptr)
498 simple_monster_message(*seen_mon, " turns to flee the light!");
499 else if (scare_count > 0)
500 mpr("The monsters scatter in all directions!");
501 }
502
503 // Range calculation for spells whose radius shrinks over time with remaining
504 // duration.
505 // dur starts at 10 (low power) and is capped at 100
506 // maximal range: 5
507 // last 6 turns: range 0, hence only the player affected
_shrinking_aoe_range(int dur)508 static int _shrinking_aoe_range(int dur)
509 {
510 if (dur <= 0)
511 return -1;
512 dur /= BASELINE_DELAY; // now roughly number of turns
513 return isqrt(max(0, min(3*(dur - 5)/4, 25)));
514 }
515
516 /////////////
517 // Silence
518
silence_radius() const519 int player::silence_radius() const
520 {
521 return _shrinking_aoe_range(duration[DUR_SILENCE]);
522 }
523
demon_silence_radius() const524 int player::demon_silence_radius() const
525 {
526 if (you.get_mutation_level(MUT_SILENCE_AURA))
527 return 1;
528 return -1;
529 }
530
silence_radius() const531 int monster::silence_radius() const
532 {
533 if (type == MONS_SILENT_SPECTRE)
534 return 10;
535
536 if (!has_ench(ENCH_SILENCE))
537 return -1;
538
539 const int dur = get_ench(ENCH_SILENCE).duration;
540 // The below is arbitrarily chosen to make monster decay look reasonable.
541 const int moddur = BASELINE_DELAY
542 * max(7, stepdown_value(dur * 10 - 60, 10, 5, 45, 100));
543 return _shrinking_aoe_range(moddur);
544 }
545
demon_silence_radius() const546 int monster::demon_silence_radius() const
547 {
548 return -1;
549 }
550
551 /// Check if a coordinate is silenced
silenced(const coord_def & p)552 bool silenced(const coord_def& p)
553 {
554 if (!map_bounds(p))
555 return false;
556 if (!_agrid_valid)
557 _update_agrid();
558 return _check_agrid_flag(p, areaprop::silence);
559 }
560
561 /////////////
562 // Halos
563
haloed(const coord_def & p)564 bool haloed(const coord_def& p)
565 {
566 if (!map_bounds(p))
567 return false;
568 if (!_agrid_valid)
569 _update_agrid();
570 return _check_agrid_flag(p, areaprop::halo);
571 }
572
haloed() const573 bool actor::haloed() const
574 {
575 return ::haloed(pos());
576 }
577
halo_radius() const578 int player::halo_radius() const
579 {
580 int size = -1;
581
582 if (have_passive(passive_t::halo))
583 {
584 // The cap is reached at piety 160 = ******.
585 size = min((int)piety, piety_breakpoint(5)) * you.normal_vision
586 / piety_breakpoint(5);
587 }
588
589 if (player_equip_unrand(UNRAND_EOS))
590 size = max(size, 3);
591 else if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
592 size = max(size, 2);
593
594 return size;
595 }
596
_mons_class_halo_radius(monster_type type)597 static int _mons_class_halo_radius(monster_type type)
598 {
599 // The values here depend on 1. power, 2. sentience. Thus, high-ranked
600 // sentient celestials have really big haloes, while holy animals get
601 // little or none.
602 switch (type)
603 {
604 case MONS_ANGEL:
605 return 4;
606 case MONS_CHERUB:
607 return 4;
608 case MONS_DAEVA:
609 return 4;
610 case MONS_OPHAN:
611 return 6;
612 case MONS_SERAPH:
613 return 7; // highest rank among sentient ones
614 case MONS_HOLY_SWINE:
615 return 1; // only notionally holy
616 case MONS_MENNAS:
617 return 2; // ??? Low on grace or what?
618 default:
619 return -1;
620 }
621 }
622
halo_radius() const623 int monster::halo_radius() const
624 {
625 item_def* weap = mslot_item(MSLOT_WEAPON);
626 int size = -1;
627
628 if (weap && is_unrandom_artefact(*weap, UNRAND_EOS))
629 size = 3;
630
631 if (!(holiness() & MH_HOLY))
632 return size;
633
634 return _mons_class_halo_radius(type);
635 }
636
637 //////////////////////
638 // Leda's Liquefaction
639 //
640
liquefying_radius() const641 int player::liquefying_radius() const
642 {
643 return _shrinking_aoe_range(duration[DUR_LIQUEFYING]);
644 }
645
liquefying_radius() const646 int monster::liquefying_radius() const
647 {
648 if (!has_ench(ENCH_LIQUEFYING))
649 return -1;
650 const int dur = get_ench(ENCH_LIQUEFYING).duration;
651 // The below is arbitrarily chosen to make monster decay look reasonable.
652 const int moddur = BASELINE_DELAY *
653 max(7, stepdown_value(dur * 10 - 60, 10, 5, 45, 100));
654 return _shrinking_aoe_range(moddur);
655 }
656
liquefied(const coord_def & p,bool check_actual)657 bool liquefied(const coord_def& p, bool check_actual)
658 {
659 if (!map_bounds(p))
660 return false;
661
662 if (!_agrid_valid)
663 _update_agrid();
664
665 if (feat_is_water(env.grid(p)) || feat_is_lava(env.grid(p)))
666 return false;
667
668 // "actually" liquefied (ie, check for movement)
669 if (check_actual)
670 return _check_agrid_flag(p, areaprop::actual_liquid);
671 // just recoloured for consistency
672 else
673 return _check_agrid_flag(p, areaprop::liquid);
674 }
675
676 /////////////
677 // Orb's glow
678 //
679
orb_haloed(const coord_def & p)680 bool orb_haloed(const coord_def& p)
681 {
682 if (!map_bounds(p))
683 return false;
684 if (!_agrid_valid)
685 _update_agrid();
686
687 return _check_agrid_flag(p, areaprop::orb);
688 }
689
690 /////////////
691 // Quad damage glow
692 //
693
quad_haloed(const coord_def & p)694 bool quad_haloed(const coord_def& p)
695 {
696 if (!map_bounds(p))
697 return false;
698 if (!_agrid_valid)
699 _update_agrid();
700
701 return _check_agrid_flag(p, areaprop::quad);
702 }
703
704 /////////////
705 // Disjunction Glow
706 //
707
disjunction_haloed(const coord_def & p)708 bool disjunction_haloed(const coord_def& p)
709 {
710 if (!map_bounds(p))
711 return false;
712 if (!_agrid_valid)
713 _update_agrid();
714
715 return _check_agrid_flag(p, areaprop::disjunction);
716 }
717
718 /////////////
719 // Umbra
720 //
721
umbraed(const coord_def & p)722 bool umbraed(const coord_def& p)
723 {
724 if (!map_bounds(p))
725 return false;
726 if (!_agrid_valid)
727 _update_agrid();
728
729 return _check_agrid_flag(p, areaprop::umbra);
730 }
731
732 // Whether actor is in an umbra.
umbraed() const733 bool actor::umbraed() const
734 {
735 return ::umbraed(pos());
736 }
737
umbra_radius() const738 int player::umbra_radius() const
739 {
740 int size = -1;
741
742 if (have_passive(passive_t::umbra))
743 {
744 // The cap is reached at piety 160 = ******.
745 size = min((int)piety, piety_breakpoint(5)) * you.normal_vision
746 / piety_breakpoint(5);
747 }
748
749 if (player_equip_unrand(UNRAND_SHADOWS))
750 size = max(size, 3);
751
752 return size;
753 }
754
umbra_radius() const755 int monster::umbra_radius() const
756 {
757 item_def* ring = mslot_item(MSLOT_JEWELLERY);
758 if (ring && is_unrandom_artefact(*ring, UNRAND_SHADOWS))
759 return 3;
760
761 if (!(holiness() & MH_UNDEAD))
762 return -1;
763
764 // Enslaved holies get an umbra.
765 if (mons_enslaved_soul(*this))
766 return _mons_class_halo_radius(base_monster);
767
768 switch (type)
769 {
770 case MONS_PROFANE_SERVITOR:
771 return 5; // Very unholy!
772 default:
773 return -1;
774 }
775 }
776