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