1 /**
2 * @file
3 * @brief God-granted abilities.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "god-abil.h"
9
10 #include <cmath>
11 #include <numeric>
12 #include <sstream>
13
14 #include "act-iter.h"
15 #include "areas.h"
16 #include "artefact.h"
17 #include "art-enum.h"
18 #include "attitude-change.h"
19 #include "bloodspatter.h"
20 #include "branch.h"
21 #include "chardump.h"
22 #include "cloud.h"
23 #include "colour.h"
24 #include "coordit.h"
25 #include "curse-type.h"
26 #include "dactions.h"
27 #include "database.h"
28 #include "describe.h"
29 #include "dgn-overview.h"
30 #include "directn.h"
31 #include "dungeon.h"
32 #include "english.h"
33 #include "fight.h"
34 #include "files.h"
35 #include "fineff.h"
36 #include "format.h" // formatted_string
37 #include "god-blessing.h"
38 #include "god-companions.h"
39 #include "god-item.h"
40 #include "god-passive.h"
41 #include "hints.h"
42 #include "hiscores.h"
43 #include "invent.h"
44 #include "item-prop.h"
45 #include "item-status-flag-type.h"
46 #include "items.h"
47 #include "item-use.h"
48 #include "libutil.h"
49 #include "losglobal.h"
50 #include "macro.h"
51 #include "mapmark.h"
52 #include "maps.h"
53 #include "message.h"
54 #include "mon-act.h"
55 #include "mon-behv.h"
56 #include "mon-death.h"
57 #include "mon-gear.h" // H: give_weapon()/give_armour()
58 #include "mon-place.h"
59 #include "mon-poly.h"
60 #include "mon-tentacle.h"
61 #include "mon-util.h"
62 #include "movement.h"
63 #include "mutation.h"
64 #include "notes.h"
65 #include "ouch.h"
66 #include "output.h"
67 #include "place.h"
68 #include "player-equip.h"
69 #include "player-stats.h"
70 #include "potion.h"
71 #include "prompt.h"
72 #include "religion.h"
73 #include "shout.h"
74 #include "skill-menu.h"
75 #include "spl-book.h"
76 #include "spl-goditem.h"
77 #include "spl-monench.h"
78 #include "spl-transloc.h"
79 #include "spl-util.h"
80 #include "spl-wpnench.h"
81 #include "sprint.h"
82 #include "state.h"
83 #include "stringutil.h"
84 #include "tag-version.h"
85 #include "target.h"
86 #include "teleport.h" // monster_teleport
87 #include "terrain.h"
88 #ifdef USE_TILE
89 #include "rltiles/tiledef-main.h"
90 #endif
91 #include "timed-effects.h"
92 #include "traps.h"
93 #include "viewchar.h"
94 #include "view.h"
95
96 static bool _player_sacrificed_arcana();
97
98 // Load the sacrifice_def definition and the sac_data array.
99 #include "sacrifice-data.h"
100
101 /** Would a god currently allow using a one-time six-star ability?
102 * Does not check whether the god actually grants such an ability.
103 */
can_do_capstone_ability(god_type god)104 bool can_do_capstone_ability(god_type god)
105 {
106 return in_good_standing(god, 5) && !you.one_time_ability_used[god];
107 }
108
_god_blessing_description(god_type god)109 static const char *_god_blessing_description(god_type god)
110 {
111 switch (god)
112 {
113 case GOD_SHINING_ONE:
114 return "blessed by the Shining One";
115 case GOD_LUGONU:
116 return "corrupted by Lugonu";
117 case GOD_KIKUBAAQUDGHA:
118 return "bloodied by Kikubaaqudgha";
119 default:
120 return "touched by the gods";
121 }
122 }
123
124 /**
125 * Perform a capstone god ability that blesses a weapon with the god's
126 * brand.
127
128 * @param god The god performing the blessing.
129 * @param brand The brand being granted.
130 * @param colour The colour to flash when the weapon is branded.
131 * @returns True if the weapon was successfully branded, false otherwise.
132 */
bless_weapon(god_type god,brand_type brand,colour_t colour)133 bool bless_weapon(god_type god, brand_type brand, colour_t colour)
134 {
135 ASSERT(can_do_capstone_ability(god));
136
137 int item_slot = prompt_invent_item("Brand which weapon?",
138 menu_type::invlist,
139 OSEL_BLESSABLE_WEAPON, OPER_ANY,
140 invprompt_flag::escape_only);
141
142 if (item_slot == PROMPT_NOTHING || item_slot == PROMPT_ABORT)
143 {
144 canned_msg(MSG_OK);
145 return false;
146 }
147
148 item_def& wpn(you.inv[item_slot]);
149 // Only TSO allows blessing ranged weapons.
150 if (!is_brandable_weapon(wpn, brand == SPWPN_HOLY_WRATH, true))
151 return false;
152
153 string prompt = "Do you wish to have " + wpn.name(DESC_YOUR)
154 + " ";
155 if (brand == SPWPN_PAIN)
156 prompt += "bloodied with pain";
157 else if (brand == SPWPN_DISTORTION)
158 prompt += "corrupted with distortion";
159 else
160 prompt += "blessed with holy wrath";
161 prompt += "?";
162 if (!yesno(prompt.c_str(), true, 'n'))
163 {
164 canned_msg(MSG_OK);
165 return false;
166 }
167
168 if (you.duration[DUR_EXCRUCIATING_WOUNDS]) // just in case
169 {
170 ASSERT(you.weapon());
171 end_weapon_brand(*you.weapon());
172 }
173
174 string old_name = wpn.name(DESC_A);
175 set_equip_desc(wpn, ISFLAG_GLOWING);
176 set_item_ego_type(wpn, OBJ_WEAPONS, brand);
177 enchant_weapon(wpn, true);
178 enchant_weapon(wpn, true);
179
180 if (god == GOD_SHINING_ONE)
181 {
182 convert2good(wpn);
183
184 if (is_blessed_convertible(wpn))
185 origin_acquired(wpn, GOD_SHINING_ONE);
186 }
187 else if (is_evil_god(god))
188 convert2bad(wpn);
189
190 you.wield_change = true;
191 you.one_time_ability_used.set(god);
192 calc_mp(); // in case the old brand was antimagic,
193 you.redraw_armour_class = true; // protection,
194 you.redraw_evasion = true; // or evasion
195 const string desc = old_name + " " + _god_blessing_description(god);
196 take_note(Note(NOTE_ID_ITEM, 0, 0, wpn.name(DESC_A), desc));
197 wpn.flags |= ISFLAG_NOTED_ID;
198 wpn.props[FORCED_ITEM_COLOUR_KEY] = colour;
199
200 mprf(MSGCH_GOD, "Your %s shines brightly!", wpn.name(DESC_QUALNAME).c_str());
201 flash_view(UA_PLAYER, colour);
202 simple_god_message(" booms: Use this gift wisely!");
203 you.one_time_ability_used.set(you.religion);
204 take_note(Note(NOTE_GOD_GIFT, you.religion));
205
206 if (god == GOD_SHINING_ONE)
207 {
208 holy_word(100, HOLY_WORD_TSO, you.pos(), true);
209 // Un-bloodify surrounding squares.
210 for (radius_iterator ri(you.pos(), 3, C_SQUARE, LOS_SOLID); ri; ++ri)
211 if (is_bloodcovered(*ri))
212 env.pgrid(*ri) &= ~FPROP_BLOODY;
213 }
214 else if (god == GOD_KIKUBAAQUDGHA)
215 {
216 you.gift_timeout = 1; // no protection during pain branding weapon
217 torment(&you, TORMENT_KIKUBAAQUDGHA, you.pos());
218 you.gift_timeout = 0; // protection after pain branding weapon
219 // Bloodify surrounding squares (75% chance).
220 for (radius_iterator ri(you.pos(), 2, C_SQUARE, LOS_SOLID); ri; ++ri)
221 if (!one_chance_in(4))
222 maybe_bloodify_square(*ri);
223 }
224
225 #ifndef USE_TILE_LOCAL
226 // Allow extra time for the flash to linger.
227 scaled_delay(1000);
228 #endif
229 return true;
230 }
231
_gold_to_donation(int gold)232 static int _gold_to_donation(int gold)
233 {
234 return static_cast<int>((gold * log((float)gold)) / MAX_PIETY);
235 }
236
237 // donate gold to gain piety distributed over time
zin_donate_gold()238 bool zin_donate_gold()
239 {
240 if (you.gold == 0)
241 {
242 mpr("You don't have anything to sacrifice.");
243 return false;
244 }
245
246 if (!yesno("Do you wish to donate half of your money?", true, 'n'))
247 {
248 canned_msg(MSG_OK);
249 return false;
250 }
251
252 const int donation_cost = (you.gold / 2) + 1;
253 const int donation = _gold_to_donation(donation_cost);
254
255 #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_SACRIFICE) || defined(DEBUG_PIETY)
256 mprf(MSGCH_DIAGNOSTICS, "A donation of $%d amounts to an "
257 "increase of piety by %d.", donation_cost, donation);
258 #endif
259 // Take a note of the donation.
260 take_note(Note(NOTE_DONATE_MONEY, donation_cost));
261
262 you.attribute[ATTR_DONATIONS] += donation_cost;
263
264 you.del_gold(donation_cost);
265
266 if (donation < 1)
267 {
268 simple_god_message(" finds your generosity lacking.");
269 return false;
270 }
271
272 you.duration[DUR_PIETY_POOL] += donation;
273 if (you.duration[DUR_PIETY_POOL] > 30000)
274 you.duration[DUR_PIETY_POOL] = 30000;
275
276 const int estimated_piety =
277 min(MAX_PENANCE + MAX_PIETY, you.piety + you.duration[DUR_PIETY_POOL]);
278
279 if (player_under_penance())
280 {
281 if (estimated_piety >= you.penance[GOD_ZIN])
282 mpr("You feel that you will soon be absolved of all your sins.");
283 else
284 mpr("You feel that your burden of sins will soon be lighter.");
285 }
286 else
287 {
288 string result = "You feel that " + god_name(GOD_ZIN) + " will soon be ";
289 result +=
290 (estimated_piety >= piety_breakpoint(5)) ? "exalted by your worship" :
291 (estimated_piety >= piety_breakpoint(4)) ? "extremely pleased with you" :
292 (estimated_piety >= piety_breakpoint(3)) ? "greatly pleased with you" :
293 (estimated_piety >= piety_breakpoint(2)) ? "most pleased with you" :
294 (estimated_piety >= piety_breakpoint(1)) ? "pleased with you" :
295 (estimated_piety >= piety_breakpoint(0)) ? "aware of your devotion"
296 : "noncommittal";
297 result += (donation >= 30 && you.piety < piety_breakpoint(5)) ? "!" : ".";
298
299 mpr(result);
300 }
301
302 return true;
303 }
304
305 static void _zin_saltify(monster* mon);
306
307 static const char * const book_of_zin[][3] =
308 {
309 {
310 "It was the word of Zin that there would not be @sin_noun@...",
311 "...and did the people not suffer until they had @smitten@...",
312 "...the @sinners@, after which all was well?",
313 },
314
315 {
316 "The voice of Zin, pure and clear, did say that the @sinners@...",
317 "...were not @virtuous@! And hearing this, the people rose up...",
318 "...and embraced @virtue@, for they feared Zin's wrath.",
319 },
320
321 {
322 "Zin spoke of the doctrine of @virtue@, and...",
323 "...saw the @sinners@ filled with fear, for they were...",
324 "...@sin_adj@ and knew Zin's wrath would come for them.",
325 },
326
327 {
328 "And so Zin bade the @sinners@ to come before...",
329 "...the altar, that judgement might be passed...",
330 "...upon those who were not @virtuous@.",
331 },
332
333 {
334 "To the devout, Zin provideth. From the rest...",
335 "...ye @sinners@, ye guilty...",
336 "...of @sin_noun@, Zin taketh.",
337 },
338
339 {
340 "Zin saw the @sin_noun@ of the @sinners@, and...",
341 "...was displeased, for did the law not say that...",
342 "...those who did not become @virtuous@ would be @smitten@?",
343 },
344
345 {
346 "Zin said that @virtue@ shall be the law of the land, and...",
347 "...those who turn to @sin_noun@ will be @smitten@. This was fair...",
348 "...and just, and not a voice dissented.",
349 },
350
351 {
352 "Damned, damned be the @sinners@ and...",
353 "...all else who abandon @virtue@! Let them...",
354 "...be @smitten@ by the jurisprudence of Zin!",
355 },
356
357 {
358
359 "And Zin said to all in attendance, 'Which of ye...",
360 "...number among the @sinners@? Come before me, that...",
361 "...I may @smite@ you now for your @sin_noun@!'",
362 },
363
364 {
365 "Yea, I say unto thee, bring forth...",
366 "...the @sinners@ that they may know...",
367 "...the wrath of Zin, and thus be @smitten@!",
368 },
369
370 {
371 "In a great set of silver scales are weighed the...",
372 "...souls of the @sinners@, and with their @sin_adj@...",
373 "...ways, the balance hath tipped against them!",
374 },
375
376 {
377 "It is just that the @sinners@ shall be @smitten@...",
378 "...in due time, for @virtue@ is what Zin has declared...",
379 "...the law of the land, and Zin's word is law!",
380 },
381
382 {
383 "Thus the people made the covenant of @virtue@ with...",
384 "...Zin, and all was good, for they knew that the...",
385 "...@sinners@ would trouble them no longer.",
386 },
387
388 {
389 "What of the @sinners@? @Smitten@ for their...",
390 "...@sin_noun@ they shall be! Zin will @smite@ them again...",
391 "...and again, and again!",
392 },
393
394 {
395 "And lo, the wrath of Zin did find...",
396 "...them wherever they hid, and the @sinners@...",
397 "...were @smitten@ for their @sin_noun@!",
398 },
399
400 {
401 "Zin looked out upon the remains of the @sinners@...",
402 "...and declared it good that they had been...",
403 "...@smitten@. And thus justice was done.",
404 },
405
406 {
407 "The law of Zin demands thee...",
408 "...be @virtuous@, and that the punishment for @sin_noun@...",
409 "...shall be swift and harsh!",
410 },
411
412 {
413 "It was then that Zin bade them...",
414 "...not to stray from @virtue@, lest...",
415 "...they become as damned as the @sinners@.",
416 },
417
418 {
419 "Only the @virtuous@ shall be judged worthy, and...",
420 "...all the @sinners@ will be found wanting. Such is...",
421 "...the word of Zin, and such is the law!",
422 },
423
424 {
425 "To those who would swear an oath of @virtue@ on my altar...",
426 "...I bring ye salvation. To the rest, ye @sinners@...",
427 "...and the @sin_adj@, the name of Zin shall be thy damnation.",
428 },
429
430 {
431 "And Zin decreed that the people would be...",
432 "...protected from @sin_noun@ in all its forms, and...",
433 "...preserved in their @virtue@ for all the days to come.",
434 },
435
436 {
437 "For those who would enter Zin's holy bosom...",
438 "...and live in @virtue@, Zin provideth. Such is...",
439 "...the covenant, and such is the way of things.",
440 },
441
442 {
443 "Zin hath not damned the @sinners@, but it is they...",
444 "...that have damned themselves for their @sin_noun@, for...",
445 "...did Zin not decree that to be @sin_adj@ was wrong?",
446 },
447
448 {
449 "And Zin, furious at their @sin_noun@, held...",
450 "...aloft a silver sceptre! The @sinners@...",
451 "...were @smitten@, and thus the way of things was maintained.",
452 },
453
454 {
455 "When the law of the land faltered, Zin rose...",
456 "...from the silver throne, and the @sinners@ were...",
457 "...@smitten@. And it was thus that the law was made good.",
458 },
459
460 {
461 "Zin descended from on high in a silver chariot...",
462 "...to @smite@ the @sinners@ for their...",
463 "...@sin_noun@, and thus judgement was rendered.",
464 },
465
466 {
467 "The @sinners@ stood before Zin, and in that instant...",
468 "...they knew they would be found guilty of @sin_noun@...",
469 "...for that is the word of Zin, and Zin's word is law.",
470 },
471 };
472
473 static const char * const sinner_text[] =
474 {
475 "hordes of the Abyss",
476 "bastard children of Xom",
477 "amorphous wretches",
478 "fetid masses",
479 "agents of filth",
480 "squalid dregs",
481 "unbelievers",
482 "heretics",
483 "guilty",
484 "legions of the damned",
485 "servants of Hell",
486 "forces of darkness",
487 };
488
489 // First column is adjective, then noun.
490 static const char * const sin_text[][2] =
491 {
492 { "chaotic", "chaos" },
493 { "discordant", "discord" },
494 { "anarchic", "anarchy" },
495 { "unclean", "uncleanliness" },
496 { "impure", "impurity" },
497 { "contaminated", "contamination" },
498 { "unfaithful", "unfaithfulness" },
499 { "disloyal", "disloyalty" },
500 { "doubting", "doubt" },
501 { "profane", "profanity" },
502 { "blasphemous", "blasphemy" },
503 { "sacrilegious", "sacrilege" },
504 };
505
506 // First column is adjective, then noun.
507 static const char * const virtue_text[][2] =
508 {
509 { "ordered", "order" },
510 { "harmonic", "harmony" },
511 { "lawful", "lawfulness" },
512 { "clean", "cleanliness" },
513 { "pure", "purity" },
514 { "hygienic", "hygiene" },
515 { "faithful", "faithfulness" },
516 { "loyal", "loyalty" },
517 { "believing", "belief" },
518 { "reverent", "reverence" },
519 { "pious", "piety" },
520 { "obedient", "obedience" },
521 };
522
523 // First column is infinitive, then gerund.
524 static const char * const smite_text[][2] =
525 {
526 { "purify", "purified" },
527 { "censure", "censured" },
528 { "condemn", "condemned" },
529 { "strike down", "struck down" },
530 { "expel", "expelled" },
531 { "oust", "ousted" },
532 { "smite", "smitten" },
533 { "castigate", "castigated" },
534 { "rebuke", "rebuked" },
535 };
536
537 /** Get the verse to recite this turn.
538 *
539 * @param seed The seed to keep the book coherent between turns.
540 * @param prayertype One of the four recite types.
541 * @param step -1: We're either starting or stopping, so we just want the passage name.
542 * 2/1/0: That many rounds are left. So, if step = 2, we want to show the passage #1/3.
543 * @returns the verse to be said this turn, or if step == -1, which verse it is.
544 */
zin_recite_text(const int seed,const int prayertype,int step)545 string zin_recite_text(const int seed, const int prayertype, int step)
546 {
547 // To have deterministic passages we extract portions of the seed.
548 // We use trits: "digits" in the base-3 expansion of seed.
549
550 COMPILE_CHECK(ARRAYSZ(book_of_zin) == 27);
551 const int chapter = seed % 27;
552 const int verse = (seed/27) % 81;
553
554 // Change step to turn 1, turn 2, or turn 3.
555 if (step > -1)
556 {
557 step = abs(step-3);
558 if (step > 3)
559 step = 1;
560 }
561 else
562 {
563 const string bookname = (prayertype == RECITE_CHAOTIC) ? "Abominations" :
564 (prayertype == RECITE_IMPURE) ? "Ablutions" :
565 (prayertype == RECITE_HERETIC) ? "Apostates" :
566 (prayertype == RECITE_UNHOLY) ? "Anathema" :
567 "Bugginess";
568 ostringstream out;
569 out << bookname << ' ' << (chapter + 1) << ':' << (verse + 1);
570 return out.str();
571 }
572
573 // These mad-libs are deterministically derived from the verse number
574 // and prayer type. Sins and virtues are paired, so use the same sub-
575 // seed. Sinners, sins, and smites are uncorrelated so do not share
576 // trits.
577 COMPILE_CHECK(ARRAYSZ(sinner_text) == 12);
578 COMPILE_CHECK(ARRAYSZ(sin_text) == 12);
579 COMPILE_CHECK(ARRAYSZ(virtue_text) == 12);
580 const int sinner_seed = verse % 3 + prayertype * 3;
581 const int sin_seed = (verse/27) % 3 + prayertype * 3;
582 const int virtue_seed = sin_seed;
583
584 COMPILE_CHECK(ARRAYSZ(smite_text) == 9);
585 const int smite_seed = (verse/3) % 9;
586
587 string recite = book_of_zin[chapter][step-1];
588
589 const map<string, string> replacements =
590 {
591 { "sinners", sinner_text[sinner_seed] },
592
593 { "sin_adj", sin_text[sin_seed][0] },
594 { "sin_noun", sin_text[sin_seed][1] },
595
596 { "virtuous", virtue_text[virtue_seed][0] },
597 { "virtue", virtue_text[virtue_seed][1] },
598
599 { "smite", smite_text[smite_seed][0] },
600 { "smitten", smite_text[smite_seed][1] },
601 { "Smitten", uppercase_first(smite_text[smite_seed][1]) },
602 };
603
604 return replace_keys(recite, replacements);
605 }
606
607 /** How vulnerable to RECITE_HERETIC is this monster?
608 *
609 * @param[in] mon The monster to check.
610 * @returns the susceptibility.
611 */
_heretic_recite_weakness(const monster * mon)612 static int _heretic_recite_weakness(const monster *mon)
613 {
614 int degree = 0;
615
616 // Sleeping or paralyzed monsters will wake up or still perceive their
617 // surroundings, respectively. So, you can still recite to them.
618 if (mons_intel(*mon) >= I_HUMAN
619 && !(mon->has_ench(ENCH_DUMB) || mons_is_confused(*mon)))
620 {
621 // In the eyes of Zin, everyone is a sinner until proven otherwise!
622 degree++;
623
624 // Any priest is a heretic...
625 if (mon->is_priest())
626 degree++;
627
628 // Or those who believe in themselves...
629 if (mon->type == MONS_DEMIGOD)
630 degree++;
631
632 // ...but evil gods are worse.
633 if (is_evil_god(mon->god) || is_unknown_god(mon->god))
634 degree++;
635
636 // (The above mean that worshipers will be treated as
637 // priests for reciting, even if they aren't actually.)
638
639
640 // Sanity check: monsters that won't attack you, and aren't
641 // priests/evil, don't get recited against.
642 if (mon->wont_attack() && degree <= 1)
643 degree = 0;
644
645 // Sanity check: monsters that are holy, know holy spells, or worship
646 // holy gods aren't heretics.
647 if (mon->is_holy() || is_good_god(mon->god))
648 degree = 0;
649 }
650
651 return degree;
652 }
653
654 /** Check whether this monster might be influenced by Recite.
655 *
656 * @param[in] mon The monster to check.
657 * @param[out] eligibility A vector, indexed by recite_type, that indicates
658 * which recitation types the monster is affected by, if any:
659 * eligibility[RECITE_FOO] is nonzero if the monster is affected
660 * by RECITE_FOO.
661 * @param quiet[in] Whether to suppress messaging.
662 * @returns Whether the monster is eligible for recite. If the monster can be
663 * recited to, the eligibility vector indicates the valid types of
664 * recite.
665 */
zin_check_recite_to_single_monster(const monster * mon,recite_counts & eligibility,bool quiet)666 recite_eligibility zin_check_recite_to_single_monster(const monster *mon,
667 recite_counts &eligibility,
668 bool quiet)
669 {
670 ASSERT(mon);
671
672 // Can't recite anyway if they were recently recited to.
673 if (mon->has_ench(ENCH_RECITE_TIMER))
674 return RE_RECITE_TIMER;
675
676 const mon_holy_type holiness = mon->holiness();
677 eligibility.init(0);
678
679 // Anti-chaos prayer: Hits things vulnerable to silver, or with chaotic spells/gods.
680 eligibility[RECITE_CHAOTIC] = mon->how_chaotic(true);
681
682 // Anti-impure prayer: Hits things that Zin hates in general.
683 // Don't look at the monster's god; that's what RECITE_HERETIC is for.
684 eligibility[RECITE_IMPURE] = mon->how_unclean(false);
685
686 // Anti-unholy prayer: Hits demons and incorporeal undead.
687 if (holiness & MH_UNDEAD && mon->is_insubstantial()
688 || holiness & MH_DEMONIC)
689 {
690 eligibility[RECITE_UNHOLY]++;
691 }
692
693 // Anti-heretic prayer: Hits intelligent monsters, especially priests.
694 eligibility[RECITE_HERETIC] = _heretic_recite_weakness(mon);
695
696 #ifdef DEBUG_DIAGNOSTICS
697 if (!quiet)
698 {
699 string elig;
700 for (int i = 0; i < NUM_RECITE_TYPES; i++)
701 elig += '0' + eligibility[i];
702 dprf("Eligibility: %s", elig.c_str());
703 }
704 #else
705 UNUSED(quiet);
706 #endif
707
708 bool maybe_eligible = false;
709 // Checking to see whether they were eligible for anything at all.
710 for (int i = 0; i < NUM_RECITE_TYPES; i++)
711 if (eligibility[i] > 0)
712 maybe_eligible = true;
713
714 if (maybe_eligible)
715 {
716 // Too high HD to be affected at current power.
717 if (mon->get_hit_dice() >= zin_recite_power())
718 return RE_TOO_STRONG;
719 return RE_ELIGIBLE;
720 }
721
722 return RE_INELIGIBLE;
723 }
724
zin_recite_power()725 int zin_recite_power()
726 {
727 // Resistance is now based on HD.
728 // Anything at or above (30+30)/2 = 30 'power' (HD) is completely immune.
729 const int power_mult = 10;
730 const int invo_power = you.skill_rdiv(SK_INVOCATIONS, power_mult)
731 + 3 * power_mult;
732 const int piety_power = you.piety * 3 / 2;
733 return (invo_power + piety_power) / 2 / power_mult;
734 }
735
zin_check_able_to_recite(bool quiet)736 bool zin_check_able_to_recite(bool quiet)
737 {
738 if (you.duration[DUR_RECITE])
739 {
740 if (!quiet)
741 mpr("Finish your current sermon first, please.");
742 return false;
743 }
744
745 if (you.duration[DUR_RECITE_COOLDOWN])
746 {
747 if (!quiet)
748 mpr("You're not ready to recite again yet.");
749 return false;
750 }
751
752 return true;
753 }
754
755 /**
756 * Check whether there are monsters who might be influenced by Recite.
757 * If prayertype is null, we're just checking whether we can.
758 * Otherwise we're actually reciting, and may need to present a menu.
759 *
760 * @param quiet Whether to suppress messages.
761 * @return 0 if no eligible monsters were found.
762 * @return 1 if an eligible audience was found.
763 * @return -1 if the only monsters found cannot currently be affected (either
764 * due to lack of recite power, or already having been affected)
765 *
766 */
zin_check_recite_to_monsters(bool quiet)767 int zin_check_recite_to_monsters(bool quiet)
768 {
769 bool found_temp_ineligible = false;
770 bool found_eligible = false;
771
772 for (radius_iterator ri(you.pos(), LOS_DEFAULT); ri; ++ri)
773 {
774 const monster *mon = monster_at(*ri);
775 if (!mon || !you.can_see(*mon))
776 continue;
777
778 recite_counts retval;
779 switch (zin_check_recite_to_single_monster(mon, retval, quiet))
780 {
781 case RE_TOO_STRONG:
782 case RE_RECITE_TIMER:
783 found_temp_ineligible = true;
784 // Intentional fallthrough
785 case RE_INELIGIBLE:
786 continue;
787
788 case RE_ELIGIBLE:
789 found_eligible = true;
790 }
791 }
792
793 if (!found_eligible && !found_temp_ineligible)
794 {
795 if (!quiet)
796 dprf("No audience found!");
797 return 0;
798 }
799 else if (!found_eligible && found_temp_ineligible)
800 {
801 if (!quiet)
802 dprf("No sensible audience found!");
803 return -1;
804 }
805 else
806 return 1; // We just recite against everything.
807 }
808
809 enum class zin_eff
810 {
811 nothing,
812 daze,
813 confuse,
814 paralyse,
815 smite,
816 blind,
817 silver_corona,
818 antimagic,
819 mute,
820 mad,
821 dumb,
822 ignite_chaos,
823 saltify,
824 holy_word,
825 };
826
zin_recite_to_single_monster(const coord_def & where)827 bool zin_recite_to_single_monster(const coord_def& where)
828 {
829 // That's a pretty good sanity check, I guess.
830 ASSERT(you_worship(GOD_ZIN));
831
832 monster* mon = monster_at(where);
833
834 // Once you're already reciting, invis is ok.
835 if (!mon || !cell_see_cell(where, you.pos(), LOS_DEFAULT))
836 return false;
837
838 recite_counts eligibility;
839 bool affected = false;
840
841 if (zin_check_recite_to_single_monster(mon, eligibility) != RE_ELIGIBLE)
842 return false;
843
844 recite_type prayertype = RECITE_HERETIC;
845 for (int i = NUM_RECITE_TYPES - 1; i >= RECITE_HERETIC; i--)
846 {
847 if (eligibility[i] > 0)
848 {
849 prayertype = static_cast <recite_type>(i);
850 break;
851 }
852 }
853
854 // Second check: because this affects the whole screen over several turns,
855 // its effects are staggered. There's a 50% chance per monster, per turn,
856 // that nothing will happen - so the cumulative odds of nothing happening
857 // are one in eight, since you recite three times.
858 if (coinflip())
859 return false;
860
861 const int power = zin_recite_power();
862 // Old recite was mostly deterministic, which is bad.
863 const int resist = mon->get_hit_dice() + random2(6);
864 const int check = power - resist;
865
866 // We abort if we didn't *beat* their HD - but first we might get a cute message.
867 if (mon->can_speak() && one_chance_in(5))
868 {
869 if (check < -10)
870 simple_monster_message(*mon, " guffaws at your puny god.");
871 else if (check < -5)
872 simple_monster_message(*mon, " sneers at your recitation.");
873 }
874
875 if (check <= 0)
876 return false;
877
878 // To what degree are they eligible for this prayertype?
879 const int degree = eligibility[prayertype];
880 const bool minor = degree <= (prayertype == RECITE_HERETIC ? 2 : 1);
881 const int spellpower = power * 2 + degree * 20;
882 zin_eff effect = zin_eff::nothing;
883
884 switch (prayertype)
885 {
886 case RECITE_HERETIC:
887 if (degree == 1)
888 {
889 if (mon->asleep())
890 break;
891 // This is the path for 'conversion' effects.
892 // Their degree is only 1 if they weren't a priest,
893 // a worshiper of an evil or chaotic god, etc.
894
895 // Right now, it only has the 'failed conversion' effects, though.
896 // This branch can't hit sleeping monsters - until they wake up.
897
898 if (check < 5)
899 effect = zin_eff::daze;
900 else if (check < 10)
901 {
902 if (coinflip())
903 effect = zin_eff::confuse;
904 else
905 effect = zin_eff::daze;
906 }
907 else if (check < 15)
908 effect = zin_eff::confuse;
909 else
910 {
911 if (one_chance_in(3))
912 effect = zin_eff::paralyse;
913 else
914 effect = zin_eff::confuse;
915 }
916 }
917 else
918 {
919 // This is the path for 'smiting' effects.
920 // Their degree is only greater than 1 if
921 // they're unable to be redeemed.
922 if (check < 5)
923 {
924 if (coinflip())
925 effect = zin_eff::confuse;
926 else
927 effect = zin_eff::smite;
928 }
929 else if (check < 10)
930 {
931 if (one_chance_in(3))
932 effect = zin_eff::blind;
933 else if (mon->antimagic_susceptible())
934 effect = zin_eff::antimagic;
935 else
936 effect = zin_eff::silver_corona;
937 }
938 else if (check < 15)
939 {
940 if (one_chance_in(3))
941 effect = zin_eff::blind;
942 else if (coinflip())
943 effect = zin_eff::paralyse;
944 else
945 effect = zin_eff::mute;
946 }
947 else
948 {
949 if (coinflip())
950 effect = zin_eff::mad;
951 else
952 effect = zin_eff::dumb;
953 }
954 }
955 break;
956
957 case RECITE_CHAOTIC:
958 if (check < 5)
959 effect = zin_eff::smite;
960 else if (check < 10)
961 effect = zin_eff::silver_corona;
962 else if (check < 15)
963 effect = zin_eff::ignite_chaos;
964 else
965 effect = zin_eff::saltify;
966 break;
967
968 case RECITE_IMPURE:
969 if (check < 5)
970 effect = zin_eff::smite;
971 else if (check < 10)
972 effect = zin_eff::silver_corona;
973 else if (check < 15)
974 {
975 if (mon->undead_or_demonic() && coinflip())
976 effect = zin_eff::holy_word;
977 else
978 effect = zin_eff::silver_corona;
979 }
980 else
981 effect = zin_eff::saltify;
982 break;
983
984 case RECITE_UNHOLY:
985 if (check < 5)
986 {
987 if (coinflip())
988 effect = zin_eff::daze;
989 else
990 effect = zin_eff::confuse;
991 }
992 else if (check < 10)
993 {
994 if (coinflip())
995 effect = zin_eff::confuse;
996 else
997 effect = zin_eff::silver_corona;
998 }
999 // Half of the time, the anti-unholy prayer will be capped at this
1000 // level of effect.
1001 else if (check < 15 || coinflip())
1002 {
1003 if (coinflip())
1004 effect = zin_eff::holy_word;
1005 else
1006 effect = zin_eff::silver_corona;
1007 }
1008 else
1009 effect = zin_eff::saltify;
1010 break;
1011
1012 case NUM_RECITE_TYPES:
1013 die("invalid recite type");
1014 }
1015
1016 // And the actual effects...
1017 switch (effect)
1018 {
1019 case zin_eff::nothing:
1020 break;
1021
1022 case zin_eff::daze:
1023 if (mon->add_ench(mon_enchant(ENCH_DAZED, degree, &you,
1024 (degree + random2(spellpower)) * BASELINE_DELAY)))
1025 {
1026 simple_monster_message(*mon, " is dazed by your recitation.");
1027 affected = true;
1028 }
1029 break;
1030
1031 case zin_eff::confuse:
1032 if (!mon->clarity()
1033 && mon->add_ench(mon_enchant(ENCH_CONFUSION, degree, &you,
1034 (degree + random2(spellpower)) * BASELINE_DELAY)))
1035 {
1036 if (prayertype == RECITE_HERETIC)
1037 simple_monster_message(*mon, " is confused by your recitation.");
1038 else
1039 simple_monster_message(*mon, " stumbles about in disarray.");
1040 affected = true;
1041 }
1042 break;
1043
1044 case zin_eff::paralyse:
1045 if (mon->add_ench(mon_enchant(ENCH_PARALYSIS, 0, &you,
1046 (degree + random2(spellpower)) * BASELINE_DELAY)))
1047 {
1048 simple_monster_message(*mon,
1049 minor ? " is awed by your recitation."
1050 : " is aghast at the heresy of your recitation.");
1051 affected = true;
1052 }
1053 break;
1054
1055 case zin_eff::smite:
1056 if (minor)
1057 simple_monster_message(*mon, " is smitten by the wrath of Zin.");
1058 else
1059 simple_monster_message(*mon, " is blasted by the fury of Zin!");
1060 // XXX: This duplicates code in cast_smiting().
1061 mon->hurt(&you, 7 + (random2(spellpower) * 33 / 191));
1062 if (mon->alive())
1063 print_wounds(*mon);
1064 affected = true;
1065 break;
1066
1067 case zin_eff::blind:
1068 if (mon->add_ench(mon_enchant(ENCH_BLIND, degree, &you, INFINITE_DURATION)))
1069 {
1070 simple_monster_message(*mon, " is struck blind by the wrath of Zin!");
1071 affected = true;
1072 }
1073 break;
1074
1075 case zin_eff::silver_corona:
1076 if (mon->add_ench(mon_enchant(ENCH_SILVER_CORONA, degree, &you,
1077 (degree + random2(spellpower)) * BASELINE_DELAY)))
1078 {
1079 simple_monster_message(*mon, " is limned with silver light.");
1080 affected = true;
1081 }
1082 break;
1083
1084 case zin_eff::antimagic:
1085 ASSERT(prayertype == RECITE_HERETIC);
1086 if (mon->add_ench(mon_enchant(ENCH_ANTIMAGIC, degree, &you,
1087 (degree + random2(spellpower)) * BASELINE_DELAY)))
1088 {
1089 simple_monster_message(*mon,
1090 minor ? " quails at your recitation."
1091 : " looks feeble and powerless before your recitation.");
1092 affected = true;
1093 }
1094 break;
1095
1096 case zin_eff::mute:
1097 if (mon->add_ench(mon_enchant(ENCH_MUTE, degree, &you, INFINITE_DURATION)))
1098 {
1099 simple_monster_message(*mon, " is struck mute by the wrath of Zin!");
1100 affected = true;
1101 }
1102 break;
1103
1104 case zin_eff::mad:
1105 if (mon->add_ench(mon_enchant(ENCH_MAD, degree, &you, INFINITE_DURATION)))
1106 {
1107 simple_monster_message(*mon, " is driven mad by the wrath of Zin!");
1108 affected = true;
1109 }
1110 break;
1111
1112 case zin_eff::dumb:
1113 if (mon->add_ench(mon_enchant(ENCH_DUMB, degree, &you, INFINITE_DURATION)))
1114 {
1115 simple_monster_message(*mon, " is left stupefied by the wrath of Zin!");
1116 affected = true;
1117 }
1118 break;
1119
1120 case zin_eff::ignite_chaos:
1121 ASSERT(prayertype == RECITE_CHAOTIC);
1122 {
1123 bolt beam;
1124 dice_def dam_dice(0, 5 + spellpower/7); // Dice added below if applicable.
1125 dam_dice.num = degree;
1126
1127 int damage = dam_dice.roll();
1128 if (damage > 0)
1129 {
1130 mon->hurt(&you, damage, BEAM_MISSILE, KILLED_BY_BEAM,
1131 "", "", false);
1132
1133 if (mon->alive())
1134 {
1135 simple_monster_message(*mon,
1136 (damage < 25) ? "'s chaotic flesh sizzles and spatters!" :
1137 (damage < 50) ? "'s chaotic flesh bubbles and boils."
1138 : "'s chaotic flesh runs like molten wax.");
1139
1140 print_wounds(*mon);
1141 behaviour_event(mon, ME_WHACK, &you);
1142 affected = true;
1143 }
1144 else
1145 {
1146 simple_monster_message(*mon,
1147 " melts away into a sizzling puddle of chaotic flesh.");
1148 monster_die(*mon, KILL_YOU, NON_MONSTER);
1149 }
1150 }
1151 }
1152 break;
1153
1154 case zin_eff::saltify:
1155 _zin_saltify(mon);
1156 break;
1157
1158 case zin_eff::holy_word:
1159 holy_word_monsters(where, spellpower, HOLY_WORD_ZIN, &you);
1160 affected = true;
1161 break;
1162 }
1163
1164 // Recite time, to prevent monsters from being recited against
1165 // more than once in a given recite instance.
1166 if (affected)
1167 mon->add_ench(mon_enchant(ENCH_RECITE_TIMER, degree, &you, 40));
1168
1169 // Monsters that have been affected may shout.
1170 if (affected
1171 && one_chance_in(3)
1172 && mon->alive()
1173 && mons_can_shout(mon->type))
1174 {
1175 monster_attempt_shout(*mon);
1176 }
1177
1178 return true;
1179 }
1180
_zin_saltify(monster * mon)1181 static void _zin_saltify(monster* mon)
1182 {
1183 const coord_def where = mon->pos();
1184 const monster_type pillar_type =
1185 mons_is_zombified(*mon) ? mons_zombie_base(*mon)
1186 : mons_species(mon->type);
1187 const int hd = mon->get_hit_dice();
1188
1189 simple_monster_message(*mon, " is turned into a pillar of salt by the wrath of Zin!");
1190
1191 // If the monster leaves a corpse when it dies, destroy the corpse.
1192 item_def* corpse = monster_die(*mon, KILL_YOU, NON_MONSTER);
1193 if (corpse)
1194 destroy_item(corpse->index());
1195
1196 if (monster *pillar = create_monster(
1197 mgen_data(MONS_PILLAR_OF_SALT,
1198 BEH_HOSTILE,
1199 where,
1200 MHITNOT,
1201 MG_FORCE_PLACE).set_base(pillar_type),
1202 false))
1203 {
1204 // Enemies with more HD leave longer-lasting pillars of salt.
1205 int time_left = (random2(8) + hd) * BASELINE_DELAY;
1206 mon_enchant temp_en(ENCH_SLOWLY_DYING, 1, 0, time_left);
1207 pillar->update_ench(temp_en);
1208 }
1209 }
1210
zin_vitalisation()1211 bool zin_vitalisation()
1212 {
1213 simple_god_message(" grants you divine stamina.");
1214
1215 // Add divine stamina.
1216 const int stamina_amt =
1217 max(1, you.skill_rdiv(SK_INVOCATIONS, 1, 3));
1218 you.attribute[ATTR_DIVINE_STAMINA] = stamina_amt;
1219 you.set_duration(DUR_DIVINE_STAMINA, 60 + roll_dice(2, 10));
1220
1221 notify_stat_change(STAT_STR, stamina_amt, true);
1222 notify_stat_change(STAT_INT, stamina_amt, true);
1223 notify_stat_change(STAT_DEX, stamina_amt, true);
1224
1225 return true;
1226 }
1227
zin_remove_divine_stamina()1228 void zin_remove_divine_stamina()
1229 {
1230 mprf(MSGCH_DURATION, "Your divine stamina fades away.");
1231 notify_stat_change(STAT_STR, -you.attribute[ATTR_DIVINE_STAMINA], true);
1232 notify_stat_change(STAT_INT, -you.attribute[ATTR_DIVINE_STAMINA], true);
1233 notify_stat_change(STAT_DEX, -you.attribute[ATTR_DIVINE_STAMINA], true);
1234 you.duration[DUR_DIVINE_STAMINA] = 0;
1235 you.attribute[ATTR_DIVINE_STAMINA] = 0;
1236 }
1237
zin_remove_all_mutations()1238 bool zin_remove_all_mutations()
1239 {
1240 ASSERT(you.how_mutated());
1241 ASSERT(can_do_capstone_ability(you.religion));
1242
1243 if (!yesno("Do you wish to cure all of your mutations?", true, 'n'))
1244 {
1245 canned_msg(MSG_OK);
1246 return false;
1247 }
1248 flash_view(UA_PLAYER, WHITE);
1249 #ifndef USE_TILE_LOCAL
1250 // Allow extra time for the flash to linger.
1251 scaled_delay(1000);
1252 #endif
1253
1254 you.one_time_ability_used.set(GOD_ZIN);
1255 take_note(Note(NOTE_GOD_GIFT, you.religion));
1256 simple_god_message(" draws all chaos from your body!");
1257 delete_all_mutations("Zin's power");
1258 return true;
1259 }
1260
zin_sanctuary()1261 void zin_sanctuary()
1262 {
1263 ASSERT(!env.sanctuary_time);
1264
1265 // Yes, shamelessly stolen from NetHack...
1266 if (!silenced(you.pos())) // How did you manage that?
1267 mprf(MSGCH_SOUND, "You hear a choir sing!");
1268 else
1269 mpr("You are suddenly bathed in radiance!");
1270
1271 flash_view(UA_PLAYER, WHITE);
1272 #ifndef USE_TILE_LOCAL
1273 // Allow extra time for the flash to linger.
1274 scaled_delay(1000);
1275 #endif
1276
1277 // Pets stop attacking and converge on you.
1278 you.pet_target = MHITYOU;
1279 create_sanctuary(you.pos(), 7 + you.skill_rdiv(SK_INVOCATIONS) / 2);
1280 }
1281
1282 // shield bonus = attribute for duration turns, then decreasing by 1
1283 // every two out of three turns
1284 // overall shield duration = duration + attribute
1285 // recasting simply resets those two values (to better values, presumably)
tso_divine_shield()1286 void tso_divine_shield()
1287 {
1288 if (!you.duration[DUR_DIVINE_SHIELD])
1289 {
1290 if (you.shield())
1291 {
1292 mprf("Your shield is strengthened by %s divine power.",
1293 apostrophise(god_name(GOD_SHINING_ONE)).c_str());
1294 }
1295 else
1296 mpr("A divine shield forms around you!");
1297 }
1298 else
1299 mpr("Your divine shield is renewed.");
1300
1301 // Duration from 35-80 turns.
1302 you.set_duration(DUR_DIVINE_SHIELD,
1303 35 + you.skill_rdiv(SK_INVOCATIONS, 5, 3));
1304
1305 // Size of SH bonus.
1306 you.attribute[ATTR_DIVINE_SHIELD] =
1307 12 + you.skill_rdiv(SK_INVOCATIONS, 4, 5);
1308
1309 you.redraw_armour_class = true;
1310 }
1311
tso_remove_divine_shield()1312 void tso_remove_divine_shield()
1313 {
1314 mprf(MSGCH_DURATION, "Your divine shield fades away.");
1315 you.duration[DUR_DIVINE_SHIELD] = 0;
1316 you.attribute[ATTR_DIVINE_SHIELD] = 0;
1317 you.redraw_armour_class = true;
1318 }
1319
elyvilon_purification()1320 void elyvilon_purification()
1321 {
1322 mpr("You feel purified!");
1323
1324 you.duration[DUR_SICKNESS] = 0;
1325 you.duration[DUR_POISONING] = 0;
1326 you.duration[DUR_CONF] = 0;
1327 you.duration[DUR_SLOW] = 0;
1328 you.duration[DUR_PETRIFYING] = 0;
1329 you.duration[DUR_WEAK] = 0;
1330 restore_stat(STAT_ALL, 0, false);
1331 undrain_hp(9999);
1332 you.redraw_evasion = true;
1333 }
1334
elyvilon_divine_vigour()1335 bool elyvilon_divine_vigour()
1336 {
1337 bool success = false;
1338
1339 if (!you.duration[DUR_DIVINE_VIGOUR])
1340 {
1341 mprf("%s grants you divine vigour.",
1342 god_name(GOD_ELYVILON).c_str());
1343
1344 const int vigour_amt = 1 + you.skill_rdiv(SK_INVOCATIONS, 1, 3);
1345 const int old_hp_max = you.hp_max;
1346 const int old_mp_max = you.max_magic_points;
1347 you.attribute[ATTR_DIVINE_VIGOUR] = vigour_amt;
1348 you.set_duration(DUR_DIVINE_VIGOUR,
1349 40 + you.skill_rdiv(SK_INVOCATIONS, 5, 2));
1350
1351 calc_hp();
1352 inc_hp((you.hp_max * you.hp + old_hp_max - 1)/old_hp_max - you.hp);
1353 calc_mp();
1354 if (old_mp_max > 0)
1355 {
1356 inc_mp((you.max_magic_points * you.magic_points + old_mp_max - 1)
1357 / old_mp_max
1358 - you.magic_points);
1359 }
1360
1361 success = true;
1362 }
1363 else
1364 canned_msg(MSG_NOTHING_HAPPENS);
1365
1366 return success;
1367 }
1368
elyvilon_remove_divine_vigour()1369 void elyvilon_remove_divine_vigour()
1370 {
1371 mprf(MSGCH_DURATION, "Your divine vigour fades away.");
1372 you.duration[DUR_DIVINE_VIGOUR] = 0;
1373 you.attribute[ATTR_DIVINE_VIGOUR] = 0;
1374 calc_hp();
1375 calc_mp();
1376 }
1377
vehumet_supports_spell(spell_type spell)1378 bool vehumet_supports_spell(spell_type spell)
1379 {
1380 if (spell_typematch(spell, spschool::conjuration))
1381 return true;
1382
1383 // Conjurations work by conjuring up a chunk of short-lived matter and
1384 // propelling it towards the victim. This is the most popular way, but
1385 // by no means it has a monopoly for being destructive.
1386 // Vehumet loves all direct physical destruction.
1387 if (spell == SPELL_SHATTER
1388 || spell == SPELL_LRD
1389 || spell == SPELL_SANDBLAST
1390 || spell == SPELL_AIRSTRIKE
1391 || spell == SPELL_POLAR_VORTEX
1392 || spell == SPELL_FREEZE
1393 || spell == SPELL_IGNITE_POISON
1394 || spell == SPELL_OZOCUBUS_REFRIGERATION
1395 || spell == SPELL_OLGREBS_TOXIC_RADIANCE
1396 || spell == SPELL_VIOLENT_UNRAVELLING
1397 || spell == SPELL_INNER_FLAME
1398 || spell == SPELL_IGNITION
1399 || spell == SPELL_FROZEN_RAMPARTS
1400 || spell == SPELL_MAXWELLS_COUPLING
1401 || spell == SPELL_NOXIOUS_BOG
1402 || spell == SPELL_POISONOUS_VAPOURS)
1403 {
1404 return true;
1405 }
1406
1407 return false;
1408 }
1409
trog_do_trogs_hand(int pow)1410 void trog_do_trogs_hand(int pow)
1411 {
1412 you.increase_duration(DUR_TROGS_HAND,
1413 5 + roll_dice(2, pow / 3 + 1), 100,
1414 "Your skin crawls.");
1415 mprf(MSGCH_DURATION, "You feel strong-willed.");
1416 }
1417
trog_remove_trogs_hand()1418 void trog_remove_trogs_hand()
1419 {
1420 mprf(MSGCH_DURATION, "Your skin stops crawling.");
1421 mprf(MSGCH_DURATION, "You feel less strong-willed.");
1422 you.duration[DUR_TROGS_HAND] = 0;
1423 }
1424
1425 /**
1426 * Has the monster been given a Beogh gift?
1427 *
1428 * @param mon the orc in question.
1429 * @returns whether you have given the monster a Beogh gift before now.
1430 */
given_gift(const monster * mon)1431 bool given_gift(const monster* mon)
1432 {
1433 return mon->props.exists(BEOGH_RANGE_WPN_GIFT_KEY)
1434 || mon->props.exists(BEOGH_MELEE_WPN_GIFT_KEY)
1435 || mon->props.exists(BEOGH_ARM_GIFT_KEY)
1436 || mon->props.exists(BEOGH_SH_GIFT_KEY);
1437 }
1438
1439 /**
1440 * Checks whether the target monster is a valid target for beogh item-gifts.
1441 *
1442 * @param mons[in] The monster to consider giving an item to.
1443 * @param quiet Whether to print messages if the target is invalid.
1444 * @return Whether the player can give an item to the monster.
1445 */
beogh_can_gift_items_to(const monster * mons,bool quiet)1446 bool beogh_can_gift_items_to(const monster* mons, bool quiet)
1447 {
1448 if (!mons || !mons->visible_to(&you))
1449 {
1450 if (!quiet)
1451 canned_msg(MSG_NOTHING_THERE);
1452 return false;
1453 }
1454
1455 if (!is_orcish_follower(*mons) || mons_genus(mons->type) != MONS_ORC)
1456 {
1457 if (!quiet)
1458 mpr("That's not an orcish ally!");
1459 return false;
1460 }
1461
1462 if (!mons->is_named())
1463 {
1464 if (!quiet)
1465 mpr("That orc has not proved itself worthy of your gift.");
1466 return false;
1467 }
1468
1469 if (given_gift(mons))
1470 {
1471 if (!quiet)
1472 {
1473 mprf("%s has already been given a gift.",
1474 mons->name(DESC_THE, false).c_str());
1475 }
1476 return false;
1477 }
1478
1479 return true;
1480 }
1481
1482 /**
1483 * Checks whether there are any valid targets for beogh gifts in LOS.
1484 */
_valid_beogh_gift_targets_in_sight()1485 static bool _valid_beogh_gift_targets_in_sight()
1486 {
1487 for (monster_near_iterator rad(you.pos(), LOS_NO_TRANS); rad; ++rad)
1488 if (beogh_can_gift_items_to(*rad))
1489 return true;
1490 return false;
1491 }
1492
1493 /**
1494 * Allow the player to give an item to a named orcish ally that hasn't
1495 * been given a gift before
1496 *
1497 * @returns whether an item was given.
1498 */
beogh_gift_item()1499 bool beogh_gift_item()
1500 {
1501 if (!_valid_beogh_gift_targets_in_sight())
1502 {
1503 mpr("No worthy followers in sight.");
1504 return false;
1505 }
1506
1507 dist spd;
1508
1509 direction_chooser_args args;
1510 args.restricts = DIR_TARGET;
1511 args.mode = TARG_BEOGH_GIFTABLE;
1512 args.range = LOS_RADIUS;
1513 args.needs_path = false;
1514 args.self = confirm_prompt_type::cancel;
1515 args.show_floor_desc = true;
1516 args.top_prompt = "Select a follower to give a gift to.";
1517
1518 direction(spd, args);
1519
1520 if (!spd.isValid)
1521 return false;
1522
1523 monster* mons = monster_at(spd.target);
1524 if (!beogh_can_gift_items_to(mons, false))
1525 return false;
1526
1527 int item_slot = prompt_invent_item("Give which item?",
1528 menu_type::invlist, OSEL_BEOGH_GIFT);
1529
1530 if (item_slot == PROMPT_ABORT || item_slot == PROMPT_NOTHING)
1531 {
1532 canned_msg(MSG_OK);
1533 return false;
1534 }
1535
1536 item_def& gift = you.inv[item_slot];
1537
1538 const bool shield = is_shield(gift);
1539 const bool body_armour = gift.base_type == OBJ_ARMOUR
1540 && get_armour_slot(gift) == EQ_BODY_ARMOUR;
1541 const bool weapon = gift.base_type == OBJ_WEAPONS;
1542 const bool range_weapon = weapon && is_range_weapon(gift);
1543 const item_def* mons_weapon = mons->weapon();
1544 const item_def* mons_alt_weapon = mons->mslot_item(MSLOT_ALT_WEAPON);
1545
1546 if (weapon && !mons->could_wield(gift)
1547 || body_armour && !check_armour_size(gift, mons->body_size())
1548 || !item_is_selected(gift, OSEL_BEOGH_GIFT))
1549 {
1550 mprf("You can't give that to %s.", mons->name(DESC_THE, false).c_str());
1551
1552 return false;
1553 }
1554 else if (shield
1555 && (mons_weapon && mons->hands_reqd(*mons_weapon) == HANDS_TWO
1556 || mons_alt_weapon
1557 && mons->hands_reqd(*mons_alt_weapon) == HANDS_TWO))
1558 {
1559 mprf("%s can't equip that with a two-handed weapon.",
1560 mons->name(DESC_THE, false).c_str());
1561 return false;
1562 }
1563
1564 // if we're giving a ranged weapon to an orc holding a melee weapon in
1565 // their hands, or vice versa, put it in their carried slot instead.
1566 // this will of course drop anything that's there.
1567 const bool use_alt_slot = weapon && mons_weapon
1568 && is_range_weapon(gift) !=
1569 is_range_weapon(*mons_weapon);
1570
1571 const auto mslot = body_armour ? MSLOT_ARMOUR :
1572 shield ? MSLOT_SHIELD :
1573 use_alt_slot ? MSLOT_ALT_WEAPON :
1574 MSLOT_WEAPON;
1575
1576 item_def *floor_item = mons->take_item(item_slot, mslot);
1577 if (!floor_item)
1578 {
1579 // this probably means move_to_grid in drop_item failed?
1580 mprf(MSGCH_ERROR, "Gift failed: %s is unable to take %s.",
1581 mons->name(DESC_THE, false).c_str(),
1582 gift.name(DESC_THE, false).c_str());
1583 return false;
1584 }
1585 if (use_alt_slot)
1586 mons->swap_weapons();
1587
1588 dprf("is_ranged weap: %d", range_weapon);
1589 if (range_weapon)
1590 gift_ammo_to_orc(mons);
1591
1592 if (shield)
1593 mons->props[BEOGH_SH_GIFT_KEY] = true;
1594 else if (body_armour)
1595 mons->props[BEOGH_ARM_GIFT_KEY] = true;
1596 else if (range_weapon)
1597 mons->props[BEOGH_RANGE_WPN_GIFT_KEY] = true;
1598 else
1599 mons->props[BEOGH_MELEE_WPN_GIFT_KEY] = true;
1600
1601 return true;
1602 }
1603
beogh_resurrect()1604 bool beogh_resurrect()
1605 {
1606 item_def* corpse = nullptr;
1607 bool found_any = false;
1608 for (stack_iterator si(you.pos()); si; ++si)
1609 if (si->props.exists(ORC_CORPSE_KEY))
1610 {
1611 found_any = true;
1612 if (yesno(("Resurrect "
1613 + si->props[ORC_CORPSE_KEY].get_monster().full_name(DESC_THE)
1614 + "?").c_str(), true, 'n'))
1615 {
1616 corpse = &*si;
1617 break;
1618 }
1619 }
1620 if (!corpse)
1621 {
1622 mprf("There's nobody %shere you can resurrect.",
1623 found_any ? "else " : "");
1624 return false;
1625 }
1626
1627 coord_def pos;
1628 ASSERT(corpse->props.exists(ORC_CORPSE_KEY));
1629 for (fair_adjacent_iterator ai(you.pos()); ai; ++ai)
1630 {
1631 if (!actor_at(*ai)
1632 && corpse->props[ORC_CORPSE_KEY].get_monster().is_location_safe(*ai))
1633 {
1634 pos = *ai;
1635 }
1636 }
1637 if (pos.origin())
1638 {
1639 mpr("There's no room!");
1640 return false;
1641 }
1642
1643 monster* mon = get_free_monster();
1644 *mon = corpse->props[ORC_CORPSE_KEY];
1645 destroy_item(corpse->index());
1646 env.mid_cache[mon->mid] = mon->mindex();
1647 mon->hit_points = mon->max_hit_points;
1648 mon->inv.init(NON_ITEM);
1649 for (stack_iterator si(you.pos()); si; ++si)
1650 {
1651 if (!si->props.exists(DROPPER_MID_KEY)
1652 || si->props[DROPPER_MID_KEY].get_int() != int(mon->mid))
1653 {
1654 continue;
1655 }
1656 unwind_var<int> save_speedinc(mon->speed_increment);
1657 mon->pickup_item(*si, false, true);
1658 }
1659 mon->move_to_pos(pos);
1660 mon->timeout_enchantments(100);
1661 beogh_convert_orc(mon, conv_t::resurrection);
1662
1663 return true;
1664 }
1665
jiyva_remove_bad_mutation()1666 bool jiyva_remove_bad_mutation()
1667 {
1668 if (!you.how_mutated())
1669 {
1670 mpr("You have no bad mutations to be cured!");
1671 return false;
1672 }
1673
1674 // Ensure that only bad mutations are removed.
1675 if (!delete_mutation(RANDOM_BAD_MUTATION, "Jiyva's power", true, false, true, true))
1676 {
1677 canned_msg(MSG_NOTHING_HAPPENS);
1678 return false;
1679 }
1680
1681 mpr("You feel cleansed.");
1682 return true;
1683 }
1684
yred_injury_mirror()1685 bool yred_injury_mirror()
1686 {
1687 return in_good_standing(GOD_YREDELEMNUL, 1)
1688 && you.duration[DUR_MIRROR_DAMAGE]
1689 && crawl_state.which_god_acting() != GOD_YREDELEMNUL;
1690 }
1691
yred_can_enslave_soul(monster * mon)1692 bool yred_can_enslave_soul(monster* mon)
1693 {
1694 return (mon->holiness() & MH_NATURAL
1695 || mon->holiness() & MH_DEMONIC
1696 || mon->holiness() & MH_HOLY)
1697 && !mon->is_summoned()
1698 && !mons_enslaved_body_and_soul(*mon)
1699 && mon->attitude != ATT_FRIENDLY
1700 && mons_intel(*mon) >= I_HUMAN
1701 && mon->type != MONS_PANDEMONIUM_LORD;
1702 }
1703
yred_make_enslaved_soul(monster * mon,bool force_hostile)1704 void yred_make_enslaved_soul(monster* mon, bool force_hostile)
1705 {
1706 ASSERT(mon); // XXX: change to monster &mon
1707 ASSERT(mons_enslaved_body_and_soul(*mon));
1708
1709 add_daction(DACT_OLD_CHARMD_SOULS_POOF);
1710 remove_enslaved_soul_companion();
1711
1712 const string whose = you.can_see(*mon) ? apostrophise(mon->name(DESC_THE))
1713 : mon->pronoun(PRONOUN_POSSESSIVE);
1714
1715 // Remove the monster's soul-enslaving enchantment, as it's no
1716 // longer needed.
1717 mon->del_ench(ENCH_SOUL_RIPE, false, false);
1718
1719 // Remove the monster's invisibility enchantment. If we don't do
1720 // this, it'll stay invisible after being remade as a spectral thing
1721 // below.
1722 mon->del_ench(ENCH_INVIS, false, false);
1723
1724 // If the monster's held in a net, get it out.
1725 mons_clear_trapping_net(mon);
1726
1727 // Rebrand or drop any holy equipment, and keep wielding the rest. Also
1728 // remove any active avatars.
1729 for (int slot = MSLOT_WEAPON; slot <= MSLOT_ALT_WEAPON; slot++)
1730 {
1731 item_def *wpn = mon->mslot_item(static_cast<mon_inv_type>(slot));
1732 if (wpn && get_weapon_brand(*wpn) == SPWPN_HOLY_WRATH)
1733 {
1734 set_item_ego_type(*wpn, OBJ_WEAPONS, SPWPN_DRAINING);
1735 convert2bad(*wpn);
1736 }
1737 }
1738 monster_drop_things(mon, false, [](const item_def& item)
1739 { return is_holy_item(item); });
1740 mon->remove_summons();
1741
1742 const monster orig = *mon;
1743
1744 // Use the original monster type as the zombified type here, to get
1745 // the proper stats from it.
1746 define_zombie(mon, mon->type, MONS_SPECTRAL_THING);
1747
1748 // If the original monster has been levelled up, its HD might be different
1749 // from its class HD, in which case its HP should be rerolled to match.
1750 if (mon->get_experience_level() != orig.get_experience_level())
1751 {
1752 mon->set_hit_dice(max(orig.get_experience_level(), 1));
1753 roll_zombie_hp(mon);
1754 }
1755
1756 mon->colour = ETC_UNHOLY;
1757
1758 mon->flags |= MF_NO_REWARD;
1759 mon->flags |= MF_ENSLAVED_SOUL;
1760
1761 // If the original monster type has melee abilities, make sure
1762 // its spectral thing has them as well.
1763 mon->flags |= orig.flags & MF_MELEE_MASK;
1764 monster_spells spl = orig.spells;
1765 for (const mon_spell_slot &slot : spl)
1766 if (!(get_spell_flags(slot.spell) & spflag::holy))
1767 mon->spells.push_back(slot);
1768 if (mon->spells.size())
1769 mon->props[CUSTOM_SPELLS_KEY] = true;
1770
1771 name_zombie(*mon, orig);
1772
1773 mons_make_god_gift(*mon, GOD_YREDELEMNUL);
1774 add_companion(mon);
1775
1776 mon->attitude = !force_hostile ? ATT_FRIENDLY : ATT_HOSTILE;
1777 behaviour_event(mon, ME_ALERT, force_hostile ? &you : 0);
1778
1779 mon->stop_constricting_all();
1780 mon->stop_being_constricted();
1781
1782 if (orig.halo_radius()
1783 || orig.umbra_radius()
1784 || orig.silence_radius())
1785 {
1786 invalidate_agrid();
1787 }
1788
1789 mprf("%s soul %s.", whose.c_str(),
1790 !force_hostile ? "is now yours" : "fights you");
1791 }
1792
kiku_receive_corpses(int pow)1793 bool kiku_receive_corpses(int pow)
1794 {
1795 // pow = necromancy * 4, ranges from 0 to 108
1796 dprf("kiku_receive_corpses() power: %d", pow);
1797
1798 // Kiku gives branch-appropriate corpses (like shadow creatures).
1799 // 1d2 at 0 Nec, up to 8 at 27 Nec.
1800 int expected_extra_corpses = 1 + random2(2) + random2(pow / 18);
1801 int corpse_delivery_radius = 1;
1802
1803 // We should get the same number of corpses
1804 // in a hallway as in an open room.
1805 int spaces_for_corpses = 0;
1806 for (radius_iterator ri(you.pos(), corpse_delivery_radius, C_SQUARE,
1807 LOS_NO_TRANS, true); ri; ++ri)
1808 {
1809 if (mons_class_can_pass(MONS_HUMAN, env.grid(*ri)))
1810 spaces_for_corpses++;
1811 }
1812 // floating over lava, heavy tomb abuse, etc
1813 if (!spaces_for_corpses)
1814 spaces_for_corpses++;
1815
1816 int percent_chance_a_square_receives_extra_corpse = // can be > 100
1817 int(float(expected_extra_corpses) / float(spaces_for_corpses) * 100.0);
1818
1819 int corpses_created = 0;
1820
1821 for (radius_iterator ri(you.pos(), corpse_delivery_radius, C_SQUARE,
1822 LOS_NO_TRANS); ri; ++ri)
1823 {
1824 bool square_is_walkable = mons_class_can_pass(MONS_HUMAN, env.grid(*ri));
1825 bool square_is_player_square = (*ri == you.pos());
1826 bool square_gets_corpse =
1827 random2(100) < percent_chance_a_square_receives_extra_corpse
1828 || square_is_player_square && random2(100) < 97;
1829
1830 if (!square_is_walkable || !square_gets_corpse)
1831 continue;
1832
1833 corpses_created++;
1834
1835 // Find an appropriate monster corpse for level and power.
1836 const int adjusted_power = min(pow / 4, random2(random2(pow)));
1837 // Pick a place based on the power. This may be below the branch's
1838 // start, that's ok.
1839 const level_id lev(you.where_are_you, adjusted_power
1840 - absdungeon_depth(you.where_are_you, 0));
1841 const monster_type mon_type = pick_local_corpsey_monster(lev);
1842 ASSERT(mons_class_can_be_zombified(mons_species(mon_type)));
1843
1844 // Create corpse object.
1845 monster dummy;
1846 dummy.type = mon_type;
1847 define_monster(dummy);
1848 dummy.position = *ri;
1849
1850 item_def* corpse = place_monster_corpse(dummy, true);
1851 if (!corpse)
1852 continue;
1853
1854 // Higher piety means fresher corpses.
1855 int rottedness = 200 -
1856 (!one_chance_in(10) ? random2(200 - you.piety)
1857 : random2(100 + random2(75)));
1858 corpse->freshness = rottedness;
1859 }
1860
1861 if (corpses_created)
1862 {
1863 if (you_worship(GOD_KIKUBAAQUDGHA))
1864 {
1865 simple_god_message(corpses_created > 1 ? " delivers you corpses!"
1866 : " delivers you a corpse!");
1867 }
1868 maybe_update_stashes();
1869 return true;
1870 }
1871 else
1872 {
1873 if (you_worship(GOD_KIKUBAAQUDGHA))
1874 simple_god_message(" can find no cadavers for you!");
1875 return false;
1876 }
1877 }
1878
1879 /**
1880 * Destroy a corpse at the player's location
1881 *
1882 * @return True if a corpse was destroyed, false otherwise.
1883 */
kiku_take_corpse()1884 bool kiku_take_corpse()
1885 {
1886 for (int i = you.visible_igrd(you.pos()); i != NON_ITEM; i = env.item[i].link)
1887 {
1888 item_def &item(env.item[i]);
1889
1890 if (item.base_type != OBJ_CORPSES || item.sub_type != CORPSE_BODY)
1891 continue;
1892 item_was_destroyed(item);
1893 destroy_item(i);
1894 return true;
1895 }
1896
1897 return false;
1898 }
1899
kiku_gift_capstone_spells()1900 bool kiku_gift_capstone_spells()
1901 {
1902 ASSERT(can_do_capstone_ability(you.religion));
1903
1904 vector<spell_type> spells = { SPELL_HAUNT,
1905 SPELL_BORGNJORS_REVIVIFICATION,
1906 SPELL_INFESTATION,
1907 SPELL_NECROMUTATION,
1908 SPELL_DEATHS_DOOR };
1909
1910 string msg = "Do you wish to receive knowledge of "
1911 + comma_separated_fn(spells.begin(), spells.end(), spell_title)
1912 + "?";
1913
1914 if (!yesno(msg.c_str(), true, 'n'))
1915 {
1916 canned_msg(MSG_OK);
1917 return false;
1918 }
1919
1920 simple_god_message(" grants you forbidden knowledge!");
1921 library_add_spells(spells);
1922 flash_view(UA_PLAYER, RED);
1923 #ifndef USE_TILE_LOCAL
1924 // Allow extra time for the flash to linger.
1925 scaled_delay(1000);
1926 #endif
1927 more();
1928 you.one_time_ability_used.set(you.religion);
1929 take_note(Note(NOTE_GOD_GIFT, you.religion));
1930 return true;
1931 }
1932
fedhas_passthrough_class(const monster_type mc)1933 bool fedhas_passthrough_class(const monster_type mc)
1934 {
1935 return have_passive(passive_t::pass_through_plants)
1936 && mons_class_is_plant(mc)
1937 && mons_class_is_stationary(mc)
1938 && mc != MONS_SNAPLASHER_VINE
1939 && mc != MONS_SNAPLASHER_VINE_SEGMENT;
1940 }
1941
1942 // Fedhas allows worshipers to walk on top of stationary plants and
1943 // fungi.
fedhas_passthrough(const monster * target)1944 bool fedhas_passthrough(const monster* target)
1945 {
1946 return target
1947 && fedhas_passthrough_class(target->type)
1948 && (mons_species(target->type) != MONS_OKLOB_PLANT
1949 || target->attitude != ATT_HOSTILE);
1950 }
1951
fedhas_passthrough(const monster_info * target)1952 bool fedhas_passthrough(const monster_info* target)
1953 {
1954 return target
1955 && fedhas_passthrough_class(target->type)
1956 && (mons_species(target->type) != MONS_OKLOB_PLANT
1957 || target->attitude != ATT_HOSTILE);
1958 }
1959
_lugonu_warp_monster(monster & mon,int pow)1960 static bool _lugonu_warp_monster(monster& mon, int pow)
1961 {
1962 if (coinflip())
1963 return false;
1964
1965 if (!mon.friendly())
1966 behaviour_event(&mon, ME_ANNOY, &you);
1967
1968 mon.hurt(&you, 1 + random2(pow / 6));
1969
1970 if (mon.alive() && !mon.no_tele(true, false))
1971 mon.blink();
1972
1973 return true;
1974 }
1975
_lugonu_warp_area(int pow)1976 static void _lugonu_warp_area(int pow)
1977 {
1978 apply_monsters_around_square([pow] (monster& mon) {
1979 return _lugonu_warp_monster(mon, pow);
1980 }, you.pos());
1981 }
1982
lugonu_bend_space()1983 void lugonu_bend_space()
1984 {
1985 const int pow = 4 + skill_bump(SK_INVOCATIONS);
1986 const bool area_warp = random2(pow) > 9;
1987
1988 mprf("Space bends %saround you!", area_warp ? "sharply " : "");
1989
1990 if (area_warp)
1991 _lugonu_warp_area(pow);
1992
1993 uncontrolled_blink(true);
1994 }
1995
cheibriados_time_bend(int pow)1996 void cheibriados_time_bend(int pow)
1997 {
1998 mpr("The flow of time bends around you.");
1999
2000 for (adjacent_iterator ai(you.pos()); ai; ++ai)
2001 {
2002 monster* mon = monster_at(*ai);
2003 if (mon && !mon->is_stationary())
2004 {
2005 int res_margin = roll_dice(mon->get_hit_dice(), 3);
2006 res_margin -= random2avg(pow, 2);
2007 if (res_margin > 0)
2008 {
2009 mprf("%s%s",
2010 mon->name(DESC_THE).c_str(),
2011 mon->resist_margin_phrase(res_margin).c_str());
2012 continue;
2013 }
2014
2015 simple_god_message(
2016 make_stringf(" rebukes %s.",
2017 mon->name(DESC_THE).c_str()).c_str(),
2018 GOD_CHEIBRIADOS);
2019 do_slow_monster(*mon, &you);
2020 }
2021 }
2022 }
2023
_slouch_damage(monster * mon)2024 static int _slouch_damage(monster *mon)
2025 {
2026 // Please change handle_monster_move in mon-act.cc to match.
2027 const int jerk_num = mon->type == MONS_SIXFIRHY ? 8
2028 : mon->type == MONS_JIANGSHI ? 48
2029 : 1;
2030
2031 const int jerk_denom = mon->type == MONS_SIXFIRHY ? 24
2032 : mon->type == MONS_JIANGSHI ? 90
2033 : 1;
2034
2035 const int player_numer = BASELINE_DELAY * BASELINE_DELAY * BASELINE_DELAY;
2036 return 4 * (mon->speed * BASELINE_DELAY * jerk_num
2037 / mon->action_energy(EUT_MOVE) / jerk_denom
2038 - player_numer / player_movement_speed() / player_speed());
2039 }
2040
_slouchable(coord_def where)2041 static bool _slouchable(coord_def where)
2042 {
2043 monster* mon = monster_at(where);
2044 if (mon == nullptr || mon->is_stationary() || mon->cannot_move()
2045 || mons_is_projectile(mon->type)
2046 || mon->asleep() && !mons_is_confused(*mon))
2047 {
2048 return false;
2049 }
2050
2051 return _slouch_damage(mon) > 0;
2052 }
2053
_act_slouchable(const actor * act)2054 static bool _act_slouchable(const actor *act)
2055 {
2056 if (act->is_player())
2057 return false; // too slow-witted
2058 return _slouchable(act->pos());
2059 }
2060
_slouch_monsters(coord_def where)2061 static int _slouch_monsters(coord_def where)
2062 {
2063 if (!_slouchable(where))
2064 return 0;
2065
2066 monster* mon = monster_at(where);
2067 ASSERT(mon);
2068
2069 // Between 1/2 and 3/2 of _slouch_damage(), but weighted strongly
2070 // towards the middle.
2071 const int dmg = roll_dice(_slouch_damage(mon), 3) / 2;
2072
2073 mon->hurt(&you, dmg, BEAM_MMISSILE, KILLED_BY_BEAM, "", "", true);
2074 return 1;
2075 }
2076
cheibriados_slouch()2077 bool cheibriados_slouch()
2078 {
2079 int count = apply_area_visible(_slouchable, you.pos());
2080 if (!count)
2081 if (!yesno("There's no one hasty visible. Invoke Slouch anyway?",
2082 true, 'n'))
2083 {
2084 canned_msg(MSG_OK);
2085 return false;
2086 }
2087
2088 targeter_radius hitfunc(&you, LOS_DEFAULT);
2089 if (stop_attack_prompt(hitfunc, "harm", _act_slouchable))
2090 return false;
2091
2092 mpr("You can feel time thicken for a moment.");
2093 dprf("your speed is %d", player_movement_speed());
2094
2095 apply_area_visible(_slouch_monsters, you.pos());
2096 return true;
2097 }
2098
_run_time_step()2099 static void _run_time_step()
2100 {
2101 ASSERT(you.duration[DUR_TIME_STEP] > 0);
2102 do
2103 {
2104 run_environment_effects();
2105 handle_monsters();
2106 manage_clouds();
2107 }
2108 while (--you.duration[DUR_TIME_STEP] > 0);
2109 }
2110
2111 // A low-duration step from time, allowing monsters to get closer
2112 // to the player safely.
cheibriados_temporal_distortion()2113 void cheibriados_temporal_distortion()
2114 {
2115 const coord_def old_pos = you.pos();
2116
2117 you.duration[DUR_TIME_STEP] = 3 + random2(3);
2118 you.moveto(coord_def(0, 0));
2119
2120 _run_time_step();
2121
2122 you.los_noise_level = 0;
2123 you.los_noise_last_turn = 0;
2124
2125 if (monster *mon = monster_at(old_pos))
2126 {
2127 mon->props[FAKE_BLINK_KEY].get_bool() = true;
2128 mon->blink();
2129 mon->props.erase(FAKE_BLINK_KEY);
2130 if (monster *stubborn = monster_at(old_pos))
2131 monster_teleport(stubborn, true, true);
2132 }
2133
2134 you.moveto(old_pos);
2135 you.duration[DUR_TIME_STEP] = 0;
2136
2137 mpr("You warp the flow of time around you!");
2138 }
2139
cheibriados_time_step(int pow)2140 void cheibriados_time_step(int pow) // pow is the number of turns to skip
2141 {
2142 const coord_def old_pos = you.pos();
2143
2144 mpr("You step out of the flow of time.");
2145 flash_view(UA_PLAYER, LIGHTBLUE);
2146 you.duration[DUR_TIME_STEP] = pow;
2147 you.moveto(coord_def(0, 0));
2148
2149 you.time_taken = 10;
2150 _run_time_step();
2151 // Update corpses, etc. This does also shift monsters, but only by
2152 // a tiny bit.
2153 update_level(pow * 10);
2154
2155 #ifndef USE_TILE_LOCAL
2156 scaled_delay(1000);
2157 #endif
2158
2159 if (monster *mon = monster_at(old_pos))
2160 {
2161 mon->props[FAKE_BLINK_KEY].get_bool() = true;
2162 mon->blink();
2163 mon->props.erase(FAKE_BLINK_KEY);
2164 if (monster *stubborn = monster_at(old_pos))
2165 monster_teleport(stubborn, true, true);
2166 }
2167
2168 you.moveto(old_pos);
2169 you.duration[DUR_TIME_STEP] = 0;
2170
2171 flash_view(UA_PLAYER, 0);
2172 mpr("You return to the normal time flow.");
2173 }
2174
2175 struct curse_data
2176 {
2177 string name;
2178 string abbr;
2179 vector<skill_type> boosted;
2180 };
2181
2182 static map<curse_type, curse_data> _ashenzari_curses =
2183 {
2184 { CURSE_MELEE, {
2185 "Melee Combat", "Melee",
2186 { SK_SHORT_BLADES, SK_LONG_BLADES, SK_AXES, SK_MACES_FLAILS,
2187 SK_POLEARMS, SK_STAVES, SK_UNARMED_COMBAT },
2188 } },
2189 { CURSE_RANGED, {
2190 "Ranged Combat", "Range",
2191 { SK_SLINGS, SK_BOWS, SK_CROSSBOWS, SK_THROWING },
2192 } },
2193 { CURSE_ELEMENTS, {
2194 "Elements", "Elem",
2195 { SK_FIRE_MAGIC, SK_ICE_MAGIC, SK_AIR_MAGIC, SK_EARTH_MAGIC },
2196 } },
2197 { CURSE_ALCHEMY, {
2198 "Alchemy", "Alch",
2199 { SK_POISON_MAGIC, SK_TRANSMUTATIONS },
2200 } },
2201 { CURSE_COMPANIONS, {
2202 "Companions", "Comp",
2203 { SK_SUMMONINGS, SK_NECROMANCY },
2204 } },
2205 { CURSE_BEGUILING, {
2206 "Beguiling", "Bglg",
2207 { SK_CONJURATIONS, SK_HEXES, SK_TRANSLOCATIONS },
2208 } },
2209 { CURSE_SELF, {
2210 "Introspection", "Self",
2211 { SK_FIGHTING, SK_SPELLCASTING },
2212 } },
2213 { CURSE_FORTITUDE, {
2214 "Fortitude", "Fort",
2215 { SK_ARMOUR, SK_SHIELDS },
2216 } },
2217 { CURSE_CUNNING, {
2218 "Cunning", "Cun",
2219 { SK_DODGING, SK_STEALTH },
2220 } },
2221 { CURSE_EVOCATIONS, {
2222 "Evocations", "Evo",
2223 { SK_EVOCATIONS },
2224 } },
2225 };
2226
_can_use_curse(const curse_data & c)2227 static bool _can_use_curse(const curse_data& c)
2228 {
2229 for (skill_type sk : c.boosted)
2230 if (you.can_currently_train[sk])
2231 return true;
2232
2233 return false;
2234 }
2235
curse_name(const CrawlStoreValue & c)2236 string curse_name(const CrawlStoreValue& c)
2237 {
2238 return _ashenzari_curses[static_cast<curse_type>(c.get_int())].name;
2239 }
2240
curse_abbr(const CrawlStoreValue & curse)2241 string curse_abbr(const CrawlStoreValue& curse)
2242 {
2243 const curse_data& c =
2244 _ashenzari_curses[static_cast<curse_type>(curse.get_int())];
2245
2246 return c.abbr;
2247 }
2248
curse_skills(const CrawlStoreValue & curse)2249 const vector<skill_type>& curse_skills(const CrawlStoreValue& curse)
2250 {
2251 const curse_data& c =
2252 _ashenzari_curses[static_cast<curse_type>(curse.get_int())];
2253
2254 return c.boosted;
2255 }
2256
ashenzari_curse_knowledge_list()2257 static string ashenzari_curse_knowledge_list()
2258 {
2259 if (!you_worship(GOD_ASHENZARI))
2260 return "";
2261
2262 const CrawlVector& curses = you.props[CURSE_KNOWLEDGE_KEY].get_vector();
2263
2264 return lowercase_string(comma_separated_fn(curses.begin(), curses.end(),
2265 curse_name));
2266 }
2267
desc_curse_skills(const CrawlStoreValue & curse)2268 string desc_curse_skills(const CrawlStoreValue& curse)
2269 {
2270 const curse_data& c =
2271 _ashenzari_curses[static_cast<curse_type>(curse.get_int())];
2272
2273 vector<skill_type> trainable;
2274
2275 for (skill_type sk : c.boosted)
2276 if (you.can_currently_train[sk])
2277 trainable.push_back(sk);
2278
2279 return c.name + ": "
2280 + comma_separated_fn(trainable.begin(), trainable.end(), skill_name);
2281 }
2282
2283 /**
2284 * Choose skills to boost accompanying the current curse.
2285 */
_choose_curse_knowledge()2286 static void _choose_curse_knowledge()
2287 {
2288 // This loop choses two available skills without replacement,
2289 // it is a two element version of a reservoir sampling algorithm.
2290 //
2291 // If Ashenzari curses need some fancier weighting this is the
2292 // place to do that weighting.
2293 curse_type first_choice = NUM_CURSES;
2294 curse_type second_choice = NUM_CURSES;
2295 int valid_curses = 0;
2296 for (auto const& curse : _ashenzari_curses)
2297 {
2298 if (_can_use_curse(curse.second))
2299 {
2300 ++valid_curses;
2301 if (valid_curses == 1)
2302 first_choice = curse.first;
2303 else if (valid_curses == 2)
2304 {
2305 second_choice = curse.first;
2306 if (coinflip())
2307 swap(first_choice, second_choice);
2308 }
2309 else if (one_chance_in(valid_curses))
2310 first_choice = curse.first;
2311 else if (one_chance_in(valid_curses - 1))
2312 second_choice = curse.first;
2313 }
2314 }
2315
2316 you.props.erase(CURSE_KNOWLEDGE_KEY);
2317 CrawlVector &curses = you.props[CURSE_KNOWLEDGE_KEY].get_vector();
2318
2319 if (first_choice != NUM_CURSES)
2320 curses.push_back(first_choice);
2321 if (second_choice != NUM_CURSES)
2322 curses.push_back(second_choice);
2323
2324 // It's not an error for this to be empty, curses are still useful for
2325 // piety alone
2326 }
2327
2328 /**
2329 * Offer a new curse to the player, letting them know their new curse is
2330 * available.
2331 */
ashenzari_offer_new_curse()2332 void ashenzari_offer_new_curse()
2333 {
2334 // No curse at full piety, since shattering resets the curse timer anyway
2335 if (piety_rank() > 5)
2336 return;
2337
2338 _choose_curse_knowledge();
2339
2340 you.props[AVAILABLE_CURSE_KEY] = true;
2341 you.props[ASHENZARI_CURSE_PROGRESS_KEY] = 0;
2342 const string curse_names = ashenzari_curse_knowledge_list();
2343 const string offer_string = curse_names.empty() ? "" :
2344 (" of " + curse_names);
2345
2346 mprf(MSGCH_GOD, "Ashenzari invites you to partake of a vision"
2347 " and a curse%s.", offer_string.c_str());
2348 }
2349
_do_curse_item(item_def & item)2350 static void _do_curse_item(item_def &item)
2351 {
2352 mprf("Your %s glows black for a moment.", item.name(DESC_PLAIN).c_str());
2353 item.flags |= ISFLAG_CURSED;
2354
2355 if (you.equip[EQ_WEAPON] == item.link)
2356 {
2357 // Redraw the weapon.
2358 you.wield_change = true;
2359 }
2360
2361 for (auto & curse : you.props[CURSE_KNOWLEDGE_KEY].get_vector())
2362 {
2363 add_inscription(item,
2364 curse_abbr(static_cast<curse_type>(curse.get_int())));
2365 item.props[CURSE_KNOWLEDGE_KEY].get_vector().push_back(curse);
2366 }
2367 }
2368
2369 /**
2370 * Give a prompt to curse an item.
2371 *
2372 * This is the core logic behind Ash's Curse Item ability.
2373 * Player can abort without penalty.
2374 * Player can curse only worn items.
2375 *
2376 * @return Whether the player cursed anything.
2377 */
ashenzari_curse_item()2378 bool ashenzari_curse_item()
2379 {
2380 const string prompt_msg = make_stringf("Curse which item? (Esc to abort)");
2381 const int item_slot = prompt_invent_item(prompt_msg.c_str(),
2382 menu_type::invlist,
2383 OSEL_CURSABLE, OPER_ANY,
2384 invprompt_flag::escape_only);
2385 if (prompt_failed(item_slot))
2386 return false;
2387
2388 item_def& item(you.inv[item_slot]);
2389
2390 if (!item_is_cursable(item))
2391 {
2392 mpr("You can't curse that!");
2393 return false;
2394 }
2395
2396 _do_curse_item(item);
2397 make_ashenzari_randart(item);
2398 ash_check_bondage();
2399
2400 you.props.erase(CURSE_KNOWLEDGE_KEY);
2401 you.props.erase(AVAILABLE_CURSE_KEY);
2402
2403 return true;
2404 }
2405
2406 /**
2407 * Give a prompt to uncurse (and destroy an item).
2408 *
2409 * Player can abort without penalty.
2410 *
2411 * @return Whether the player uncursed anything.
2412 */
ashenzari_uncurse_item()2413 bool ashenzari_uncurse_item()
2414 {
2415 int item_slot = prompt_invent_item("Uncurse and destroy which item?",
2416 menu_type::invlist,
2417 OSEL_CURSED_WORN, OPER_ANY,
2418 invprompt_flag::escape_only);
2419 if (prompt_failed(item_slot))
2420 return false;
2421
2422 item_def& item(you.inv[item_slot]);
2423
2424 if (is_unrandom_artefact(item, UNRAND_FINGER_AMULET)
2425 && you.equip[EQ_RING_AMULET] != -1)
2426 {
2427 mprf(MSGCH_PROMPT, "You must shatter the curse binding the ring to "
2428 "the amulet's finger first!");
2429 return false;
2430 }
2431
2432 if (item_is_melded(item))
2433 {
2434 mprf(MSGCH_PROMPT, "You cannot shatter the curse on %s while it is "
2435 "melded with your body!",
2436 item.name(DESC_THE).c_str());
2437 return false;
2438 }
2439
2440 if (!yesno(make_stringf("Really remove and destroy %s?%s",
2441 item.name(DESC_THE).c_str(),
2442 you.props.exists(AVAILABLE_CURSE_KEY) ?
2443 " Ashenzari will withdraw the offered vision "
2444 "and curse!"
2445 : "").c_str(),
2446 false, 'n'))
2447 {
2448 canned_msg(MSG_OK);
2449 return false;
2450 }
2451
2452 mprf("You shatter the curse binding %s!", item.name(DESC_THE).c_str());
2453 unequip_item(item_equip_slot(you.inv[item_slot]));
2454 ash_check_bondage();
2455
2456 you.props[ASHENZARI_CURSE_PROGRESS_KEY] = 0;
2457 if (you.props.exists(AVAILABLE_CURSE_KEY))
2458 {
2459 simple_god_message(" withdraws the vision and curse.");
2460 you.props.erase(AVAILABLE_CURSE_KEY);
2461 }
2462
2463 return true;
2464 }
2465
can_convert_to_beogh()2466 bool can_convert_to_beogh()
2467 {
2468 if (silenced(you.pos()))
2469 return false;
2470
2471 for (monster* m : monster_near_iterator(you.pos(), LOS_NO_TRANS))
2472 if (mons_allows_beogh_now(*m))
2473 return true;
2474
2475 return false;
2476 }
2477
spare_beogh_convert()2478 void spare_beogh_convert()
2479 {
2480 if (you.one_time_ability_used[GOD_BEOGH])
2481 {
2482 // You still get to convert, but orcs will remain hostile.
2483 mprf(MSGCH_TALK, "%s", getSpeakString("orc_priest_apostate").c_str());
2484 return;
2485 }
2486
2487 set<mid_t> witnesses;
2488
2489 you.religion = GOD_NO_GOD;
2490 for (radius_iterator ri(you.pos(), LOS_DEFAULT); ri; ++ri)
2491 {
2492 const monster *mon = monster_at(*ri);
2493 // An invis player converting is ok, for simplicity.
2494 if (!mon || !cell_see_cell(you.pos(), *ri, LOS_DEFAULT))
2495 continue;
2496 if (mon->attitude != ATT_HOSTILE)
2497 continue;
2498 if (mons_genus(mon->type) != MONS_ORC)
2499 continue;
2500 witnesses.insert(mon->mid);
2501
2502 // Anyone who has seen the priest perform the ceremony will spare you
2503 // as well.
2504 if (mons_allows_beogh(*mon))
2505 {
2506 for (radius_iterator pi(you.pos(), LOS_DEFAULT); pi; ++pi)
2507 {
2508 const monster *orc = monster_at(*pi);
2509 if (!orc || !cell_see_cell(*ri, *pi, LOS_DEFAULT))
2510 continue;
2511 if (mons_genus(orc->type) != MONS_ORC)
2512 continue;
2513 if (mon->attitude != ATT_HOSTILE)
2514 continue;
2515 witnesses.insert(orc->mid);
2516 }
2517 }
2518 }
2519
2520 int witc = 0;
2521 for (auto wit : witnesses)
2522 {
2523 monster *orc = monster_by_mid(wit);
2524 if (!orc || !orc->alive())
2525 continue;
2526
2527 ++witc;
2528 orc->del_ench(ENCH_CHARM);
2529 mons_pacify(*orc, ATT_GOOD_NEUTRAL, true);
2530 }
2531
2532 you.religion = GOD_BEOGH;
2533 you.one_time_ability_used.set(GOD_BEOGH);
2534
2535 if (witc == 1)
2536 mpr("The priest welcomes you and lets you live.");
2537 else
2538 {
2539 mpr("With a roar of approval, the orcs welcome you as one of their own,"
2540 " and spare your life.");
2541 }
2542 }
2543
dithmenos_shadow_step()2544 bool dithmenos_shadow_step()
2545 {
2546 // You can shadow-step anywhere within your umbra.
2547 ASSERT(you.umbra_radius() > -1);
2548 const int range = you.umbra_radius();
2549
2550 targeter_shadow_step tgt(&you, you.umbra_radius());
2551 direction_chooser_args args;
2552 args.hitfunc = &tgt;
2553 args.restricts = DIR_SHADOW_STEP;
2554 args.mode = TARG_HOSTILE;
2555 args.range = range;
2556 args.just_looking = false;
2557 args.needs_path = false;
2558 args.top_prompt = "Aiming: <white>Shadow Step</white>";
2559 dist sdirect;
2560 direction(sdirect, args);
2561 if (!sdirect.isValid || tgt.landing_site.origin())
2562 return false;
2563
2564 // Check for hazards.
2565 bool zot_trap_prompted = false,
2566 trap_prompted = false,
2567 exclusion_prompted = false,
2568 cloud_prompted = false,
2569 terrain_prompted = false;
2570
2571 for (auto site : tgt.additional_sites)
2572 {
2573 if (!cloud_prompted
2574 && !check_moveto_cloud(site, "shadow step", &cloud_prompted))
2575 {
2576 return false;
2577 }
2578
2579 if (!zot_trap_prompted)
2580 {
2581 trap_def* trap = trap_at(site);
2582 if (trap && trap->type == TRAP_ZOT)
2583 {
2584 if (!check_moveto_trap(site, "shadow step",
2585 &trap_prompted))
2586 {
2587 you.turn_is_over = false;
2588 return false;
2589 }
2590 zot_trap_prompted = true;
2591 }
2592 else if (!trap_prompted
2593 && !check_moveto_trap(site, "shadow step",
2594 &trap_prompted))
2595 {
2596 you.turn_is_over = false;
2597 return false;
2598 }
2599 }
2600
2601 if (!exclusion_prompted
2602 && !check_moveto_exclusion(site, "shadow step",
2603 &exclusion_prompted))
2604 {
2605 return false;
2606 }
2607
2608 if (!terrain_prompted
2609 && !check_moveto_terrain(site, "shadow step", "",
2610 &terrain_prompted))
2611 {
2612 return false;
2613 }
2614 }
2615
2616 const coord_def old_pos = you.pos();
2617 // XXX: This only ever fails if something's on the landing site;
2618 // perhaps this should be handled more gracefully.
2619 if (!you.move_to_pos(tgt.landing_site))
2620 {
2621 mpr("Something blocks your shadow step.");
2622 return true;
2623 }
2624
2625 const actor *victim = actor_at(sdirect.target);
2626 mprf("You step into %s shadow.",
2627 apostrophise(victim->name(DESC_THE)).c_str());
2628 // Using 'stepped = true' here because it's Shadow *Step*.
2629 // This helps to evade splash upon landing on water.
2630 moveto_location_effects(env.grid(old_pos), true, old_pos);
2631
2632 return true;
2633 }
2634
2635 static potion_type _gozag_potion_list[][4] =
2636 {
2637 { POT_HEAL_WOUNDS, NUM_POTIONS, NUM_POTIONS, NUM_POTIONS },
2638 { POT_HEAL_WOUNDS, POT_CURING, NUM_POTIONS, NUM_POTIONS },
2639 { POT_HEAL_WOUNDS, POT_MAGIC, NUM_POTIONS, NUM_POTIONS },
2640 { POT_CURING, POT_MAGIC, NUM_POTIONS, NUM_POTIONS },
2641 { POT_HEAL_WOUNDS, POT_BERSERK_RAGE, NUM_POTIONS, NUM_POTIONS },
2642 { POT_HASTE, NUM_POTIONS, NUM_POTIONS, NUM_POTIONS },
2643 { POT_HASTE, POT_HEAL_WOUNDS, NUM_POTIONS, NUM_POTIONS },
2644 { POT_HASTE, POT_BRILLIANCE, NUM_POTIONS, NUM_POTIONS },
2645 { POT_HASTE, POT_RESISTANCE, NUM_POTIONS, NUM_POTIONS },
2646 { POT_BRILLIANCE, POT_MAGIC, NUM_POTIONS, NUM_POTIONS },
2647 { POT_INVISIBILITY, POT_MIGHT, NUM_POTIONS, NUM_POTIONS },
2648 { POT_HEAL_WOUNDS, POT_CURING, POT_MAGIC, NUM_POTIONS },
2649 { POT_HEAL_WOUNDS, POT_CURING, POT_BERSERK_RAGE, NUM_POTIONS },
2650 { POT_MIGHT, POT_BRILLIANCE, NUM_POTIONS, NUM_POTIONS },
2651 { POT_RESISTANCE, NUM_POTIONS, NUM_POTIONS, NUM_POTIONS },
2652 { POT_RESISTANCE, POT_MIGHT, NUM_POTIONS, NUM_POTIONS },
2653 { POT_RESISTANCE, POT_MIGHT, POT_HASTE, NUM_POTIONS },
2654 { POT_RESISTANCE, POT_INVISIBILITY, NUM_POTIONS, NUM_POTIONS },
2655 { POT_LIGNIFY, POT_MIGHT, POT_RESISTANCE, NUM_POTIONS },
2656 };
2657
_gozag_add_potions(CrawlVector & vec,potion_type * which)2658 static void _gozag_add_potions(CrawlVector &vec, potion_type *which)
2659 {
2660 for (; *which != NUM_POTIONS; which++)
2661 {
2662 // Check cases where a potion is permanently useless to the player
2663 // species - temporarily useless potions can still be offered.
2664 if (*which == POT_BERSERK_RAGE
2665 && !you.can_go_berserk(true, false, true, nullptr, false))
2666 {
2667 continue;
2668 }
2669 if (*which == POT_HASTE && you.stasis())
2670 continue;
2671 if (*which == POT_MAGIC && you.has_mutation(MUT_HP_CASTING))
2672 continue;
2673 if (*which == POT_LIGNIFY && you.undead_state(false) == US_UNDEAD)
2674 continue;
2675
2676 // Don't add potions which are already in the list
2677 bool dup = false;
2678 for (unsigned int i = 0; i < vec.size(); i++)
2679 if (vec[i].get_int() == *which)
2680 {
2681 dup = true;
2682 break;
2683 }
2684 if (!dup)
2685 vec.push_back(*which);
2686 }
2687 }
2688
2689 #define ADD_POTIONS(a,b) _gozag_add_potions(a, b[random2(ARRAYSZ(b))])
2690
gozag_setup_potion_petition(bool quiet)2691 bool gozag_setup_potion_petition(bool quiet)
2692 {
2693 if (you.gold < GOZAG_POTION_PETITION_AMOUNT)
2694 {
2695 if (!quiet)
2696 {
2697 mprf("You need at least %d gold to purchase potions right now!",
2698 GOZAG_POTION_PETITION_AMOUNT);
2699 }
2700 return false;
2701 }
2702
2703 return true;
2704 }
2705
gozag_potion_petition()2706 bool gozag_potion_petition()
2707 {
2708 CrawlVector *pots[GOZAG_MAX_POTIONS];
2709 int prices[GOZAG_MAX_POTIONS];
2710
2711 item_def dummy;
2712 dummy.base_type = OBJ_POTIONS;
2713 dummy.quantity = 1;
2714
2715 if (!you.props.exists(make_stringf(GOZAG_POTIONS_KEY, 0)))
2716 {
2717 bool affordable_potions = false;
2718 while (!affordable_potions)
2719 {
2720 for (int i = 0; i < GOZAG_MAX_POTIONS; i++)
2721 {
2722 prices[i] = 0;
2723 const int multiplier = random_range(20, 30); // arbitrary
2724
2725 string key = make_stringf(GOZAG_POTIONS_KEY, i);
2726 you.props.erase(key);
2727 you.props[key].new_vector(SV_INT, SFLAG_CONST_TYPE);
2728 pots[i] = &you.props[key].get_vector();
2729
2730 do
2731 {
2732 ADD_POTIONS(*pots[i], _gozag_potion_list);
2733 if (coinflip())
2734 ADD_POTIONS(*pots[i], _gozag_potion_list);
2735 }
2736 while (pots[i]->empty());
2737
2738 for (const CrawlStoreValue& store : *pots[i])
2739 {
2740 dummy.sub_type = store.get_int();
2741 prices[i] += item_value(dummy, true);
2742 dprf("%d", item_value(dummy, true));
2743 }
2744 dprf("pre: %d", prices[i]);
2745 prices[i] *= multiplier;
2746 dprf("mid: %d", prices[i]);
2747 prices[i] /= 10;
2748 dprf("post: %d", prices[i]);
2749 key = make_stringf(GOZAG_PRICE_KEY, i);
2750 you.props[key].get_int() = prices[i];
2751
2752 if (prices[i] <= GOZAG_POTION_PETITION_AMOUNT)
2753 affordable_potions = true;
2754 }
2755 }
2756 }
2757 else
2758 {
2759 for (int i = 0; i < GOZAG_MAX_POTIONS; i++)
2760 {
2761 string key = make_stringf(GOZAG_POTIONS_KEY, i);
2762 pots[i] = &you.props[key].get_vector();
2763 key = make_stringf(GOZAG_PRICE_KEY, i);
2764 prices[i] = you.props[key].get_int();
2765 }
2766 }
2767
2768 int keyin = 0;
2769
2770 while (true)
2771 {
2772 if (crawl_state.seen_hups)
2773 return false;
2774
2775 clear_messages();
2776 for (int i = 0; i < GOZAG_MAX_POTIONS; i++)
2777 {
2778 string line = make_stringf(" [%c] - %d gold - ", i + 'a',
2779 prices[i]);
2780 vector<string> pot_names;
2781 for (const CrawlStoreValue& store : *pots[i])
2782 pot_names.emplace_back(potion_type_name(store.get_int()));
2783 line += comma_separated_line(pot_names.begin(), pot_names.end());
2784 mpr_nojoin(MSGCH_PLAIN, line);
2785 }
2786 mprf(MSGCH_PROMPT, "Purchase which effect?");
2787 keyin = toalower(get_ch()) - 'a';
2788 if (keyin < 0 || keyin > GOZAG_MAX_POTIONS - 1)
2789 continue;
2790
2791 if (you.gold < prices[keyin])
2792 {
2793 mpr("You don't have enough gold for that!");
2794 more();
2795 continue;
2796 }
2797
2798 break;
2799 }
2800
2801 ASSERT(you.gold >= prices[keyin]);
2802 you.del_gold(prices[keyin]);
2803 you.attribute[ATTR_GOZAG_GOLD_USED] += prices[keyin];
2804
2805 for (auto pot : *pots[keyin])
2806 potionlike_effect(static_cast<potion_type>(pot.get_int()), 40);
2807
2808 for (int i = 0; i < GOZAG_MAX_POTIONS; i++)
2809 {
2810 string key = make_stringf(GOZAG_POTIONS_KEY, i);
2811 you.props.erase(key);
2812 key = make_stringf(GOZAG_PRICE_KEY, i);
2813 you.props.erase(key);
2814 }
2815
2816 return true;
2817 }
2818
2819 /**
2820 * The price to order a merchant from Gozag. Doesn't depend on the shop's
2821 * type or contents. The maximum possible price is used as the minimum amount
2822 * of gold you need to use the ability.
2823 */
gozag_price_for_shop(bool max)2824 int gozag_price_for_shop(bool max)
2825 {
2826 // This value probably needs tweaking.
2827 const int max_base = 800;
2828 const int base = max ? max_base : random_range(max_base/2, max_base);
2829 const int price = base
2830 * (GOZAG_SHOP_BASE_MULTIPLIER
2831 + GOZAG_SHOP_MOD_MULTIPLIER
2832 * you.attribute[ATTR_GOZAG_SHOPS])
2833 / GOZAG_SHOP_BASE_MULTIPLIER;
2834 return price;
2835 }
2836
gozag_setup_call_merchant(bool quiet)2837 bool gozag_setup_call_merchant(bool quiet)
2838 {
2839 const int gold_min = gozag_price_for_shop(true);
2840 if (you.gold < gold_min)
2841 {
2842 if (!quiet)
2843 {
2844 mprf("You currently need %d gold to open negotiations with a "
2845 "merchant.", gold_min);
2846 }
2847 return false;
2848 }
2849 if (!is_connected_branch(level_id::current().branch))
2850 {
2851 if (!quiet)
2852 {
2853 mprf("No merchants are willing to come to this location.");
2854 return false;
2855 }
2856 }
2857 if (env.grid(you.pos()) != DNGN_FLOOR)
2858 {
2859 if (!quiet)
2860 {
2861 mprf("You need to be standing on open floor to call a merchant.");
2862 return false;
2863 }
2864 }
2865
2866 return true;
2867 }
2868
2869 /**
2870 * Is the given index within the valid range for gozag shop offers?
2871 */
_gozag_valid_shop_index(int index)2872 static bool _gozag_valid_shop_index(int index)
2873 {
2874 return index >= 0 && index < GOZAG_MAX_SHOPS;
2875 }
2876
2877 /**
2878 * What is the type of shop that gozag is offering at the given index?
2879 */
_gozag_shop_type(int index)2880 static shop_type _gozag_shop_type(int index)
2881 {
2882 ASSERT(_gozag_valid_shop_index(index));
2883 const int type =
2884 you.props[make_stringf(GOZAG_SHOP_TYPE_KEY, index)].get_int();
2885 return static_cast<shop_type>(type);
2886 }
2887
2888 /**
2889 * What is the price of calling the shop that gozag is offering at the given
2890 * index?
2891 */
_gozag_shop_price(int index)2892 static int _gozag_shop_price(int index)
2893 {
2894 ASSERT(_gozag_valid_shop_index(index));
2895
2896 return you.props[make_stringf(GOZAG_SHOP_COST_KEY, index)].get_int();
2897 }
2898
2899 /**
2900 * Initialize the set of shops currently offered to the player through Call
2901 * Merchant.
2902 *
2903 * @param index The index of the shop offer to be defined.
2904 * @param valid_shops Vector of acceptable shop types based on the player and
2905 * previous choices for this merchant call.
2906 */
_setup_gozag_shop(int index,vector<shop_type> & valid_shops)2907 static void _setup_gozag_shop(int index, vector<shop_type> &valid_shops)
2908 {
2909 ASSERT(!you.props.exists(make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)));
2910
2911 shop_type type = NUM_SHOPS;
2912 int choice = random2(valid_shops.size());
2913 type = valid_shops[choice];
2914 // Don't choose this shop type again for this merchant call.
2915 valid_shops.erase(valid_shops.begin() + choice);
2916 you.props[make_stringf(GOZAG_SHOP_TYPE_KEY, index)].get_int() = type;
2917
2918 you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)].get_string()
2919 = make_name();
2920
2921 const bool need_suffix = type != SHOP_GENERAL
2922 && type != SHOP_GENERAL_ANTIQUE
2923 && type != SHOP_DISTILLERY;
2924 you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY, index)].get_string()
2925 = need_suffix
2926 ? random_choose("Shoppe", "Boutique",
2927 "Emporium", "Shop")
2928 : "";
2929
2930 you.props[make_stringf(GOZAG_SHOP_COST_KEY, index)].get_int()
2931 = gozag_price_for_shop();
2932 }
2933
2934 /**
2935 * Build a string describing the name, price & type of the shop being offered
2936 * at the given index.
2937 *
2938 * @param index The index of the shop to be described.
2939 * @return The shop description.
2940 * E.g. "[a] 973 gold - Cranius' Magic Scroll Boutique"
2941 */
_describe_gozag_shop(int index)2942 static string _describe_gozag_shop(int index)
2943 {
2944 const int cost = _gozag_shop_price(index);
2945
2946 const char offer_letter = 'a' + index;
2947 const string shop_name =
2948 apostrophise(you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY,
2949 index)].get_string());
2950 const shop_type type = _gozag_shop_type(index);
2951 const string type_name = shop_type_name(type);
2952 const string suffix =
2953 you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY, index)].get_string();
2954
2955 return make_stringf(" [%c] %5d gold - %s %s %s",
2956 offer_letter,
2957 cost,
2958 shop_name.c_str(),
2959 type_name.c_str(),
2960 suffix.c_str());
2961 }
2962
2963 /**
2964 * Let the player choose from the currently available merchants to call.
2965 *
2966 * @param The index of the chosen shop; -1 if none was chosen (due to e.g.
2967 * a seen_hup).
2968 */
_gozag_choose_shop()2969 static int _gozag_choose_shop()
2970 {
2971 if (crawl_state.seen_hups)
2972 return -1;
2973
2974 clear_messages();
2975 for (int i = 0; i < GOZAG_MAX_SHOPS; i++)
2976 mpr_nojoin(MSGCH_PLAIN, _describe_gozag_shop(i).c_str());
2977
2978 mprf(MSGCH_PROMPT, "Fund which merchant?");
2979 const int shop_index = toalower(get_ch()) - 'a';
2980 if (shop_index < 0 || shop_index > GOZAG_MAX_SHOPS - 1)
2981 return _gozag_choose_shop(); // tail recurse
2982
2983 if (you.gold < _gozag_shop_price(shop_index))
2984 {
2985 mpr("You don't have enough gold to fund that merchant!");
2986 more();
2987 return _gozag_choose_shop(); // tail recurse
2988 }
2989
2990 return shop_index;
2991 }
2992
2993 /**
2994 * Make a vault spec for the gozag shop offer at the given index.
2995 */
_gozag_shop_spec(int index)2996 static string _gozag_shop_spec(int index)
2997 {
2998 const shop_type type = _gozag_shop_type(index);
2999 const string name =
3000 you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)];
3001
3002 string suffix = replace_all(
3003 you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY,
3004 index)]
3005 .get_string(), " ", "_");
3006 if (!suffix.empty())
3007 suffix = " suffix:" + suffix;
3008
3009 return make_stringf("%s shop name:%s%s gozag",
3010 shoptype_to_str(type),
3011 replace_all(name, " ", "_").c_str(),
3012 suffix.c_str());
3013
3014 }
3015
3016 /**
3017 * Attempt to call the shop specified at the given index at your position.
3018 *
3019 * @param index The index of the shop (in gozag props)
3020 */
_gozag_place_shop(int index)3021 static void _gozag_place_shop(int index)
3022 {
3023 ASSERT(env.grid(you.pos()) == DNGN_FLOOR);
3024 keyed_mapspec kmspec;
3025 kmspec.set_feat(_gozag_shop_spec(index), false);
3026
3027 feature_spec feat = kmspec.get_feat();
3028 if (!feat.shop)
3029 die("Invalid shop spec?");
3030 place_spec_shop(you.pos(), *feat.shop, you.experience_level);
3031
3032 link_items();
3033 env.markers.add(new map_feature_marker(you.pos(), DNGN_ABANDONED_SHOP));
3034 env.markers.clear_need_activate();
3035
3036 shop_struct *shop = shop_at(you.pos());
3037 ASSERT(shop);
3038
3039 const gender_type gender = random_choose(GENDER_FEMALE, GENDER_MALE,
3040 GENDER_NEUTRAL);
3041
3042 mprf(MSGCH_GOD, "%s invites you to visit %s %s%s%s.",
3043 shop->shop_name.c_str(),
3044 decline_pronoun(gender, PRONOUN_POSSESSIVE),
3045 shop_type_name(shop->type).c_str(),
3046 !shop->shop_suffix_name.empty() ? " " : "",
3047 shop->shop_suffix_name.c_str());
3048 }
3049
gozag_call_merchant()3050 bool gozag_call_merchant()
3051 {
3052 // Only offer useful shops.
3053 vector<shop_type> valid_shops;
3054 for (int i = 0; i < NUM_SHOPS; i++)
3055 {
3056 shop_type type = static_cast<shop_type>(i);
3057 #if TAG_MAJOR_VERSION == 34
3058 if (type == SHOP_FOOD || type == SHOP_EVOKABLES)
3059 continue;
3060 #endif
3061 if (type == SHOP_DISTILLERY && you.has_mutation(MUT_NO_DRINK))
3062 continue;
3063
3064 if (you.has_mutation(MUT_NO_ARMOUR) &&
3065 (type == SHOP_ARMOUR
3066 || type == SHOP_ARMOUR_ANTIQUE))
3067 {
3068 continue;
3069 }
3070 if ((type == SHOP_WEAPON || type == SHOP_WEAPON_ANTIQUE)
3071 && you.has_mutation(MUT_NO_GRASPING))
3072 {
3073 continue;
3074 }
3075 valid_shops.push_back(type);
3076 }
3077
3078 // Set up some dummy shops.
3079 // Generate some shop inventory and store it as a store spec.
3080 // We still set up the shops in advance in case of hups.
3081 for (int i = 0; i < GOZAG_MAX_SHOPS; i++)
3082 if (!you.props.exists(make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, i)))
3083 _setup_gozag_shop(i, valid_shops);
3084
3085 const int shop_index = _gozag_choose_shop();
3086 if (shop_index == -1) // hup!
3087 return false;
3088
3089 ASSERT(shop_index >= 0 && shop_index < GOZAG_MAX_SHOPS);
3090
3091 const int cost = _gozag_shop_price(shop_index);
3092 ASSERT(you.gold >= cost);
3093
3094 you.del_gold(cost);
3095 you.attribute[ATTR_GOZAG_GOLD_USED] += cost;
3096
3097 _gozag_place_shop(shop_index);
3098
3099 you.attribute[ATTR_GOZAG_SHOPS]++;
3100 you.attribute[ATTR_GOZAG_SHOPS_CURRENT]++;
3101
3102 for (int j = 0; j < GOZAG_MAX_SHOPS; j++)
3103 {
3104 you.props.erase(make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, j));
3105 you.props.erase(make_stringf(GOZAG_SHOP_TYPE_KEY, j));
3106 you.props.erase(make_stringf(GOZAG_SHOP_SUFFIX_KEY, j));
3107 you.props.erase(make_stringf(GOZAG_SHOP_COST_KEY, j));
3108 }
3109
3110 return true;
3111 }
3112
gozag_fixup_branch(branch_type branch)3113 branch_type gozag_fixup_branch(branch_type branch)
3114 {
3115 if (is_hell_subbranch(branch))
3116 return BRANCH_VESTIBULE;
3117
3118 return branch;
3119 }
3120
3121 static const map<branch_type, int> branch_bribability_factor =
3122 {
3123 { BRANCH_DUNGEON, 2 },
3124 { BRANCH_ORC, 2 },
3125 { BRANCH_ELF, 3 },
3126 { BRANCH_SNAKE, 3 },
3127 { BRANCH_SHOALS, 3 },
3128 { BRANCH_CRYPT, 3 },
3129 { BRANCH_TOMB, 3 },
3130 { BRANCH_DEPTHS, 4 },
3131 { BRANCH_VAULTS, 4 },
3132 { BRANCH_ZOT, 4 },
3133 { BRANCH_VESTIBULE, 4 },
3134 { BRANCH_PANDEMONIUM, 4 },
3135 };
3136
3137 // An x-in-8 chance of a monster of the given type being bribed.
3138 // Tougher monsters have a stronger chance of being bribed.
gozag_type_bribable(monster_type type)3139 int gozag_type_bribable(monster_type type)
3140 {
3141 if (!you_worship(GOD_GOZAG))
3142 return 0;
3143
3144 if (mons_class_intel(type) < I_HUMAN)
3145 return 0;
3146
3147 // Unique rune guardians can't be bribed, sorry!
3148 if (mons_is_unique(type)
3149 && (mons_genus(type) == MONS_HELL_LORD
3150 || mons_genus(type) == MONS_PANDEMONIUM_LORD
3151 || type == MONS_ANTAEUS))
3152 {
3153 return 0;
3154 }
3155
3156 const int *factor = map_find(branch_bribability_factor,
3157 gozag_fixup_branch(you.where_are_you));
3158 if (!factor)
3159 return 0;
3160
3161 const int chance = max(mons_class_hit_dice(type) / *factor, 1);
3162 dprf("%s, bribe chance: %d", mons_type_name(type, DESC_PLAIN).c_str(),
3163 chance);
3164
3165 return chance;
3166 }
3167
gozag_branch_bribable(branch_type branch)3168 bool gozag_branch_bribable(branch_type branch)
3169 {
3170 return map_find(branch_bribability_factor, gozag_fixup_branch(branch));
3171 }
3172
gozag_deduct_bribe(branch_type br,int amount)3173 void gozag_deduct_bribe(branch_type br, int amount)
3174 {
3175 if (branch_bribe[br] <= 0)
3176 return;
3177
3178 branch_bribe[br] = max(0, branch_bribe[br] - amount);
3179 if (branch_bribe[br] <= 0)
3180 {
3181 mprf(MSGCH_DURATION, "Your bribe of %s has been exhausted.",
3182 branches[br].longname);
3183 add_daction(DACT_BRIBE_TIMEOUT);
3184 }
3185 }
3186
gozag_check_bribe_branch(bool quiet)3187 bool gozag_check_bribe_branch(bool quiet)
3188 {
3189 const int bribe_amount = GOZAG_BRIBE_AMOUNT;
3190 if (you.gold < bribe_amount)
3191 {
3192 if (!quiet)
3193 mprf("You need at least %d gold to offer a bribe.", bribe_amount);
3194 return false;
3195 }
3196 branch_type branch = you.where_are_you;
3197 branch_type branch2 = NUM_BRANCHES;
3198 if (feat_is_branch_entrance(env.grid(you.pos())))
3199 {
3200 for (branch_iterator it; it; ++it)
3201 if (it->entry_stairs == env.grid(you.pos())
3202 && gozag_branch_bribable(it->id))
3203 {
3204 branch2 = it->id;
3205 break;
3206 }
3207 }
3208 const string who = make_stringf("the denizens of %s",
3209 branches[branch].longname);
3210 const string who2 = branch2 != NUM_BRANCHES
3211 ? make_stringf("the denizens of %s",
3212 branches[branch2].longname)
3213 : "";
3214 if (!gozag_branch_bribable(branch)
3215 && (branch2 == NUM_BRANCHES
3216 || !gozag_branch_bribable(branch2)))
3217 {
3218 if (!quiet)
3219 {
3220 if (branch2 != NUM_BRANCHES)
3221 mprf("You can't bribe %s or %s.", who.c_str(), who2.c_str());
3222 else
3223 mprf("You can't bribe %s.", who.c_str());
3224 }
3225 return false;
3226 }
3227 return true;
3228 }
3229
gozag_bribe_branch()3230 bool gozag_bribe_branch()
3231 {
3232 const int bribe_amount = GOZAG_BRIBE_AMOUNT;
3233 ASSERT(you.gold >= bribe_amount);
3234 bool prompted = false;
3235 branch_type branch = gozag_fixup_branch(you.where_are_you);
3236 if (feat_is_branch_entrance(env.grid(you.pos())))
3237 {
3238 for (branch_iterator it; it; ++it)
3239 if (it->entry_stairs == env.grid(you.pos())
3240 && gozag_branch_bribable(it->id))
3241 {
3242 branch_type stair_branch = gozag_fixup_branch(it->id);
3243 string prompt =
3244 make_stringf("Do you want to bribe the denizens of %s?",
3245 stair_branch == BRANCH_VESTIBULE ? "the Hells"
3246 : branches[stair_branch].longname);
3247 if (yesno(prompt.c_str(), true, 'n'))
3248 {
3249 branch = stair_branch;
3250 prompted = true;
3251 }
3252 // If we're in the Vestibule, standing on a portal to a Hell
3253 // sub-branch, don't prompt twice to bribe the Hells.
3254 else if (branch == stair_branch)
3255 {
3256 canned_msg(MSG_OK);
3257 return false;
3258 }
3259 break;
3260 }
3261 }
3262 string who = make_stringf("the denizens of %s",
3263 branches[branch].longname);
3264 if (!gozag_branch_bribable(branch))
3265 {
3266 mprf("You can't bribe %s.", who.c_str());
3267 return false;
3268 }
3269
3270 string prompt =
3271 make_stringf("Do you want to bribe the denizens of %s?",
3272 branch == BRANCH_VESTIBULE ? "the Hells" :
3273 branches[branch].longname);
3274
3275 if (prompted || yesno(prompt.c_str(), true, 'n'))
3276 {
3277 you.del_gold(bribe_amount);
3278 you.attribute[ATTR_GOZAG_GOLD_USED] += bribe_amount;
3279 branch_bribe[branch] += bribe_amount;
3280 string msg = make_stringf(" spreads your bribe to %s!",
3281 branch == BRANCH_VESTIBULE ? "the Hells" :
3282 branches[branch].longname);
3283 simple_god_message(msg.c_str());
3284 add_daction(DACT_SET_BRIBES);
3285 return true;
3286 }
3287
3288 canned_msg(MSG_OK);
3289 return false;
3290 }
3291
_upheaval_radius(int pow)3292 static int _upheaval_radius(int pow)
3293 {
3294 return pow >= 100 ? 2 : 1;
3295 }
3296
qazlal_upheaval(coord_def target,bool quiet,bool fail,dist * player_target)3297 spret qazlal_upheaval(coord_def target, bool quiet, bool fail, dist *player_target)
3298 {
3299 int pow = you.skill(SK_INVOCATIONS, 6);
3300 const int max_radius = _upheaval_radius(pow);
3301
3302 bolt beam;
3303 beam.name = "****";
3304 beam.source_id = MID_PLAYER;
3305 beam.source_name = "you";
3306 beam.thrower = KILL_YOU;
3307 beam.range = LOS_RADIUS;
3308 beam.damage = calc_dice(3, 27 + div_rand_round(2 * pow, 5));
3309 beam.hit = AUTOMATIC_HIT;
3310 beam.glyph = dchar_glyph(DCHAR_EXPLOSION);
3311 beam.loudness = 10;
3312 #ifdef USE_TILE
3313 beam.tile_beam = -1;
3314 #endif
3315
3316 if (target.origin())
3317 {
3318 dist target_local;
3319 if (!player_target)
3320 player_target = &target_local;
3321
3322 targeter_smite tgt(&you, LOS_RADIUS, 0, max_radius);
3323 direction_chooser_args args;
3324 args.restricts = DIR_TARGET;
3325 args.mode = TARG_HOSTILE;
3326 args.needs_path = false;
3327 args.top_prompt = "Aiming: <white>Upheaval</white>";
3328 args.self = confirm_prompt_type::cancel;
3329 args.hitfunc = &tgt;
3330 if (!spell_direction(*player_target, beam, &args))
3331 return spret::abort;
3332
3333 if (cell_is_solid(beam.target))
3334 {
3335 mprf("There is %s there.",
3336 article_a(feat_type_name(env.grid(beam.target))).c_str());
3337 return spret::abort;
3338 }
3339
3340 bolt tempbeam;
3341 tempbeam.source = beam.target;
3342 tempbeam.target = beam.target;
3343 tempbeam.flavour = BEAM_MISSILE;
3344 tempbeam.ex_size = max_radius;
3345 tempbeam.hit = AUTOMATIC_HIT;
3346 tempbeam.damage = dice_def(AUTOMATIC_HIT, 1);
3347 tempbeam.thrower = KILL_YOU;
3348 tempbeam.is_tracer = true;
3349 tempbeam.explode(false);
3350 if (tempbeam.beam_cancelled)
3351 return spret::abort;
3352 }
3353 else
3354 beam.target = target;
3355
3356 fail_check();
3357
3358 string message = "";
3359
3360 switch (random2(4))
3361 {
3362 case 0:
3363 beam.name = "blast of magma";
3364 beam.flavour = BEAM_LAVA;
3365 beam.colour = RED;
3366 beam.hit_verb = "engulfs";
3367 message = "Magma suddenly erupts from the ground!";
3368 break;
3369 case 1:
3370 beam.name = "blast of ice";
3371 beam.flavour = BEAM_ICE;
3372 beam.colour = WHITE;
3373 message = "A blizzard blasts the area with ice!";
3374 break;
3375 case 2:
3376 beam.name = "cutting wind";
3377 beam.flavour = BEAM_AIR;
3378 beam.colour = LIGHTGRAY;
3379 message = "A storm cloud blasts the area with cutting wind!";
3380 break;
3381 case 3:
3382 beam.name = "blast of rubble";
3383 beam.flavour = BEAM_FRAG;
3384 beam.colour = BROWN;
3385 message = "The ground shakes violently, spewing rubble!";
3386 break;
3387 default:
3388 break;
3389 }
3390
3391 vector<coord_def> affected;
3392 affected.push_back(beam.target);
3393 for (radius_iterator ri(beam.target, max_radius, C_SQUARE, LOS_SOLID, true);
3394 ri; ++ri)
3395 {
3396 if (!in_bounds(*ri) || cell_is_solid(*ri))
3397 continue;
3398
3399 int chance = pow;
3400
3401 bool adj = adjacent(beam.target, *ri);
3402 if (!adj && max_radius > 1)
3403 chance -= 100;
3404 if (adj && max_radius > 1 || x_chance_in_y(chance, 100))
3405 {
3406 if (beam.flavour == BEAM_FRAG || !cell_is_solid(*ri))
3407 affected.push_back(*ri);
3408 }
3409 }
3410 if (!quiet)
3411 shuffle_array(affected);
3412
3413 // for `quiet` calls (i.e. disaster area), don't delay for individual tiles
3414 // at all -- do the delay per upheaval draw. This will also fully suppress
3415 // the redraw per tile.
3416 beam.draw_delay = quiet ? 0 : 25;
3417 for (coord_def pos : affected)
3418 beam.draw(pos, false);
3419
3420 if (quiet)
3421 {
3422 // When `quiet`, refresh the view after each complete draw pass.
3423 // why this call dance to refresh? I just copied it from bolt::draw
3424 viewwindow(false);
3425 update_screen();
3426 scaled_delay(50); // add some delay per upheaval draw, otherwise it all
3427 // goes by too fast.
3428 }
3429 else
3430 {
3431 scaled_delay(200); // This is here to make it easy for the player to
3432 // see the overall impact of the upheaval
3433 mprf(MSGCH_GOD, "%s", message.c_str());
3434 }
3435
3436 int wall_count = 0;
3437 beam.animate = false; // already drawn
3438
3439 for (coord_def pos : affected)
3440 {
3441 beam.source = pos;
3442 beam.target = pos;
3443 beam.fire();
3444
3445 switch (beam.flavour)
3446 {
3447 case BEAM_LAVA:
3448 if (env.grid(pos) == DNGN_FLOOR && !actor_at(pos) && coinflip())
3449 {
3450 temp_change_terrain(
3451 pos, DNGN_LAVA,
3452 random2(you.skill(SK_INVOCATIONS, BASELINE_DELAY)),
3453 TERRAIN_CHANGE_FLOOD);
3454 }
3455 break;
3456 case BEAM_AIR:
3457 if (!cell_is_solid(pos) && !cloud_at(pos) && coinflip())
3458 {
3459 place_cloud(CLOUD_STORM, pos,
3460 random2(you.skill_rdiv(SK_INVOCATIONS, 1, 4)),
3461 &you);
3462 }
3463 break;
3464 case BEAM_FRAG:
3465 if (((env.grid(pos) == DNGN_ROCK_WALL
3466 || env.grid(pos) == DNGN_CLEAR_ROCK_WALL
3467 || env.grid(pos) == DNGN_SLIMY_WALL)
3468 && x_chance_in_y(pow / 4, 100)
3469 || feat_is_door(env.grid(pos))
3470 || env.grid(pos) == DNGN_GRATE))
3471 {
3472 noisy(30, pos);
3473 destroy_wall(pos);
3474 wall_count++;
3475 }
3476 break;
3477 default:
3478 break;
3479 }
3480 }
3481
3482 if (wall_count && !quiet)
3483 mpr("Ka-crash!");
3484
3485 return spret::success;
3486 }
3487
qazlal_elemental_force(bool fail)3488 spret qazlal_elemental_force(bool fail)
3489 {
3490 static const map<cloud_type, monster_type> elemental_clouds = {
3491 { CLOUD_FIRE, MONS_FIRE_ELEMENTAL },
3492 { CLOUD_FOREST_FIRE, MONS_FIRE_ELEMENTAL },
3493 { CLOUD_COLD, MONS_WATER_ELEMENTAL },
3494 { CLOUD_RAIN, MONS_WATER_ELEMENTAL },
3495 { CLOUD_DUST, MONS_EARTH_ELEMENTAL },
3496 { CLOUD_PETRIFY, MONS_EARTH_ELEMENTAL },
3497 { CLOUD_BLACK_SMOKE, MONS_AIR_ELEMENTAL },
3498 { CLOUD_GREY_SMOKE, MONS_AIR_ELEMENTAL },
3499 { CLOUD_BLUE_SMOKE, MONS_AIR_ELEMENTAL },
3500 { CLOUD_PURPLE_SMOKE, MONS_AIR_ELEMENTAL },
3501 { CLOUD_STORM, MONS_AIR_ELEMENTAL },
3502 };
3503
3504 vector<coord_def> targets;
3505 for (radius_iterator ri(you.pos(), LOS_RADIUS, C_SQUARE, true); ri; ++ri)
3506 {
3507 const cloud_struct* cloud = cloud_at(*ri);
3508 if (!cloud || !elemental_clouds.count(cloud->type))
3509 continue;
3510
3511 const actor *agent = actor_by_mid(cloud->source);
3512 if (agent && agent->is_player())
3513 targets.push_back(*ri);
3514 }
3515
3516 if (targets.empty())
3517 {
3518 mpr("You can't see any clouds you can empower.");
3519 return spret::abort;
3520 }
3521
3522 fail_check();
3523
3524 shuffle_array(targets);
3525 const int count = max(1, min((int)targets.size(),
3526 random2avg(you.skill(SK_INVOCATIONS), 2)));
3527 mgen_data mg;
3528 mg.summon_type = MON_SUMM_AID;
3529 mg.abjuration_duration = 1;
3530 mg.flags |= MG_FORCE_PLACE | MG_AUTOFOE;
3531 mg.summoner = &you;
3532 int placed = 0;
3533 for (unsigned int i = 0; placed < count && i < targets.size(); i++)
3534 {
3535 coord_def pos = targets[i];
3536 ASSERT(cloud_at(pos));
3537 const cloud_struct &cl = *cloud_at(pos);
3538 mg.behaviour = BEH_FRIENDLY;
3539 mg.pos = pos;
3540 auto mons_type = map_find(elemental_clouds, cl.type);
3541 // it is not impossible that earlier placements caused new clouds not
3542 // in the map.
3543 if (!mons_type)
3544 continue;
3545 mg.cls = *mons_type;
3546 if (!create_monster(mg))
3547 continue;
3548 delete_cloud(pos);
3549 placed++;
3550 }
3551
3552 if (placed)
3553 mprf(MSGCH_GOD, "Clouds arounds you coalesce and take form!");
3554 else
3555 canned_msg(MSG_NOTHING_HAPPENS); // can this ever happen?
3556
3557 return spret::success;
3558 }
3559
qazlal_disaster_area()3560 bool qazlal_disaster_area()
3561 {
3562 bool friendlies = false;
3563 vector<coord_def> targets;
3564 vector<int> weights;
3565 const int pow = you.skill(SK_INVOCATIONS, 6);
3566 const int upheaval_radius = _upheaval_radius(pow);
3567 for (radius_iterator ri(you.pos(), LOS_RADIUS, C_SQUARE, LOS_NO_TRANS, true);
3568 ri; ++ri)
3569 {
3570 if (!in_bounds(*ri) || cell_is_solid(*ri))
3571 continue;
3572
3573 const monster_info* m = env.map_knowledge(*ri).monsterinfo();
3574 if (m && mons_att_wont_attack(m->attitude)
3575 && !mons_is_projectile(m->type))
3576 {
3577 friendlies = true;
3578 }
3579
3580 const int range = you.pos().distance_from(*ri);
3581 const int dist = grid_distance(you.pos(), *ri);
3582 if (range <= upheaval_radius)
3583 continue;
3584
3585 targets.push_back(*ri);
3586 // We weight using the square of grid distance, so monsters fewer tiles
3587 // away are more likely to be hit.
3588 int weight = LOS_RADIUS * LOS_RADIUS + 1 - dist * dist;
3589 if (actor_at(*ri))
3590 weight *= 10;
3591 weights.push_back(weight);
3592 }
3593
3594 if (targets.empty())
3595 {
3596 mpr("There isn't enough space here!");
3597 return false;
3598 }
3599
3600 if (friendlies
3601 && !yesno("There are friendlies around; are you sure you want to hurt "
3602 "them?", true, 'n'))
3603 {
3604 canned_msg(MSG_OK);
3605 return false;
3606 }
3607
3608 mprf(MSGCH_GOD, "Nature churns violently around you!");
3609
3610 // TODO: should count get a cap proportional to targets.size()?
3611 int count = max(1, min((int)targets.size(),
3612 max(you.skill_rdiv(SK_INVOCATIONS, 1, 2),
3613 random2avg(you.skill(SK_INVOCATIONS, 2), 2))));
3614
3615 for (int i = 0; i < count; i++)
3616 {
3617 if (targets.size() == 0)
3618 break;
3619 int which = choose_random_weighted(weights.begin(), weights.end());
3620 // Downweight adjacent potential targets (but don't rule them out
3621 // entirely).
3622 for (unsigned int j = 0; j < targets.size(); j++)
3623 if (adjacent(targets[which], targets[j]))
3624 weights[j] = max(weights[j] / 2, 1);
3625 qazlal_upheaval(targets[which], true);
3626 targets.erase(targets.begin() + which);
3627 weights.erase(weights.begin() + which);
3628 }
3629 scaled_delay(200);
3630
3631 return true;
3632 }
3633
3634 static map<ability_type, const sacrifice_def *> sacrifice_data_map;
3635
init_sac_index()3636 void init_sac_index()
3637 {
3638 for (unsigned int i = ABIL_FIRST_SACRIFICE; i <= ABIL_FINAL_SACRIFICE; ++i)
3639 {
3640 unsigned int sac_index = i - ABIL_FIRST_SACRIFICE;
3641 sacrifice_data_map[static_cast<ability_type>(i)] = &sac_data[sac_index];
3642 }
3643 }
3644
_get_sacrifice_def(ability_type sac)3645 static const sacrifice_def &_get_sacrifice_def(ability_type sac)
3646 {
3647 ASSERT_RANGE(sac, ABIL_FIRST_SACRIFICE, ABIL_FINAL_SACRIFICE+1);
3648 return *sacrifice_data_map[sac];
3649 }
3650
3651 /// A map between sacrifice_def.sacrifice_vector strings & possible mut lists.
3652 /// Abilities that map to a single mutation are not here!
3653 static map<const char*, vector<mutation_type>> sacrifice_vector_map =
3654 {
3655 /// Mutations granted by ABIL_RU_SACRIFICE_HEALTH
3656 { HEALTH_SAC_KEY, {
3657 MUT_FRAIL,
3658 MUT_PHYSICAL_VULNERABILITY,
3659 MUT_SLOW_REFLEXES,
3660 }},
3661 /// Mutations granted by ABIL_RU_SACRIFICE_ESSENCE
3662 { ESSENCE_SAC_KEY, {
3663 MUT_ANTI_WIZARDRY,
3664 MUT_WEAK_WILLED,
3665 MUT_WEAK_WILLED,
3666 MUT_LOW_MAGIC,
3667 }},
3668 /// Mutations granted by ABIL_RU_SACRIFICE_PURITY
3669 { PURITY_SAC_KEY, {
3670 MUT_SCREAM,
3671 MUT_INHIBITED_REGENERATION,
3672 MUT_NO_POTION_HEAL,
3673 MUT_DOPEY,
3674 MUT_CLUMSY,
3675 MUT_WEAK,
3676 }},
3677 };
3678
3679 /// School-disabling mutations that will be painful for most characters.
3680 static const vector<mutation_type> _major_arcane_sacrifices =
3681 {
3682 MUT_NO_CONJURATION_MAGIC,
3683 MUT_NO_NECROMANCY_MAGIC,
3684 MUT_NO_SUMMONING_MAGIC,
3685 MUT_NO_TRANSLOCATION_MAGIC,
3686 };
3687
3688 /// School-disabling mutations that are unfortunate for most characters.
3689 static const vector<mutation_type> _moderate_arcane_sacrifices =
3690 {
3691 MUT_NO_TRANSMUTATION_MAGIC,
3692 MUT_NO_HEXES_MAGIC,
3693 };
3694
3695 /// School-disabling mutations that are mostly easy to deal with.
3696 static const vector<mutation_type> _minor_arcane_sacrifices =
3697 {
3698 MUT_NO_AIR_MAGIC,
3699 MUT_NO_FIRE_MAGIC,
3700 MUT_NO_ICE_MAGIC,
3701 MUT_NO_EARTH_MAGIC,
3702 MUT_NO_POISON_MAGIC,
3703 };
3704
3705 /// The list of all lists of arcana sacrifice mutations.
3706 static const vector<mutation_type> _arcane_sacrifice_lists[] =
3707 {
3708 _minor_arcane_sacrifices,
3709 _moderate_arcane_sacrifices,
3710 _major_arcane_sacrifices,
3711 };
3712
3713 // this function is for checks that can be done with the mutation_type alone.
_sac_mut_maybe_valid(mutation_type mut)3714 static bool _sac_mut_maybe_valid(mutation_type mut)
3715 {
3716 // can't give the player this if they're already at max
3717 if (you.get_mutation_level(mut) >= mutation_max_levels(mut))
3718 return false;
3719
3720 // can't give the player this if they have an innate mut that conflicts
3721 if (mut_check_conflict(mut, true))
3722 return false;
3723
3724 // Don't offer sacrifices of skills that a player already can't use.
3725 if (!can_sacrifice_skill(mut))
3726 return false;
3727
3728 // Special case a few weird interactions:
3729
3730 // Don't offer to sacrifice summoning magic when already hated by all.
3731 if (mut == MUT_NO_SUMMONING_MAGIC
3732 && you.get_mutation_level(MUT_NO_LOVE))
3733 {
3734 return false;
3735 }
3736
3737 // Vampires can't get inhibited regeneration for some reason related
3738 // to their existing regen silliness.
3739 // Neither can deep dwarf, for obvious reasons.
3740 if (mut == MUT_INHIBITED_REGENERATION
3741 && you.has_mutation(MUT_VAMPIRISM))
3742 {
3743 return false;
3744 }
3745
3746 // demonspawn can't get frail if they have a robust facet
3747 if (you.species == SP_DEMONSPAWN && mut == MUT_FRAIL
3748 && any_of(begin(you.demonic_traits), end(you.demonic_traits),
3749 [] (player::demon_trait t)
3750 { return t.mutation == MUT_ROBUST; }))
3751 {
3752 return false;
3753 }
3754
3755 // No potion heal doesn't affect mummies since they can't quaff potions
3756 if (mut == MUT_NO_POTION_HEAL && you.has_mutation(MUT_NO_DRINK))
3757 return false;
3758
3759 return true;
3760 }
3761
3762 /**
3763 * Choose a random mutation from the given list, only including those that are
3764 * valid choices for a Ru sacrifice. (Not already at the max level, not
3765 * conflicting with an innate mut.)
3766 * N.b. this is *only* used for choosing among the sublists for sac health,
3767 * essence, and purity.
3768 *
3769 * @param muts The list of possible sacrifice mutations.
3770 * @return A mutation from the list, or MUT_NON_MUTATION if no valid
3771 * result was found.
3772 */
_random_valid_sacrifice(const vector<mutation_type> & muts)3773 static mutation_type _random_valid_sacrifice(const vector<mutation_type> &muts)
3774 {
3775 int valid_sacrifices = 0;
3776 mutation_type chosen_sacrifice = MUT_NON_MUTATION;
3777 for (auto mut : muts)
3778 {
3779 if (!_sac_mut_maybe_valid(mut))
3780 continue;
3781 // The Grunt Algorithm
3782 // (choose a random element from a set of unknown size without building
3783 // an explicit list, by giving each one a chance to be chosen equal to
3784 // the size of the known list so far, but not returning until the whole
3785 // set has been seen.)
3786 // TODO: export this to a function?
3787 ++valid_sacrifices;
3788 if (one_chance_in(valid_sacrifices))
3789 chosen_sacrifice = mut;
3790 }
3791
3792 return chosen_sacrifice;
3793 }
3794
3795 /**
3796 * Choose a random valid mutation from the given list & insert it into the
3797 * single-element vector player prop.
3798 *
3799 * @param key The key of the player prop to insert the mut into.
3800 */
_choose_sacrifice_mutation(const char * key)3801 static void _choose_sacrifice_mutation(const char *key)
3802 {
3803 ASSERT(you.props.exists(key));
3804 CrawlVector ¤t_sacrifice = you.props[key].get_vector();
3805 ASSERT(current_sacrifice.empty());
3806
3807 const mutation_type mut
3808 = _random_valid_sacrifice(sacrifice_vector_map[key]);
3809 if (mut != MUT_NON_MUTATION)
3810 {
3811 // XXX: why on earth is this a one-element vector?
3812 current_sacrifice.push_back(static_cast<int>(mut));
3813 }
3814 }
3815
3816 /**
3817 * Choose a set of three spellschools to sacrifice: one major, one moderate,
3818 * and one minor.
3819 */
_choose_arcana_mutations()3820 static void _choose_arcana_mutations()
3821 {
3822 ASSERT(you.props.exists(ARCANA_SAC_KEY));
3823 CrawlVector ¤t_arcane_sacrifices
3824 = you.props[ARCANA_SAC_KEY].get_vector();
3825 ASSERT(current_arcane_sacrifices.empty());
3826
3827 for (const vector<mutation_type> &arcane_sacrifice_list :
3828 _arcane_sacrifice_lists)
3829 {
3830 const mutation_type sacrifice =
3831 _random_valid_sacrifice(arcane_sacrifice_list);
3832
3833 if (sacrifice == MUT_NON_MUTATION)
3834 return; // don't bother filling out the others, we failed
3835 current_arcane_sacrifices.push_back(sacrifice);
3836 }
3837
3838 ASSERT(current_arcane_sacrifices.size()
3839 == ARRAYSZ(_arcane_sacrifice_lists));
3840 }
3841
3842 /**
3843 * Has the player sacrificed any arcana?
3844 */
_player_sacrificed_arcana()3845 static bool _player_sacrificed_arcana()
3846 {
3847 for (const vector<mutation_type> &arcane_sacrifice_list :
3848 _arcane_sacrifice_lists)
3849 {
3850 for (mutation_type sacrifice : arcane_sacrifice_list)
3851 if (you.get_mutation_level(sacrifice))
3852 return true;
3853 }
3854 return false;
3855 }
3856
3857 /**
3858 * Is the given sacrifice a valid one for Ru to offer to the player right now?
3859 *
3860 * @param sacrifice The sacrifice in question.
3861 * @return Whether Ru can offer the player that sacrifice, or
3862 * whether something is blocking it (e.g. no sacrificing
3863 * armour for races that can't wear any...)
3864 */
_sacrifice_is_possible(sacrifice_def & sacrifice)3865 static bool _sacrifice_is_possible(sacrifice_def &sacrifice)
3866 {
3867 // for sacrifices other than health, essence, and arcana there is a
3868 // deterministic mapping between the sacrifice_def and a mutation_type.
3869 if (sacrifice.mutation != MUT_NON_MUTATION
3870 && !_sac_mut_maybe_valid(sacrifice.mutation))
3871 {
3872 return false;
3873 }
3874
3875 // For health, essence, and arcana, we still need to choose from the
3876 // sublists in sacrifice_vector_map.
3877 if (sacrifice.sacrifice_vector)
3878 {
3879 const char* key = sacrifice.sacrifice_vector;
3880 // XXX: changing state in this function seems sketchy
3881 if (sacrifice.sacrifice == ABIL_RU_SACRIFICE_ARCANA)
3882 _choose_arcana_mutations();
3883 else
3884 _choose_sacrifice_mutation(sacrifice.sacrifice_vector);
3885
3886 if (you.props[key].get_vector().empty())
3887 return false;
3888 }
3889
3890 // finally, sacrifices may have custom validity checks.
3891 if (sacrifice.valid != nullptr && !sacrifice.valid())
3892 return false;
3893
3894 return true;
3895 }
3896
3897 /**
3898 * Which sacrifices are valid for Ru to potentially present to the player?
3899 *
3900 * @return A list of potential sacrifices (e.g. ABIL_RU_SACRIFICE_WORDS).
3901 */
_get_possible_sacrifices()3902 static vector<ability_type> _get_possible_sacrifices()
3903 {
3904 vector<ability_type> possible_sacrifices;
3905
3906 for (auto sacrifice : sac_data)
3907 if (_sacrifice_is_possible(sacrifice))
3908 possible_sacrifices.push_back(sacrifice.sacrifice);
3909
3910 return possible_sacrifices;
3911 }
3912
3913 /**
3914 * What's the name of the spell school corresponding to the given Ru mutation?
3915 *
3916 * @param mutation The variety of MUT_NO_*_MAGIC in question.
3917 * @return A long school name ("Summoning", "Translocations", etc.)
3918 */
_arcane_mutation_to_school_name(mutation_type mutation)3919 static const char* _arcane_mutation_to_school_name(mutation_type mutation)
3920 {
3921 // XXX: this does a really silly dance back and forth between school &
3922 // spelltype.
3923 const skill_type sk = arcane_mutation_to_skill(mutation);
3924 const spschool school = skill2spell_type(sk);
3925 return spelltype_long_name(school);
3926 }
3927
3928 /**
3929 * What's the abbreviation of the spell school corresponding to the given Ru
3930 * mutation?
3931 *
3932 * @param mutation The variety of MUT_NO_*_MAGIC in question.
3933 * @return A school abbreviation ("Summ", "Tloc", etc.)
3934 */
_arcane_mutation_to_school_abbr(mutation_type mutation)3935 static const char* _arcane_mutation_to_school_abbr(mutation_type mutation)
3936 {
3937 return skill_abbr(arcane_mutation_to_skill(mutation));
3938 }
3939
_piety_for_skill(skill_type skill)3940 static int _piety_for_skill(skill_type skill)
3941 {
3942 // Gnolls didn't have a choice about training the skill, so don't give
3943 // them more piety for waiting longer before taking the sacrifice.
3944 if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
3945 return 0;
3946
3947 // This should be mostly redundant with other checks, but it's a useful
3948 // sanitizer
3949 if (is_useless_skill(skill))
3950 return 0;
3951
3952 return skill_exp_needed(you.skills[skill], skill, you.species) / 500;
3953 }
3954
_piety_for_skill_by_sacrifice(ability_type sacrifice)3955 static int _piety_for_skill_by_sacrifice(ability_type sacrifice)
3956 {
3957 int piety_gain = 0;
3958 const sacrifice_def &sac_def = _get_sacrifice_def(sacrifice);
3959
3960 piety_gain += _piety_for_skill(sac_def.sacrifice_skill);
3961 if (sacrifice == ABIL_RU_SACRIFICE_HAND)
3962 {
3963 // No one-handed staves for small races.
3964 if (species::size(you.species, PSIZE_TORSO) <= SIZE_SMALL)
3965 piety_gain += _piety_for_skill(SK_STAVES);
3966 // No one-handed bows.
3967 if (!you.has_innate_mutation(MUT_QUADRUMANOUS))
3968 piety_gain += _piety_for_skill(SK_BOWS);
3969 }
3970 return piety_gain;
3971 }
3972
3973 #define AS_MUT(csv) (static_cast<mutation_type>((csv).get_int()))
3974
3975 /**
3976 * Adjust piety based on stat ranking. You get less piety if you're looking at
3977 * your lower stats.
3978 *
3979 * @param stat_type input_stat The stat we're checking.
3980 * @param int multiplier How much piety for each rank position off.
3981 * @return The piety to add.
3982 */
_get_stat_piety(stat_type input_stat,int multiplier)3983 static int _get_stat_piety(stat_type input_stat, int multiplier)
3984 {
3985 int stat_val = 3; // If this is your highest stat.
3986 if (you.base_stats[STAT_INT] > you.base_stats[input_stat])
3987 stat_val -= 1;
3988 if (you.base_stats[STAT_STR] > you.base_stats[input_stat])
3989 stat_val -= 1;
3990 if (you.base_stats[STAT_DEX] > you.base_stats[input_stat])
3991 stat_val -= 1;
3992 return stat_val * multiplier;
3993 }
3994
get_sacrifice_piety(ability_type sac,bool include_skill)3995 int get_sacrifice_piety(ability_type sac, bool include_skill)
3996 {
3997 if (sac == ABIL_RU_REJECT_SACRIFICES)
3998 return INT_MAX; // used as the null sacrifice
3999
4000 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4001 int piety_gain = sac_def.base_piety;
4002 ability_type sacrifice = sac_def.sacrifice;
4003 mutation_type mut = MUT_NON_MUTATION;
4004 int num_sacrifices = 0;
4005
4006 // Initialize data
4007 if (sac_def.sacrifice_vector)
4008 {
4009 ASSERT(you.props.exists(sac_def.sacrifice_vector));
4010 CrawlVector &sacrifice_muts =
4011 you.props[sac_def.sacrifice_vector].get_vector();
4012 num_sacrifices = sacrifice_muts.size();
4013 // mut can only meaningfully be set here if we have exactly one.
4014 if (num_sacrifices == 1)
4015 mut = AS_MUT(sacrifice_muts[0]);
4016 }
4017 else
4018 mut = sac_def.mutation;
4019
4020 // Increase piety each skill point removed.
4021 if (sacrifice == ABIL_RU_SACRIFICE_ARCANA)
4022 {
4023 skill_type arcane_skill;
4024 mutation_type arcane_mut;
4025 CrawlVector &sacrifice_muts =
4026 you.props[sac_def.sacrifice_vector].get_vector();
4027 for (int i = 0; i < num_sacrifices; i++)
4028 {
4029 arcane_mut = AS_MUT(sacrifice_muts[i]);
4030 arcane_skill = arcane_mutation_to_skill(arcane_mut);
4031 piety_gain += _piety_for_skill(arcane_skill);
4032 }
4033 }
4034 else if (sac_def.sacrifice_skill != SK_NONE && include_skill)
4035 piety_gain += _piety_for_skill_by_sacrifice(sac_def.sacrifice);
4036
4037 switch (sacrifice)
4038 {
4039 case ABIL_RU_SACRIFICE_HEALTH:
4040 if (mut == MUT_FRAIL)
4041 piety_gain += 20; // -health is pretty much always quite bad.
4042 else if (mut == MUT_PHYSICAL_VULNERABILITY)
4043 piety_gain += 5; // -AC is a bit worse than -EV
4044 break;
4045 case ABIL_RU_SACRIFICE_ESSENCE:
4046 if (mut == MUT_LOW_MAGIC)
4047 {
4048 piety_gain += 10 + max(you.skill_rdiv(SK_INVOCATIONS, 1, 2),
4049 you.skill_rdiv(SK_SPELLCASTING, 1, 2));
4050 }
4051 else if (mut == MUT_WEAK_WILLED)
4052 piety_gain += 38;
4053 else
4054 piety_gain += 2 + _get_stat_piety(STAT_INT, 6)
4055 + you.skill_rdiv(SK_SPELLCASTING, 1, 2);
4056 break;
4057 case ABIL_RU_SACRIFICE_PURITY:
4058 if (mut == MUT_WEAK || mut == MUT_DOPEY || mut == MUT_CLUMSY)
4059 {
4060 const stat_type stat = mut == MUT_WEAK ? STAT_STR
4061 : mut == MUT_CLUMSY ? STAT_DEX
4062 : mut == MUT_DOPEY ? STAT_INT
4063 : NUM_STATS;
4064 piety_gain += 4 + _get_stat_piety(stat, 4);
4065 }
4066 // the other sacrifices get sharply worse if you already
4067 // have levels of them.
4068 else if (you.get_mutation_level(mut) == 2)
4069 piety_gain += 28;
4070 else if (you.get_mutation_level(mut) == 1)
4071 piety_gain += 21;
4072 else
4073 piety_gain += 14;
4074
4075 if (mut == MUT_SCREAM)
4076 piety_gain /= 2; // screaming just isn't that bad.
4077
4078 break;
4079 case ABIL_RU_SACRIFICE_ARTIFICE:
4080 if (you.get_mutation_level(MUT_NO_LOVE))
4081 piety_gain -= 10; // You've already lost some value here
4082 break;
4083 case ABIL_RU_SACRIFICE_SKILL:
4084 // give a small bonus if sacrifice skill is taken multiple times
4085 piety_gain += 7 * you.get_mutation_level(mut);
4086 break;
4087 case ABIL_RU_SACRIFICE_NIMBLENESS:
4088 if (you.get_mutation_level(MUT_NO_ARMOUR_SKILL))
4089 piety_gain += 20;
4090 else if (species_apt(SK_ARMOUR) == UNUSABLE_SKILL)
4091 piety_gain += 28; // this sacrifice is worse for these races
4092 break;
4093 // words and drink cut off a lot of options if taken together
4094 case ABIL_RU_SACRIFICE_DRINK:
4095 if (you.get_mutation_level(MUT_READ_SAFETY))
4096 piety_gain += 10;
4097 break;
4098 case ABIL_RU_SACRIFICE_WORDS:
4099 if (you.get_mutation_level(MUT_DRINK_SAFETY))
4100 piety_gain += 10;
4101 else if (you.get_mutation_level(MUT_NO_DRINK))
4102 piety_gain += 15; // extra bad for mummies
4103 break;
4104 case ABIL_RU_SACRIFICE_DURABILITY:
4105 if (you.get_mutation_level(MUT_NO_DODGING))
4106 piety_gain += 20;
4107 break;
4108 case ABIL_RU_SACRIFICE_LOVE:
4109 if (you.get_mutation_level(MUT_NO_SUMMONING_MAGIC)
4110 && you.get_mutation_level(MUT_NO_ARTIFICE))
4111 {
4112 // this is virtually useless, aside from zot_tub
4113 piety_gain = 1;
4114 }
4115 else if (you.get_mutation_level(MUT_NO_SUMMONING_MAGIC)
4116 || you.get_mutation_level(MUT_NO_ARTIFICE))
4117 {
4118 piety_gain /= 2;
4119 }
4120 break;
4121 case ABIL_RU_SACRIFICE_EXPERIENCE:
4122 if (you.get_mutation_level(MUT_COWARDICE))
4123 piety_gain += 12;
4124 // Ds are highly likely to miss at least one mutation. This isn't
4125 // absolutely certain, but it's very likely and they should still
4126 // get a bonus for the risk. Could check the exact mutation
4127 // schedule, but this seems too leaky.
4128 // Dj are guaranteed to lose a spell each time, which is pretty sad too.
4129 if (you.species == SP_DEMONSPAWN || you.species == SP_DJINNI)
4130 piety_gain += 16;
4131 break;
4132 case ABIL_RU_SACRIFICE_COURAGE:
4133 piety_gain += 12 * you.get_mutation_level(MUT_INEXPERIENCED);
4134 break;
4135
4136 default:
4137 break;
4138 }
4139
4140 // Award piety for any mutations removed by adding new innate muts
4141 // These can only be removed positive mutations, so we'll always give piety.
4142 if (sacrifice == ABIL_RU_SACRIFICE_PURITY
4143 || sacrifice == ABIL_RU_SACRIFICE_HEALTH
4144 || sacrifice == ABIL_RU_SACRIFICE_ESSENCE)
4145 {
4146 piety_gain *= 1 + mut_check_conflict(mut);
4147 }
4148
4149 // Randomize piety gain very slightly to prevent counting.
4150 // We fuzz the piety gain by up to +-10%, or 5 piety, whichever is smaller.
4151 int piety_blur_inc = min(5, piety_gain / 10);
4152 int piety_blur = random2((2 * piety_blur_inc) + 1) - piety_blur_inc;
4153
4154 return piety_gain + piety_blur;
4155 }
4156
4157 // Remove the offer of sacrifices after they've been offered for sufficient
4158 // time or it's time to offer something new.
_ru_expire_sacrifices()4159 static void _ru_expire_sacrifices()
4160 {
4161 static const char *sacrifice_keys[] =
4162 {
4163 AVAILABLE_SAC_KEY,
4164 ESSENCE_SAC_KEY,
4165 HEALTH_SAC_KEY,
4166 PURITY_SAC_KEY,
4167 ARCANA_SAC_KEY,
4168 };
4169
4170 for (auto key : sacrifice_keys)
4171 {
4172 ASSERT(you.props.exists(key));
4173 you.props[key].get_vector().clear();
4174 }
4175
4176 // Clear out stored sacrfiice values.
4177 for (int i = 0; i < NUM_ABILITIES; ++i)
4178 you.sacrifice_piety[i] = 0;
4179 }
4180
4181 /**
4182 * Choose a random sacrifice from those in the list, filtering to only those
4183 * with piety values <= the given cap.
4184 *
4185 * @param possible_sacrifices The list of sacrifices to choose from.
4186 * @param min_piety The maximum sac piety cost to accept.
4187 * @return The ability_type of a valid sacrifice, or
4188 * ABIL_RU_REJECT_SACRIFICES if none were found
4189 * (should never happen!)
4190 */
_random_cheap_sacrifice(const vector<ability_type> & possible_sacrifices,int piety_cap)4191 static ability_type _random_cheap_sacrifice(
4192 const vector<ability_type> &possible_sacrifices,
4193 int piety_cap)
4194 {
4195 // XXX: replace this with random_if when that's merged
4196 ability_type chosen_sacrifice = ABIL_RU_REJECT_SACRIFICES;
4197 int valid_sacrifices = 0;
4198 for (auto sacrifice : possible_sacrifices)
4199 {
4200 if (get_sacrifice_piety(sacrifice) + you.piety > piety_cap)
4201 continue;
4202
4203 ++valid_sacrifices;
4204 if (one_chance_in(valid_sacrifices))
4205 chosen_sacrifice = sacrifice;
4206 }
4207
4208 dprf("found %d valid sacrifices; chose %d",
4209 valid_sacrifices, chosen_sacrifice);
4210
4211 return chosen_sacrifice;
4212 }
4213
4214 /**
4215 * Choose the cheapest remaining sacrifice. This is used when the cheapest
4216 * remaining sacrifice is over the piety cap and we still need to fill out 3
4217 * options.
4218 *
4219 * @param possible_sacrifices The list of sacrifices to choose from.
4220 * @return The ability_type of the cheapest remaining
4221 * sacrifice.
4222 */
_get_cheapest_sacrifice(const vector<ability_type> & possible_sacrifices)4223 static ability_type _get_cheapest_sacrifice(
4224 const vector<ability_type> &possible_sacrifices)
4225 {
4226 // XXX: replace this with random_if when that's merged
4227 ability_type chosen_sacrifice = ABIL_RU_REJECT_SACRIFICES;
4228 int last_piety = 999;
4229 int cheapest_sacrifices = 0;
4230 for (auto sacrifice : possible_sacrifices)
4231 {
4232 int sac_piety = get_sacrifice_piety(sacrifice);
4233 if (sac_piety >= last_piety)
4234 continue;
4235
4236 ++cheapest_sacrifices;
4237 if (one_chance_in(cheapest_sacrifices))
4238 {
4239 chosen_sacrifice = sacrifice;
4240 last_piety = sac_piety;
4241 }
4242 }
4243
4244 dprf("found %d cheapest sacrifices; chose %d",
4245 cheapest_sacrifices, chosen_sacrifice);
4246
4247 return chosen_sacrifice;
4248 }
4249
4250 /**
4251 * Chooses three distinct sacrifices to offer the player, store them in
4252 * available_sacrifices, and print a message to the player letting them
4253 * know that their new sacrifices are ready.
4254 */
ru_offer_new_sacrifices()4255 void ru_offer_new_sacrifices()
4256 {
4257 _ru_expire_sacrifices();
4258
4259 vector<ability_type> possible_sacrifices = _get_possible_sacrifices();
4260
4261 // for now we'll just pick three at random
4262 int num_sacrifices = possible_sacrifices.size();
4263
4264 const int num_expected_offers = 3;
4265
4266 // This can't happen outside wizmode, but may as well handle gracefully
4267 if (num_sacrifices < num_expected_offers)
4268 return;
4269
4270 ASSERT(you.props.exists(AVAILABLE_SAC_KEY));
4271 CrawlVector &available_sacrifices
4272 = you.props[AVAILABLE_SAC_KEY].get_vector();
4273
4274 for (int sac_num = 0; sac_num < num_expected_offers; ++sac_num)
4275 {
4276 // find the cheapest available sacrifice, in case we're close to ru's
4277 // max piety. (minimize 'wasted' piety in those cases.)
4278 const ability_type min_piety_sacrifice
4279 = accumulate(possible_sacrifices.begin(),
4280 possible_sacrifices.end(),
4281 ABIL_RU_REJECT_SACRIFICES,
4282 [](ability_type a, ability_type b) {
4283 return get_sacrifice_piety(a)
4284 < get_sacrifice_piety(b) ? a : b;
4285 });
4286 const int min_piety = get_sacrifice_piety(min_piety_sacrifice);
4287 const int piety_cap = max(179, you.piety + min_piety);
4288
4289 dprf("cheapest sac %d (%d piety); cap %d",
4290 min_piety_sacrifice, min_piety, piety_cap);
4291
4292 // XXX: replace this with random_if when that's merged
4293 ability_type chosen_sacrifice
4294 = _random_cheap_sacrifice(possible_sacrifices, piety_cap);
4295
4296 if (chosen_sacrifice < ABIL_FIRST_SACRIFICE ||
4297 chosen_sacrifice > ABIL_FINAL_SACRIFICE)
4298 {
4299 chosen_sacrifice = _get_cheapest_sacrifice(possible_sacrifices);
4300 }
4301
4302 if (chosen_sacrifice > ABIL_FINAL_SACRIFICE)
4303 {
4304 // We don't have three sacrifices to offer for some reason.
4305 // Either the player is messing around in wizmode or has rejoined
4306 // Ru repeatedly. In either case, we'll just stop offering
4307 // sacrifices rather than crashing.
4308 _ru_expire_sacrifices();
4309 ru_reset_sacrifice_timer(false);
4310 return;
4311 }
4312
4313 // add it to the list of chosen sacrifices to offer, and remove it from
4314 // the list of possibilities for the later sacrifices
4315 available_sacrifices.push_back(chosen_sacrifice);
4316 you.sacrifice_piety[chosen_sacrifice] =
4317 get_sacrifice_piety(chosen_sacrifice, false);
4318 possible_sacrifices.erase(remove(possible_sacrifices.begin(),
4319 possible_sacrifices.end(),
4320 chosen_sacrifice),
4321 possible_sacrifices.end());
4322 }
4323
4324 simple_god_message(" believes you are ready to make a new sacrifice.");
4325 // included in default force_more_message
4326 }
4327
4328 /// What key corresponds to the potential/chosen mut(s) for this sacrifice?
ru_sacrifice_vector(ability_type sac)4329 string ru_sacrifice_vector(ability_type sac)
4330 {
4331 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4332 return sac_def.sacrifice_vector ? sac_def.sacrifice_vector : "";
4333 }
4334
_describe_sacrifice_piety_gain(int piety_gain)4335 static const char* _describe_sacrifice_piety_gain(int piety_gain)
4336 {
4337 if (piety_gain >= 40)
4338 return "an incredible";
4339 else if (piety_gain >= 29)
4340 return "a major";
4341 else if (piety_gain >= 21)
4342 return "a significant";
4343 else if (piety_gain >= 13)
4344 return "a modest";
4345 else
4346 return "a trivial";
4347 }
4348
_piety_asterisks(int piety)4349 static const string _piety_asterisks(int piety)
4350 {
4351 const int prank = piety_rank(piety);
4352 return string(prank, '*') + string(NUM_PIETY_STARS - prank, '.');
4353 }
4354
_apply_ru_sacrifice(mutation_type sacrifice)4355 static void _apply_ru_sacrifice(mutation_type sacrifice)
4356 {
4357 perma_mutate(sacrifice, 1, "Ru sacrifice");
4358 you.sacrifices[sacrifice] += 1;
4359 }
4360
_execute_sacrifice(ability_type sac,const char * message)4361 static bool _execute_sacrifice(ability_type sac, const char* message)
4362 {
4363 mprf("Ru asks you to %s.", message);
4364 mpr(ru_sacrifice_description(sac));
4365 if (!yesno("Do you really want to make this sacrifice?",
4366 false, 'n'))
4367 {
4368 canned_msg(MSG_OK);
4369 return false;
4370 }
4371 return true;
4372 }
4373
_ru_kill_skill(skill_type skill)4374 static void _ru_kill_skill(skill_type skill)
4375 {
4376 change_skill_points(skill, -you.skill_points[skill], true);
4377 you.can_currently_train.set(skill, false);
4378 reset_training();
4379 check_selected_skills();
4380 }
4381
_extra_sacrifice_code(ability_type sac)4382 static void _extra_sacrifice_code(ability_type sac)
4383 {
4384 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4385 if (sac_def.sacrifice == ABIL_RU_SACRIFICE_HAND)
4386 {
4387 auto ring_slots = species::ring_slots(you.species, true);
4388 equipment_type sac_ring_slot = species::sacrificial_arm(you.species);
4389
4390 item_def* const shield = you.slot_item(EQ_SHIELD, true);
4391 item_def* const weapon = you.slot_item(EQ_WEAPON, true);
4392 item_def* const ring = you.slot_item(sac_ring_slot, true);
4393 int ring_inv_slot = you.equip[sac_ring_slot];
4394 equipment_type open_ring_slot = EQ_NONE;
4395
4396 // Drop your shield if there is one
4397 if (shield != nullptr)
4398 {
4399 mprf("You can no longer hold %s!",
4400 shield->name(DESC_YOUR).c_str());
4401 unequip_item(EQ_SHIELD);
4402 }
4403
4404 // And your two-handed weapon
4405 if (weapon != nullptr)
4406 {
4407 if (you.hands_reqd(*weapon) == HANDS_TWO)
4408 {
4409 mprf("You can no longer hold %s!",
4410 weapon->name(DESC_YOUR).c_str());
4411 unequip_item(EQ_WEAPON);
4412 }
4413 }
4414
4415 // And one ring
4416 if (ring != nullptr)
4417 {
4418 // XX does not handle an open slot on the finger amulet
4419 for (const auto &eq : ring_slots)
4420 if (!you.slot_item(eq, true))
4421 {
4422 open_ring_slot = eq;
4423 break;
4424 }
4425
4426 const bool can_keep = open_ring_slot != EQ_NONE;
4427
4428 mprf("You can no longer wear %s!",
4429 ring->name(DESC_YOUR).c_str());
4430 unequip_item(sac_ring_slot, true, can_keep);
4431 if (can_keep)
4432 {
4433 mprf("You put %s back on %s %s!",
4434 ring->name(DESC_YOUR).c_str(),
4435 (ring_slots.size() > 1 ? "another" : "your other"),
4436 you.hand_name(true).c_str());
4437 equip_item(open_ring_slot, ring_inv_slot, false, true);
4438 }
4439 }
4440 }
4441 else if (sac_def.sacrifice == ABIL_RU_SACRIFICE_EXPERIENCE)
4442 level_change();
4443 else if (sac_def.sacrifice == ABIL_RU_SACRIFICE_SKILL)
4444 {
4445 uint8_t saved_skills[NUM_SKILLS];
4446 for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
4447 {
4448 saved_skills[sk] = you.skills[sk];
4449 check_skill_level_change(sk, false);
4450 }
4451
4452 // Produce messages about skill increases/decreases. We
4453 // restore one skill level at a time so that at most the
4454 // skill being checked is at the wrong level.
4455 for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
4456 {
4457 you.skills[sk] = saved_skills[sk];
4458 check_skill_level_change(sk);
4459 }
4460
4461 redraw_screen();
4462 update_screen();
4463 }
4464 }
4465
4466 /**
4467 * Describe variable costs for a given Ru sacrifice being offered.
4468 *
4469 * @param sac The sacrifice in question.
4470 * @return Extra costs.
4471 * For ABIL_RU_SACRIFICE_ARCANA: e.g. " (Tloc/Air/Fire)"
4472 * For other variable muts: e.g. " (frail)"
4473 * Otherwise, "".
4474 */
ru_sac_text(ability_type sac)4475 string ru_sac_text(ability_type sac)
4476 {
4477 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4478 if (!sac_def.sacrifice_vector)
4479 return "";
4480
4481 ASSERT(you.props.exists(sac_def.sacrifice_vector));
4482 const CrawlVector &sacrifice_muts =
4483 you.props[sac_def.sacrifice_vector].get_vector();
4484
4485 if (sac != ABIL_RU_SACRIFICE_ARCANA)
4486 {
4487 ASSERT(sacrifice_muts.size() == 1);
4488 const mutation_type mut = AS_MUT(sacrifice_muts[0]);
4489 return make_stringf(" (%s)", mutation_name(mut));
4490 }
4491
4492 // "Tloc/Fire/Ice"
4493 const string school_names
4494 = comma_separated_fn(sacrifice_muts.begin(), sacrifice_muts.end(),
4495 [](CrawlStoreValue mut) {
4496 return _arcane_mutation_to_school_abbr(AS_MUT(mut));
4497 }, "/", "/");
4498
4499 return make_stringf(" (%s)", school_names.c_str());
4500 }
4501
_ru_get_sac_piety_gain(ability_type sac)4502 static int _ru_get_sac_piety_gain(ability_type sac)
4503 {
4504 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4505
4506 // If we're haven't yet calculated piety gain, get it now. Otherwise,
4507 // use the calculated value and then add in the skill piety, which isn't
4508 // saved because it can change over time.
4509 const int base_piety_gain = you.sacrifice_piety[sac];
4510
4511 if (base_piety_gain == 0)
4512 return get_sacrifice_piety(sac);
4513
4514 if (sac_def.sacrifice_skill != SK_NONE)
4515 return base_piety_gain + _piety_for_skill_by_sacrifice(sac);
4516
4517 return base_piety_gain;
4518 }
4519
ru_sacrifice_description(ability_type sac)4520 string ru_sacrifice_description(ability_type sac)
4521 {
4522 if (!you_worship(GOD_RU))
4523 return "";
4524
4525 const int piety_gain = _ru_get_sac_piety_gain(sac);
4526 return make_stringf("This is %s sacrifice. Piety after sacrifice: %s",
4527 _describe_sacrifice_piety_gain(piety_gain),
4528 _piety_asterisks(you.piety + piety_gain).c_str());
4529 }
4530
4531
ru_do_sacrifice(ability_type sac)4532 bool ru_do_sacrifice(ability_type sac)
4533 {
4534 const sacrifice_def &sac_def = _get_sacrifice_def(sac);
4535 bool variable_sac;
4536 mutation_type mut = MUT_NON_MUTATION;
4537 int num_sacrifices;
4538 string offer_text;
4539 string mile_text;
4540 string sac_text;
4541 const bool is_sac_arcana = sac == ABIL_RU_SACRIFICE_ARCANA;
4542
4543 // For variable sacrifices, we need to compose the text that will be
4544 // displayed at the time of sacrifice offer and as a milestone if the
4545 // sacrifice is accepted. We also need to figure out piety.
4546 if (sac_def.sacrifice_vector)
4547 {
4548 variable_sac = true;
4549 ASSERT(you.props.exists(sac_def.sacrifice_vector));
4550 CrawlVector &sacrifice_muts =
4551 you.props[sac_def.sacrifice_vector].get_vector();
4552 num_sacrifices = sacrifice_muts.size();
4553
4554 for (int i = 0; i < num_sacrifices; i++)
4555 {
4556 mut = AS_MUT(sacrifice_muts[i]);
4557
4558 // format the text that will be displayed
4559 if (is_sac_arcana)
4560 {
4561 if (i == num_sacrifices - 1)
4562 {
4563 sac_text = make_stringf("%sand %s", sac_text.c_str(),
4564 _arcane_mutation_to_school_name(mut));
4565 }
4566 else
4567 {
4568 sac_text = make_stringf("%s%s, ", sac_text.c_str(),
4569 _arcane_mutation_to_school_name(mut));
4570 }
4571 }
4572 else
4573 sac_text = mut_upgrade_summary(mut);
4574 }
4575 offer_text = make_stringf("%s: %s", sac_def.sacrifice_text,
4576 sac_text.c_str());
4577 mile_text = make_stringf("%s: %s.", sac_def.milestone_text,
4578 sac_text.c_str());
4579 }
4580 else
4581 {
4582 variable_sac = false;
4583 mut = sac_def.mutation;
4584 num_sacrifices = 1;
4585 string handtxt = "";
4586 if (sac == ABIL_RU_SACRIFICE_HAND)
4587 handtxt = you.hand_name(true);
4588
4589 offer_text = sac_def.sacrifice_text + handtxt;
4590 mile_text = make_stringf("%s.", sac_def.milestone_text);
4591 }
4592
4593 // get confirmation that the sacrifice is desired.
4594 if (!_execute_sacrifice(sac, offer_text.c_str()))
4595 return false;
4596 // save piety gain, since sacrificing skills can lower the piety gain
4597 const int piety_gain = _ru_get_sac_piety_gain(sac);
4598 // Apply the sacrifice, starting by mutating the player.
4599 if (variable_sac)
4600 {
4601 CrawlVector &sacrifice_muts =
4602 you.props[sac_def.sacrifice_vector].get_vector();
4603 for (int i = 0; i < num_sacrifices; i++)
4604 {
4605 mut = AS_MUT(sacrifice_muts[i]);
4606 _apply_ru_sacrifice(mut);
4607 }
4608 }
4609 else
4610 _apply_ru_sacrifice(mut);
4611
4612 // Remove any no-longer-usable skills.
4613 if (is_sac_arcana)
4614 {
4615 skill_type arcane_skill;
4616 mutation_type arcane_mut;
4617 CrawlVector &sacrifice_muts =
4618 you.props[sac_def.sacrifice_vector].get_vector();
4619 for (int i = 0; i < num_sacrifices; i++)
4620 {
4621 arcane_mut = AS_MUT(sacrifice_muts[i]);
4622 arcane_skill = arcane_mutation_to_skill(arcane_mut);
4623 _ru_kill_skill(arcane_skill);
4624 }
4625 }
4626 else if (sac_def.sacrifice_skill != SK_NONE)
4627 _ru_kill_skill(sac_def.sacrifice_skill);
4628
4629 // Maybe this should go in _extra_sacrifice_code, but it would be
4630 // inconsistent for the milestone to have reduced Shields skill
4631 // but not the others.
4632 if (sac == ABIL_RU_SACRIFICE_HAND)
4633 {
4634 // No one-handed staves for small races.
4635 if (species::size(you.species, PSIZE_TORSO) <= SIZE_SMALL)
4636 _ru_kill_skill(SK_STAVES);
4637 // No one-handed bows.
4638 if (!you.has_innate_mutation(MUT_QUADRUMANOUS))
4639 _ru_kill_skill(SK_BOWS);
4640 }
4641
4642 mark_milestone("sacrifice", mile_text);
4643
4644 // Any special handling that's needed.
4645 _extra_sacrifice_code(sac);
4646
4647 // Update how many Ru sacrifices you have. This is used to avoid giving the
4648 // player extra silver damage.
4649 if (you.props.exists("num_sacrifice_muts"))
4650 {
4651 you.props["num_sacrifice_muts"] = num_sacrifices +
4652 you.props["num_sacrifice_muts"].get_int();
4653 }
4654 else
4655 you.props["num_sacrifice_muts"] = num_sacrifices;
4656
4657 // Actually give the piety for this sacrifice.
4658 set_piety(min(piety_breakpoint(5), you.piety + piety_gain));
4659
4660 if (you.piety == piety_breakpoint(5))
4661 simple_god_message(" indicates that your awakening is complete.");
4662
4663 // Clean up.
4664 _ru_expire_sacrifices();
4665 ru_reset_sacrifice_timer(true);
4666 redraw_screen(); // pretty much everything could have changed
4667 update_screen();
4668 return true;
4669 }
4670
4671 /**
4672 * If forced_rejection is false, prompt the player if they want to reject the
4673 * currently offered sacrifices. If true, or if the prompt is accepted,
4674 * remove the currently offered sacrifices & increase the time until the next
4675 * sacrifices will be offered.
4676 *
4677 * @param forced_rejection Whether the rejection is caused by the removal
4678 * of an amulet of faith, in which case there's
4679 * no prompt & an increased sac time penalty.
4680 * @return Whether the sacrifices were actually rejected.
4681 */
ru_reject_sacrifices(bool forced_rejection)4682 bool ru_reject_sacrifices(bool forced_rejection)
4683 {
4684 if (!forced_rejection &&
4685 !yesno("Do you really want to reject the sacrifices Ru is offering?",
4686 false, 'n'))
4687 {
4688 canned_msg(MSG_OK);
4689 return false;
4690 }
4691
4692 ru_reset_sacrifice_timer(false, true);
4693 _ru_expire_sacrifices();
4694 simple_god_message(" will take longer to evaluate your readiness.");
4695 return true;
4696 }
4697
4698 /**
4699 * Reset the time until the next set of Ru sacrifices are offered.
4700 *
4701 * @param clear_timer Whether to reset the timer to the base time-between-
4702 * sacrifices, rather than adding to it.
4703 * @param faith_penalty Whether this is a penalty for removing "faith.
4704 */
ru_reset_sacrifice_timer(bool clear_timer,bool faith_penalty)4705 void ru_reset_sacrifice_timer(bool clear_timer, bool faith_penalty)
4706 {
4707 ASSERT(you.props.exists(RU_SACRIFICE_PROGRESS_KEY));
4708 ASSERT(you.props.exists(RU_SACRIFICE_DELAY_KEY));
4709 ASSERT(you.props.exists(RU_SACRIFICE_PENALTY_KEY));
4710
4711 // raise the delay if there's an active sacrifice, and more so the more
4712 // often you pass on a sacrifice and the more piety you have.
4713 const int base_delay = 90;
4714 int delay = you.props[RU_SACRIFICE_DELAY_KEY].get_int();
4715 int added_delay;
4716 if (clear_timer)
4717 {
4718 added_delay = 0;
4719 delay = base_delay;
4720 you.props[RU_SACRIFICE_PENALTY_KEY] = 0;
4721 }
4722 else
4723 {
4724 // if you rejected a sacrifice, add between 33 and 53 to the timer,
4725 // based on piety. This extra delay stacks with any added delay for
4726 // previous rejections.
4727 added_delay = you.props[RU_SACRIFICE_PENALTY_KEY].get_int();
4728 const int new_penalty = (max(100, static_cast<int>(you.piety))) / 3;
4729 added_delay += new_penalty;
4730
4731 // longer delay for each real rejection
4732 if (!you.props[AVAILABLE_SAC_KEY].get_vector().empty())
4733 you.props[RU_SACRIFICE_PENALTY_KEY] = added_delay;
4734
4735 if (faith_penalty)
4736 {
4737 // the player will end up waiting longer than they would otherwise,
4738 // but multiple removals of the amulet of faith won't stack -
4739 // they'll just put the player back to around the same delay
4740 // each time.
4741 added_delay += new_penalty * 2;
4742 delay = base_delay;
4743 }
4744 }
4745
4746 delay = div_rand_round((delay + added_delay) * (3 - you.faith()), 3);
4747 if (crawl_state.game_is_sprint())
4748 delay /= SPRINT_MULTIPLIER;
4749
4750 you.props[RU_SACRIFICE_DELAY_KEY] = delay;
4751 you.props[RU_SACRIFICE_PROGRESS_KEY] = 0;
4752 }
4753
4754 // Check to see if you're eligible to retaliate.
4755 //Your chance of eligiblity scales with piety.
will_ru_retaliate()4756 bool will_ru_retaliate()
4757 {
4758 // Scales up to a 25% chance of retribution
4759 return have_passive(passive_t::upgraded_aura_of_power)
4760 && crawl_state.which_god_acting() != GOD_RU
4761 && one_chance_in(div_rand_round(640, you.piety));
4762 }
4763
4764 // Power of retribution increases with damage, decreases with monster HD.
ru_do_retribution(monster * mons,int damage)4765 void ru_do_retribution(monster* mons, int damage)
4766 {
4767 int power = max(0, random2(div_rand_round(you.piety*10, 32))
4768 + damage - (2 * mons->get_hit_dice()));
4769 const actor* act = &you;
4770
4771 if (power > 50 && (mons->antimagic_susceptible()))
4772 {
4773 mprf(MSGCH_GOD, "You focus your inner power and drain %s's magic in "
4774 "retribution!", mons->name(DESC_THE).c_str());
4775 mons->add_ench(mon_enchant(ENCH_ANTIMAGIC, 1, act, power+random2(320)));
4776 }
4777 else if (power > 35)
4778 {
4779 mprf(MSGCH_GOD, "You focus your inner power and paralyse %s in retribution!",
4780 mons->name(DESC_THE).c_str());
4781 mons->add_ench(mon_enchant(ENCH_PARALYSIS, 1, act, power+random2(60)));
4782 }
4783 else if (power > 25)
4784 {
4785 mprf(MSGCH_GOD, "You focus your inner power and slow %s in retribution!",
4786 mons->name(DESC_THE).c_str());
4787 mons->add_ench(mon_enchant(ENCH_SLOW, 1, act, power+random2(100)));
4788 }
4789 else if (power > 10 && mons_can_be_blinded(mons->type))
4790 {
4791 mprf(MSGCH_GOD, "You focus your inner power and blind %s in retribution!",
4792 mons->name(DESC_THE).c_str());
4793 mons->add_ench(mon_enchant(ENCH_BLIND, 1, act, power+random2(100)));
4794 }
4795 else if (power > 0)
4796 {
4797 mprf(MSGCH_GOD, "You focus your inner power and illuminate %s in retribution!",
4798 mons->name(DESC_THE).c_str());
4799 mons->add_ench(mon_enchant(ENCH_CORONA, 1, act, power+random2(150)));
4800 }
4801 }
4802
ru_draw_out_power()4803 void ru_draw_out_power()
4804 {
4805 mpr("You are restored by drawing out deep reserves of power within.");
4806
4807 //Escape nets and webs
4808 int net = get_trapping_net(you.pos());
4809 if (net == NON_ITEM)
4810 {
4811 trap_def *trap = trap_at(you.pos());
4812 if (trap && trap->type == TRAP_WEB)
4813 {
4814 destroy_trap(you.pos());
4815 // XXX: destroying them is dubious in general - abuseable by loons?
4816 // (but definitely destroy if ammo == 1, per trap-def.h!)
4817 mpr("You burst free from the webs!");
4818 }
4819 }
4820 else
4821 {
4822 destroy_item(net);
4823 mpr("You burst free from the net!");
4824 }
4825 stop_being_held();
4826
4827 // Escape constriction
4828 you.stop_being_constricted(false);
4829
4830 // cancel petrification/confusion/slow
4831 you.duration[DUR_CONF] = 0;
4832 you.duration[DUR_SLOW] = 0;
4833 you.duration[DUR_PETRIFYING] = 0;
4834
4835 int hp_inc = div_rand_round(you.piety, 16);
4836 hp_inc += roll_dice(div_rand_round(you.piety, 20), 6);
4837 inc_hp(hp_inc);
4838 int mp_inc = div_rand_round(you.piety, 48);
4839 mp_inc += roll_dice(div_rand_round(you.piety, 40), 4);
4840 inc_mp(mp_inc);
4841 drain_player(30, false, true);
4842 }
4843
ru_power_leap()4844 bool ru_power_leap()
4845 {
4846 ASSERT(!crawl_state.game_is_arena());
4847
4848 if (crawl_state.is_replaying_keys())
4849 {
4850 crawl_state.cancel_cmd_all("You can't repeat Power Leap.");
4851 return false;
4852 }
4853 if (you.is_nervous())
4854 {
4855 mpr("You are too terrified to leap around!");
4856 return false;
4857 }
4858
4859 // query for location:
4860 dist beam;
4861
4862 while (1)
4863 {
4864 direction_chooser_args args;
4865 args.restricts = DIR_LEAP;
4866 args.mode = TARG_ANY;
4867 args.range = 3;
4868 args.needs_path = false;
4869 args.top_prompt = "Aiming: <white>Power Leap</white>";
4870 args.self = confirm_prompt_type::cancel;
4871 const int explosion_size = 1;
4872 targeter_smite tgt(&you, args.range, explosion_size, explosion_size);
4873 tgt.obeys_mesmerise = true;
4874 args.hitfunc = &tgt;
4875 direction(beam, args);
4876 if (crawl_state.seen_hups)
4877 {
4878 clear_messages();
4879 mpr("Cancelling leap due to HUP.");
4880 return false;
4881 }
4882
4883 if (!beam.isValid || beam.target == you.pos())
4884 return false; // early return
4885
4886 monster* beholder = you.get_beholder(beam.target);
4887 if (beholder)
4888 {
4889 clear_messages();
4890 mprf("You cannot leap away from %s!",
4891 beholder->name(DESC_THE, true).c_str());
4892 continue;
4893 }
4894
4895 monster* fearmonger = you.get_fearmonger(beam.target);
4896 if (fearmonger)
4897 {
4898 clear_messages();
4899 mprf("You cannot leap closer to %s!",
4900 fearmonger->name(DESC_THE, true).c_str());
4901 continue;
4902 }
4903
4904 monster* mons = monster_at(beam.target);
4905 if (mons && you.can_see(*mons))
4906 {
4907 clear_messages();
4908 mpr("You can't leap on top of the monster!");
4909 continue;
4910 }
4911
4912 if (env.grid(beam.target) == DNGN_OPEN_SEA)
4913 {
4914 clear_messages();
4915 mpr("You can't leap into the sea!");
4916 continue;
4917 }
4918 else if (env.grid(beam.target) == DNGN_LAVA_SEA)
4919 {
4920 clear_messages();
4921 mpr("You can't leap into the sea of lava!");
4922 continue;
4923 }
4924 else if (!check_moveto(beam.target, "leap", false))
4925 {
4926 // try again (messages handled by check_moveto)
4927 }
4928 else if (you.see_cell_no_trans(beam.target))
4929 {
4930 // Grid in los, no problem.
4931 break;
4932 }
4933 else if (you.trans_wall_blocking(beam.target))
4934 {
4935 clear_messages();
4936 canned_msg(MSG_SOMETHING_IN_WAY);
4937 }
4938 else
4939 {
4940 clear_messages();
4941 canned_msg(MSG_CANNOT_SEE);
4942 }
4943 }
4944
4945 if (!you.attempt_escape(2)) // returns true if not constricted
4946 return true;
4947
4948 if (cell_is_solid(beam.target) || monster_at(beam.target))
4949 {
4950 // XXX: try to jump somewhere nearby?
4951 mpr("Something unexpectedly blocked you, preventing you from leaping!");
4952 return true;
4953 }
4954
4955 move_player_to_grid(beam.target, false);
4956 apply_barbs_damage();
4957
4958 crawl_state.cancel_cmd_again();
4959 crawl_state.cancel_cmd_repeat();
4960
4961 bolt wave;
4962 wave.thrower = KILL_YOU;
4963 wave.name = "power leap";
4964 wave.source_name = "you";
4965 wave.source_id = MID_PLAYER;
4966 wave.flavour = BEAM_VISUAL;
4967 wave.colour = BROWN;
4968 wave.glyph = dchar_glyph(DCHAR_EXPLOSION);
4969 wave.range = 1;
4970 wave.ex_size = 1;
4971 wave.is_explosion = true;
4972 wave.source = you.pos();
4973 wave.target = you.pos();
4974 wave.hit = AUTOMATIC_HIT;
4975 wave.loudness = 2;
4976 wave.explode();
4977
4978 // we need to exempt the player from damage.
4979 for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
4980 {
4981 monster* mon = monster_at(*ai);
4982 if (mon == nullptr || mons_is_projectile(mon->type) || mon->friendly())
4983 continue;
4984 ASSERT(mon);
4985
4986 //damage scales with XL amd piety
4987 mon->hurt((actor*)&you, roll_dice(1 + div_rand_round(you.piety *
4988 (54 + you.experience_level), 777), 3),
4989 BEAM_ENERGY, KILLED_BY_BEAM, "", "", true);
4990 }
4991
4992 return true;
4993 }
4994
cell_has_valid_target(coord_def where)4995 int cell_has_valid_target(coord_def where)
4996 {
4997 monster* mon = monster_at(where);
4998 if (mon == nullptr || mons_is_projectile(mon->type) || mon->friendly())
4999 return 0;
5000 return 1;
5001 }
5002
_apply_apocalypse(coord_def where)5003 static int _apply_apocalypse(coord_def where)
5004 {
5005 if (!cell_has_valid_target(where))
5006 return 0;
5007 monster* mons = monster_at(where);
5008 ASSERT(mons);
5009
5010 int duration = 0;
5011 string message = "";
5012 enchant_type enchantment = ENCH_NONE;
5013
5014 int effect = random2(4);
5015 if (mons_is_firewood(*mons))
5016 effect = 99; // > 2 is just damage -- no slowed toadstools
5017
5018 int num_dice;
5019 switch (effect)
5020 {
5021 case 0:
5022 if (mons->antimagic_susceptible())
5023 {
5024 message = " doubts " + mons->pronoun(PRONOUN_POSSESSIVE)
5025 + " magic when faced with ultimate truth!";
5026 enchantment = ENCH_ANTIMAGIC;
5027 duration = 500 + random2(200);
5028 num_dice = 4;
5029 break;
5030 } // if not antimagicable, fall through to paralysis.
5031 case 1:
5032 message = " is paralysed by terrible understanding!";
5033 enchantment = ENCH_PARALYSIS;
5034 duration = 80 + random2(60);
5035 num_dice = 4;
5036 break;
5037
5038 case 2:
5039 message = " slows down under the weight of truth!";
5040 enchantment = ENCH_SLOW;
5041 duration = 300 + random2(100);
5042 num_dice = 6;
5043 break;
5044
5045 default:
5046 num_dice = 8;
5047 break;
5048 }
5049
5050 //damage scales with XL and piety
5051 const int pow = you.piety;
5052 int die_size = 1 + div_rand_round(pow * (54 + you.experience_level), 584);
5053 int dmg = 10 + roll_dice(num_dice, die_size);
5054
5055 mons->hurt(&you, dmg, BEAM_ENERGY, KILLED_BY_BEAM, "", "", true);
5056
5057 if (mons->alive() && enchantment != ENCH_NONE)
5058 {
5059 simple_monster_message(*mons, message.c_str());
5060 mons->add_ench(mon_enchant(enchantment, 1, &you, duration));
5061 }
5062 return 1;
5063 }
5064
ru_apocalypse()5065 bool ru_apocalypse()
5066 {
5067 int count = apply_area_visible(cell_has_valid_target, you.pos());
5068 if (!count)
5069 {
5070 if (!yesno("There are no visible enemies. Unleash your apocalypse anyway?",
5071 true, 'n'))
5072 {
5073 return false;
5074 }
5075 }
5076 mpr("You reveal the great annihilating truth to your foes!");
5077 noisy(30, you.pos());
5078 apply_area_visible(_apply_apocalypse, you.pos());
5079 drain_player(100, false, true);
5080 return true;
5081 }
5082
_mons_stompable(const monster & mons)5083 static bool _mons_stompable(const monster &mons)
5084 {
5085 // Don't hurt your own demonic guardians
5086 return !testbits(mons.flags, MF_DEMONIC_GUARDIAN) || !mons.friendly();
5087 }
5088
_get_stomped(monster & mons)5089 static bool _get_stomped(monster& mons)
5090 {
5091 if (!_mons_stompable(mons))
5092 return false;
5093
5094 behaviour_event(&mons, ME_ANNOY, &you);
5095
5096 // Damage starts at 1/6th of monster current HP, then gets some damage
5097 // scaling off Invo power.
5098 int damage = div_rand_round(mons.hit_points, 6);
5099 int die_size = 2 + div_rand_round(you.skill(SK_INVOCATIONS), 2);
5100 damage += roll_dice(2, die_size);
5101
5102 mons.hurt(&you, damage, BEAM_ENERGY, KILLED_BY_BEAM, "", "", true);
5103
5104 if (mons.alive() && you.can_see(mons))
5105 print_wounds(mons);
5106
5107 return true;
5108 }
5109
uskayaw_stomp()5110 bool uskayaw_stomp()
5111 {
5112 // Demonic guardians are immune but check for other friendlies
5113 const bool friendlies = apply_monsters_around_square([] (monster& mons) {
5114 return _mons_stompable(mons) && mons_att_wont_attack(mons.attitude);
5115 }, you.pos());
5116
5117 // XXX: this 'friendlies' wording feels a little odd, but we do use it in a
5118 // a few places already; see spl-vortex.cc, disaster area, etc.
5119 if (friendlies
5120 && !yesno("There are friendlies around, "
5121 "are you sure you want to hurt them?",
5122 true, 'n'))
5123 {
5124 canned_msg(MSG_OK);
5125 return false;
5126 }
5127
5128 mpr("You stomp with the beat, sending a shockwave through the revellers "
5129 "around you!");
5130 apply_monsters_around_square(_get_stomped, you.pos());
5131 return true;
5132 }
5133
uskayaw_line_pass()5134 bool uskayaw_line_pass()
5135 {
5136 ASSERT(!crawl_state.game_is_arena());
5137
5138 if (crawl_state.is_replaying_keys())
5139 {
5140 crawl_state.cancel_cmd_all("You can't repeat Line Pass.");
5141 return false;
5142 }
5143
5144 // query for location:
5145 int range = 8;
5146 int invo_skill = you.skill(SK_INVOCATIONS);
5147 int pow = (25 + invo_skill + random2(invo_skill));
5148 dist beam;
5149 bolt line_pass;
5150 line_pass.thrower = KILL_YOU;
5151 line_pass.name = "line pass";
5152 line_pass.source_name = "you";
5153 line_pass.source_id = MID_PLAYER;
5154 line_pass.flavour = BEAM_IRRESISTIBLE_CONFUSION;
5155 line_pass.source = you.pos();
5156 line_pass.hit = AUTOMATIC_HIT;
5157 line_pass.range = range;
5158 line_pass.ench_power = pow;
5159 line_pass.pierce = true;
5160
5161 while (1)
5162 {
5163 unique_ptr<targeter> hitfunc;
5164 hitfunc = make_unique<targeter_monster_sequence>(&you, pow, range);
5165
5166 direction_chooser_args args;
5167 args.hitfunc = hitfunc.get();
5168 args.restricts = DIR_LEAP;
5169 args.mode = TARG_ANY;
5170 args.needs_path = false;
5171 args.top_prompt = "Aiming: <white>Line Pass</white>";
5172 args.range = 8;
5173
5174 if (!spell_direction(beam, line_pass, &args))
5175 return false;
5176
5177 if (crawl_state.seen_hups)
5178 {
5179 clear_messages();
5180 mpr("Cancelling line pass due to HUP.");
5181 return false;
5182 }
5183
5184 if (!beam.isValid || beam.target == you.pos())
5185 return false; // early return
5186
5187 monster* beholder = you.get_beholder(beam.target);
5188 if (beholder)
5189 {
5190 clear_messages();
5191 mprf("You cannot move away from %s!",
5192 beholder->name(DESC_THE, true).c_str());
5193 continue;
5194 }
5195
5196 monster* fearmonger = you.get_fearmonger(beam.target);
5197 if (fearmonger)
5198 {
5199 clear_messages();
5200 mprf("You cannot move closer to %s!",
5201 fearmonger->name(DESC_THE, true).c_str());
5202 continue;
5203 }
5204
5205 monster* mons = monster_at(beam.target);
5206 if (mons && you.can_see(*mons))
5207 {
5208 clear_messages();
5209 mpr("You can't stand on top of the monster!");
5210 continue;
5211 }
5212
5213 if (env.grid(beam.target) == DNGN_OPEN_SEA)
5214 {
5215 clear_messages();
5216 mpr("You can't line pass into the sea!");
5217 continue;
5218 }
5219 else if (env.grid(beam.target) == DNGN_LAVA_SEA)
5220 {
5221 clear_messages();
5222 mpr("You can't line pass into the sea of lava!");
5223 continue;
5224 }
5225 else if (cell_is_solid(beam.target))
5226 {
5227 clear_messages();
5228 mpr("You can't walk through walls!");
5229 continue;
5230 }
5231 else if (!check_moveto(beam.target, "line pass", false))
5232 {
5233 // try again (messages handled by check_moveto)
5234 }
5235 else if (you.see_cell_no_trans(beam.target))
5236 {
5237 // Grid in los, no problem.
5238 break;
5239 }
5240 else if (you.trans_wall_blocking(beam.target))
5241 {
5242 clear_messages();
5243 canned_msg(MSG_SOMETHING_IN_WAY);
5244 }
5245 else
5246 {
5247 clear_messages();
5248 canned_msg(MSG_CANNOT_SEE);
5249 }
5250 }
5251
5252 if (monster_at(beam.target))
5253 mpr("Something unexpectedly blocked you, preventing you from passing!");
5254 else
5255 {
5256 line_pass.fire();
5257 you.stop_being_constricted(false);
5258 move_player_to_grid(beam.target, false);
5259 apply_barbs_damage();
5260 }
5261
5262 crawl_state.cancel_cmd_again();
5263 crawl_state.cancel_cmd_repeat();
5264
5265 return true;
5266 }
5267
uskayaw_grand_finale(bool fail)5268 spret uskayaw_grand_finale(bool fail)
5269 {
5270 ASSERT(!crawl_state.game_is_arena());
5271
5272 if (crawl_state.is_replaying_keys())
5273 {
5274 crawl_state.cancel_cmd_all("No encores!");
5275 return spret::abort;
5276 }
5277
5278 // query for location:
5279 dist beam;
5280
5281 monster* mons;
5282
5283 while (1)
5284 {
5285 direction_chooser_args args;
5286 args.mode = TARG_HOSTILE;
5287 args.needs_path = false;
5288 args.top_prompt = "Aiming: <white>Grand Finale</white>";
5289 args.self = confirm_prompt_type::cancel;
5290 targeter_smite tgt(&you);
5291 args.hitfunc = &tgt;
5292 direction(beam, args);
5293 if (crawl_state.seen_hups)
5294 {
5295 clear_messages();
5296 mpr("Cancelling grand finale due to HUP.");
5297 return spret::abort;
5298 }
5299
5300 if (!beam.isValid || beam.target == you.pos())
5301 return spret::abort; // early return
5302
5303 mons = monster_at(beam.target);
5304 if (!mons || !you.can_see(*mons))
5305 {
5306 clear_messages();
5307 mpr("You can't perceive a target there!");
5308 continue;
5309 }
5310
5311 if (!check_moveto(beam.target, "move", false))
5312 {
5313 // try again (messages handled by check_moveto)
5314 }
5315 else if (you.see_cell_no_trans(beam.target))
5316 {
5317 // Grid in los, no problem.
5318 break;
5319 }
5320 else if (you.trans_wall_blocking(beam.target))
5321 {
5322 clear_messages();
5323 canned_msg(MSG_SOMETHING_IN_WAY);
5324 }
5325 else
5326 {
5327 clear_messages();
5328 canned_msg(MSG_CANNOT_SEE);
5329 }
5330 }
5331
5332 fail_check();
5333
5334 ASSERT(mons);
5335
5336 // kill the target
5337 if (mons->type == MONS_ROYAL_JELLY && !mons->is_summoned())
5338 {
5339 // need to do this here, because react_to_damage is never called
5340 mprf("%s explodes violently into a cloud of jellies!",
5341 mons->name(DESC_THE, false).c_str());
5342 trj_spawn_fineff::schedule(&you, mons, mons->pos(), mons->hit_points);
5343 }
5344 else
5345 mprf("%s explodes violently!", mons->name(DESC_THE, false).c_str());
5346 mons->flags |= MF_EXPLODE_KILL;
5347 if (!mons->is_insubstantial())
5348 {
5349 blood_spray(mons->pos(), mons->mons_species(), mons->hit_points / 5);
5350 throw_monster_bits(*mons); // have some fun while we're at it
5351 }
5352
5353 // throw_monster_bits can cause mons to be killed already, e.g. via pain
5354 // bond or dismissing summons
5355 if (mons->alive())
5356 monster_die(*mons, KILL_YOU, NON_MONSTER, false);
5357
5358 if (!mons->alive())
5359 move_player_to_grid(beam.target, false);
5360 else
5361 mpr("You spring back to your original position.");
5362
5363 crawl_state.cancel_cmd_again();
5364 crawl_state.cancel_cmd_repeat();
5365
5366 set_piety(piety_breakpoint(0)); // Reset piety to 1*.
5367
5368 return spret::success;
5369 }
5370
5371 /**
5372 * Permanently choose a class for the player's companion,
5373 * after prompting to make sure the player is certain.
5374 *
5375 * @param ancestor_choice The ancestor's class; should be an ability enum.
5376 * @return Whether the player went through with the choice.
5377 */
hepliaklqana_choose_ancestor_type(int ancestor_choice)5378 bool hepliaklqana_choose_ancestor_type(int ancestor_choice)
5379 {
5380 if (hepliaklqana_ancestor()
5381 && companion_is_elsewhere(hepliaklqana_ancestor()))
5382 {
5383 // ugly hack to avoid dealing with upgrading offlevel ancestors
5384 mpr("You can't make this choice while your ancestor is elsewhere.");
5385 return false;
5386 }
5387
5388 static const map<int, monster_type> ancestor_types = {
5389 { ABIL_HEPLIAKLQANA_TYPE_KNIGHT, MONS_ANCESTOR_KNIGHT },
5390 { ABIL_HEPLIAKLQANA_TYPE_BATTLEMAGE, MONS_ANCESTOR_BATTLEMAGE },
5391 { ABIL_HEPLIAKLQANA_TYPE_HEXER, MONS_ANCESTOR_HEXER },
5392 };
5393
5394 auto ancestor_mapped = map_find(ancestor_types, ancestor_choice);
5395 ASSERT(ancestor_mapped);
5396 const auto ancestor_type = *ancestor_mapped;
5397 const string ancestor_type_name = mons_type_name(ancestor_type, DESC_A);
5398
5399 if (!yesno(make_stringf("Are you sure you want to remember your ancestor "
5400 "as %s?", ancestor_type_name.c_str()).c_str(),
5401 false, 'n'))
5402 {
5403 canned_msg(MSG_OK);
5404 return false;
5405 }
5406
5407 you.props[HEPLIAKLQANA_ALLY_TYPE_KEY] = ancestor_type;
5408
5409 if (monster* ancestor = hepliaklqana_ancestor_mon())
5410 {
5411 ancestor->type = ancestor_type;
5412 give_weapon(ancestor, -1);
5413 ASSERT(ancestor->weapon());
5414 give_shield(ancestor);
5415 set_ancestor_spells(*ancestor);
5416 }
5417
5418 god_speaks(you.religion, "It is so.");
5419 take_note(Note(NOTE_ANCESTOR_TYPE, 0, 0, ancestor_type_name));
5420 const string mile_text
5421 = make_stringf("remembered their ancestor %s as %s.",
5422 hepliaklqana_ally_name().c_str(),
5423 ancestor_type_name.c_str());
5424 mark_milestone("ancestor.class", mile_text);
5425
5426 return true;
5427 }
5428
5429
5430 /**
5431 * Heal the player's ancestor, and apply the Idealised buff for a few turns.
5432 *
5433 * @param fail Whether the effect should fail after checking validity.
5434 * @return Whether the healing succeeded, failed, or was aborted.
5435 */
hepliaklqana_idealise(bool fail)5436 spret hepliaklqana_idealise(bool fail)
5437 {
5438 const mid_t ancestor_mid = hepliaklqana_ancestor();
5439 if (ancestor_mid == MID_NOBODY)
5440 {
5441 mpr("You have no ancestor to preserve!");
5442 return spret::abort;
5443 }
5444
5445 monster *ancestor = monster_by_mid(ancestor_mid);
5446 if (!ancestor || !you.can_see(*ancestor))
5447 {
5448 mprf("%s is not nearby!", hepliaklqana_ally_name().c_str());
5449 return spret::abort;
5450 }
5451
5452 fail_check();
5453
5454 simple_god_message(make_stringf(" grants %s healing and protection!",
5455 ancestor->name(DESC_YOUR).c_str()).c_str());
5456
5457 // 1/3 mhp healed at 0 skill, full at 27 invo
5458 const int healing = ancestor->max_hit_points
5459 * (9 + you.skill(SK_INVOCATIONS)) / 36;
5460
5461 if (ancestor->heal(healing))
5462 {
5463 if (ancestor->hit_points == ancestor->max_hit_points)
5464 simple_monster_message(*ancestor, " is fully restored!");
5465 else
5466 simple_monster_message(*ancestor, " is healed somewhat.");
5467 }
5468
5469 const int dur = random_range(50, 80)
5470 + random2avg(you.skill(SK_INVOCATIONS, 20), 2);
5471 ancestor->add_ench({ ENCH_IDEALISED, 1, &you, dur});
5472 return spret::success;
5473 }
5474
5475 /**
5476 * Prompt to allow the player to choose a target for the Transference ability.
5477 *
5478 * @return The chosen target, or the origin if none was chosen.
5479 */
_get_transference_target()5480 static coord_def _get_transference_target()
5481 {
5482 dist spd;
5483
5484 const int aoe_radius = have_passive(passive_t::transfer_drain) ? 1 : 0;
5485 targeter_transference tgt(&you, aoe_radius);
5486 direction_chooser_args args;
5487 args.hitfunc = &tgt;
5488 args.restricts = DIR_TARGET;
5489 args.mode = TARG_MOBILE_MONSTER;
5490 args.range = LOS_RADIUS;
5491 args.needs_path = false;
5492 args.self = confirm_prompt_type::none;
5493 args.show_floor_desc = true;
5494 args.top_prompt = "Select a target.";
5495
5496 direction(spd, args);
5497
5498 if (!spd.isValid)
5499 return coord_def();
5500 return spd.target;
5501 }
5502
5503 /// Drain any monsters near the destination of Tranference.
_transfer_drain_nearby(coord_def destination)5504 static void _transfer_drain_nearby(coord_def destination)
5505 {
5506 for (adjacent_iterator it(destination); it; ++it)
5507 {
5508 monster* mon = monster_at(*it);
5509 if (!mon || god_protects(mon))
5510 continue;
5511
5512 const int dur = random_range(60, 150);
5513 // 1-2 at 0 skill, 2-6 at 27 skill.
5514 const int degree
5515 = random_range(1 + you.skill_rdiv(SK_INVOCATIONS, 1, 27),
5516 2 + you.skill_rdiv(SK_INVOCATIONS, 4, 27));
5517 if (mon->add_ench(mon_enchant(ENCH_DRAINED, degree, &you, dur)))
5518 simple_monster_message(*mon, " is drained by nostalgia.");
5519 }
5520 }
5521
5522 /**
5523 * Activate Hepliaklqana's Transference ability, swapping the player's
5524 * ancestor with a targeted creature & potentially slowing monsters adjacent
5525 * to the target.
5526 *
5527 * @param fail Whether the effect should fail after checking validity.
5528 * @return Whether the ability succeeded, failed, or was aborted.
5529 */
hepliaklqana_transference(bool fail)5530 spret hepliaklqana_transference(bool fail)
5531 {
5532 monster *ancestor = hepliaklqana_ancestor_mon();
5533 if (!ancestor || !you.can_see(*ancestor))
5534 {
5535 mprf("%s is not nearby!", hepliaklqana_ally_name().c_str());
5536 return spret::abort;
5537 }
5538
5539 coord_def target = _get_transference_target();
5540 if (target.origin())
5541 {
5542 canned_msg(MSG_OK);
5543 return spret::abort;
5544 }
5545
5546 actor* victim = actor_at(target);
5547 const bool victim_visible = victim && you.can_see(*victim);
5548 if ((!victim || !victim_visible)
5549 && !yesno("You can't see anything there. Try transferring anyway?",
5550 true, 'n'))
5551 {
5552 canned_msg(MSG_OK);
5553 return spret::abort;
5554 }
5555
5556 if (victim == ancestor)
5557 {
5558 mprf("You can't transfer your ancestor with %s.",
5559 ancestor->pronoun(PRONOUN_REFLEXIVE).c_str());
5560 return spret::abort;
5561 }
5562
5563 const bool victim_immovable
5564 = victim && (mons_is_tentacle_or_tentacle_segment(victim->type)
5565 || victim->is_stationary());
5566 if (victim_visible && victim_immovable)
5567 {
5568 mpr("You can't transfer that.");
5569 return spret::abort;
5570 }
5571
5572 const coord_def destination = ancestor->pos();
5573 if (victim == &you && !check_moveto(destination, "transfer", false))
5574 return spret::abort;
5575
5576 const bool uninhabitable = victim && !victim->is_habitable(destination);
5577 if (uninhabitable && victim_visible)
5578 {
5579 mprf("%s can't be transferred there.", victim->name(DESC_THE).c_str());
5580 return spret::abort;
5581 }
5582
5583 // we assume the ancestor flies & so can survive anywhere anything can.
5584
5585 fail_check();
5586
5587 if (!victim || uninhabitable || victim_immovable)
5588 {
5589 canned_msg(MSG_NOTHING_HAPPENS);
5590 return spret::success;
5591 }
5592
5593 if (victim->is_player())
5594 {
5595 if (cancel_harmful_move(false))
5596 return spret::abort;
5597 ancestor->move_to_pos(target, true, true);
5598 victim->move_to_pos(destination, true, true);
5599 }
5600 else
5601 ancestor->swap_with(victim->as_monster());
5602
5603 mprf("%s swap%s with %s!",
5604 victim->name(DESC_THE).c_str(),
5605 victim->is_player() ? "" : "s",
5606 ancestor->name(DESC_YOUR).c_str());
5607
5608 check_place_cloud(CLOUD_MIST, target, random_range(10,20), ancestor);
5609 check_place_cloud(CLOUD_MIST, destination, random_range(10,20), ancestor);
5610
5611 if (victim->is_monster())
5612 mons_relocated(victim->as_monster());
5613
5614 ancestor->apply_location_effects(destination);
5615 victim->apply_location_effects(target);
5616 if (victim->is_monster())
5617 behaviour_event(victim->as_monster(), ME_DISTURB, &you, target);
5618
5619 if (have_passive(passive_t::transfer_drain))
5620 _transfer_drain_nearby(target);
5621
5622 return spret::success;
5623 }
5624
5625 /// Prompt to rename your ancestor.
_hepliaklqana_choose_name()5626 static void _hepliaklqana_choose_name()
5627 {
5628 const string old_name = hepliaklqana_ally_name();
5629 string prompt = make_stringf("Remember %s name as what? ",
5630 apostrophise(old_name).c_str());
5631
5632 char buf[18];
5633 int ret = msgwin_get_line(prompt, buf, sizeof buf, nullptr, old_name);
5634 if (ret)
5635 {
5636 canned_msg(MSG_OK);
5637 return;
5638 }
5639
5640 // strip whitespace & colour tags
5641 const string new_name
5642 = trimmed_string(formatted_string::parse_string(buf).tostring());
5643 if (old_name == new_name || !new_name.size())
5644 {
5645 canned_msg(MSG_OK);
5646 return;
5647 }
5648
5649 you.props[HEPLIAKLQANA_ALLY_NAME_KEY] = new_name;
5650 mprf("Yes, %s is definitely a better name.", new_name.c_str());
5651 upgrade_hepliaklqana_ancestor(true);
5652 }
5653
_hepliaklqana_choose_gender()5654 static void _hepliaklqana_choose_gender()
5655 {
5656 static const map<gender_type, string> gender_map =
5657 {
5658 { GENDER_NEUTRAL, "neither" },
5659 { GENDER_MALE, "male" },
5660 { GENDER_FEMALE, "female" },
5661 };
5662
5663 const gender_type current_gender =
5664 (gender_type)you.props[HEPLIAKLQANA_ALLY_GENDER_KEY].get_int();
5665 const string* desc = map_find(gender_map, current_gender);
5666 ASSERT(desc);
5667
5668 mprf(MSGCH_PROMPT,
5669 "Was %s a) male, b) female, or c) neither? (Currently %s.)",
5670 hepliaklqana_ally_name().c_str(),
5671 desc->c_str());
5672
5673 int keyin = toalower(get_ch());
5674 if (!isaalpha(keyin))
5675 {
5676 canned_msg(MSG_OK);
5677 return;
5678 }
5679
5680 static const gender_type gender_options[] = { GENDER_MALE,
5681 GENDER_FEMALE,
5682 GENDER_NEUTRAL };
5683
5684 const uint32_t choice = keyin - 'a';
5685 if (choice >= ARRAYSZ(gender_options))
5686 {
5687 canned_msg(MSG_OK);
5688 return;
5689 }
5690
5691 const gender_type new_gender = gender_options[choice];
5692
5693 if (new_gender == current_gender)
5694 {
5695 canned_msg(MSG_OK);
5696 return;
5697 }
5698
5699 you.props[HEPLIAKLQANA_ALLY_GENDER_KEY] = new_gender;
5700 mprf("%s was always %s, you're pretty sure.",
5701 hepliaklqana_ally_name().c_str(),
5702 map_find(gender_map, new_gender)->c_str());
5703 upgrade_hepliaklqana_ancestor(true);
5704 }
5705
5706 /// Rename and/or re-gender your ancestor.
hepliaklqana_choose_identity()5707 void hepliaklqana_choose_identity()
5708 {
5709 _hepliaklqana_choose_name();
5710 _hepliaklqana_choose_gender();
5711 }
5712
wu_jian_can_wall_jump_in_principle(const coord_def & target)5713 bool wu_jian_can_wall_jump_in_principle(const coord_def& target)
5714 {
5715 if (!have_passive(passive_t::wu_jian_wall_jump)
5716 || !feat_can_wall_jump_against(env.grid(target))
5717 || you.is_stationary()
5718 || you.digging)
5719 {
5720 return false;
5721 }
5722 return true;
5723 }
5724
wu_jian_can_wall_jump(const coord_def & target,string & error_ret)5725 bool wu_jian_can_wall_jump(const coord_def& target, string &error_ret)
5726 {
5727 if (target.distance_from(you.pos()) != 1)
5728 {
5729 error_ret = "Please select an adjacent position to wall jump against.";
5730 return false;
5731 }
5732
5733 if (!wu_jian_can_wall_jump_in_principle(target))
5734 {
5735 if (!feat_can_wall_jump_against(env.grid(target)))
5736 {
5737 error_ret = string("You cannot wall jump against ") +
5738 feature_description_at(target, false, DESC_THE) + ".";
5739 }
5740 else
5741 error_ret = "";
5742 return false;
5743 }
5744
5745 auto wall_jump_direction = (you.pos() - target).sgn();
5746 auto wall_jump_landing_spot = (you.pos() + wall_jump_direction
5747 + wall_jump_direction);
5748
5749 monster* beholder = you.get_beholder(wall_jump_landing_spot);
5750 if (beholder)
5751 {
5752 error_ret = make_stringf("You cannot wall jump away from %s!",
5753 beholder->name(DESC_THE, true).c_str());
5754 return false;
5755 }
5756
5757 monster* fearmonger = you.get_fearmonger(wall_jump_landing_spot);
5758 if (fearmonger)
5759 {
5760 error_ret = make_stringf("You cannot wall jump closer to %s!",
5761 fearmonger->name(DESC_THE, true).c_str());
5762 return false;
5763 }
5764
5765 const actor* landing_actor = actor_at(wall_jump_landing_spot);
5766 if (feat_is_solid(env.grid(you.pos() + wall_jump_direction))
5767 || !in_bounds(wall_jump_landing_spot)
5768 || !you.is_habitable(wall_jump_landing_spot)
5769 || landing_actor)
5770 {
5771 if (landing_actor)
5772 {
5773 error_ret = make_stringf(
5774 "You have no room to wall jump there; %s is in the way.",
5775 landing_actor->observable()
5776 ? landing_actor->name(DESC_THE).c_str()
5777 : "something you can't see");
5778 }
5779 else
5780 error_ret = "You have no room to wall jump there.";
5781 return false;
5782 }
5783 error_ret = "";
5784 return true;
5785 }
5786
5787 /**
5788 * Do a walljump.
5789 *
5790 * This doesn't check whether there's space; see `wu_jian_can_wall_jump`.
5791 * It does check whether the landing spot is safe, excluded, etc.
5792 *
5793 * @param targ the movement target (i.e. the wall being moved against).
5794 * @return whether the jump culminated.
5795 */
wu_jian_do_wall_jump(coord_def targ)5796 bool wu_jian_do_wall_jump(coord_def targ)
5797 {
5798 // whether there's space in the first place is checked earlier
5799 // in wu_jian_can_wall_jump.
5800 auto wall_jump_direction = (you.pos() - targ).sgn();
5801 auto wall_jump_landing_spot = (you.pos() + wall_jump_direction
5802 + wall_jump_direction);
5803 if (!check_moveto(wall_jump_landing_spot, "wall jump"))
5804 {
5805 you.turn_is_over = false;
5806 return false;
5807 }
5808
5809 auto initial_position = you.pos();
5810 move_player_to_grid(wall_jump_landing_spot, false);
5811 wu_jian_wall_jump_effects();
5812 remove_water_hold();
5813
5814 int wall_jump_modifier = (you.attribute[ATTR_SERPENTS_LASH] != 1) ? 2
5815 : 1;
5816
5817 you.time_taken = player_speed() * wall_jump_modifier
5818 * player_movement_speed();
5819 you.time_taken = div_rand_round(you.time_taken, 10);
5820
5821 // need to set this here in case serpent's lash isn't active
5822 you.turn_is_over = true;
5823 request_autopickup();
5824 wu_jian_post_move_effects(true, initial_position);
5825
5826 return true;
5827 }
5828
wu_jian_wall_jump_ability()5829 spret wu_jian_wall_jump_ability()
5830 {
5831 ASSERT(!crawl_state.game_is_arena());
5832
5833 if (crawl_state.is_replaying_keys())
5834 {
5835 crawl_state.cancel_cmd_all("You can't repeat a wall jump.");
5836 return spret::abort;
5837 }
5838
5839 if (you.digging)
5840 {
5841 you.digging = false;
5842 mpr("You retract your mandibles.");
5843 }
5844
5845 string wj_error;
5846 bool has_targets = false;
5847
5848 for (adjacent_iterator ai(you.pos()); ai; ++ai)
5849 if (wu_jian_can_wall_jump(*ai, wj_error))
5850 {
5851 has_targets = true;
5852 break;
5853 }
5854
5855 if (!has_targets)
5856 {
5857 mpr("There is nothing to wall jump against here.");
5858 return spret::abort;
5859 }
5860
5861 if (you.is_nervous())
5862 {
5863 mpr("You are too terrified to wall jump!");
5864 return spret::abort;
5865 }
5866
5867 if (you.attribute[ATTR_HELD])
5868 {
5869 mprf("You cannot wall jump while caught in a %s.",
5870 get_trapping_net(you.pos()) == NON_ITEM ? "web" : "net");
5871 return spret::abort;
5872 }
5873
5874 if (!you.attempt_escape())
5875 return spret::fail;
5876
5877 // query for location:
5878 dist beam;
5879
5880 while (1)
5881 {
5882 direction_chooser_args args;
5883 args.restricts = DIR_TARGET;
5884 args.mode = TARG_ANY;
5885 args.range = 1;
5886 args.needs_path = false; // TODO: overridden by hitfunc?
5887 args.top_prompt = "Aiming: <white>Wall Jump</white>";
5888 args.self = confirm_prompt_type::cancel;
5889 targeter_walljump tgt;
5890 tgt.obeys_mesmerise = true;
5891 args.hitfunc = &tgt;
5892 {
5893 // TODO: make this unnecessary
5894 direction_chooser dc(beam, args);
5895 dc.needs_path = false;
5896 dc.choose_direction();
5897 }
5898 if (crawl_state.seen_hups)
5899 {
5900 clear_messages();
5901 mpr("Cancelling wall jump due to HUP.");
5902 return spret::abort;
5903 }
5904
5905 if (!beam.isValid || beam.target == you.pos())
5906 return spret::abort; // early return
5907
5908 if (wu_jian_can_wall_jump(beam.target, wj_error))
5909 break;
5910 }
5911
5912 if (!wu_jian_do_wall_jump(beam.target))
5913 return spret::abort;
5914
5915 crawl_state.cancel_cmd_again();
5916 crawl_state.cancel_cmd_repeat();
5917
5918 apply_barbs_damage();
5919 return spret::success;
5920 }
5921
wu_jian_heavenly_storm()5922 void wu_jian_heavenly_storm()
5923 {
5924 mprf(MSGCH_GOD, "The air is filled with shimmering golden clouds!");
5925 wu_jian_sifu_message(" says: The storm will not cease as long as you "
5926 "keep fighting, disciple!");
5927
5928 for (radius_iterator ai(you.pos(), 2, C_SQUARE, LOS_SOLID); ai; ++ai)
5929 if (!cell_is_solid(*ai))
5930 place_cloud(CLOUD_GOLD_DUST, *ai, 5 + random2(5), &you);
5931
5932 you.set_duration(DUR_HEAVENLY_STORM, random_range(2, 3));
5933 you.props[WU_JIAN_HEAVENLY_STORM_KEY] = WU_JIAN_HEAVENLY_STORM_INITIAL;
5934 invalidate_agrid(true);
5935 }
5936
okawaru_remove_heroism()5937 void okawaru_remove_heroism()
5938 {
5939 mprf(MSGCH_DURATION, "You feel like a meek peon again.");
5940 you.duration[DUR_HEROISM] = 0;
5941 you.redraw_evasion = true;
5942 you.redraw_armour_class = true;
5943 }
5944
okawaru_remove_finesse()5945 void okawaru_remove_finesse()
5946 {
5947 mprf(MSGCH_DURATION, "%s", you.hands_act("slow", "down.").c_str());
5948 you.duration[DUR_FINESSE] = 0;
5949 }
5950