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