1 /**
2  * @file
3  * @brief All things Xomly
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "xom.h"
9 
10 #include <algorithm>
11 #include <functional>
12 
13 #include "abyss.h"
14 #include "acquire.h"
15 #include "act-iter.h"
16 #include "areas.h"
17 #include "artefact.h"
18 #include "cloud.h"
19 #include "coordit.h"
20 #include "database.h"
21 #ifdef WIZARD
22 #include "dbg-util.h"
23 #endif
24 #include "delay.h"
25 #include "directn.h"
26 #include "english.h"
27 #include "env.h"
28 #include "errors.h"
29 #include "god-item.h"
30 #include "item-name.h"
31 #include "item-prop.h"
32 #include "item-status-flag-type.h"
33 #include "items.h"
34 #include "item-use.h"
35 #include "losglobal.h"
36 #include "makeitem.h"
37 #include "map-knowledge.h"
38 #include "message.h"
39 #include "misc.h"
40 #include "mon-behv.h"
41 #include "mon-death.h"
42 #include "mon-place.h"
43 #include "mon-poly.h"
44 #include "mon-tentacle.h"
45 #include "mutation.h"
46 #include "nearby-danger.h"
47 #include "notes.h"
48 #include "output.h"
49 #include "player-stats.h"
50 #include "potion.h"
51 #include "prompt.h"
52 #include "religion.h"
53 #include "shout.h"
54 #include "spl-clouds.h"
55 #include "spl-goditem.h"
56 #include "spl-monench.h"
57 #include "spl-transloc.h"
58 #include "stairs.h"
59 #include "stash.h"
60 #include "state.h"
61 #include "stepdown.h"
62 #include "stringutil.h"
63 #include "tag-version.h"
64 #include "teleport.h"
65 #include "terrain.h"
66 #include "transform.h"
67 #include "traps.h"
68 #include "travel.h"
69 #include "viewchar.h"
70 #include "view.h"
71 
72 #ifdef DEBUG_XOM
73 #    define DEBUG_RELIGION
74 #    define NOTE_DEBUG_XOM
75 #endif
76 
77 #ifdef DEBUG_RELIGION
78 #    define DEBUG_DIAGNOSTICS
79 #    define DEBUG_GIFTS
80 #endif
81 
82 static void _do_xom_event(xom_event_type event_type, int sever);
83 static int _xom_event_badness(xom_event_type event_type);
84 
_action_is_bad(xom_event_type action)85 static bool _action_is_bad(xom_event_type action)
86 {
87     return action > XOM_LAST_GOOD_ACT && action <= XOM_LAST_BAD_ACT;
88 }
89 
90 // Spells to be cast at tension > 0, i.e. usually in battle situations.
91 // Spells later in the list require higher severity to have a chance of being
92 // selected.
93 static const vector<spell_type> _xom_random_spells =
94 {
95     SPELL_SUMMON_SMALL_MAMMAL,
96     SPELL_CALL_CANINE_FAMILIAR,
97     SPELL_OLGREBS_TOXIC_RADIANCE,
98     SPELL_SUMMON_ICE_BEAST,
99     SPELL_LEDAS_LIQUEFACTION,
100     SPELL_CAUSE_FEAR,
101     SPELL_INTOXICATE,
102     SPELL_SHADOW_CREATURES,
103     SPELL_SUMMON_MANA_VIPER,
104     SPELL_STATUE_FORM,
105     SPELL_DISPERSAL,
106     SPELL_ENGLACIATION,
107     SPELL_DEATH_CHANNEL,
108     SPELL_SUMMON_HYDRA,
109     SPELL_MONSTROUS_MENAGERIE,
110     SPELL_DISCORD,
111     SPELL_DISJUNCTION,
112     SPELL_SUMMON_HORRIBLE_THINGS,
113     SPELL_SUMMON_DRAGON,
114     SPELL_NECROMUTATION,
115     SPELL_CHAIN_OF_CHAOS
116 };
117 
118 static const char *_xom_message_arrays[NUM_XOM_MESSAGE_TYPES][6] =
119 {
120     // XM_NORMAL
121     {
122         "Xom is interested.",
123         "Xom is mildly amused.",
124         "Xom is amused.",
125         "Xom is highly amused!",
126         "Xom thinks this is hilarious!",
127         "Xom roars with laughter!"
128     },
129 
130     // XM_INTRIGUED
131     {
132         "Xom is interested.",
133         "Xom is very interested.",
134         "Xom is extremely interested.",
135         "Xom is intrigued!",
136         "Xom is very intrigued!",
137         "Xom is fascinated!"
138     }
139 };
140 
141 /**
142  * How much does Xom like you right now?
143  *
144  * Doesn't account for boredom, or whether or not you actually worship Xom.
145  *
146  * @return An index mapping to an entry in xom_moods.
147  */
xom_favour_rank()148 int xom_favour_rank()
149 {
150     static const int breakpoints[] = { 20, 50, 80, 120, 150, 180};
151     for (unsigned int i = 0; i < ARRAYSZ(breakpoints); ++i)
152         if (you.piety <= breakpoints[i])
153             return i;
154     return ARRAYSZ(breakpoints);
155 }
156 
157 static const char* xom_moods[] = {
158     "a very special plaything of Xom.",
159     "a special plaything of Xom.",
160     "a plaything of Xom.",
161     "a toy of Xom.",
162     "a favourite toy of Xom.",
163     "a beloved toy of Xom.",
164     "Xom's teddy bear."
165 };
166 
describe_xom_mood()167 static const char *describe_xom_mood()
168 {
169     const int mood = xom_favour_rank();
170     ASSERT(mood >= 0);
171     ASSERT((size_t) mood < ARRAYSZ(xom_moods));
172     return xom_moods[mood];
173 }
174 
describe_xom_favour()175 const string describe_xom_favour()
176 {
177     string favour;
178     if (!you_worship(GOD_XOM))
179         favour = "a very buggy toy of Xom.";
180     else if (you.gift_timeout < 1)
181         favour = "a BORING thing.";
182     else
183         favour = describe_xom_mood();
184 
185     return favour;
186 }
187 
188 #define XOM_SPEECH(x) x
_get_xom_speech(const string & key)189 static string _get_xom_speech(const string &key)
190 {
191     string result = getSpeakString("Xom " + key);
192 
193     if (result.empty())
194         result = getSpeakString("Xom " XOM_SPEECH("general effect"));
195 
196     if (result.empty())
197         return "Xom makes something happen.";
198 
199     return result;
200 }
201 
_xom_is_bored()202 static bool _xom_is_bored()
203 {
204     return you_worship(GOD_XOM) && !you.gift_timeout;
205 }
206 
_xom_feels_nasty()207 static bool _xom_feels_nasty()
208 {
209     // Xom will only directly kill you with a bad effect if you're under
210     // penance from him, or if he's bored.
211     return you.penance[GOD_XOM] || _xom_is_bored();
212 }
213 
xom_is_nice(int tension)214 bool xom_is_nice(int tension)
215 {
216     if (player_under_penance(GOD_XOM))
217         return false;
218 
219     if (you_worship(GOD_XOM))
220     {
221         // If you.gift_timeout is 0, then Xom is BORED. He HATES that.
222         if (!you.gift_timeout)
223             return false;
224 
225         // At high tension Xom is more likely to be nice, at zero
226         // tension the opposite.
227         const int tension_bonus
228             = (tension == -1 ? 0 // :
229 // Xom needs to be less negative
230 //              : tension ==  0 ? -min(abs(HALF_MAX_PIETY - you.piety) / 2,
231 //                                     you.piety / 10)
232                              : min((MAX_PIETY - you.piety) / 2,
233                                    random2(tension)));
234 
235         const int effective_piety = you.piety + tension_bonus;
236         ASSERT_RANGE(effective_piety, 0, MAX_PIETY + 1);
237 
238 #ifdef DEBUG_XOM
239         mprf(MSGCH_DIAGNOSTICS,
240              "Xom: tension: %d, piety: %d -> tension bonus = %d, eff. piety: %d",
241              tension, you.piety, tension_bonus, effective_piety);
242 #endif
243 
244         // Whether Xom is nice depends largely on his mood (== piety).
245         return x_chance_in_y(effective_piety, MAX_PIETY);
246     }
247     else // CARD_XOM
248         return coinflip();
249 }
250 
_xom_is_stimulated(int maxinterestingness,const char * message_array[],bool force_message)251 static void _xom_is_stimulated(int maxinterestingness,
252                                const char *message_array[],
253                                bool force_message)
254 {
255     if (!you_worship(GOD_XOM) || maxinterestingness <= 0)
256         return;
257 
258     // Xom is not directly stimulated by his own acts.
259     if (crawl_state.which_god_acting() == GOD_XOM)
260         return;
261 
262     int interestingness = random2(piety_scale(maxinterestingness));
263 
264     interestingness = min(200, interestingness);
265 
266 #if defined(DEBUG_RELIGION) || defined(DEBUG_GIFTS) || defined(DEBUG_XOM)
267     mprf(MSGCH_DIAGNOSTICS,
268          "Xom: gift_timeout: %d, maxinterestingness = %d, interestingness = %d",
269          you.gift_timeout, maxinterestingness, interestingness);
270 #endif
271 
272     bool was_stimulated = false;
273     if (interestingness > you.gift_timeout && interestingness >= 10)
274     {
275         you.gift_timeout = interestingness;
276         was_stimulated = true;
277     }
278 
279     if (was_stimulated || force_message)
280     {
281         god_speaks(GOD_XOM,
282                    ((interestingness > 160) ? message_array[5] :
283                     (interestingness >  80) ? message_array[4] :
284                     (interestingness >  60) ? message_array[3] :
285                     (interestingness >  40) ? message_array[2] :
286                     (interestingness >  20) ? message_array[1]
287                                             : message_array[0]));
288         //updating piety status line
289         you.redraw_title = true;
290     }
291 }
292 
xom_is_stimulated(int maxinterestingness,xom_message_type message_type,bool force_message)293 void xom_is_stimulated(int maxinterestingness, xom_message_type message_type,
294                        bool force_message)
295 {
296     _xom_is_stimulated(maxinterestingness, _xom_message_arrays[message_type],
297                        force_message);
298 }
299 
xom_is_stimulated(int maxinterestingness,const string & message,bool force_message)300 void xom_is_stimulated(int maxinterestingness, const string& message,
301                        bool force_message)
302 {
303     if (!you_worship(GOD_XOM))
304         return;
305 
306     const char *message_array[6];
307 
308     for (int i = 0; i < 6; ++i)
309         message_array[i] = message.c_str();
310 
311     _xom_is_stimulated(maxinterestingness, message_array, force_message);
312 }
313 
xom_tick()314 void xom_tick()
315 {
316     // Xom now ticks every action, not every 20 turns.
317     if (one_chance_in(20))
318     {
319         // Xom semi-randomly drifts your piety.
320         const string old_xom_favour = describe_xom_favour();
321         const bool good = (you.piety == HALF_MAX_PIETY ? coinflip()
322                                                        : you.piety > HALF_MAX_PIETY);
323         int size = abs(you.piety - HALF_MAX_PIETY);
324 
325         // Piety slowly drifts towards the extremes.
326         const int delta = piety_scale(x_chance_in_y(511, 1000) ? 1 : -1);
327         size += delta;
328         if (size > HALF_MAX_PIETY)
329             size = HALF_MAX_PIETY;
330 
331         you.piety = HALF_MAX_PIETY + (good ? size : -size);
332         string new_xom_favour = describe_xom_favour();
333         you.redraw_title = true; // redraw piety/boredom display
334         if (old_xom_favour != new_xom_favour)
335         {
336             // If we entered another favour state, take a big step into
337             // the new territory, to avoid oscillating favour announcements
338             // every few turns.
339             size += delta * 8;
340             if (size > HALF_MAX_PIETY)
341                 size = HALF_MAX_PIETY;
342 
343             // If size was 0 to begin with, it may become negative, but that
344             // doesn't really matter.
345             you.piety = HALF_MAX_PIETY + (good ? size : -size);
346         }
347 #ifdef DEBUG_XOM
348         const string note = make_stringf("xom_tick(), delta: %d, piety: %d",
349                                          delta, you.piety);
350         take_note(Note(NOTE_MESSAGE, 0, 0, note), true);
351 #endif
352 
353         // ...but he gets bored...
354         if (you.gift_timeout > 0 && coinflip())
355            you.gift_timeout--;
356 
357         new_xom_favour = describe_xom_favour();
358         if (old_xom_favour != new_xom_favour)
359         {
360             const string msg = "You are now " + new_xom_favour;
361             god_speaks(you.religion, msg.c_str());
362         }
363 
364         if (you.gift_timeout == 1)
365             simple_god_message(" is getting BORED.");
366     }
367 
368     if (x_chance_in_y(2 + you.faith(), 6))
369     {
370         const int tension = get_tension(GOD_XOM);
371         const int chance = (tension ==  0 ? 1 :
372                             tension <=  5 ? 2 :
373                             tension <= 10 ? 3 :
374                             tension <= 20 ? 4
375                                           : 5);
376 
377         // If Xom is bored, the chances for Xom acting are sort of reversed.
378         if (!you.gift_timeout && x_chance_in_y(25 - chance*chance, 100))
379         {
380             xom_acts(abs(you.piety - HALF_MAX_PIETY), MB_MAYBE, tension);
381             return;
382         }
383         else if (you.gift_timeout <= 1 && chance > 0
384                  && x_chance_in_y(chance - 1, 80))
385         {
386             // During tension, Xom may briefly forget about being bored.
387             const int interest = random2(chance * 15);
388             if (interest > 0)
389             {
390                 if (interest < 25)
391                     simple_god_message(" is interested.");
392                 else
393                     simple_god_message(" is intrigued.");
394 
395                 you.gift_timeout += interest;
396                 //updating piety status line
397                 you.redraw_title = true;
398 #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
399                 mprf(MSGCH_DIAGNOSTICS,
400                      "tension %d (chance: %d) -> increase interest to %d",
401                      tension, chance, you.gift_timeout);
402 #endif
403             }
404         }
405 
406         if (x_chance_in_y(chance*chance, 100))
407             xom_acts(abs(you.piety - HALF_MAX_PIETY), MB_MAYBE, tension);
408     }
409 }
410 
mon_nearby(function<bool (monster &)> filter)411 static bool mon_nearby(function<bool(monster&)> filter)
412 {
413     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
414         if (filter(**mi))
415             return true;
416     return false;
417 }
418 
419 // Picks 100 random grids from the level and checks whether they've been
420 // marked as seen (explored) or known (mapped). If seen_only is true,
421 // grids only "seen" via magic mapping don't count. Returns the
422 // estimated percentage value of exploration.
_exploration_estimate(bool seen_only=false)423 static int _exploration_estimate(bool seen_only = false)
424 {
425     int seen  = 0;
426     int total = 0;
427     int tries = 0;
428 
429     do
430     {
431         tries++;
432 
433         coord_def pos = random_in_bounds();
434         if (!seen_only && env.map_knowledge(pos).known() || env.map_knowledge(pos).seen())
435         {
436             seen++;
437             total++;
438             continue;
439         }
440 
441         bool open = true;
442         if (cell_is_solid(pos) && !feat_is_closed_door(env.grid(pos)))
443         {
444             open = false;
445             for (adjacent_iterator ai(pos); ai; ++ai)
446             {
447                 if (map_bounds(*ai) && (!feat_is_opaque(env.grid(*ai))
448                                         || feat_is_closed_door(env.grid(*ai))))
449                 {
450                     open = true;
451                     break;
452                 }
453             }
454         }
455 
456         if (open)
457             total++;
458     }
459     while (total < 100 && tries < 1000);
460 
461     // If we didn't get any qualifying grids, there are probably so few
462     // of them you've already seen them all.
463     if (total == 0)
464         return 100;
465 
466     if (total < 100)
467         seen *= 100 / total;
468 
469     return seen;
470 }
471 
_teleportation_check()472 static bool _teleportation_check()
473 {
474     if (crawl_state.game_is_sprint())
475         return false;
476 
477     return !you.no_tele(false, false);
478 }
479 
_transformation_check(const spell_type spell)480 static bool _transformation_check(const spell_type spell)
481 {
482     transformation tran = transformation::none;
483     switch (spell)
484     {
485     case SPELL_BEASTLY_APPENDAGE:
486         tran = transformation::appendage;
487         break;
488     case SPELL_SPIDER_FORM:
489         tran = transformation::spider;
490         break;
491     case SPELL_STATUE_FORM:
492         tran = transformation::statue;
493         break;
494     case SPELL_ICE_FORM:
495         tran = transformation::ice_beast;
496         break;
497     case SPELL_DRAGON_FORM:
498         tran = transformation::dragon;
499         break;
500     case SPELL_STORM_FORM:
501         tran = transformation::storm;
502         break;
503     case SPELL_NECROMUTATION:
504         tran = transformation::lich;
505         break;
506     default:
507         break;
508     }
509 
510     if (tran == transformation::none)
511         return true;
512 
513     // Check whether existing enchantments/transformations, cursed
514     // equipment or potential stat loss interfere with this
515     // transformation.
516     return transform(0, tran, true, true);
517 }
518 
519 /// Try to choose a random player-castable spell.
_choose_random_spell(int sever)520 static spell_type _choose_random_spell(int sever)
521 {
522     const int spellenum = max(1, sever);
523     vector<spell_type> ok_spells;
524     const vector<spell_type> &spell_list = _xom_random_spells;
525     for (int i = 0; i < min(spellenum, (int)spell_list.size()); ++i)
526     {
527         const spell_type spell = spell_list[i];
528         if (!spell_is_useless(spell, true, true, true)
529              && _transformation_check(spell))
530         {
531             ok_spells.push_back(spell);
532         }
533     }
534 
535     if (!ok_spells.size())
536         return SPELL_NO_SPELL;
537     return ok_spells[random2(ok_spells.size())];
538 }
539 
540 /// Cast a random spell 'through' the player.
_xom_random_spell(int sever)541 static void _xom_random_spell(int sever)
542 {
543     const spell_type spell = _choose_random_spell(sever);
544     if (spell == SPELL_NO_SPELL)
545         return;
546 
547     god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str());
548 
549 #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
550     mprf(MSGCH_DIAGNOSTICS,
551          "_xom_makes_you_cast_random_spell(); spell: %d",
552          spell);
553 #endif
554 
555     your_spells(spell, sever, false);
556     const string note = make_stringf("cast spell '%s'", spell_title(spell));
557     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
558 }
559 
560 /// Map out the level.
_xom_magic_mapping(int sever)561 static void _xom_magic_mapping(int sever)
562 {
563     god_speaks(GOD_XOM, _get_xom_speech("divination").c_str());
564 
565     // power isn't relevant at present, but may again be, someday?
566     const int power = stepdown_value(sever, 10, 10, 40, 45);
567     magic_mapping(5 + power, 50 + random2avg(power * 2, 2), false);
568     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
569                    "divination: magic mapping"), true);
570 }
571 
572 /// Detect items across the level.
_xom_detect_items(int sever)573 static void _xom_detect_items(int sever)
574 {
575     god_speaks(GOD_XOM, _get_xom_speech("divination").c_str());
576 
577     if (detect_items(sever) == 0)
578         canned_msg(MSG_DETECT_NOTHING);
579     else
580         mpr("You detect items!");
581 
582     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
583                    "divination: detect items"), true);
584 }
585 
586 /// Detect creatures across the level.
_xom_detect_creatures(int sever)587 static void _xom_detect_creatures(int sever)
588 {
589     god_speaks(GOD_XOM, _get_xom_speech("divination").c_str());
590 
591     const int prev_detected = count_detected_mons();
592     const int num_creatures = detect_creatures(sever);
593 
594     if (num_creatures == 0)
595         canned_msg(MSG_DETECT_NOTHING);
596     else if (num_creatures == prev_detected)
597     {
598         // This is not strictly true. You could have cast Detect
599         // Creatures with a big enough fuzz that the detected glyph is
600         // still on the map when the original one has been killed. Then
601         // another one is spawned, so the number is the same as before.
602         // There's no way we can check this, however.
603         mpr("You detect no further creatures.");
604     }
605     else
606         mpr("You detect creatures!");
607 
608     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
609                    "divination: detect creatures"), true);
610 }
611 
_try_brand_switch(const int item_index)612 static void _try_brand_switch(const int item_index)
613 {
614     if (item_index == NON_ITEM)
615         return;
616 
617     item_def &item(env.item[item_index]);
618 
619     if (item.base_type != OBJ_WEAPONS)
620         return;
621 
622     if (is_unrandom_artefact(item))
623         return;
624 
625     // Only do it some of the time.
626     if (one_chance_in(3))
627         return;
628 
629     if (get_weapon_brand(item) == SPWPN_NORMAL)
630         return;
631 
632     // TODO: shared code with _do_chaos_upgrade
633     mprf("%s erupts in a glittering mayhem of colour.",
634                             item.name(DESC_THE, false, false, false).c_str());
635     if (is_random_artefact(item))
636         artefact_set_property(item, ARTP_BRAND, SPWPN_CHAOS);
637     else
638         item.brand = SPWPN_CHAOS;
639 }
640 
_xom_make_item(object_class_type base,int subtype,int power)641 static void _xom_make_item(object_class_type base, int subtype, int power)
642 {
643     god_acting gdact(GOD_XOM);
644 
645     int thing_created = items(true, base, subtype, power, 0, GOD_XOM);
646 
647     if (thing_created == NON_ITEM)
648     {
649         god_speaks(GOD_XOM, "\"No, never mind.\"");
650         return;
651     }
652 
653     _try_brand_switch(thing_created);
654 
655     static char gift_buf[100];
656     snprintf(gift_buf, sizeof(gift_buf), "god gift: %s",
657              env.item[thing_created].name(DESC_PLAIN).c_str());
658     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, gift_buf), true);
659 
660     canned_msg(MSG_SOMETHING_APPEARS);
661     move_item_to_grid(&thing_created, you.pos());
662 
663     if (thing_created == NON_ITEM) // if it fell into lava
664         simple_god_message(" snickers.", GOD_XOM);
665 
666     stop_running();
667 }
668 
669 /// Xom's 'acquirement'. A gift for the player, of a sort...
_xom_acquirement(int)670 static void _xom_acquirement(int /*sever*/)
671 {
672     god_speaks(GOD_XOM, _get_xom_speech("general gift").c_str());
673 
674     const object_class_type types[] =
675     {
676         OBJ_WEAPONS, OBJ_ARMOUR, OBJ_JEWELLERY,  OBJ_BOOKS,
677         OBJ_STAVES,  OBJ_WANDS,  OBJ_MISCELLANY, OBJ_GOLD,
678         OBJ_MISSILES
679     };
680     const object_class_type force_class = RANDOM_ELEMENT(types);
681 
682     const int item_index = acquirement_create_item(force_class, GOD_XOM,
683             false, you.pos());
684     if (item_index == NON_ITEM)
685     {
686         god_speaks(GOD_XOM, "\"No, never mind.\"");
687         return;
688     }
689 
690     _try_brand_switch(item_index);
691 
692     const string note = make_stringf("god gift: %s",
693                                      env.item[item_index].name(DESC_PLAIN).c_str());
694     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
695 
696     stop_running();
697     more();
698 }
699 
700 /// Create a random item and give it to the player.
_xom_random_item(int sever)701 static void _xom_random_item(int sever)
702 {
703     god_speaks(GOD_XOM, _get_xom_speech("general gift").c_str());
704     _xom_make_item(OBJ_RANDOM, OBJ_RANDOM, sever * 3);
705     more();
706 }
707 
_choose_mutatable_monster(const monster & mon)708 static bool _choose_mutatable_monster(const monster& mon)
709 {
710     return mon.alive() && mon.can_safely_mutate()
711            && !mon.submerged();
712 }
713 
_choose_enchantable_monster(const monster & mon)714 static bool _choose_enchantable_monster(const monster& mon)
715 {
716     return mon.alive() && !mon.wont_attack()
717            && !mons_invuln_will(mon);
718 }
719 
_is_chaos_upgradeable(const item_def & item,const monster * mon)720 static bool _is_chaos_upgradeable(const item_def &item,
721                                   const monster* mon)
722 {
723     // Since Xom is a god, he is capable of changing randarts, but not
724     // other artefacts.
725     if (is_unrandom_artefact(item))
726         return false;
727 
728     // Staves can't be changed either, since they don't have brands in the way
729     // other weapons do.
730     if (item.base_type == OBJ_STAVES
731 #if TAG_MAJOR_VERSION == 34
732         || item.base_type == OBJ_RODS
733 #endif
734        )
735 {
736         return false;
737 }
738 
739     // Only upgrade permanent items, since the player should get a
740     // chance to use the item if he or she can defeat the monster.
741     if (item.flags & ISFLAG_SUMMONED)
742         return false;
743 
744     // Blessed weapons are protected, being gifts from good gods.
745     if (is_blessed(item))
746         return false;
747 
748     // God gifts are protected -- but not his own!
749     if (item.orig_monnum < 0)
750     {
751         god_type iorig = static_cast<god_type>(-item.orig_monnum);
752         if (iorig > GOD_NO_GOD && iorig < NUM_GODS && iorig != GOD_XOM)
753             return false;
754     }
755 
756     // Leave branded items alone, since this is supposed to be an
757     // upgrade.
758     if (item.base_type == OBJ_MISSILES)
759     {
760         // Don't make boulders or throwing nets of chaos.
761         if (item.sub_type == MI_LARGE_ROCK
762             || item.sub_type == MI_THROWING_NET)
763         {
764             return false;
765         }
766 
767         if (get_ammo_brand(item) == SPMSL_NORMAL)
768             return true;
769     }
770     else
771     {
772         // If the weapon is a launcher, and the monster is either out
773         // of ammo or is carrying javelins, then don't bother upgrading
774         // the launcher.
775         if (is_range_weapon(item)
776             && (mon->inv[MSLOT_MISSILE] == NON_ITEM
777                 || !has_launcher(env.item[mon->inv[MSLOT_MISSILE]])))
778         {
779             return false;
780         }
781 
782         if (get_weapon_brand(item) == SPWPN_NORMAL)
783             return true;
784     }
785 
786     return false;
787 }
788 
_choose_chaos_upgrade(const monster & mon)789 static bool _choose_chaos_upgrade(const monster& mon)
790 {
791     // Only choose monsters that will attack.
792     if (!mon.alive() || mons_attitude(mon) != ATT_HOSTILE
793         || mons_is_fleeing(mon))
794     {
795         return false;
796     }
797 
798     if (mons_itemuse(mon) < MONUSE_STARTING_EQUIPMENT)
799         return false;
800 
801     // Holy beings are presumably protected by another god, unless
802     // they're gifts from a chaotic god.
803     if (mon.is_holy() && !is_chaotic_god(mon.god))
804         return false;
805 
806     // God gifts from good gods will be protected by their god from
807     // being given chaos weapons, while other gods won't mind the help
808     // in their servants' killing the player.
809     if (is_good_god(mon.god))
810         return false;
811 
812     // Beogh presumably doesn't want Xom messing with his orcs, even if
813     // it would give them a better weapon.
814     if (mons_genus(mon.type) == MONS_ORC
815         && (mon.is_priest() || coinflip()))
816     {
817         return false;
818     }
819 
820     mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE};
821 
822     // NOTE: Code assumes that the monster will only be carrying one
823     // missile launcher at a time.
824     bool special_launcher = false;
825     for (int i = 0; i < 3; ++i)
826     {
827         const mon_inv_type slot = slots[i];
828         const int          midx = mon.inv[slot];
829 
830         if (midx == NON_ITEM)
831             continue;
832         const item_def &item(env.item[midx]);
833 
834         // The monster already has a chaos weapon. Give the upgrade to
835         // a different monster.
836         if (is_chaotic_item(item))
837             return false;
838 
839         if (_is_chaos_upgradeable(item, &mon))
840         {
841             if (item.base_type != OBJ_MISSILES)
842                 return true;
843 
844             // If, for some weird reason, a monster is carrying a bow
845             // and javelins, then branding the javelins is okay, since
846             // they won't be fired by the bow.
847             if (!special_launcher || !has_launcher(item))
848                 return true;
849         }
850 
851         if (is_range_weapon(item))
852         {
853             // If the launcher alters its ammo, then branding the
854             // monster's ammo won't be an upgrade.
855             int brand = get_weapon_brand(item);
856             if (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
857                 || brand == SPWPN_VENOM)
858             {
859                 special_launcher = true;
860             }
861         }
862     }
863 
864     return false;
865 }
866 
_do_chaos_upgrade(item_def & item,const monster * mon)867 static void _do_chaos_upgrade(item_def &item, const monster* mon)
868 {
869     ASSERT(item.base_type == OBJ_MISSILES
870            || item.base_type == OBJ_WEAPONS);
871     ASSERT(!is_unrandom_artefact(item));
872 
873     bool seen = false;
874     if (mon && you.can_see(*mon) && item.base_type == OBJ_WEAPONS)
875     {
876         seen = true;
877 
878         const description_level_type desc = mon->friendly() ? DESC_YOUR
879                                                             : DESC_THE;
880         mprf("%s %s erupts in a glittering mayhem of colour.",
881             apostrophise(mon->name(desc)).c_str(),
882             item.name(DESC_PLAIN, false, false, false).c_str());
883     }
884 
885     const int brand = (item.base_type == OBJ_WEAPONS) ? (int) SPWPN_CHAOS
886                                                       : (int) SPMSL_CHAOS;
887 
888     if (is_random_artefact(item))
889     {
890         artefact_set_property(item, ARTP_BRAND, brand);
891 
892         if (seen)
893             artefact_learn_prop(item, ARTP_BRAND);
894     }
895     else
896     {
897         item.brand = brand;
898 
899         if (seen)
900             set_ident_flags(item, ISFLAG_KNOW_TYPE);
901 
902         // Make sure it's visibly special.
903         if (!(item.flags & ISFLAG_COSMETIC_MASK))
904             item.flags |= ISFLAG_GLOWING;
905 
906         // Make the pluses more like a randomly generated ego item.
907         if (item.base_type == OBJ_WEAPONS)
908             item.plus  += random2(5);
909     }
910 }
911 
_xom_random_demon(int sever)912 static monster_type _xom_random_demon(int sever)
913 {
914     const int roll = random2(1000 - (MAX_PIETY - sever) * 5);
915 #ifdef DEBUG_DIAGNOSTICS
916     mprf(MSGCH_DIAGNOSTICS, "_xom_random_demon(); sever = %d, roll: %d",
917          sever, roll);
918 #endif
919     monster_type dct = (roll >= 340) ? RANDOM_DEMON_COMMON
920                                      : RANDOM_DEMON_LESSER;
921 
922     monster_type demon = MONS_PROGRAM_BUG;
923 
924     if (dct == RANDOM_DEMON_COMMON && one_chance_in(10))
925         demon = MONS_CHAOS_SPAWN;
926     else
927         demon = summon_any_demon(dct);
928 
929     return demon;
930 }
931 
_player_is_dead()932 static bool _player_is_dead()
933 {
934     return you.hp <= 0
935         || is_feat_dangerous(env.grid(you.pos()))
936         || you.did_escape_death();
937 }
938 
_note_potion_effect(potion_type pot)939 static void _note_potion_effect(potion_type pot)
940 {
941     string potion_name = potion_type_name(static_cast<int>(pot));
942 
943     string potion_msg = "potion effect ";
944 
945     potion_msg += ("(" + potion_name + ")");
946 
947     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, potion_msg), true);
948 }
949 
950 
951 /// Feed the player a notionally-good potion effect.
_xom_do_potion(int)952 static void _xom_do_potion(int /*sever*/)
953 {
954     potion_type pot = POT_CURING;
955     do
956     {
957         pot = random_choose_weighted(10, POT_CURING,
958                                      10, POT_HEAL_WOUNDS,
959                                      10, POT_MAGIC,
960                                      10, POT_HASTE,
961                                      10, POT_MIGHT,
962                                      10, POT_BRILLIANCE,
963                                      10, POT_INVISIBILITY,
964                                      5,  POT_BERSERK_RAGE,
965                                      1,  POT_EXPERIENCE);
966     }
967     while (!get_potion_effect(pot)->can_quaff()); // ugh
968 
969     god_speaks(GOD_XOM, _get_xom_speech("potion effect").c_str());
970 
971     _note_potion_effect(pot);
972 
973     get_potion_effect(pot)->effect(true, 150);
974 
975     level_change(); // need this for !xp - see mantis #3245
976 }
977 
_confuse_monster(monster * mons,int sever)978 static void _confuse_monster(monster* mons, int sever)
979 {
980     if (mons->clarity())
981         return;
982     if (mons->holiness() & (MH_NONLIVING | MH_PLANT))
983         return;
984 
985     const bool was_confused = mons->confused();
986     if (mons->add_ench(mon_enchant(ENCH_CONFUSION, 0,
987           &env.mons[ANON_FRIENDLY_MONSTER], random2(sever) * 10)))
988     {
989         if (was_confused)
990             simple_monster_message(*mons, " looks rather more confused.");
991         else
992             simple_monster_message(*mons, " looks rather confused.");
993     }
994 }
995 
_xom_confuse_monsters(int sever)996 static void _xom_confuse_monsters(int sever)
997 {
998     bool spoke = false;
999     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1000     {
1001         if (mi->wont_attack() || one_chance_in(20))
1002             continue;
1003 
1004         // Only give this message once.
1005         if (!spoke)
1006             god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str());
1007         spoke = true;
1008 
1009         _confuse_monster(*mi, sever);
1010     }
1011 
1012     if (spoke)
1013     {
1014         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "confuse monster(s)"),
1015                   true);
1016     }
1017 }
1018 
1019 /// Post a passel of pals to the player.
_xom_send_allies(int sever)1020 static void _xom_send_allies(int sever)
1021 {
1022     // The number of allies is dependent on severity, though heavily
1023     // randomised.
1024     int numdemons = sever;
1025     for (int i = 0; i < 3; i++)
1026         numdemons = random2(numdemons + 1);
1027     numdemons = min(numdemons + 2, 16);
1028 
1029     // Limit number of demons by experience level.
1030     const int maxdemons = (you.experience_level);
1031     if (numdemons > maxdemons)
1032         numdemons = maxdemons;
1033 
1034     int num_actually_summoned = 0;
1035 
1036     for (int i = 0; i < numdemons; ++i)
1037     {
1038         monster_type mon_type = _xom_random_demon(sever);
1039 
1040         mgen_data mg(mon_type, BEH_FRIENDLY, you.pos(), MHITYOU, MG_FORCE_BEH);
1041         mg.set_summoned(&you, 3, MON_SUMM_AID, GOD_XOM);
1042 
1043         // Even though the friendlies are charged to you for accounting,
1044         // they should still show as Xom's fault if one of them kills you.
1045         mg.non_actor_summoner = "Xom";
1046 
1047         if (create_monster(mg))
1048             num_actually_summoned++;
1049     }
1050 
1051     if (num_actually_summoned)
1052     {
1053         god_speaks(GOD_XOM, _get_xom_speech("multiple summons").c_str());
1054 
1055         const string note = make_stringf("summons %d friendly demon%s",
1056                                          num_actually_summoned,
1057                                          num_actually_summoned > 1 ? "s" : "");
1058         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
1059     }
1060 }
1061 
1062 /// Send a single pal to the player's aid, hopefully.
_xom_send_one_ally(int sever)1063 static void _xom_send_one_ally(int sever)
1064 {
1065     const monster_type mon_type = _xom_random_demon(sever);
1066 
1067     mgen_data mg(mon_type, BEH_FRIENDLY, you.pos(), MHITYOU, MG_FORCE_BEH);
1068     mg.set_summoned(&you, 6, MON_SUMM_AID, GOD_XOM);
1069 
1070     mg.non_actor_summoner = "Xom";
1071 
1072     if (monster *summons = create_monster(mg))
1073     {
1074         god_speaks(GOD_XOM, _get_xom_speech("single summon").c_str());
1075 
1076         const string note = make_stringf("summons friendly %s",
1077                                          summons->name(DESC_PLAIN).c_str());
1078         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
1079     }
1080 }
1081 
1082 /**
1083  * Try to polymorph the given monster. If 'helpful', hostile monsters will
1084  * (try to) turn into weaker ones, and friendly monsters into stronger ones;
1085  * if (!helpful), the reverse is true.
1086  *
1087  * @param mons      The monster in question.
1088  * @param helpful   Whether to try to be helpful.
1089  */
_xom_polymorph_monster(monster & mons,bool helpful)1090 static void _xom_polymorph_monster(monster &mons, bool helpful)
1091 {
1092     god_speaks(GOD_XOM,
1093                helpful ? _get_xom_speech("good monster polymorph").c_str()
1094                        : _get_xom_speech("bad monster polymorph").c_str());
1095 
1096     const bool see_old = you.can_see(mons);
1097     const string old_name = see_old ? mons.full_name(DESC_PLAIN)
1098                                     : "something unseen";
1099 
1100     if (one_chance_in(8)
1101         && !mons_is_ghost_demon(mons.type)
1102         && !mons.is_shapeshifter()
1103         && mons.holiness() & MH_NATURAL)
1104     {
1105         mons.add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
1106                                        : ENCH_SHAPESHIFTER);
1107     }
1108 
1109     const bool powerup = !(mons.wont_attack() ^ helpful);
1110     mons.polymorph(powerup ? PPT_MORE : PPT_LESS);
1111 
1112     const bool see_new = you.can_see(mons);
1113 
1114     if (see_old || see_new)
1115     {
1116         const string new_name = see_new ? mons.full_name(DESC_PLAIN)
1117                                         : "something unseen";
1118 
1119         string note = make_stringf("polymorph %s -> %s",
1120                                    old_name.c_str(), new_name.c_str());
1121 
1122 #ifdef NOTE_DEBUG_XOM
1123         note += " (";
1124         note += (powerup ? "upgrade" : "downgrade");
1125         note += ")";
1126 #endif
1127         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
1128     }
1129 }
1130 
1131 /// Find a monster to poly.
_xom_mons_poly_target()1132 static monster* _xom_mons_poly_target()
1133 {
1134     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1135         if (_choose_mutatable_monster(**mi) && !mons_is_firewood(**mi))
1136             return *mi;
1137     return nullptr;
1138 }
1139 
1140 /// Try to polymporph a nearby monster into something weaker... or stronger.
_xom_polymorph_nearby_monster(bool helpful)1141 static void _xom_polymorph_nearby_monster(bool helpful)
1142 {
1143     monster* mon = _xom_mons_poly_target();
1144     if (mon)
1145         _xom_polymorph_monster(*mon, helpful);
1146 }
1147 
1148 /// Try to polymporph a nearby monster into something weaker.
_xom_good_polymorph(int)1149 static void _xom_good_polymorph(int /*sever*/)
1150 {
1151     _xom_polymorph_nearby_monster(true);
1152 }
1153 
1154 /// Try to polymporph a nearby monster into something stronger.
_xom_bad_polymorph(int)1155 static void _xom_bad_polymorph(int /*sever*/)
1156 {
1157     _xom_polymorph_nearby_monster(false);
1158 }
1159 
swap_monsters(monster * m1,monster * m2)1160 bool swap_monsters(monster* m1, monster* m2)
1161 {
1162     monster& mon1(*m1);
1163     monster& mon2(*m2);
1164 
1165     const bool mon1_caught = mon1.caught();
1166     const bool mon2_caught = mon2.caught();
1167 
1168     // Make submerged monsters unsubmerge.
1169     mon1.del_ench(ENCH_SUBMERGED);
1170     mon2.del_ench(ENCH_SUBMERGED);
1171 
1172     mon1.swap_with(m2);
1173 
1174     if (mon1_caught && !mon2_caught)
1175     {
1176         check_net_will_hold_monster(&mon2);
1177         mon1.del_ench(ENCH_HELD, true);
1178 
1179     }
1180     else if (mon2_caught && !mon1_caught)
1181     {
1182         check_net_will_hold_monster(&mon1);
1183         mon2.del_ench(ENCH_HELD, true);
1184     }
1185 
1186     return true;
1187 }
1188 
1189 /// Which monsters, if any, can Xom currently swap with the player?
_rearrangeable_pieces()1190 static vector<monster*> _rearrangeable_pieces()
1191 {
1192     vector<monster* > mons;
1193     if (player_stair_delay() || monster_at(you.pos()))
1194         return mons;
1195 
1196     for (monster_near_iterator mi(&you, LOS_NO_TRANS); mi; ++mi)
1197     {
1198         if (!mons_is_tentacle_or_tentacle_segment(mi->type))
1199             mons.push_back(*mi);
1200     }
1201     return mons;
1202 }
1203 
1204 // Swap places with a random monster and, depending on severity, also
1205 // between monsters. This can be pretty bad if there are a lot of
1206 // hostile monsters around.
_xom_rearrange_pieces(int sever)1207 static void _xom_rearrange_pieces(int sever)
1208 {
1209     vector<monster*> mons = _rearrangeable_pieces();
1210     if (mons.empty())
1211         return;
1212 
1213     god_speaks(GOD_XOM, _get_xom_speech("rearrange the pieces").c_str());
1214 
1215     const int num_mons = mons.size();
1216 
1217     // Swap places with a random monster.
1218     monster* mon = mons[random2(num_mons)];
1219     swap_with_monster(mon);
1220 
1221     // Sometimes confuse said monster.
1222     if (coinflip())
1223         _confuse_monster(mon, sever);
1224 
1225     if (num_mons > 1 && x_chance_in_y(sever, 70))
1226     {
1227         bool did_message = false;
1228         const int max_repeats = min(num_mons / 2, 8);
1229         const int repeats     = min(random2(sever / 10) + 1, max_repeats);
1230         for (int i = 0; i < repeats; ++i)
1231         {
1232             const int mon1 = random2(num_mons);
1233             int mon2 = mon1;
1234             while (mon1 == mon2)
1235                 mon2 = random2(num_mons);
1236 
1237             if (swap_monsters(mons[mon1], mons[mon2]))
1238             {
1239                 if (!did_message)
1240                 {
1241                     mpr("Some monsters swap places.");
1242                     did_message = true;
1243                 }
1244                 if (one_chance_in(4))
1245                     _confuse_monster(mons[mon1], sever);
1246                 if (one_chance_in(4))
1247                     _confuse_monster(mons[mon2], sever);
1248             }
1249         }
1250     }
1251     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "swap monsters"), true);
1252 }
1253 
_xom_random_stickable(const int HD)1254 static int _xom_random_stickable(const int HD)
1255 {
1256     unsigned int c;
1257 
1258     static const int arr[] =
1259     {
1260         WPN_CLUB,    WPN_SPEAR,      WPN_TRIDENT,      WPN_HALBERD,
1261         WPN_SCYTHE,  WPN_GLAIVE,     WPN_QUARTERSTAFF,
1262         WPN_SHORTBOW,   WPN_LONGBOW,      WPN_GIANT_CLUB,
1263         WPN_GIANT_SPIKED_CLUB
1264     };
1265 
1266     // Maximum snake hd is 11 (anaconda) so random2(hd) gives us 0-10, and
1267     // weapon_rarity also gives us 1-10.
1268     do
1269     {
1270         c = random2(HD);
1271     }
1272     while (c >= ARRAYSZ(arr)
1273            || random2(HD) > weapon_rarity(arr[c]) && x_chance_in_y(c, HD));
1274 
1275     return arr[c];
1276 }
1277 
_hostile_snake(monster & mon)1278 static bool _hostile_snake(monster& mon)
1279 {
1280     return mon.attitude == ATT_HOSTILE
1281             && mons_genus(mon.type) == MONS_SNAKE;
1282 }
1283 
1284 // An effect similar to old sticks to snakes (which worked on "sticks" other
1285 // than arrows)
1286 //  * Transformations are permanent.
1287 //  * Weapons are always non-cursed.
1288 //  * HD influences the enchantment and type of the weapon.
1289 //  * Weapon is not guaranteed to be useful.
1290 //  * Weapon will never be branded.
_xom_snakes_to_sticks(int)1291 static void _xom_snakes_to_sticks(int /*sever*/)
1292 {
1293     bool action = false;
1294     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1295     {
1296         if (!_hostile_snake(**mi))
1297             continue;
1298 
1299         if (!action)
1300         {
1301             take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
1302                            "snakes to sticks"), true);
1303             god_speaks(GOD_XOM, _get_xom_speech("snakes to sticks").c_str());
1304             action = true;
1305         }
1306 
1307         const object_class_type base_type = x_chance_in_y(3,5) ? OBJ_MISSILES
1308                                                                : OBJ_WEAPONS;
1309 
1310         const int sub_type =
1311             (base_type == OBJ_MISSILES ?
1312              (x_chance_in_y(3,5) ? MI_ARROW : MI_JAVELIN)
1313              : _xom_random_stickable(mi->get_experience_level()));
1314 
1315         int item_slot = items(false, base_type, sub_type,
1316                               mi->get_experience_level() / 3 - 1,
1317                               0, -1);
1318 
1319         if (item_slot == NON_ITEM)
1320             continue;
1321 
1322         item_def &item(env.item[item_slot]);
1323 
1324         // Always limit the quantity to 1.
1325         item.quantity = 1;
1326 
1327         // Output some text since otherwise snakes will disappear silently.
1328         mprf("%s reforms as %s.", mi->name(DESC_THE).c_str(),
1329              item.name(DESC_A).c_str());
1330 
1331         // Dismiss monster silently.
1332         move_item_to_grid(&item_slot, mi->pos());
1333         monster_die(**mi, KILL_DISMISSED, NON_MONSTER, true, false);
1334     }
1335 }
1336 
1337 /// Try to find a nearby hostile monster with an animateable weapon.
_find_monster_with_animateable_weapon()1338 static monster* _find_monster_with_animateable_weapon()
1339 {
1340     vector<monster* > mons_wpn;
1341     for (monster_near_iterator mi(&you, LOS_NO_TRANS); mi; ++mi)
1342     {
1343         if (mi->wont_attack() || mi->is_summoned()
1344             || mons_itemuse(**mi) < MONUSE_STARTING_EQUIPMENT
1345             || (mi->flags & MF_HARD_RESET))
1346         {
1347             continue;
1348         }
1349 
1350         const int mweap = mi->inv[MSLOT_WEAPON];
1351         if (mweap == NON_ITEM)
1352             continue;
1353 
1354         const item_def weapon = env.item[mweap];
1355 
1356         if (weapon.base_type == OBJ_WEAPONS
1357             && !(weapon.flags & ISFLAG_SUMMONED)
1358             && weapon.quantity == 1
1359             && !is_range_weapon(weapon)
1360             && !is_special_unrandom_artefact(weapon)
1361             && get_weapon_brand(weapon) != SPWPN_DISTORTION)
1362         {
1363             mons_wpn.push_back(*mi);
1364         }
1365     }
1366     if (mons_wpn.empty())
1367         return nullptr;
1368     return mons_wpn[random2(mons_wpn.size())];
1369 }
1370 
_xom_animate_monster_weapon(int sever)1371 static void _xom_animate_monster_weapon(int sever)
1372 {
1373     // Pick a random monster...
1374     monster* mon = _find_monster_with_animateable_weapon();
1375     if (!mon)
1376         return;
1377 
1378     god_speaks(GOD_XOM, _get_xom_speech("animate monster weapon").c_str());
1379 
1380     // ...and get its weapon.
1381     const int wpn = mon->inv[MSLOT_WEAPON];
1382     ASSERT(wpn != NON_ITEM);
1383 
1384     const int dur = min(2 + (random2(sever) / 5), 6);
1385 
1386     mgen_data mg(MONS_DANCING_WEAPON, BEH_FRIENDLY, mon->pos(), mon->mindex());
1387     mg.set_summoned(&you, dur, SPELL_TUKIMAS_DANCE, GOD_XOM);
1388 
1389     mg.non_actor_summoner = "Xom";
1390 
1391     monster *dancing = create_monster(mg);
1392 
1393     if (!dancing)
1394         return;
1395 
1396     // Make the monster unwield its weapon.
1397     mon->unequip(*(mon->mslot_item(MSLOT_WEAPON)), false, true);
1398     mon->inv[MSLOT_WEAPON] = NON_ITEM;
1399 
1400     mprf("%s %s dances into the air!",
1401          apostrophise(mon->name(DESC_THE)).c_str(),
1402          env.item[wpn].name(DESC_PLAIN).c_str());
1403 
1404     destroy_item(dancing->inv[MSLOT_WEAPON]);
1405 
1406     dancing->inv[MSLOT_WEAPON] = wpn;
1407     env.item[wpn].set_holding_monster(*dancing);
1408     dancing->colour = env.item[wpn].get_colour();
1409 }
1410 
_xom_give_mutations(bool good)1411 static void _xom_give_mutations(bool good)
1412 {
1413     if (!you.can_safely_mutate())
1414         return;
1415 
1416     god_speaks(GOD_XOM, good ? _get_xom_speech("good mutations").c_str()
1417                              : _get_xom_speech("random mutations").c_str());
1418 
1419     const int num_tries = random2(4) + 1;
1420 
1421     const string note = make_stringf("give %smutation%s",
1422 #ifdef NOTE_DEBUG_XOM
1423              good ? "good " : "random ",
1424 #else
1425              "",
1426 #endif
1427              num_tries > 1 ? "s" : "");
1428 
1429     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
1430     mpr("Your body is suffused with distortional energy.");
1431 
1432     bool failMsg = true;
1433 
1434     for (int i = num_tries; i > 0; --i)
1435     {
1436         if (!mutate(good ? RANDOM_GOOD_MUTATION : RANDOM_XOM_MUTATION,
1437                     good ? "Xom's grace" : "Xom's mischief",
1438                     failMsg, false, true, false, MUTCLASS_NORMAL))
1439         {
1440             failMsg = false;
1441         }
1442     }
1443 }
1444 
_xom_give_good_mutations(int)1445 static void _xom_give_good_mutations(int) { _xom_give_mutations(true); }
_xom_give_bad_mutations(int)1446 static void _xom_give_bad_mutations(int) { _xom_give_mutations(false); }
1447 
1448 /**
1449  * Have Xom throw divine lightning.
1450  */
_xom_throw_divine_lightning(int)1451 static void _xom_throw_divine_lightning(int /*sever*/)
1452 {
1453     god_speaks(GOD_XOM, "The area is suffused with divine lightning!");
1454 
1455     bolt beam;
1456 
1457     beam.flavour      = BEAM_ELECTRICITY;
1458     beam.glyph        = dchar_glyph(DCHAR_FIRED_BURST);
1459     beam.damage       = dice_def(3, 30);
1460     beam.target       = you.pos();
1461     beam.name         = "blast of lightning";
1462     beam.colour       = LIGHTCYAN;
1463     beam.thrower      = KILL_MISC;
1464     beam.source_id    = MID_NOBODY;
1465     beam.aux_source   = "Xom's lightning strike";
1466     beam.ex_size      = 2;
1467     beam.is_explosion = true;
1468 
1469     beam.explode(true, true);
1470 
1471     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "divine lightning"), true);
1472 }
1473 
1474 /// What scenery nearby would Xom like to mess with, if any?
_xom_scenery_candidates()1475 static vector<coord_def> _xom_scenery_candidates()
1476 {
1477     vector<coord_def> candidates;
1478     vector<coord_def> closed_doors;
1479     vector<coord_def> open_doors;
1480     for (vision_iterator ri(you); ri; ++ri)
1481     {
1482         dungeon_feature_type feat = env.grid(*ri);
1483         if (feat_is_fountain(feat))
1484             candidates.push_back(*ri);
1485         else if (feat_is_closed_door(feat))
1486         {
1487             // Check whether this door is already included in a gate.
1488             if (find(begin(closed_doors), end(closed_doors), *ri)
1489                 == end(closed_doors))
1490             {
1491                 // If it's a gate, add all doors belonging to the gate.
1492                 set<coord_def> all_door;
1493                 find_connected_identical(*ri, all_door);
1494                 for (auto dc : all_door)
1495                     closed_doors.push_back(dc);
1496             }
1497         }
1498         else if (feat_is_open_door(feat) && !actor_at(*ri)
1499                  && env.igrid(*ri) == NON_ITEM)
1500         {
1501             // Check whether this door is already included in a gate.
1502             if (find(begin(open_doors), end(open_doors), *ri)
1503                 == end(open_doors))
1504             {
1505                 // Check whether any of the doors belonging to a gate is
1506                 // blocked by an item or monster.
1507                 set<coord_def> all_door;
1508                 find_connected_identical(*ri, all_door);
1509                 bool is_blocked = false;
1510                 for (auto dc : all_door)
1511                 {
1512                     if (actor_at(dc) || env.igrid(dc) != NON_ITEM)
1513                     {
1514                         is_blocked = true;
1515                         break;
1516                     }
1517                 }
1518 
1519                 // If the doorway isn't blocked, add all doors
1520                 // belonging to the gate.
1521                 if (!is_blocked)
1522                 {
1523                     for (auto dc : all_door)
1524                         open_doors.push_back(dc);
1525                 }
1526             }
1527         }
1528     }
1529     // Order needs to be the same as messaging below, else the messages might
1530     // not make sense.
1531     // FIXME: Changed fountains behind doors are not properly remembered.
1532     //        (At least in tiles.)
1533     candidates.insert(end(candidates), begin(open_doors), end(open_doors));
1534     candidates.insert(end(candidates), begin(closed_doors), end(closed_doors));
1535 
1536     return candidates;
1537 }
1538 
1539 /// Place one or more altars to Xom nearish the player.
_xom_place_altars()1540 static void _xom_place_altars()
1541 {
1542     coord_def place;
1543     bool success = false;
1544     const int max_altars = max(1, random2(random2(14)));
1545     for (int tries = max_altars; tries > 0; --tries)
1546     {
1547         if ((random_near_space(&you, you.pos(), place, false)
1548              || random_near_space(&you, you.pos(), place, true))
1549             && env.grid(place) == DNGN_FLOOR)
1550         {
1551             env.grid(place) = DNGN_ALTAR_XOM;
1552             success = true;
1553         }
1554     }
1555 
1556     if (success)
1557     {
1558         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
1559                        "scenery: create altars"), true);
1560         god_speaks(GOD_XOM, _get_xom_speech("scenery").c_str());
1561     }
1562 }
1563 
_xom_summon_butterflies()1564 static void _xom_summon_butterflies()
1565 {
1566     bool success = false;
1567     const int how_many = random_range(10, 20);
1568 
1569     for (int i = 0; i < how_many; ++i)
1570     {
1571         mgen_data mg(MONS_BUTTERFLY, BEH_FRIENDLY, you.pos(), MHITYOU,
1572                      MG_FORCE_BEH);
1573         mg.set_summoned(&you, 3, MON_SUMM_AID, GOD_XOM);
1574         if (create_monster(mg))
1575             success = true;
1576     }
1577 
1578     if (success)
1579     {
1580         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
1581                        "scenery: summon butterflies"), true);
1582         god_speaks(GOD_XOM, _get_xom_speech("scenery").c_str());
1583     }
1584 }
1585 /// Mess with nearby terrain features, more-or-less harmlessly.
_xom_change_scenery(int)1586 static void _xom_change_scenery(int /*sever*/)
1587 {
1588     vector<coord_def> candidates = _xom_scenery_candidates();
1589 
1590     if (candidates.empty())
1591     {
1592         if (coinflip())
1593             _xom_place_altars();
1594         else
1595             _xom_summon_butterflies();
1596         return;
1597     }
1598 
1599     int fountains_blood = 0;
1600     int doors_open      = 0;
1601     int doors_close     = 0;
1602     for (coord_def pos : candidates)
1603     {
1604         switch (env.grid(pos))
1605         {
1606         case DNGN_CLOSED_DOOR:
1607         case DNGN_CLOSED_CLEAR_DOOR:
1608         case DNGN_RUNED_DOOR:
1609         case DNGN_RUNED_CLEAR_DOOR:
1610             dgn_open_door(pos);
1611             set_terrain_changed(pos);
1612             if (you.see_cell(pos))
1613                 doors_open++;
1614             break;
1615         case DNGN_OPEN_DOOR:
1616         case DNGN_OPEN_CLEAR_DOOR:
1617             dgn_close_door(pos);
1618             set_terrain_changed(pos);
1619             if (you.see_cell(pos))
1620                 doors_close++;
1621             break;
1622         case DNGN_DRY_FOUNTAIN:
1623         case DNGN_FOUNTAIN_BLUE:
1624             if (x_chance_in_y(fountains_blood, 3))
1625                 continue;
1626 
1627             env.grid(pos) = DNGN_FOUNTAIN_BLOOD;
1628             set_terrain_changed(pos);
1629             if (you.see_cell(pos))
1630                 fountains_blood++;
1631             break;
1632         default:
1633             break;
1634         }
1635     }
1636     if (!doors_open && !doors_close && !fountains_blood)
1637         return;
1638 
1639     god_speaks(GOD_XOM, _get_xom_speech("scenery").c_str());
1640 
1641     vector<string> effects, terse;
1642     if (fountains_blood > 0)
1643     {
1644         string fountains = make_stringf(
1645                  "%s fountain%s start%s gushing blood",
1646                  fountains_blood == 1 ? "a" : "some",
1647                  fountains_blood == 1 ? ""  : "s",
1648                  fountains_blood == 1 ? "s" : "");
1649 
1650         if (effects.empty())
1651             fountains = uppercase_first(fountains);
1652         effects.push_back(fountains);
1653         terse.push_back(make_stringf("%d fountains blood", fountains_blood));
1654     }
1655     if (!effects.empty())
1656     {
1657         mprf("%s!",
1658              comma_separated_line(effects.begin(), effects.end(),
1659                                   ", and ").c_str());
1660         effects.clear();
1661     }
1662 
1663     if (doors_open > 0)
1664     {
1665         effects.push_back(make_stringf("%s door%s burst%s open",
1666                                        doors_open == 1 ? "A"    :
1667                                        doors_open == 2 ? "Two"
1668                                                        : "Several",
1669                                        doors_open == 1 ? ""  : "s",
1670                                        doors_open == 1 ? "s" : ""));
1671         terse.push_back(make_stringf("%d doors open", doors_open));
1672     }
1673     if (doors_close > 0)
1674     {
1675         string closed = make_stringf("%s%s door%s slam%s shut",
1676                  doors_close == 1 ? "a"    :
1677                  doors_close == 2 ? "two"
1678                                   : "several",
1679                  doors_open > 0   ? (doors_close == 1 ? "nother" : " other")
1680                                   : "",
1681                  doors_close == 1 ? ""  : "s",
1682                  doors_close == 1 ? "s" : "");
1683         if (effects.empty())
1684             closed = uppercase_first(closed);
1685         effects.push_back(closed);
1686         terse.push_back(make_stringf("%d doors close", doors_close));
1687     }
1688     if (!effects.empty())
1689     {
1690         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, ("scenery: "
1691             + comma_separated_line(terse.begin(), terse.end(), ", ", ", ")).c_str()),
1692             true);
1693         mprf("%s!",
1694              comma_separated_line(effects.begin(), effects.end(),
1695                                   ", and ").c_str());
1696     }
1697 
1698     if (doors_open || doors_close)
1699         noisy(10, you.pos());
1700 }
1701 
1702 /// Xom hurls fireballs at your foes! Or, possibly, 'fireballs'.
_xom_destruction(int sever,bool real)1703 static void _xom_destruction(int sever, bool real)
1704 {
1705     bool rc = false;
1706 
1707     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1708     {
1709         if (mons_is_projectile(**mi)
1710             || mons_is_tentacle_or_tentacle_segment(mi->type)
1711             || one_chance_in(3))
1712         {
1713             continue;
1714         }
1715 
1716         // Skip adjacent monsters, and skip non-hostile monsters if not feeling nasty.
1717         if (real
1718             && (adjacent(you.pos(), mi->pos())
1719                 || mi->wont_attack() && !_xom_feels_nasty()))
1720         {
1721             continue;
1722         }
1723 
1724         if (!real)
1725         {
1726             if (!rc)
1727                 god_speaks(GOD_XOM, _get_xom_speech("fake destruction").c_str());
1728             rc = true;
1729             backlight_monster(*mi);
1730             continue;
1731         }
1732 
1733         bolt beam;
1734 
1735         beam.flavour      = BEAM_FIRE;
1736         beam.glyph        = dchar_glyph(DCHAR_FIRED_BURST);
1737         beam.damage       = dice_def(2, 4 + sever / 10);
1738         beam.target       = mi->pos();
1739         beam.name         = "fireball";
1740         beam.colour       = RED;
1741         beam.thrower      = KILL_MISC;
1742         beam.source_id    = MID_NOBODY;
1743         beam.aux_source   = "Xom's destruction";
1744         beam.ex_size      = 1;
1745         beam.is_explosion = true;
1746 
1747         // Only give this message once.
1748         if (!rc)
1749             god_speaks(GOD_XOM, _get_xom_speech("destruction").c_str());
1750         rc = true;
1751 
1752         beam.explode();
1753     }
1754 
1755     if (rc)
1756     {
1757         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
1758                        real ? "destruction" : "fake destruction"), true);
1759     }
1760 }
1761 
_xom_real_destruction(int sever)1762 static void _xom_real_destruction(int sever) { _xom_destruction(sever, true); }
_xom_fake_destruction(int sever)1763 static void _xom_fake_destruction(int sever) { _xom_destruction(sever, false); }
1764 
_xom_enchant_monster(bool helpful)1765 static void _xom_enchant_monster(bool helpful)
1766 {
1767     monster* mon = choose_random_nearby_monster(0, _choose_enchantable_monster);
1768     if (!mon)
1769         return;
1770 
1771     god_speaks(GOD_XOM,
1772                helpful ? _get_xom_speech("good enchant monster").c_str()
1773                        : _get_xom_speech("bad enchant monster").c_str());
1774 
1775     beam_type ench;
1776 
1777     if (helpful) // To the player, not the monster.
1778     {
1779         static const beam_type enchantments[] =
1780         {
1781             BEAM_PETRIFY,
1782             BEAM_SLOW,
1783             BEAM_PARALYSIS,
1784             BEAM_CHARM,
1785         };
1786         ench = RANDOM_ELEMENT(enchantments);
1787     }
1788     else
1789     {
1790         static const beam_type enchantments[] =
1791         {
1792             BEAM_HASTE,
1793             BEAM_MIGHT,
1794             BEAM_AGILITY,
1795             BEAM_INVISIBILITY,
1796             BEAM_RESISTANCE,
1797         };
1798         ench = RANDOM_ELEMENT(enchantments);
1799     }
1800 
1801     enchant_actor_with_flavour(mon, 0, ench);
1802 
1803     // Take a note.
1804     const string note = make_stringf("enchant monster %s",
1805                                      helpful ? "(good)" : "(bad)");
1806     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
1807 }
1808 
_xom_good_enchant_monster(int)1809 static void _xom_good_enchant_monster(int /*sever*/)
1810 {
1811     _xom_enchant_monster(true);
1812 }
_xom_bad_enchant_monster(int)1813 static void _xom_bad_enchant_monster(int /*sever*/)
1814 {
1815     _xom_enchant_monster(false);
1816 }
1817 
1818 /// Toss some fog around the player. Helping...?
_xom_fog(int)1819 static void _xom_fog(int /*sever*/)
1820 {
1821     big_cloud(CLOUD_RANDOM_SMOKE, &you, you.pos(), 50, 8 + random2(8));
1822     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "fog"), true);
1823     god_speaks(GOD_XOM, _get_xom_speech("cloud").c_str());
1824 }
1825 
_xom_pseudo_miscast(int)1826 static void _xom_pseudo_miscast(int /*sever*/)
1827 {
1828     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "silly message"), true);
1829     god_speaks(GOD_XOM, _get_xom_speech("zero miscast effect").c_str());
1830 
1831     vector<string> messages;
1832     vector<string> priority;
1833 
1834     vector<item_def *> inv_items;
1835     for (auto &item : you.inv)
1836     {
1837         if (item.defined() && !item_is_equipped(item)
1838             && !item.is_critical())
1839         {
1840             inv_items.push_back(&item);
1841         }
1842     }
1843 
1844     // Assure that the messages vector has at least one element.
1845     messages.emplace_back("Nothing appears to happen... Ominous!");
1846 
1847     ///////////////////////////////////
1848     // Dungeon feature dependent stuff.
1849 
1850     FixedBitVector<NUM_FEATURES> in_view;
1851     for (radius_iterator ri(you.pos(), LOS_DEFAULT); ri; ++ri)
1852         in_view.set(env.grid(*ri));
1853 
1854     if (in_view[DNGN_LAVA])
1855         messages.emplace_back("The lava spits out sparks!");
1856 
1857     if (in_view[DNGN_SHALLOW_WATER] || in_view[DNGN_DEEP_WATER])
1858     {
1859         messages.emplace_back("The water briefly bubbles.");
1860         messages.emplace_back("The water briefly swirls.");
1861         messages.emplace_back("The water briefly glows.");
1862     }
1863 
1864     if (in_view[DNGN_DEEP_WATER])
1865     {
1866         messages.emplace_back("From the corner of your eye you spot something "
1867                            "lurking in the deep water.");
1868     }
1869 
1870     if (in_view[DNGN_ORCISH_IDOL])
1871         priority.emplace_back("The idol of Beogh turns to glare at you.");
1872 
1873     if (in_view[DNGN_GRANITE_STATUE])
1874         priority.emplace_back("The granite statue turns to stare at you.");
1875 
1876     if (in_view[DNGN_CLEAR_ROCK_WALL] || in_view[DNGN_CLEAR_STONE_WALL]
1877         || in_view[DNGN_CLEAR_PERMAROCK_WALL])
1878     {
1879         messages.emplace_back("Dim shapes swim through the translucent wall.");
1880     }
1881 
1882     if (in_view[DNGN_CRYSTAL_WALL])
1883         messages.emplace_back("Dim shapes swim through the crystal wall.");
1884 
1885     if (in_view[DNGN_METAL_WALL])
1886     {
1887         messages.emplace_back("Tendrils of electricity crawl over the metal "
1888                               "wall!");
1889     }
1890 
1891     if (in_view[DNGN_FOUNTAIN_BLUE] || in_view[DNGN_FOUNTAIN_SPARKLING])
1892     {
1893         priority.emplace_back("The water in the fountain briefly bubbles.");
1894         priority.emplace_back("The water in the fountain briefly swirls.");
1895         priority.emplace_back("The water in the fountain briefly glows.");
1896     }
1897 
1898     if (in_view[DNGN_DRY_FOUNTAIN])
1899     {
1900         priority.emplace_back("Water briefly sprays from the dry fountain.");
1901         priority.emplace_back("Dust puffs up from the dry fountain.");
1902     }
1903 
1904     if (in_view[DNGN_STONE_ARCH])
1905     {
1906         priority.emplace_back("The stone arch briefly shows a sunny meadow on "
1907                               "the other side.");
1908     }
1909 
1910     const dungeon_feature_type feat = env.grid(you.pos());
1911 
1912     if (!feat_is_solid(feat) && feat_stair_direction(feat) == CMD_NO_CMD
1913         && !feat_is_trap(feat) && feat != DNGN_STONE_ARCH
1914         && !feat_is_open_door(feat) && feat != DNGN_ABANDONED_SHOP)
1915     {
1916         const string feat_name = feature_description_at(you.pos(), false,
1917                                                         DESC_THE);
1918 
1919         if (you.airborne())
1920         {
1921             // Don't put airborne messages into the priority vector for
1922             // anyone who can fly a lot.
1923             vector<string>* vec;
1924             if (you.racial_permanent_flight())
1925                 vec = &messages;
1926             else
1927                 vec = &priority;
1928 
1929             vec->push_back(feat_name
1930                            + " seems to fall away from under you!");
1931             vec->push_back(feat_name
1932                            + " seems to rush up at you!");
1933 
1934             if (feat_is_water(feat))
1935             {
1936                 priority.emplace_back("Something invisible splashes into the "
1937                                       "water beneath you!");
1938             }
1939         }
1940         else if (feat_is_water(feat))
1941         {
1942             priority.emplace_back("The water briefly recedes away from you.");
1943             priority.emplace_back("Something invisible splashes into the water "
1944                                   "beside you!");
1945         }
1946     }
1947 
1948     if (feat_has_solid_floor(feat) && !inv_items.empty())
1949     {
1950         const item_def &item = **random_iterator(inv_items);
1951 
1952         string name;
1953         if (item.quantity == 1)
1954             name = item.name(DESC_YOUR, false, false, false);
1955         else
1956         {
1957             name  = "One of ";
1958             name += item.name(DESC_YOUR, false, false, false);
1959         }
1960         messages.push_back(name + " falls out of your pack, then "
1961                            "immediately jumps back in!");
1962     }
1963 
1964     //////////////////////////////////////////////
1965     // Body, player species, transformations, etc.
1966 
1967     if (starts_with(species::skin_name(you.species), "bandage")
1968         && you_can_wear(EQ_BODY_ARMOUR, true))
1969     {
1970         messages.emplace_back("You briefly get tangled in your bandages.");
1971         if (!you.airborne() && !you.swimming())
1972             messages.emplace_back("You trip over your bandages.");
1973     }
1974 
1975     {
1976         string str = "A monocle briefly appears over your ";
1977         str += random_choose("right", "left");
1978         if (you.form == transformation::spider)
1979         {
1980             if (coinflip())
1981                 str += " primary";
1982             else
1983             {
1984                 str += random_choose(" front", " middle", " rear");
1985                 str += " secondary";
1986             }
1987         }
1988         str += " eye.";
1989         messages.push_back(str);
1990     }
1991 
1992     if (species::has_hair(you.species))
1993     {
1994         messages.emplace_back("Your eyebrows briefly feel incredibly bushy.");
1995         messages.emplace_back("Your eyebrows wriggle.");
1996     }
1997 
1998     if (you.species != SP_NAGA && !you.fishtail && !you.airborne())
1999         messages.emplace_back("You do an impromptu tapdance.");
2000 
2001     ///////////////////////////
2002     // Equipment related stuff.
2003 
2004     if (you_can_wear(EQ_WEAPON, true)
2005         && !you.slot_item(EQ_WEAPON))
2006     {
2007         string str = "A fancy cane briefly appears in your ";
2008         str += you.hand_name(false);
2009         str += ".";
2010 
2011         messages.push_back(str);
2012     }
2013 
2014     if (you.slot_item(EQ_CLOAK))
2015     {
2016         item_def* item = you.slot_item(EQ_CLOAK);
2017 
2018         if (item->sub_type == ARM_CLOAK)
2019             messages.emplace_back("Your cloak billows in an unfelt wind.");
2020         else if (item->sub_type == ARM_SCARF)
2021             messages.emplace_back("Your scarf briefly wraps itself around your head!");
2022     }
2023 
2024     if (item_def* item = you.slot_item(EQ_HELMET))
2025     {
2026         string str = "Your ";
2027         str += item->name(DESC_BASENAME, false, false, false);
2028         str += " leaps into the air, briefly spins, then lands back on "
2029                "your head!";
2030 
2031         messages.push_back(str);
2032     }
2033 
2034     if (item_def* item = you.slot_item(EQ_BOOTS))
2035     {
2036         if (item->sub_type == ARM_BOOTS && !you.cannot_act())
2037         {
2038             string name = item->name(DESC_BASENAME, false, false, false);
2039             name = replace_all(name, "pair of ", "");
2040 
2041             string str = "You compulsively click the heels of your ";
2042             str += name;
2043             str += " together three times.";
2044             messages.push_back(str);
2045         }
2046     }
2047 
2048     if (item_def* item = you.slot_item(EQ_SHIELD))
2049     {
2050         string str = "Your ";
2051         str += item->name(DESC_BASENAME, false, false, false);
2052         str += " spins!";
2053 
2054         messages.push_back(str);
2055 
2056         str = "Your ";
2057         str += item->name(DESC_BASENAME, false, false, false);
2058         str += " briefly flashes a lurid colour!";
2059         messages.push_back(str);
2060     }
2061 
2062     if (item_def* item = you.slot_item(EQ_BODY_ARMOUR))
2063     {
2064         string str;
2065         string name = item->name(DESC_BASENAME, false, false, false);
2066 
2067         if (name.find("dragon") != string::npos)
2068         {
2069             str  = "The scales on your ";
2070             str += name;
2071             str += " wiggle briefly.";
2072         }
2073         else if (item->sub_type == ARM_ANIMAL_SKIN)
2074         {
2075             str  = "The fur on your ";
2076             str += name;
2077             str += " grows longer at an alarming rate, then retracts back "
2078                    "to normal.";
2079         }
2080         else if (item->sub_type == ARM_LEATHER_ARMOUR)
2081         {
2082             str  = "Your ";
2083             str += name;
2084             str += " briefly grows fur, then returns to normal.";
2085         }
2086         else if (item->sub_type == ARM_ROBE)
2087         {
2088             str  = "You briefly become tangled in your ";
2089             str += pluralise(name);
2090             str += ".";
2091         }
2092         else if (item->sub_type >= ARM_RING_MAIL
2093                  && item->sub_type <= ARM_PLATE_ARMOUR)
2094         {
2095             str  = "Your ";
2096             str += name;
2097             str += " briefly appears rusty.";
2098         }
2099 
2100         if (!str.empty())
2101             messages.push_back(str);
2102     }
2103 
2104     ////////
2105     // Misc.
2106     if (!inv_items.empty())
2107     {
2108         item_def &item = **random_iterator(inv_items);
2109 
2110         string name = item.name(DESC_YOUR, false, false, false);
2111         string verb = random_choose("glow", "vibrate");
2112 
2113         if (item.quantity == 1)
2114             verb += "s";
2115 
2116         messages.push_back(name + " briefly " + verb + ".");
2117     }
2118 
2119     if (!priority.empty() && coinflip())
2120         mpr(priority[random2(priority.size())]);
2121     else
2122         mpr(messages[random2(messages.size())]);
2123 }
2124 
_miscast_is_nasty(int sever)2125 static bool _miscast_is_nasty(int sever)
2126 {
2127     return sever >= 5 && _xom_feels_nasty();
2128 }
2129 
_xom_chaos_upgrade(int)2130 static void _xom_chaos_upgrade(int /*sever*/)
2131 {
2132     monster* mon = choose_random_nearby_monster(0, _choose_chaos_upgrade);
2133 
2134     if (!mon)
2135         return;
2136 
2137     god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str());
2138 
2139     mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE};
2140 
2141     bool rc = false;
2142     for (int i = 0; i < 3 && !rc; ++i)
2143     {
2144         item_def* const item = mon->mslot_item(slots[i]);
2145         if (item && _is_chaos_upgradeable(*item, mon))
2146         {
2147             _do_chaos_upgrade(*item, mon);
2148             rc = true;
2149         }
2150     }
2151     ASSERT(rc);
2152 
2153     // Wake the monster up.
2154     behaviour_event(mon, ME_ALERT, &you);
2155 
2156     if (rc)
2157         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "chaos upgrade"), true);
2158 }
2159 
_xom_player_confusion_effect(int sever)2160 static void _xom_player_confusion_effect(int sever)
2161 {
2162     const bool conf = you.confused();
2163 
2164     if (!confuse_player(5 + random2(3), true))
2165         return;
2166 
2167     god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str());
2168     mprf(MSGCH_WARN, "You are %sconfused.",
2169          conf ? "more " : "");
2170 
2171     // At higher severities, Xom is less likely to confuse surrounding
2172     // creatures.
2173     bool mons_too = false;
2174     if (random2(sever) < 30)
2175     {
2176         for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
2177         {
2178             if (random2(sever) > 30)
2179                 continue;
2180             _confuse_monster(*mi, sever);
2181             mons_too = true;
2182         }
2183     }
2184 
2185     // Take a note.
2186     string conf_msg = "confusion";
2187     if (mons_too)
2188         conf_msg += " (+ monsters)";
2189     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, conf_msg), true);
2190 }
2191 
_valid_floor_grid(coord_def pos)2192 static bool _valid_floor_grid(coord_def pos)
2193 {
2194     if (!in_bounds(pos))
2195         return false;
2196 
2197     return env.grid(pos) == DNGN_FLOOR;
2198 }
2199 
move_stair(coord_def stair_pos,bool away,bool allow_under)2200 bool move_stair(coord_def stair_pos, bool away, bool allow_under)
2201 {
2202     if (!allow_under)
2203         ASSERT(stair_pos != you.pos());
2204 
2205     dungeon_feature_type feat = env.grid(stair_pos);
2206     ASSERT(feat_stair_direction(feat) != CMD_NO_CMD);
2207 
2208     coord_def begin, towards;
2209 
2210     bool stairs_moved = false;
2211     if (away)
2212     {
2213         // If the staircase starts out under the player, first shove it
2214         // onto a neighbouring grid.
2215         if (allow_under && stair_pos == you.pos())
2216         {
2217             coord_def new_pos(stair_pos);
2218             // Loop twice through all adjacent grids. In the first round,
2219             // only consider grids whose next neighbour in the direction
2220             // away from the player is also of type floor. If we didn't
2221             // find any matching grid, try again without that restriction.
2222             for (int tries = 0; tries < 2; ++tries)
2223             {
2224                 int adj_count = 0;
2225                 for (adjacent_iterator ai(stair_pos); ai; ++ai)
2226                     if (env.grid(*ai) == DNGN_FLOOR
2227                         && (tries || _valid_floor_grid(*ai + *ai - stair_pos))
2228                         && one_chance_in(++adj_count))
2229                     {
2230                         new_pos = *ai;
2231                     }
2232 
2233                 if (!tries && new_pos != stair_pos)
2234                     break;
2235             }
2236 
2237             if (new_pos == stair_pos)
2238                 return false;
2239 
2240             if (!slide_feature_over(stair_pos, new_pos, true))
2241                 return false;
2242 
2243             stair_pos = new_pos;
2244             stairs_moved = true;
2245         }
2246 
2247         begin   = you.pos();
2248         towards = stair_pos;
2249     }
2250     else
2251     {
2252         // Can't move towards player if it's already adjacent.
2253         if (adjacent(you.pos(), stair_pos))
2254             return false;
2255 
2256         begin   = stair_pos;
2257         towards = you.pos();
2258     }
2259 
2260     ray_def ray;
2261     if (!find_ray(begin, towards, ray, opc_solid_see))
2262     {
2263         mprf(MSGCH_ERROR, "Couldn't find ray between player and stairs.");
2264         return stairs_moved;
2265     }
2266 
2267     // Don't start off under the player.
2268     if (away)
2269         ray.advance();
2270 
2271     bool found_stairs = false;
2272     int  past_stairs  = 0;
2273     while (in_bounds(ray.pos()) && you.see_cell(ray.pos())
2274            && !cell_is_solid(ray.pos()) && ray.pos() != you.pos())
2275     {
2276         if (ray.pos() == stair_pos)
2277             found_stairs = true;
2278         if (found_stairs)
2279             past_stairs++;
2280         ray.advance();
2281     }
2282     past_stairs--;
2283 
2284     if (!away && cell_is_solid(ray.pos()))
2285     {
2286         // Transparent wall between stair and player.
2287         return stairs_moved;
2288     }
2289 
2290     if (away && !found_stairs)
2291     {
2292         if (cell_is_solid(ray.pos()))
2293         {
2294             // Transparent wall between stair and player.
2295             return stairs_moved;
2296         }
2297 
2298         mprf(MSGCH_ERROR, "Ray didn't cross stairs.");
2299     }
2300 
2301     if (away && past_stairs <= 0)
2302     {
2303         // Stairs already at edge, can't move further away.
2304         return stairs_moved;
2305     }
2306 
2307     if (!in_bounds(ray.pos()) || ray.pos() == you.pos())
2308         ray.regress();
2309 
2310     while (!you.see_cell(ray.pos()) || env.grid(ray.pos()) != DNGN_FLOOR)
2311     {
2312         ray.regress();
2313         if (!in_bounds(ray.pos()) || ray.pos() == you.pos()
2314             || ray.pos() == stair_pos)
2315         {
2316             // No squares in path are a plain floor.
2317             return stairs_moved;
2318         }
2319     }
2320 
2321     ASSERT(stair_pos != ray.pos());
2322 
2323     string stair_str = feature_description_at(stair_pos, false, DESC_THE);
2324 
2325     mprf("%s slides %s you!", stair_str.c_str(),
2326          away ? "away from" : "towards");
2327 
2328     // Animate stair moving.
2329     const feature_def &feat_def = get_feature_def(feat);
2330 
2331     bolt beam;
2332 
2333     beam.range   = INFINITE_DISTANCE;
2334     beam.flavour = BEAM_VISUAL;
2335     beam.glyph   = feat_def.symbol();
2336     beam.colour  = feat_def.colour();
2337     beam.source  = stair_pos;
2338     beam.target  = ray.pos();
2339     beam.name    = "STAIR BEAM";
2340     beam.draw_delay = 50; // Make beam animation slower than normal.
2341 
2342     beam.aimed_at_spot = true;
2343     beam.fire();
2344 
2345     // Clear out "missile trails"
2346     viewwindow();
2347     update_screen();
2348 
2349     if (!swap_features(stair_pos, ray.pos(), false, false))
2350     {
2351         mprf(MSGCH_ERROR, "_move_stair(): failed to move %s",
2352              stair_str.c_str());
2353         return stairs_moved;
2354     }
2355 
2356     return true;
2357 }
2358 
_nearby_stairs()2359 static vector<coord_def> _nearby_stairs()
2360 {
2361     vector<coord_def> stairs_avail;
2362     for (radius_iterator ri(you.pos(), LOS_RADIUS, C_SQUARE); ri; ++ri)
2363     {
2364         if (!cell_see_cell(you.pos(), *ri, LOS_SOLID_SEE))
2365             continue;
2366 
2367         dungeon_feature_type feat = env.grid(*ri);
2368         if (feat_stair_direction(feat) != CMD_NO_CMD
2369             && feat != DNGN_ENTER_SHOP)
2370         {
2371             stairs_avail.push_back(*ri);
2372         }
2373     }
2374 
2375     return stairs_avail;
2376 }
2377 
_xom_repel_stairs(bool unclimbable)2378 static void _xom_repel_stairs(bool unclimbable)
2379 {
2380     vector<coord_def> stairs_avail  = _nearby_stairs();
2381 
2382     // Only works if there are stairs in view.
2383     if (stairs_avail.empty())
2384         return;
2385 
2386     bool real_stairs = false;
2387     for (auto loc : stairs_avail)
2388         if (feat_is_staircase(env.grid(loc)))
2389             real_stairs = true;
2390 
2391     // Don't mention staircases if there aren't any nearby.
2392     string stair_msg = _get_xom_speech("repel stairs");
2393     if (stair_msg.find("@staircase@") != string::npos)
2394     {
2395         string feat_name;
2396         if (!real_stairs)
2397         {
2398             if (feat_is_escape_hatch(env.grid(stairs_avail[0])))
2399                 feat_name = "escape hatch";
2400             else
2401                 feat_name = "gate";
2402         }
2403         else
2404             feat_name = "staircase";
2405         stair_msg = replace_all(stair_msg, "@staircase@", feat_name);
2406     }
2407 
2408     god_speaks(GOD_XOM, stair_msg.c_str());
2409 
2410     you.duration[DUR_REPEL_STAIRS_MOVE] = 1000; // 100 turns
2411     if (unclimbable)
2412         you.duration[DUR_REPEL_STAIRS_CLIMB] = 500; // 50 turns
2413 
2414     shuffle_array(stairs_avail);
2415     int count_moved = 0;
2416     for (coord_def stair : stairs_avail)
2417         if (move_stair(stair, true, true))
2418             count_moved++;
2419 
2420     if (!count_moved)
2421     {
2422         if (one_chance_in(8))
2423             mpr("Nothing appears to happen... Ominous!");
2424         else
2425             canned_msg(MSG_NOTHING_HAPPENS);
2426     }
2427     else
2428         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "repel stairs"), true);
2429 }
2430 
_xom_moving_stairs(int)2431 static void _xom_moving_stairs(int) { _xom_repel_stairs(false); }
_xom_unclimbable_stairs(int)2432 static void _xom_unclimbable_stairs(int) { _xom_repel_stairs(true); }
2433 
_xom_cloud_trail(int)2434 static void _xom_cloud_trail(int /*sever*/)
2435 {
2436     you.duration[DUR_CLOUD_TRAIL] = random_range(600, 1200);
2437     you.props[XOM_CLOUD_TRAIL_TYPE_KEY] =
2438         // 80% chance of a useful trail
2439         random_choose_weighted(20, CLOUD_CHAOS,
2440                                10, CLOUD_MAGIC_TRAIL,
2441                                5,  CLOUD_MIASMA,
2442                                5,  CLOUD_PETRIFY,
2443                                5,  CLOUD_MUTAGENIC,
2444                                5,  CLOUD_NEGATIVE_ENERGY);
2445 
2446     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "cloud trail"), true);
2447 
2448     const string speech = _get_xom_speech("cloud trail");
2449     god_speaks(GOD_XOM, speech.c_str());
2450 }
2451 
_xom_statloss(int)2452 static void _xom_statloss(int /*sever*/)
2453 {
2454     const string speech = _get_xom_speech("draining or torment");
2455     const bool nasty = _xom_feels_nasty();
2456 
2457     const stat_type stat = static_cast<stat_type>(random2(NUM_STATS));
2458     int loss = 1;
2459 
2460     // Don't kill the player unless Xom is being nasty.
2461     if (nasty)
2462         loss = 1 + random2(3);
2463     else if (you.stat(stat) <= loss)
2464         return;
2465 
2466     god_speaks(GOD_XOM, speech.c_str());
2467     lose_stat(stat, loss);
2468 
2469     const char* sstr[3] = { "Str", "Int", "Dex" };
2470     const string note = make_stringf("stat loss: -%d %s (%d/%d)",
2471                                      loss, sstr[stat], you.stat(stat),
2472                                      you.max_stat(stat));
2473 
2474     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
2475 }
2476 
_xom_draining(int)2477 static void _xom_draining(int /*sever*/)
2478 {
2479     const string speech = _get_xom_speech("draining or torment");
2480     god_speaks(GOD_XOM, speech.c_str());
2481 
2482     drain_player(100, true);
2483 
2484     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "draining"), true);
2485 }
2486 
_xom_torment(int)2487 static void _xom_torment(int /*sever*/)
2488 {
2489     const string speech = _get_xom_speech("draining or torment");
2490     god_speaks(GOD_XOM, speech.c_str());
2491 
2492     torment_player(0, TORMENT_XOM);
2493 
2494     const string note = make_stringf("torment (%d/%d hp)", you.hp, you.hp_max);
2495     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
2496 }
2497 
_xom_summon_hostile(monster_type hostile)2498 static monster* _xom_summon_hostile(monster_type hostile)
2499 {
2500     return create_monster(mgen_data::hostile_at(hostile, true, you.pos())
2501                           .set_summoned(nullptr, 4, MON_SUMM_WRATH, GOD_XOM)
2502                           .set_non_actor_summoner("Xom"));
2503 }
2504 
_xom_summon_hostiles(int sever)2505 static void _xom_summon_hostiles(int sever)
2506 {
2507     int num_summoned = 0;
2508     const bool shadow_creatures = one_chance_in(3);
2509 
2510     if (shadow_creatures)
2511     {
2512         // Small number of shadow creatures.
2513         int count = 1 + random2(4);
2514         for (int i = 0; i < count; ++i)
2515             if (_xom_summon_hostile(RANDOM_MOBILE_MONSTER))
2516                 num_summoned++;
2517     }
2518     else
2519     {
2520         // The number of demons is dependent on severity, though heavily
2521         // randomised.
2522         int numdemons = sever;
2523         for (int i = 0; i < 3; ++i)
2524             numdemons = random2(numdemons + 1);
2525         numdemons = min(numdemons + 1, 14);
2526 
2527         // Limit number of demons by experience level.
2528         if (!you.penance[GOD_XOM])
2529         {
2530             const int maxdemons = ((you.experience_level / 2) + 1);
2531             if (numdemons > maxdemons)
2532                 numdemons = maxdemons;
2533         }
2534 
2535         for (int i = 0; i < numdemons; ++i)
2536             if (_xom_summon_hostile(_xom_random_demon(sever)))
2537                 num_summoned++;
2538     }
2539 
2540     if (num_summoned > 0)
2541     {
2542         const string note = make_stringf("summons %d hostile %s%s",
2543                                          num_summoned,
2544                                          shadow_creatures ? "shadow creature"
2545                                                           : "demon",
2546                                          num_summoned > 1 ? "s" : "");
2547         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
2548 
2549         const string speech = _get_xom_speech("hostile monster");
2550         god_speaks(GOD_XOM, speech.c_str());
2551     }
2552 }
2553 
_has_min_banishment_level()2554 static bool _has_min_banishment_level()
2555 {
2556     return you.experience_level >= 9;
2557 }
2558 
2559 // Rolls whether banishment will be averted.
_will_not_banish()2560 static bool _will_not_banish()
2561 {
2562     return x_chance_in_y(5, you.experience_level);
2563 }
2564 
2565 // Disallow early banishment and make it much rarer later-on.
2566 // While Xom is bored, the chance is increased.
_allow_xom_banishment()2567 static bool _allow_xom_banishment()
2568 {
2569     // Always allowed if under penance.
2570     if (player_under_penance(GOD_XOM))
2571         return true;
2572 
2573     // If Xom is bored, banishment becomes viable earlier.
2574     if (_xom_is_bored())
2575         return !_will_not_banish();
2576 
2577     // Below the minimum experience level, only fake banishment is allowed.
2578     if (!_has_min_banishment_level())
2579     {
2580         // Allow banishment; it will be retracted right away.
2581         if (one_chance_in(5) && x_chance_in_y(you.piety, 1000))
2582             return true;
2583         else
2584             return false;
2585     }
2586     else if (_will_not_banish())
2587         return false;
2588 
2589     return true;
2590 }
2591 
_revert_banishment(bool xom_banished=true)2592 static void _revert_banishment(bool xom_banished = true)
2593 {
2594     more();
2595     god_speaks(GOD_XOM, xom_banished
2596                ? _get_xom_speech("revert own banishment").c_str()
2597                : _get_xom_speech("revert other banishment").c_str());
2598     down_stairs(DNGN_EXIT_ABYSS);
2599     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
2600                    "revert banishment"), true);
2601 }
2602 
xom_maybe_reverts_banishment(bool xom_banished,bool debug)2603 xom_event_type xom_maybe_reverts_banishment(bool xom_banished, bool debug)
2604 {
2605     // Never revert if Xom is bored or the player is under penance.
2606     if (_xom_feels_nasty())
2607         return XOM_BAD_BANISHMENT;
2608 
2609     // Sometimes Xom will immediately revert banishment.
2610     // Always if the banishment happened below the minimum exp level and Xom was responsible.
2611     if (xom_banished && !_has_min_banishment_level() || x_chance_in_y(you.piety, 1000))
2612     {
2613         if (!debug)
2614             _revert_banishment(xom_banished);
2615         return XOM_BAD_PSEUDO_BANISHMENT;
2616     }
2617     return XOM_BAD_BANISHMENT;
2618 }
2619 
_xom_do_banishment(bool real)2620 static void _xom_do_banishment(bool real)
2621 {
2622     god_speaks(GOD_XOM, _get_xom_speech("banishment").c_str());
2623 
2624     // Handles note taking, scales depth by XL
2625     banished("Xom", you.experience_level);
2626     if (!real)
2627         _revert_banishment();
2628 }
2629 
_xom_banishment(int)2630 static void _xom_banishment(int /*sever*/) { _xom_do_banishment(true); }
_xom_pseudo_banishment(int)2631 static void _xom_pseudo_banishment(int) { _xom_do_banishment(false); }
2632 
_xom_noise(int)2633 static void _xom_noise(int /*sever*/)
2634 {
2635     // Ranges from shout to shatter volume.
2636     const int noisiness = 12 + random2(19);
2637 
2638     god_speaks(GOD_XOM, _get_xom_speech("noise").c_str());
2639     // Xom isn't subject to silence.
2640     fake_noisy(noisiness, you.pos());
2641 
2642     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "noise"), true);
2643 }
2644 
_mon_valid_blink_victim(const monster & mon)2645 static bool _mon_valid_blink_victim(const monster& mon)
2646 {
2647     return !mon.wont_attack()
2648             && !mon.no_tele()
2649             && !mons_is_projectile(mon);
2650 }
2651 
_xom_blink_monsters(int)2652 static void _xom_blink_monsters(int /*sever*/)
2653 {
2654     int blinks = 0;
2655     // Sometimes blink towards the player, sometimes randomly. It might
2656     // end up being helpful instead of dangerous, but Xom doesn't mind.
2657     const bool blink_to_player = _xom_feels_nasty() || coinflip();
2658     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
2659     {
2660         if (blinks >= 5)
2661             break;
2662 
2663         if (!_mon_valid_blink_victim(**mi) || coinflip())
2664             continue;
2665 
2666         // Only give this message once.
2667         if (!blinks)
2668             god_speaks(GOD_XOM, _get_xom_speech("blink monsters").c_str());
2669 
2670         if (blink_to_player)
2671             blink_other_close(*mi, you.pos());
2672         else
2673             monster_blink(*mi, false);
2674 
2675         blinks++;
2676     }
2677 
2678     if (blinks)
2679     {
2680         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "blink monster(s)"),
2681                   true);
2682     }
2683 }
2684 
_xom_cleaving(int sever)2685 static void _xom_cleaving(int sever)
2686 {
2687     god_speaks(GOD_XOM, _get_xom_speech("cleaving").c_str());
2688 
2689     you.increase_duration(DUR_CLEAVE, 10 + random2(sever));
2690 
2691     if (const item_def* const weapon = you.weapon())
2692     {
2693         const bool axe = item_attack_skill(*weapon) == SK_AXES;
2694         mprf(MSGCH_DURATION,
2695              "%s %s sharp%s", weapon->name(DESC_YOUR).c_str(),
2696              conjugate_verb("look", weapon->quantity > 1).c_str(),
2697              (axe) ? " (like it always does)." : ".");
2698     }
2699     else
2700     {
2701         mprf(MSGCH_DURATION, "%s",
2702              you.hands_act("look", "sharp.").c_str());
2703     }
2704 
2705     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "cleaving"), true);
2706 }
2707 
2708 
_handle_accidental_death(const int orig_hp,const FixedVector<uint8_t,NUM_MUTATIONS> & orig_mutation,const transformation orig_form)2709 static void _handle_accidental_death(const int orig_hp,
2710     const FixedVector<uint8_t, NUM_MUTATIONS> &orig_mutation,
2711     const transformation orig_form)
2712 {
2713     // Did ouch() return early because the player died from the Xom
2714     // effect, even though neither is the player under penance nor is
2715     // Xom bored?
2716     if ((!you.did_escape_death()
2717          && you.escaped_death_aux.empty()
2718          && !_player_is_dead())
2719         || you.pending_revival) // don't let xom take credit for felid revival
2720     {
2721         // The player is fine.
2722         return;
2723     }
2724 
2725     string speech_type = XOM_SPEECH("accidental homicide");
2726 
2727     const dungeon_feature_type feat = env.grid(you.pos());
2728 
2729     switch (you.escaped_death_cause)
2730     {
2731         case NUM_KILLBY:
2732         case KILLED_BY_LEAVING:
2733         case KILLED_BY_WINNING:
2734         case KILLED_BY_QUITTING:
2735             speech_type = XOM_SPEECH("weird death");
2736             break;
2737 
2738         case KILLED_BY_LAVA:
2739         case KILLED_BY_WATER:
2740             if (!is_feat_dangerous(feat))
2741                 speech_type = "weird death";
2742             break;
2743 
2744         default:
2745             if (is_feat_dangerous(feat))
2746                 speech_type = "weird death";
2747         break;
2748     }
2749 
2750     canned_msg(MSG_YOU_DIE);
2751     god_speaks(GOD_XOM, _get_xom_speech(speech_type).c_str());
2752     god_speaks(GOD_XOM, _get_xom_speech("resurrection").c_str());
2753 
2754     int pre_mut_hp = you.hp;
2755     if (you.hp <= 0)
2756         you.hp = 9999; // avoid spurious recursive deaths if heavily rotten
2757 
2758     // If any mutation has changed, death was because of it.
2759     for (int i = 0; i < NUM_MUTATIONS; ++i)
2760     {
2761         if (orig_mutation[i] > you.mutation[i])
2762             mutate((mutation_type)i, "Xom's lifesaving", true, true, true);
2763         else if (orig_mutation[i] < you.mutation[i])
2764             delete_mutation((mutation_type)i, "Xom's lifesaving", true, true, true);
2765     }
2766 
2767     if (pre_mut_hp <= 0)
2768         set_hp(min(orig_hp, you.hp_max));
2769 
2770     if (orig_form != you.form)
2771     {
2772         dprf("Trying emergency untransformation.");
2773         you.transform_uncancellable = false;
2774         transform(10, orig_form, true);
2775     }
2776 
2777     if (is_feat_dangerous(feat) && !crawl_state.game_is_sprint())
2778         you_teleport_now();
2779 }
2780 
2781 /**
2782  * Try to choose an action for Xom to take that is at least notionally 'good'
2783  * for the player.
2784  *
2785  * @param sever         The intended magnitude of the action.
2786  * @param tension       How much danger we think the player's currently in.
2787  * @return              A good action for Xom to take, e.g. XOM_GOOD_ALLIES.
2788  */
_xom_choose_good_action(int sever,int tension)2789 static xom_event_type _xom_choose_good_action(int sever, int tension)
2790 {
2791     // This series of random calls produces a poisson-looking
2792     // distribution: initial hump, plus a long-ish tail.
2793     // a wizard has pronounced a curse on the original author of this code
2794 
2795     // Don't make the player go berserk, etc. if there's no danger.
2796     if (tension > random2(3) && x_chance_in_y(2, sever))
2797         return XOM_GOOD_POTION;
2798 
2799     if (x_chance_in_y(3, sever))
2800     {
2801         const xom_event_type divination
2802             = random_choose(XOM_GOOD_MAGIC_MAPPING,
2803                             XOM_GOOD_DETECT_CREATURES,
2804                             XOM_GOOD_DETECT_ITEMS);
2805 
2806         if (divination == XOM_GOOD_DETECT_CREATURES)
2807         {
2808             return divination; // useful regardless of exploration state
2809 
2810         // Only do mmap/detect items if there's a decent chunk of unexplored
2811         }
2812         // level left
2813         const int explored = _exploration_estimate(false);
2814         if (explored <= 80 || x_chance_in_y(explored, 100))
2815             return divination;
2816     }
2817 
2818     if (x_chance_in_y(4, sever) && tension > 0
2819         && _choose_random_spell(sever) != SPELL_NO_SPELL)
2820     {
2821         return XOM_GOOD_SPELL;
2822     }
2823 
2824     if (tension <= 0 && x_chance_in_y(5, sever)
2825         && !you.duration[DUR_CLOUD_TRAIL])
2826     {
2827         return XOM_GOOD_CLOUD_TRAIL;
2828     }
2829 
2830     if (tension > 0 && x_chance_in_y(5, sever)
2831         && mon_nearby([](monster& mon){ return !mon.wont_attack(); }))
2832     {
2833         return XOM_GOOD_CONFUSION;
2834     }
2835 
2836     if (tension > 0 && x_chance_in_y(6, sever)
2837         && mon_nearby(_choose_enchantable_monster))
2838     {
2839         return XOM_GOOD_ENCHANT_MONSTER;
2840     }
2841 
2842     if (tension > random2(5) && x_chance_in_y(7, sever)
2843         && !you.get_mutation_level(MUT_NO_LOVE))
2844     {
2845         return XOM_GOOD_SINGLE_ALLY;
2846     }
2847     if (tension < random2(5) && x_chance_in_y(8, sever)
2848         && !_xom_scenery_candidates().empty() || one_chance_in(8))
2849     {
2850         return XOM_GOOD_SCENERY;
2851     }
2852 
2853     if (x_chance_in_y(9, sever) && mon_nearby(_hostile_snake))
2854         return XOM_GOOD_SNAKES;
2855 
2856     if (tension > random2(10) && x_chance_in_y(10, sever)
2857         && !you.get_mutation_level(MUT_NO_LOVE))
2858     {
2859         return XOM_GOOD_ALLIES;
2860     }
2861     if (tension > random2(8) && x_chance_in_y(11, sever)
2862         && _find_monster_with_animateable_weapon()
2863         && !you.get_mutation_level(MUT_NO_LOVE))
2864     {
2865         return XOM_GOOD_ANIMATE_MON_WPN;
2866     }
2867 
2868     if (x_chance_in_y(12, sever) && _xom_mons_poly_target() != nullptr)
2869         return XOM_GOOD_POLYMORPH;
2870 
2871     if (tension > 0 && x_chance_in_y(13, sever))
2872     {
2873         const bool fake = one_chance_in(3);
2874         for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
2875         {
2876             if (mons_is_projectile(**mi)
2877                 || mons_is_tentacle_or_tentacle_segment(mi->type))
2878             {
2879                 continue;
2880             }
2881 
2882             if (fake)
2883                 return XOM_GOOD_FAKE_DESTRUCTION;
2884 
2885             // Skip adjacent monsters, and skip non-hostile monsters if not feeling nasty.
2886             if (!adjacent(you.pos(), mi->pos())
2887                  && (!mi->wont_attack() || _xom_feels_nasty()))
2888             {
2889                 return XOM_GOOD_DESTRUCTION;
2890             }
2891         }
2892     }
2893 
2894     if (tension > random2(5) && x_chance_in_y(14, sever))
2895         return XOM_GOOD_CLEAVING;
2896 
2897     if (tension > 0 && x_chance_in_y(15, sever) && !cloud_at(you.pos()))
2898         return XOM_GOOD_FOG;
2899 
2900     if (random2(tension) < 15 && x_chance_in_y(16, sever))
2901     {
2902         return x_chance_in_y(sever, 201) ? XOM_GOOD_ACQUIREMENT
2903                                          : XOM_GOOD_RANDOM_ITEM;
2904     }
2905 
2906     if (!player_in_branch(BRANCH_ABYSS) && x_chance_in_y(17, sever)
2907         && _teleportation_check())
2908     {
2909         // This is not very interesting if the level is already fully
2910         // explored (presumably cleared). Even then, it may
2911         // occasionally happen.
2912         const int explored = _exploration_estimate(true);
2913         if (explored < 80 || !x_chance_in_y(explored, 120))
2914             return XOM_GOOD_TELEPORT;
2915     }
2916 
2917     if (random2(tension) < 5 && x_chance_in_y(19, sever)
2918         && x_chance_in_y(16, you.how_mutated())
2919         && you.can_safely_mutate())
2920     {
2921         return XOM_GOOD_MUTATION;
2922     }
2923 
2924     if (tension > 0 && x_chance_in_y(20, sever)
2925         && player_in_a_dangerous_place())
2926     {
2927         // Make sure there's at least one enemy within the lightning radius.
2928         for (radius_iterator ri(you.pos(), 2, C_SQUARE, LOS_SOLID, true); ri;
2929              ++ri)
2930         {
2931             const monster *mon = monster_at(*ri);
2932             if (mon && !mon->wont_attack())
2933                 return XOM_GOOD_LIGHTNING;
2934         }
2935     }
2936 
2937     return XOM_DID_NOTHING;
2938 }
2939 
2940 /**
2941  * Try to choose an action for Xom to take that is at least notionally 'bad'
2942  * for the player.
2943  *
2944  * @param sever         The intended magnitude of the action.
2945  * @param tension       How much danger we think the player's currently in.
2946  * @return              A bad action for Xom to take, e.g. XOM_BAD_NOISE.
2947  */
_xom_choose_bad_action(int sever,int tension)2948 static xom_event_type _xom_choose_bad_action(int sever, int tension)
2949 {
2950     const bool nasty = _miscast_is_nasty(sever);
2951 
2952     if (!nasty && x_chance_in_y(3, sever))
2953         return XOM_BAD_MISCAST_PSEUDO;
2954 
2955     // Sometimes do noise out of combat.
2956     if ((tension > 0 || coinflip()) && x_chance_in_y(6, sever))
2957         return XOM_BAD_NOISE;
2958     if (tension > 0 && x_chance_in_y(7, sever))
2959         return XOM_BAD_ENCHANT_MONSTER;
2960 
2961     if (tension > 0 && x_chance_in_y(8, sever)
2962         && mon_nearby(_mon_valid_blink_victim))
2963     {
2964         return XOM_BAD_BLINK_MONSTERS;
2965     }
2966 
2967     // It's pointless to confuse player if there's no danger nearby.
2968     if (tension > 0 && x_chance_in_y(9, sever))
2969         return XOM_BAD_CONFUSION;
2970 
2971     if (tension > 0 && x_chance_in_y(10, sever)
2972         && _rearrangeable_pieces().size())
2973     {
2974         return XOM_BAD_SWAP_MONSTERS;
2975     }
2976 
2977     if (x_chance_in_y(14, sever) && mon_nearby(_choose_chaos_upgrade))
2978         return XOM_BAD_CHAOS_UPGRADE;
2979     if (x_chance_in_y(15, sever) && !player_in_branch(BRANCH_ABYSS)
2980         && _teleportation_check())
2981     {
2982         const int explored = _exploration_estimate(true);
2983         if (!(nasty && (explored >= 40 || tension > 10)
2984             || explored >= 60 + random2(40)))
2985         {
2986             return XOM_BAD_TELEPORT;
2987         }
2988     }
2989     if (x_chance_in_y(16, sever))
2990         return XOM_BAD_POLYMORPH;
2991  // Pushing stairs/exits is always hilarious in the Abyss!
2992     if ((tension > 0 || player_in_branch(BRANCH_ABYSS))
2993         && x_chance_in_y(17, sever) && !_nearby_stairs().empty()
2994         && !you.duration[DUR_REPEL_STAIRS_MOVE]
2995         && !you.duration[DUR_REPEL_STAIRS_CLIMB])
2996     {
2997         if (one_chance_in(5)
2998             || feat_stair_direction(env.grid(you.pos())) != CMD_NO_CMD
2999                 && env.grid(you.pos()) != DNGN_ENTER_SHOP)
3000         {
3001             return XOM_BAD_CLIMB_STAIRS;
3002         }
3003         return XOM_BAD_MOVING_STAIRS;
3004     }
3005     if (random2(tension) < 11 && x_chance_in_y(18, sever)
3006         && you.can_safely_mutate())
3007     {
3008         return XOM_BAD_MUTATION;
3009     }
3010     if (x_chance_in_y(19, sever))
3011         return XOM_BAD_SUMMON_HOSTILES;
3012 
3013     if (x_chance_in_y(21, sever))
3014     {
3015         if (coinflip())
3016             return XOM_BAD_STATLOSS;
3017         if (coinflip())
3018         {
3019             if (player_prot_life() < 3)
3020                 return XOM_BAD_DRAINING;
3021             // else choose something else
3022         }
3023         else if (!you.res_torment())
3024             return XOM_BAD_TORMENT;
3025         // else choose something else
3026     }
3027     if (tension > 0 && x_chance_in_y(22, sever)
3028         && !cloud_at(you.pos()))
3029     {
3030         return XOM_BAD_CHAOS_CLOUD;
3031     }
3032     if (one_chance_in(sever) && !player_in_branch(BRANCH_ABYSS)
3033         && _allow_xom_banishment())
3034     {
3035         return xom_maybe_reverts_banishment(true, true);
3036     }
3037 
3038     return XOM_DID_NOTHING; // ugh
3039 }
3040 
3041 /**
3042  * Try to choose an action for Xom to take.
3043  *
3044  * @param niceness      Whether the action should be 'good' for the player.
3045  * @param sever         The intended magnitude of the action.
3046  * @param tension       How much danger we think the player's currently in.
3047  * @return              An bad action for Xom to take, e.g. XOM_DID_NOTHING.
3048  */
xom_choose_action(bool niceness,int sever,int tension)3049 xom_event_type xom_choose_action(bool niceness, int sever, int tension)
3050 {
3051     sever = max(1, sever);
3052 
3053     if (_player_is_dead() && !you.pending_revival)
3054     {
3055         // This should only happen if the player used wizard mode to
3056         // escape death from deep water or lava.
3057         ASSERT(you.wizard);
3058         ASSERT(!you.did_escape_death());
3059         if (is_feat_dangerous(env.grid(you.pos())))
3060             mprf(MSGCH_DIAGNOSTICS, "Player is standing in deadly terrain, skipping Xom act.");
3061         else
3062             mprf(MSGCH_DIAGNOSTICS, "Player is already dead, skipping Xom act.");
3063         return XOM_PLAYER_DEAD;
3064     }
3065 
3066     if (niceness)
3067     {
3068         // Make good acts at zero tension less likely, especially if Xom
3069         // is in a bad mood.
3070         if (tension == 0
3071             && you_worship(GOD_XOM) && !x_chance_in_y(you.piety, MAX_PIETY))
3072         {
3073 #ifdef NOTE_DEBUG_XOM
3074             take_note(Note(NOTE_MESSAGE, 0, 0, "suppress good act because of "
3075                            "zero tension"), true);
3076 #endif
3077             return XOM_DID_NOTHING;
3078         }
3079 
3080         // {sarcastically}: Good stuff. {seriously}: remove this loop
3081         while (true)
3082         {
3083             const xom_event_type action = _xom_choose_good_action(sever,
3084                                                                   tension);
3085             if (action != XOM_DID_NOTHING)
3086                 return action;
3087         }
3088     }
3089 
3090     // Make bad acts at non-zero tension less likely, especially if Xom
3091     // is in a good mood.
3092     if (!_xom_feels_nasty() && tension > random2(10)
3093         && you_worship(GOD_XOM) && x_chance_in_y(you.piety, MAX_PIETY))
3094     {
3095 #ifdef NOTE_DEBUG_XOM
3096         const string note = string("suppress bad act because of ") +
3097                                    tension + " tension";
3098         take_note(Note(NOTE_MESSAGE, 0, 0, note), true);
3099 #endif
3100         return XOM_DID_NOTHING;
3101     }
3102 
3103     // Bad mojo. (this loop, that is)
3104     while (true)
3105     {
3106         const xom_event_type action = _xom_choose_bad_action(sever, tension);
3107         if (action != XOM_DID_NOTHING)
3108             return action;
3109     }
3110 
3111     die("This should never happen.");
3112 }
3113 
3114 /**
3115  * Execute the specified Xom Action.
3116  *
3117  * @param action        The action type in question; e.g. XOM_BAD_NOISE.
3118  * @param sever         The severity of the action.
3119  */
xom_take_action(xom_event_type action,int sever)3120 void xom_take_action(xom_event_type action, int sever)
3121 {
3122     const int  orig_hp       = you.hp;
3123     const transformation orig_form = you.form;
3124     const FixedVector<uint8_t, NUM_MUTATIONS> orig_mutation = you.mutation;
3125     const bool was_bored = _xom_is_bored();
3126 
3127     const bool bad_effect = _action_is_bad(action);
3128 
3129     if (was_bored && bad_effect && Options.note_xom_effects)
3130         take_note(Note(NOTE_MESSAGE, 0, 0, "XOM is BORED!"), true);
3131 
3132     // actually take the action!
3133     {
3134         god_acting gdact(GOD_XOM);
3135         _do_xom_event(action, sever);
3136     }
3137 
3138     // If we got here because Xom was bored, reset gift timeout according
3139     // to the badness of the effect.
3140     if (bad_effect && _xom_is_bored())
3141     {
3142         const int badness = _xom_event_badness(action);
3143         const int interest = random2avg(badness * 60, 2);
3144         you.gift_timeout   = min(interest, 255);
3145         //updating piety status line
3146         you.redraw_title = true;
3147 #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
3148         mprf(MSGCH_DIAGNOSTICS, "badness: %d, new interest: %d",
3149              badness, you.gift_timeout);
3150 #endif
3151     }
3152 
3153     _handle_accidental_death(orig_hp, orig_mutation, orig_form);
3154 
3155     if (you_worship(GOD_XOM) && one_chance_in(5))
3156     {
3157         const string old_xom_favour = describe_xom_favour();
3158         you.piety = random2(MAX_PIETY + 1);
3159         you.redraw_title = true; // redraw piety/boredom display
3160         const string new_xom_favour = describe_xom_favour();
3161         if (was_bored || old_xom_favour != new_xom_favour)
3162         {
3163             const string msg = "You are now " + new_xom_favour;
3164             god_speaks(you.religion, msg.c_str());
3165         }
3166 #ifdef NOTE_DEBUG_XOM
3167         const string note = string("reroll piety: ") + you.piety;
3168         take_note(Note(NOTE_MESSAGE, 0, 0, note), true);
3169 #endif
3170     }
3171     else if (was_bored)
3172     {
3173         // If we didn't reroll at least mention the new favour
3174         // now that it's not "BORING thing" anymore.
3175         const string new_xom_favour = describe_xom_favour();
3176         const string msg = "You are now " + new_xom_favour;
3177         god_speaks(you.religion, msg.c_str());
3178     }
3179 }
3180 
3181 /**
3182  * Let Xom take an action, probably.
3183  *
3184  * @param sever         The intended magnitude of the action.
3185  * @param nice          Whether the action should be 'good' for the player.
3186  *                      If MB_MAYBE, determined by xom's whim.
3187  *                      May be overridden.
3188  * @param tension       How much danger we think the player's currently in.
3189  * @return              Whichever action Xom took, or XOM_DID_NOTHING.
3190  */
xom_acts(int sever,maybe_bool nice,int tension,bool debug)3191 xom_event_type xom_acts(int sever, maybe_bool nice, int tension, bool debug)
3192 {
3193     bool niceness = tobool(nice, xom_is_nice(tension));
3194 
3195 #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
3196     if (!debug)
3197     {
3198         // This probably seems a bit odd, but we really don't want to display
3199         // these when doing a heavy-duty wiz-mode debug test: just ends up
3200         // as message spam and the player doesn't get any further information
3201         // anyway. (jpeg)
3202 
3203         // these numbers (sever, tension) may be modified later...
3204         mprf(MSGCH_DIAGNOSTICS, "xom_acts(%u, %d, %d); piety: %u, interest: %u",
3205              niceness, sever, tension, you.piety, you.gift_timeout);
3206 
3207         static char xom_buf[100];
3208         snprintf(xom_buf, sizeof(xom_buf), "xom_acts(%s, %d, %d), mood: %d",
3209                  (niceness ? "true" : "false"), sever, tension, you.piety);
3210         take_note(Note(NOTE_MESSAGE, 0, 0, xom_buf), true);
3211     }
3212 #endif
3213 
3214     if (tension == -1)
3215         tension = get_tension(GOD_XOM);
3216 
3217 #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM) || defined(DEBUG_TENSION)
3218     // No message during heavy-duty wizmode testing:
3219     // Instead all results are written into xom_debug.stat.
3220     if (!debug)
3221         mprf(MSGCH_DIAGNOSTICS, "Xom tension: %d", tension);
3222 #endif
3223 
3224     const xom_event_type action = xom_choose_action(niceness, sever, tension);
3225     if (!debug)
3226         xom_take_action(action, sever);
3227 
3228     return action;
3229 }
3230 
xom_check_lost_item(const item_def & item)3231 void xom_check_lost_item(const item_def& item)
3232 {
3233     if (is_unrandom_artefact(item))
3234         xom_is_stimulated(100, "Xom snickers.", true);
3235 }
3236 
xom_check_destroyed_item(const item_def & item)3237 void xom_check_destroyed_item(const item_def& item)
3238 {
3239     if (is_unrandom_artefact(item))
3240         xom_is_stimulated(100, "Xom snickers.", true);
3241 }
3242 
_death_is_funny(const kill_method_type killed_by)3243 static bool _death_is_funny(const kill_method_type killed_by)
3244 {
3245     switch (killed_by)
3246     {
3247     // The less original deaths are considered boring.
3248     case KILLED_BY_MONSTER:
3249     case KILLED_BY_BEAM:
3250     case KILLED_BY_CLOUD:
3251     case KILLED_BY_FREEZING:
3252     case KILLED_BY_BURNING:
3253     case KILLED_BY_SELF_AIMED:
3254     case KILLED_BY_SOMETHING:
3255     case KILLED_BY_TRAP:
3256         return false;
3257     default:
3258         // All others are fun (says Xom).
3259         return true;
3260     }
3261 }
3262 
xom_death_message(const kill_method_type killed_by)3263 void xom_death_message(const kill_method_type killed_by)
3264 {
3265     if (!you_worship(GOD_XOM) && (!you.worshipped[GOD_XOM] || coinflip()))
3266         return;
3267 
3268     const int death_tension = get_tension(GOD_XOM);
3269 
3270     // "Normal" deaths with only down to -2 hp and comparatively low tension
3271     // are considered particularly boring.
3272     if (!_death_is_funny(killed_by) && you.hp >= -1 * random2(3)
3273         && death_tension <= random2(10))
3274     {
3275         god_speaks(GOD_XOM, _get_xom_speech("boring death").c_str());
3276     }
3277     // Unusual methods of dying, really low hp, or high tension make
3278     // for funny deaths.
3279     else if (_death_is_funny(killed_by) || you.hp <= -10
3280              || death_tension >= 20)
3281     {
3282         god_speaks(GOD_XOM, _get_xom_speech("laughter").c_str());
3283     }
3284 
3285     // All others just get ignored by Xom.
3286 }
3287 
_death_is_worth_saving(const kill_method_type killed_by)3288 static int _death_is_worth_saving(const kill_method_type killed_by)
3289 {
3290     switch (killed_by)
3291     {
3292     // These don't count.
3293     case KILLED_BY_LEAVING:
3294     case KILLED_BY_WINNING:
3295     case KILLED_BY_QUITTING:
3296 
3297     // These are too much hassle.
3298     case KILLED_BY_LAVA:
3299     case KILLED_BY_WATER:
3300     case KILLED_BY_DRAINING:
3301     case KILLED_BY_STARVATION:
3302     case KILLED_BY_ZOT:
3303     case KILLED_BY_ROTTING:
3304 
3305     // Don't protect the player from these.
3306     case KILLED_BY_SELF_AIMED:
3307     case KILLED_BY_TARGETING:
3308         return false;
3309 
3310     // Everything else is fair game.
3311     default:
3312         return true;
3313     }
3314 }
3315 
_get_death_type_keyword(const kill_method_type killed_by)3316 static string _get_death_type_keyword(const kill_method_type killed_by)
3317 {
3318     switch (killed_by)
3319     {
3320     case KILLED_BY_MONSTER:
3321     case KILLED_BY_BEAM:
3322     case KILLED_BY_BEOGH_SMITING:
3323     case KILLED_BY_TSO_SMITING:
3324     case KILLED_BY_DIVINE_WRATH:
3325         return "actor";
3326     default:
3327         return "general";
3328     }
3329 }
3330 
3331 /**
3332  * Have Xom maybe act to save your life. There is both a flat chance
3333  * and an additional chance based on tension that he will refuse to
3334  * save you.
3335  * @param death_type  The type of death that occurred.
3336  * @return            True if Xom saves your life, false otherwise.
3337  */
xom_saves_your_life(const kill_method_type death_type)3338 bool xom_saves_your_life(const kill_method_type death_type)
3339 {
3340     if (!you_worship(GOD_XOM) || _xom_feels_nasty())
3341         return false;
3342 
3343     // If this happens, don't bother.
3344     if (you.hp_max < 1 || you.experience_level < 1)
3345         return false;
3346 
3347     // Generally a rare effect.
3348     if (!one_chance_in(20))
3349         return false;
3350 
3351     if (!_death_is_worth_saving(death_type))
3352         return false;
3353 
3354     // In addition, the chance depends on the current tension and Xom's mood.
3355     const int death_tension = get_tension(GOD_XOM);
3356     if (death_tension < random2(5) || !xom_is_nice(death_tension))
3357         return false;
3358 
3359     // Fake death message.
3360     canned_msg(MSG_YOU_DIE);
3361     more();
3362 
3363     const string key = _get_death_type_keyword(death_type);
3364     // XOM_SPEECH("life saving actor") or XOM_SPEECH("life saving general")
3365     string speech = _get_xom_speech("life saving " + key);
3366     god_speaks(GOD_XOM, speech.c_str());
3367 
3368     // Give back some hp.
3369     if (you.hp < 1)
3370         set_hp(1 + random2(you.hp_max/4));
3371 
3372     god_speaks(GOD_XOM, "Xom revives you!");
3373 
3374     // Ideally, this should contain the death cause but that is too much
3375     // trouble for now.
3376     take_note(Note(NOTE_XOM_REVIVAL));
3377 
3378     // Make sure Xom doesn't get bored within the next couple of turns.
3379     if (you.gift_timeout < 10)
3380         you.gift_timeout = 10;
3381 
3382     return true;
3383 }
3384 
3385 // Xom might have something to say when you enter a new level.
xom_new_level_noise_or_stealth()3386 void xom_new_level_noise_or_stealth()
3387 {
3388     if (!you_worship(GOD_XOM) && !player_under_penance(GOD_XOM))
3389         return;
3390 
3391     // But only occasionally.
3392     if (one_chance_in(30))
3393     {
3394         if (!player_under_penance(GOD_XOM) && coinflip())
3395         {
3396             god_speaks(GOD_XOM, _get_xom_speech("stealth player").c_str());
3397             mpr(you.duration[DUR_STEALTH] ? "You feel more catlike."
3398                                           : "You feel stealthy.");
3399             you.increase_duration(DUR_STEALTH, 10 + random2(80));
3400             take_note(Note(NOTE_XOM_EFFECT, you.piety, -1,
3401                            "stealth player"), true);
3402         }
3403         else
3404             _xom_noise(-1);
3405     }
3406     return;
3407 }
3408 
3409 /**
3410  * The Xom teleportation train takes you on instant
3411  * teleportation to a few random areas, stopping randomly but
3412  * most likely in an area that is not dangerous to you.
3413  */
_xom_good_teleport(int)3414 static void _xom_good_teleport(int /*sever*/)
3415 {
3416     god_speaks(GOD_XOM, _get_xom_speech("teleportation journey").c_str());
3417     int count = 0;
3418     do
3419     {
3420         count++;
3421         you_teleport_now();
3422         maybe_update_stashes();
3423         more();
3424         if (one_chance_in(10) || count >= 7 + random2(5))
3425             break;
3426     }
3427     while (x_chance_in_y(3, 4) || player_in_a_dangerous_place());
3428     maybe_update_stashes();
3429 
3430     // Take a note.
3431     const string note = make_stringf("%d-stop teleportation journey%s", count,
3432 #ifdef NOTE_DEBUG_XOM
3433              player_in_a_dangerous_place() ? " (dangerous)" :
3434 #endif
3435              "");
3436     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
3437 }
3438 
3439 /**
3440  * The Xom teleportation train takes you on instant
3441  * teleportation to a few random areas, stopping if either
3442  * an area is dangerous to you or randomly.
3443  */
_xom_bad_teleport(int)3444 static void _xom_bad_teleport(int /*sever*/)
3445 {
3446     god_speaks(GOD_XOM,
3447                _get_xom_speech("teleportation journey").c_str());
3448 
3449     int count = 0;
3450     do
3451     {
3452         you_teleport_now();
3453         maybe_update_stashes();
3454         more();
3455         if (count++ >= 7 + random2(5))
3456             break;
3457     }
3458     while (x_chance_in_y(3, 4) && !player_in_a_dangerous_place());
3459     maybe_update_stashes();
3460 
3461     // Take a note.
3462     const string note = make_stringf("%d-stop teleportation journey%s", count,
3463 #ifdef NOTE_DEBUG_XOM
3464              badness == 3 ? " (dangerous)" : "");
3465 #else
3466     "");
3467 #endif
3468     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, note), true);
3469 }
3470 
3471 /// Place a one-tile chaos cloud on the player, with minor spreading.
_xom_chaos_cloud(int)3472 static void _xom_chaos_cloud(int /*sever*/)
3473 {
3474     const int lifetime = 3 + random2(12) * 3;
3475     const int spread_rate = random_range(5,15);
3476     check_place_cloud(CLOUD_CHAOS, you.pos(), lifetime,
3477                       nullptr, spread_rate);
3478     take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "chaos cloud"),
3479               true);
3480     god_speaks(GOD_XOM, _get_xom_speech("cloud").c_str());
3481 }
3482 
3483 struct xom_effect_count
3484 {
3485     string effect;
3486     int    count;
3487 
xom_effect_countxom_effect_count3488     xom_effect_count(string e, int c) : effect(e), count(c) {};
3489 };
3490 
3491 /// A type of action Xom can take.
3492 struct xom_event
3493 {
3494     /// Wizmode name for the event.
3495     const char* name;
3496     /// The event action.
3497     function<void(int sever)> action;
3498     /**
3499      * Rough estimate of how hard a Xom effect hits the player,
3500      * scaled between 10 (harmless) and 50 (disastrous). Reduces boredom.
3501      */
3502     int badness_10x;
3503 };
3504 
3505 static const map<xom_event_type, xom_event> xom_events = {
3506     { XOM_DID_NOTHING, { "nothing" }},
3507     { XOM_GOOD_POTION, { "potion", _xom_do_potion }},
3508     { XOM_GOOD_MAGIC_MAPPING, { "magic mapping", _xom_magic_mapping }},
3509     { XOM_GOOD_DETECT_CREATURES, { "detect creatures", _xom_detect_creatures }},
3510     { XOM_GOOD_DETECT_ITEMS, { "detect items", _xom_detect_items }},
3511     { XOM_GOOD_SPELL, { "tension spell", _xom_random_spell }},
3512     { XOM_GOOD_CONFUSION, { "confuse monsters", _xom_confuse_monsters }},
3513     { XOM_GOOD_SINGLE_ALLY, { "single ally", _xom_send_one_ally }},
3514     { XOM_GOOD_ANIMATE_MON_WPN, { "animate monster weapon",
3515                                   _xom_animate_monster_weapon }},
3516     { XOM_GOOD_RANDOM_ITEM, { "random item gift", _xom_random_item }},
3517     { XOM_GOOD_ACQUIREMENT, { "acquirement", _xom_acquirement }},
3518     { XOM_GOOD_ALLIES, { "summon allies", _xom_send_allies }},
3519     { XOM_GOOD_POLYMORPH, { "good polymorph", _xom_good_polymorph }},
3520     { XOM_GOOD_TELEPORT, { "good teleportation", _xom_good_teleport }},
3521     { XOM_GOOD_MUTATION, { "good mutations", _xom_give_good_mutations }},
3522     { XOM_GOOD_LIGHTNING, { "lightning", _xom_throw_divine_lightning }},
3523     { XOM_GOOD_SCENERY, { "change scenery", _xom_change_scenery }},
3524     { XOM_GOOD_SNAKES, { "snakes to sticks", _xom_snakes_to_sticks }},
3525     { XOM_GOOD_DESTRUCTION, { "mass fireball", _xom_real_destruction }},
3526     { XOM_GOOD_FAKE_DESTRUCTION, { "fake fireball", _xom_fake_destruction }},
3527     { XOM_GOOD_ENCHANT_MONSTER, { "good enchant monster",
3528                                   _xom_good_enchant_monster }},
3529     { XOM_GOOD_FOG, { "fog", _xom_fog }},
3530     { XOM_GOOD_CLOUD_TRAIL, { "cloud trail", _xom_cloud_trail }},
3531     { XOM_GOOD_CLEAVING, { "cleaving", _xom_cleaving }},
3532 
3533     { XOM_BAD_MISCAST_PSEUDO, { "pseudo-miscast", _xom_pseudo_miscast, 10}},
3534     { XOM_BAD_NOISE, { "noise", _xom_noise, 10 }},
3535     { XOM_BAD_ENCHANT_MONSTER, { "bad enchant monster",
3536                                  _xom_bad_enchant_monster, 10}},
3537     { XOM_BAD_BLINK_MONSTERS, { "blink monsters", _xom_blink_monsters, 10}},
3538     { XOM_BAD_CONFUSION, { "confuse player", _xom_player_confusion_effect, 13}},
3539     { XOM_BAD_SWAP_MONSTERS, { "swap monsters", _xom_rearrange_pieces, 20 }},
3540     { XOM_BAD_CHAOS_UPGRADE, { "chaos upgrade", _xom_chaos_upgrade, 20}},
3541     { XOM_BAD_TELEPORT, { "bad teleportation", _xom_bad_teleport, -1}},
3542     { XOM_BAD_POLYMORPH, { "bad polymorph", _xom_bad_polymorph, 30}},
3543     { XOM_BAD_MOVING_STAIRS, { "moving stairs", _xom_moving_stairs, 20}},
3544     { XOM_BAD_CLIMB_STAIRS, { "unclimbable stairs", _xom_unclimbable_stairs,
3545                               30}},
3546     { XOM_BAD_MUTATION, { "bad mutations", _xom_give_bad_mutations, 30}},
3547     { XOM_BAD_SUMMON_HOSTILES, { "summon hostiles", _xom_summon_hostiles, 35}},
3548     { XOM_BAD_STATLOSS, { "statloss", _xom_statloss, 23}},
3549     { XOM_BAD_DRAINING, { "draining", _xom_draining, 23}},
3550     { XOM_BAD_TORMENT, { "torment", _xom_torment, 23}},
3551     { XOM_BAD_CHAOS_CLOUD, { "chaos cloud", _xom_chaos_cloud, 20}},
3552     { XOM_BAD_BANISHMENT, { "banishment", _xom_banishment, 50}},
3553     { XOM_BAD_PSEUDO_BANISHMENT, {"psuedo-banishment", _xom_pseudo_banishment,
3554                                   10}},
3555 };
3556 
_do_xom_event(xom_event_type event_type,int sever)3557 static void _do_xom_event(xom_event_type event_type, int sever)
3558 {
3559     const xom_event *event = map_find(xom_events, event_type);
3560     if (event && event->action)
3561         event->action(sever);
3562 }
3563 
_xom_event_badness(xom_event_type event_type)3564 static int _xom_event_badness(xom_event_type event_type)
3565 {
3566     if (event_type == XOM_BAD_TELEPORT)
3567         return player_in_a_dangerous_place() ? 3 : 1;
3568 
3569     const xom_event *event = map_find(xom_events, event_type);
3570     if (event)
3571         return div_rand_round(event->badness_10x, 10);
3572     return 0;
3573 }
3574 
xom_effect_to_name(xom_event_type effect)3575 string xom_effect_to_name(xom_event_type effect)
3576 {
3577     const xom_event *event = map_find(xom_events, effect);
3578     return event ? event->name : "bugginess";
3579 }
3580 
3581 /// Basic sanity checks on xom_events.
validate_xom_events()3582 void validate_xom_events()
3583 {
3584     string fails;
3585     set<string> action_names;
3586 
3587     for (int i = 0; i < XOM_LAST_REAL_ACT; ++i)
3588     {
3589         const xom_event_type event_type = static_cast<xom_event_type>(i);
3590         const xom_event *event = map_find(xom_events, event_type);
3591         if (!event)
3592         {
3593             fails += make_stringf("Xom event %d has no associated data!\n", i);
3594             continue;
3595         }
3596 
3597         if (action_names.count(event->name))
3598             fails += make_stringf("Duplicate name '%s'!\n", event->name);
3599         action_names.insert(event->name);
3600 
3601         if (_action_is_bad(event_type))
3602         {
3603             if ((event->badness_10x < 10 || event->badness_10x > 50)
3604                 && event->badness_10x != -1) // implies it's special-cased
3605             {
3606                 fails += make_stringf("'%s' badness %d outside 10-50 range.\n",
3607                                       event->name, event->badness_10x);
3608             }
3609         }
3610         else if (event->badness_10x)
3611         {
3612             fails += make_stringf("'%s' is not bad, but has badness!\n",
3613                                   event->name);
3614         }
3615 
3616         if (event_type != XOM_DID_NOTHING && !event->action)
3617             fails += make_stringf("No action for '%s'!\n", event->name);
3618     }
3619 
3620     dump_test_fails(fails, "xom-data");
3621 }
3622 
3623 #ifdef WIZARD
_sort_xom_effects(const xom_effect_count & a,const xom_effect_count & b)3624 static bool _sort_xom_effects(const xom_effect_count &a,
3625                               const xom_effect_count &b)
3626 {
3627     if (a.count == b.count)
3628         return a.effect < b.effect;
3629 
3630     return a.count > b.count;
3631 }
3632 
_list_exploration_estimate()3633 static string _list_exploration_estimate()
3634 {
3635     int explored = 0;
3636     int mapped   = 0;
3637     for (int k = 0; k < 10; ++k)
3638     {
3639         mapped   += _exploration_estimate(false);
3640         explored += _exploration_estimate(true);
3641     }
3642     mapped /= 10;
3643     explored /= 10;
3644 
3645     return make_stringf("mapping estimate: %d%%\nexploration estimate: %d%%\n",
3646                         mapped, explored);
3647 }
3648 
3649 // Loops over the entire piety spectrum and calls xom_acts() multiple
3650 // times for each value, then prints the results into a file.
3651 // TODO: Allow specification of niceness, tension, and boredness.
debug_xom_effects()3652 void debug_xom_effects()
3653 {
3654     // Repeat N times.
3655     const int N = prompt_for_int("How many iterations over the "
3656                                  "entire piety range? ", true);
3657 
3658     if (N == 0)
3659     {
3660         canned_msg(MSG_OK);
3661         return;
3662     }
3663 
3664     FILE *ostat = fopen("xom_debug.stat", "w");
3665     if (!ostat)
3666     {
3667         mprf(MSGCH_ERROR, "Can't write 'xom_debug.stat'. Aborting.");
3668         return;
3669     }
3670 
3671     const int real_piety    = you.piety;
3672     const god_type real_god = you.religion;
3673     you.religion            = GOD_XOM;
3674     const int tension       = get_tension(GOD_XOM);
3675 
3676     fprintf(ostat, "---- STARTING XOM DEBUG TESTING ----\n");
3677     fprintf(ostat, "%s\n", dump_overview_screen(false).c_str());
3678     fprintf(ostat, "%s\n", screenshot().c_str());
3679     fprintf(ostat, "%s\n", _list_exploration_estimate().c_str());
3680     fprintf(ostat, "%s\n", mpr_monster_list().c_str());
3681     fprintf(ostat, " --> Tension: %d\n", tension);
3682 
3683     if (player_under_penance(GOD_XOM))
3684         fprintf(ostat, "You are under Xom's penance!\n");
3685     else if (_xom_is_bored())
3686         fprintf(ostat, "Xom is BORED.\n");
3687     fprintf(ostat, "\nRunning %d times through entire mood cycle.\n", N);
3688     fprintf(ostat, "---- OUTPUT EFFECT PERCENTAGES ----\n");
3689 
3690     vector<xom_event_type>          mood_effects;
3691     vector<vector<xom_event_type>>  all_effects;
3692     vector<string>                  moods;
3693     vector<int>                     mood_good_acts;
3694 
3695     string old_mood = "";
3696     string     mood = "";
3697 
3698     // Add an empty list to later add all effects to.
3699     all_effects.push_back(mood_effects);
3700     moods.emplace_back("total");
3701     mood_good_acts.push_back(0); // count total good acts
3702 
3703     int mood_good = 0;
3704     for (int p = 0; p <= MAX_PIETY; ++p)
3705     {
3706         you.piety     = p;
3707         int sever     = abs(p - HALF_MAX_PIETY);
3708         mood          = describe_xom_mood();
3709         if (old_mood != mood)
3710         {
3711             if (!old_mood.empty())
3712             {
3713                 all_effects.push_back(mood_effects);
3714                 mood_effects.clear();
3715                 mood_good_acts.push_back(mood_good);
3716                 mood_good_acts[0] += mood_good;
3717                 mood_good = 0;
3718             }
3719             moods.push_back(mood);
3720             old_mood = mood;
3721         }
3722 
3723         // Repeat N times.
3724         for (int i = 0; i < N; ++i)
3725         {
3726             const xom_event_type result = xom_acts(sever, MB_MAYBE, tension,
3727                                                    true);
3728 
3729             mood_effects.push_back(result);
3730             all_effects[0].push_back(result);
3731 
3732             if (result <= XOM_LAST_GOOD_ACT)
3733                 mood_good++;
3734         }
3735     }
3736     all_effects.push_back(mood_effects);
3737     mood_effects.clear();
3738     mood_good_acts.push_back(mood_good);
3739     mood_good_acts[0] += mood_good;
3740 
3741     const int num_moods = moods.size();
3742     vector<xom_effect_count> xom_ec_pairs;
3743     for (int i = 0; i < num_moods; ++i)
3744     {
3745         mood_effects    = all_effects[i];
3746         const int total = mood_effects.size();
3747 
3748         if (i == 0)
3749             fprintf(ostat, "\nTotal effects (all piety ranges)\n");
3750         else
3751             fprintf(ostat, "\nMood: You are %s\n", moods[i].c_str());
3752 
3753         fprintf(ostat, "GOOD%7.2f%%\n",
3754                 (100.0 * (float) mood_good_acts[i] / (float) total));
3755         fprintf(ostat, "BAD %7.2f%%\n",
3756                 (100.0 * (float) (total - mood_good_acts[i]) / (float) total));
3757 
3758         sort(mood_effects.begin(), mood_effects.end());
3759 
3760         xom_ec_pairs.clear();
3761         xom_event_type old_effect = XOM_DID_NOTHING;
3762         int count      = 0;
3763         for (int k = 0; k < total; ++k)
3764         {
3765             if (mood_effects[k] != old_effect)
3766             {
3767                 if (count > 0)
3768                 {
3769                     xom_ec_pairs.emplace_back(xom_effect_to_name(old_effect),
3770                                               count);
3771                 }
3772                 old_effect = mood_effects[k];
3773                 count = 1;
3774             }
3775             else
3776                 count++;
3777         }
3778 
3779         if (count > 0)
3780             xom_ec_pairs.emplace_back(xom_effect_to_name(old_effect), count);
3781 
3782         sort(xom_ec_pairs.begin(), xom_ec_pairs.end(), _sort_xom_effects);
3783         for (const xom_effect_count &xec : xom_ec_pairs)
3784         {
3785             fprintf(ostat, "%7.2f%%    %s\n",
3786                     (100.0 * xec.count / total),
3787                     xec.effect.c_str());
3788         }
3789     }
3790     fprintf(ostat, "---- FINISHED XOM DEBUG TESTING ----\n");
3791     fclose(ostat);
3792     mpr("Results written into 'xom_debug.stat'.");
3793 
3794     you.piety    = real_piety;
3795     you.religion = real_god;
3796 }
3797 #endif // WIZARD
3798