/
god-abil.cc
6114 lines (5311 loc) · 184 KB
/
god-abil.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file
* @brief God-granted abilities.
**/
#include "AppHdr.h"
#include "god-abil.h"
#include <cmath>
#include <numeric>
#include <sstream>
#include "act-iter.h"
#include "areas.h"
#include "artefact.h"
#include "art-enum.h"
#include "attitude-change.h"
#include "bloodspatter.h"
#include "branch.h"
#include "chardump.h"
#include "cloud.h"
#include "colour.h"
#include "coordit.h"
#include "curse-type.h"
#include "dactions.h"
#include "database.h"
#include "delay.h"
#include "describe.h"
#include "dgn-overview.h"
#include "directn.h"
#include "dungeon.h"
#include "english.h"
#include "fight.h"
#include "files.h"
#include "fineff.h"
#include "format.h" // formatted_string
#include "god-blessing.h"
#include "god-companions.h"
#include "god-item.h"
#include "god-passive.h"
#include "hints.h"
#include "hiscores.h"
#include "invent.h"
#include "item-prop.h"
#include "item-status-flag-type.h"
#include "items.h"
#include "item-use.h"
#include "libutil.h"
#include "losglobal.h"
#include "macro.h"
#include "mapmark.h"
#include "maps.h"
#include "message.h"
#include "mon-act.h"
#include "mon-behv.h"
#include "mon-death.h"
#include "mon-gear.h" // H: give_weapon()/give_armour()
#include "mon-place.h"
#include "mon-poly.h"
#include "mon-tentacle.h"
#include "mon-util.h"
#include "movement.h"
#include "mutation.h"
#include "notes.h"
#include "ouch.h"
#include "output.h"
#include "place.h"
#include "player-equip.h"
#include "player-stats.h"
#include "potion.h"
#include "prompt.h"
#include "religion.h"
#include "shout.h"
#include "skill-menu.h"
#include "spl-book.h"
#include "spl-goditem.h"
#include "spl-monench.h"
#include "spl-transloc.h"
#include "spl-util.h"
#include "spl-wpnench.h"
#include "sprint.h"
#include "stairs.h"
#include "state.h"
#include "stringutil.h"
#include "tag-version.h"
#include "target.h"
#include "teleport.h" // monster_teleport
#include "terrain.h"
#ifdef USE_TILE
#include "rltiles/tiledef-main.h"
#endif
#include "timed-effects.h"
#include "traps.h"
#include "viewchar.h"
#include "view.h"
static bool _player_sacrificed_arcana();
// Load the sacrifice_def definition and the sac_data array.
#include "sacrifice-data.h"
/** Would a god currently allow using a one-time six-star ability?
* Does not check whether the god actually grants such an ability.
*/
bool can_do_capstone_ability(god_type god)
{
// Worshippers of Ignis can use their capstone with any amount of piety
int pbreak = (god == GOD_IGNIS) ? -1 : 5;
return in_good_standing(god, pbreak) && !you.one_time_ability_used[god];
}
static const char *_god_blessing_description(god_type god)
{
switch (god)
{
case GOD_SHINING_ONE:
return "blessed by the Shining One";
case GOD_LUGONU:
return "corrupted by Lugonu";
case GOD_KIKUBAAQUDGHA:
return "bloodied by Kikubaaqudgha";
default:
return "touched by the gods";
}
}
/**
* Perform a capstone god ability that blesses a weapon with the god's
* brand.
* @param god The god performing the blessing.
* @param brand The brand being granted.
* @param colour The colour to flash when the weapon is branded.
* @returns True if the weapon was successfully branded, false otherwise.
*/
bool bless_weapon(god_type god, brand_type brand, colour_t colour)
{
ASSERT(can_do_capstone_ability(god));
int item_slot = prompt_invent_item("Brand which weapon?",
menu_type::invlist,
OSEL_BLESSABLE_WEAPON, OPER_ANY,
invprompt_flag::escape_only);
if (item_slot == PROMPT_NOTHING || item_slot == PROMPT_ABORT)
{
canned_msg(MSG_OK);
return false;
}
item_def& wpn(you.inv[item_slot]);
// Only TSO allows blessing ranged weapons.
if (!is_brandable_weapon(wpn, brand == SPWPN_HOLY_WRATH, true))
return false;
string prompt = "Do you wish to have " + wpn.name(DESC_YOUR)
+ " ";
if (brand == SPWPN_PAIN)
prompt += "bloodied with pain";
else if (brand == SPWPN_DISTORTION)
prompt += "corrupted with distortion";
else
prompt += "blessed with holy wrath";
prompt += "?";
if (!yesno(prompt.c_str(), true, 'n'))
{
canned_msg(MSG_OK);
return false;
}
if (you.duration[DUR_EXCRUCIATING_WOUNDS]) // just in case
{
ASSERT(you.weapon());
end_weapon_brand(*you.weapon());
}
string old_name = wpn.name(DESC_A);
set_equip_desc(wpn, ISFLAG_GLOWING);
set_item_ego_type(wpn, OBJ_WEAPONS, brand);
enchant_weapon(wpn, true);
enchant_weapon(wpn, true);
if (god == GOD_SHINING_ONE)
{
convert2good(wpn);
if (is_blessed_convertible(wpn))
origin_acquired(wpn, GOD_SHINING_ONE);
}
else if (is_evil_god(god))
convert2bad(wpn);
you.wield_change = true;
you.one_time_ability_used.set(god);
calc_mp(); // in case the old brand was antimagic,
you.redraw_armour_class = true; // protection,
you.redraw_evasion = true; // or evasion
const string desc = old_name + " " + _god_blessing_description(god);
take_note(Note(NOTE_ID_ITEM, 0, 0, wpn.name(DESC_A), desc));
wpn.flags |= ISFLAG_NOTED_ID;
wpn.props[FORCED_ITEM_COLOUR_KEY] = colour;
mprf(MSGCH_GOD, "Your %s shines brightly!", wpn.name(DESC_QUALNAME).c_str());
flash_view(UA_PLAYER, colour);
simple_god_message(" booms: Use this gift wisely!");
you.one_time_ability_used.set(you.religion);
take_note(Note(NOTE_GOD_GIFT, you.religion));
if (god == GOD_SHINING_ONE)
{
holy_word(100, HOLY_WORD_TSO, you.pos(), true);
// Un-bloodify surrounding squares.
for (radius_iterator ri(you.pos(), 3, C_SQUARE, LOS_SOLID); ri; ++ri)
{
if (is_bloodcovered(*ri))
env.pgrid(*ri) &= ~FPROP_BLOODY;
if (env.grid(*ri) == DNGN_FOUNTAIN_BLOOD)
dungeon_terrain_changed(*ri, DNGN_FOUNTAIN_BLUE);
}
}
else if (god == GOD_KIKUBAAQUDGHA)
{
you.gift_timeout = 1; // no protection during pain branding weapon
torment(&you, TORMENT_KIKUBAAQUDGHA, you.pos());
you.gift_timeout = 0; // protection after pain branding weapon
// Bloodify surrounding squares (75% chance).
for (radius_iterator ri(you.pos(), 2, C_SQUARE, LOS_SOLID); ri; ++ri)
if (!one_chance_in(4))
maybe_bloodify_square(*ri);
}
#ifndef USE_TILE_LOCAL
// Allow extra time for the flash to linger.
scaled_delay(1000);
#endif
return true;
}
static int _gold_to_donation(int gold)
{
return static_cast<int>((gold * log((float)gold)) / MAX_PIETY);
}
// donate gold to gain piety distributed over time
bool zin_donate_gold()
{
if (you.gold == 0)
{
mpr("You have nothing to donate!");
return false;
}
if (!yesno("Do you wish to donate half of your money?", true, 'n'))
{
canned_msg(MSG_OK);
return false;
}
const int donation_cost = (you.gold / 2) + 1;
const int donation = _gold_to_donation(donation_cost);
#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_SACRIFICE) || defined(DEBUG_PIETY)
mprf(MSGCH_DIAGNOSTICS, "A donation of $%d amounts to an "
"increase of piety by %d.", donation_cost, donation);
#endif
// Take a note of the donation.
take_note(Note(NOTE_DONATE_MONEY, donation_cost));
you.attribute[ATTR_DONATIONS] += donation_cost;
you.del_gold(donation_cost);
if (donation < 1)
{
simple_god_message(" finds your generosity lacking.");
return true;
}
you.duration[DUR_PIETY_POOL] += donation;
if (you.duration[DUR_PIETY_POOL] > 30000)
you.duration[DUR_PIETY_POOL] = 30000;
const int estimated_piety =
min(MAX_PENANCE + MAX_PIETY, you.piety + you.duration[DUR_PIETY_POOL]);
if (player_under_penance())
{
if (estimated_piety >= you.penance[GOD_ZIN])
mpr("You feel that you will soon be absolved of all your sins.");
else
mpr("You feel that your burden of sins will soon be lighter.");
}
else
{
string result = "You feel that " + god_name(GOD_ZIN) + " will soon be ";
result +=
(estimated_piety >= piety_breakpoint(5)) ? "exalted by your worship" :
(estimated_piety >= piety_breakpoint(4)) ? "extremely pleased with you" :
(estimated_piety >= piety_breakpoint(3)) ? "greatly pleased with you" :
(estimated_piety >= piety_breakpoint(2)) ? "most pleased with you" :
(estimated_piety >= piety_breakpoint(1)) ? "pleased with you" :
(estimated_piety >= piety_breakpoint(0)) ? "aware of your devotion"
: "noncommittal";
result += (donation >= 30 && you.piety < piety_breakpoint(5)) ? "!" : ".";
mpr(result);
}
return true;
}
static void _zin_saltify(monster* mon);
static const char * const book_of_zin[][3] =
{
{
"It was the word of Zin that there would not be @sin_noun@...",
"...and did the people not suffer until they had @smitten@...",
"...the @sinners@, after which all was well?",
},
{
"The voice of Zin, pure and clear, did say that the @sinners@...",
"...were not @virtuous@! And hearing this, the people rose up...",
"...and embraced @virtue@, for they feared Zin's wrath.",
},
{
"Zin spoke of the doctrine of @virtue@, and...",
"...saw the @sinners@ filled with fear, for they were...",
"...@sin_adj@ and knew Zin's wrath would come for them.",
},
{
"And so Zin bade the @sinners@ to come before...",
"...the altar, that judgement might be passed...",
"...upon those who were not @virtuous@.",
},
{
"To the devout, Zin provideth. From the rest...",
"...ye @sinners@, ye guilty...",
"...of @sin_noun@, Zin taketh.",
},
{
"Zin saw the @sin_noun@ of the @sinners@, and...",
"...was displeased, for did the law not say that...",
"...those who did not become @virtuous@ would be @smitten@?",
},
{
"Zin said that @virtue@ shall be the law of the land, and...",
"...those who turn to @sin_noun@ will be @smitten@. This was fair...",
"...and just, and not a voice dissented.",
},
{
"Damned, damned be the @sinners@ and...",
"...all else who abandon @virtue@! Let them...",
"...be @smitten@ by the jurisprudence of Zin!",
},
{
"And Zin said to all in attendance, 'Which of ye...",
"...number among the @sinners@? Come before me, that...",
"...I may @smite@ you now for your @sin_noun@!'",
},
{
"Yea, I say unto thee, bring forth...",
"...the @sinners@ that they may know...",
"...the wrath of Zin, and thus be @smitten@!",
},
{
"In a great set of silver scales are weighed the...",
"...souls of the @sinners@, and with their @sin_adj@...",
"...ways, the balance hath tipped against them!",
},
{
"It is just that the @sinners@ shall be @smitten@...",
"...in due time, for @virtue@ is what Zin has declared...",
"...the law of the land, and Zin's word is law!",
},
{
"Thus the people made the covenant of @virtue@ with...",
"...Zin, and all was good, for they knew that the...",
"...@sinners@ would trouble them no longer.",
},
{
"What of the @sinners@? @Smitten@ for their...",
"...@sin_noun@ they shall be! Zin will @smite@ them again...",
"...and again, and again!",
},
{
"And lo, the wrath of Zin did find...",
"...them wherever they hid, and the @sinners@...",
"...were @smitten@ for their @sin_noun@!",
},
{
"Zin looked out upon the remains of the @sinners@...",
"...and declared it good that they had been...",
"...@smitten@. And thus justice was done.",
},
{
"The law of Zin demands thee...",
"...be @virtuous@, and that the punishment for @sin_noun@...",
"...shall be swift and harsh!",
},
{
"It was then that Zin bade them...",
"...not to stray from @virtue@, lest...",
"...they become as damned as the @sinners@.",
},
{
"Only the @virtuous@ shall be judged worthy, and...",
"...all the @sinners@ will be found wanting. Such is...",
"...the word of Zin, and such is the law!",
},
{
"To those who would swear an oath of @virtue@ on my altar...",
"...I bring ye salvation. To the rest, ye @sinners@...",
"...and the @sin_adj@, the name of Zin shall be thy damnation.",
},
{
"And Zin decreed that the people would be...",
"...protected from @sin_noun@ in all its forms, and...",
"...preserved in their @virtue@ for all the days to come.",
},
{
"For those who would enter Zin's holy bosom...",
"...and live in @virtue@, Zin provideth. Such is...",
"...the covenant, and such is the way of things.",
},
{
"Zin hath not damned the @sinners@, but it is they...",
"...that have damned themselves for their @sin_noun@, for...",
"...did Zin not decree that to be @sin_adj@ was wrong?",
},
{
"And Zin, furious at their @sin_noun@, held...",
"...aloft a silver sceptre! The @sinners@...",
"...were @smitten@, and thus the way of things was maintained.",
},
{
"When the law of the land faltered, Zin rose...",
"...from the silver throne, and the @sinners@ were...",
"...@smitten@. And it was thus that the law was made good.",
},
{
"Zin descended from on high in a silver chariot...",
"...to @smite@ the @sinners@ for their...",
"...@sin_noun@, and thus judgement was rendered.",
},
{
"The @sinners@ stood before Zin, and in that instant...",
"...they knew they would be found guilty of @sin_noun@...",
"...for that is the word of Zin, and Zin's word is law.",
},
};
static const char * const sinner_text[] =
{
"unbelievers",
"heretics",
"guilty",
"hordes of the Abyss",
"bastard children of Xom",
"amorphous wretches",
"fetid masses",
"agents of filth",
"squalid dregs",
"legions of the damned",
"servants of Hell",
"forces of darkness",
};
// First column is adjective, then noun.
static const char * const sin_text[][2] =
{
{ "unfaithful", "unfaithfulness" },
{ "disloyal", "disloyalty" },
{ "doubting", "doubt" },
{ "chaotic", "chaos" },
{ "discordant", "discord" },
{ "anarchic", "anarchy" },
{ "unclean", "uncleanliness" },
{ "impure", "impurity" },
{ "contaminated", "contamination" },
{ "profane", "profanity" },
{ "blasphemous", "blasphemy" },
{ "sacrilegious", "sacrilege" },
};
// First column is adjective, then noun.
static const char * const virtue_text[][2] =
{
{ "faithful", "faithfulness" },
{ "loyal", "loyalty" },
{ "believing", "belief" },
{ "ordered", "order" },
{ "harmonic", "harmony" },
{ "lawful", "lawfulness" },
{ "clean", "cleanliness" },
{ "pure", "purity" },
{ "hygienic", "hygiene" },
{ "reverent", "reverence" },
{ "pious", "piety" },
{ "obedient", "obedience" },
};
// First column is infinitive, then gerund.
static const char * const smite_text[][2] =
{
{ "purify", "purified" },
{ "censure", "censured" },
{ "condemn", "condemned" },
{ "strike down", "struck down" },
{ "expel", "expelled" },
{ "oust", "ousted" },
{ "smite", "smitten" },
{ "castigate", "castigated" },
{ "rebuke", "rebuked" },
};
/** Get the verse to recite this turn.
*
* @param seed The seed to keep the book coherent between turns.
* @param prayertype One of the four recite types.
* @param step -1: We're either starting or stopping, so we just want the passage name.
* 2/1/0: That many rounds are left. So, if step = 2, we want to show the passage #1/3.
* @returns the verse to be said this turn, or if step == -1, which verse it is.
*/
string zin_recite_text(const int seed, const int prayertype, int step)
{
// To have deterministic passages we extract portions of the seed.
// We use trits: "digits" in the base-3 expansion of seed.
COMPILE_CHECK(ARRAYSZ(book_of_zin) == 27);
const int chapter = seed % 27;
const int verse = (seed/27) % 81;
// Change step to turn 1, turn 2, or turn 3.
if (step > -1)
{
step = abs(step-3);
if (step > 3)
step = 1;
}
else
{
const string bookname = (prayertype == RECITE_CHAOTIC) ? "Abominations" :
(prayertype == RECITE_IMPURE) ? "Ablutions" :
(prayertype == RECITE_HERETIC) ? "Apostates" :
(prayertype == RECITE_UNHOLY) ? "Anathema" :
"Bugginess";
ostringstream out;
out << bookname << ' ' << (chapter + 1) << ':' << (verse + 1);
return out.str();
}
// These mad-libs are deterministically derived from the verse number
// and prayer type. Sins and virtues are paired, so use the same sub-
// seed. Sinners, sins, and smites are uncorrelated so do not share
// trits.
COMPILE_CHECK(ARRAYSZ(sinner_text) == 12);
COMPILE_CHECK(ARRAYSZ(sin_text) == 12);
COMPILE_CHECK(ARRAYSZ(virtue_text) == 12);
const int sinner_seed = verse % 3 + prayertype * 3;
const int sin_seed = (verse/27) % 3 + prayertype * 3;
const int virtue_seed = sin_seed;
COMPILE_CHECK(ARRAYSZ(smite_text) == 9);
const int smite_seed = (verse/3) % 9;
string recite = book_of_zin[chapter][step-1];
const map<string, string> replacements =
{
{ "sinners", sinner_text[sinner_seed] },
{ "sin_adj", sin_text[sin_seed][0] },
{ "sin_noun", sin_text[sin_seed][1] },
{ "virtuous", virtue_text[virtue_seed][0] },
{ "virtue", virtue_text[virtue_seed][1] },
{ "smite", smite_text[smite_seed][0] },
{ "smitten", smite_text[smite_seed][1] },
{ "Smitten", uppercase_first(smite_text[smite_seed][1]) },
};
return replace_keys(recite, replacements);
}
/** How vulnerable to RECITE_HERETIC is this monster?
*
* @param[in] mon The monster to check.
* @returns the susceptibility.
*/
static int _heretic_recite_weakness(const monster *mon)
{
int degree = 0;
// Sleeping or paralyzed monsters will wake up or still perceive their
// surroundings, respectively. So, you can still recite to them.
if (mons_intel(*mon) >= I_HUMAN
&& !(mon->has_ench(ENCH_DUMB) || mons_is_confused(*mon)))
{
// In the eyes of Zin, everyone is a sinner until proven otherwise!
degree++;
// Any priest is a heretic...
if (mon->is_priest())
degree++;
// Or those who believe in themselves...
if (mon->type == MONS_DEMIGOD)
degree++;
// ...but evil gods are worse.
if (is_evil_god(mon->god) || is_unknown_god(mon->god))
degree++;
// (The above mean that worshipers will be treated as
// priests for reciting, even if they aren't actually.)
// Sanity check: monsters that won't attack you, and aren't
// priests/evil, don't get recited against.
if (mon->wont_attack() && degree <= 1)
degree = 0;
// Sanity check: monsters that are holy, know holy spells, or worship
// holy gods aren't heretics.
if (mon->is_holy() || is_good_god(mon->god))
degree = 0;
}
return degree;
}
/** Check whether this monster might be influenced by Recite.
*
* @param[in] mon The monster to check.
* @param[out] eligibility A vector, indexed by recite_type, that indicates
* which recitation types the monster is affected by, if any:
* eligibility[RECITE_FOO] is nonzero if the monster is affected
* by RECITE_FOO.
* @param quiet[in] Whether to suppress messaging.
* @returns Whether the monster is eligible for recite. If the monster can be
* recited to, the eligibility vector indicates the valid types of
* recite.
*/
recite_eligibility zin_check_recite_to_single_monster(const monster *mon,
recite_counts &eligibility,
bool quiet)
{
ASSERT(mon);
// Can't recite anyway if they were recently recited to.
if (mon->has_ench(ENCH_RECITE_TIMER))
return RE_RECITE_TIMER;
const mon_holy_type holiness = mon->holiness();
eligibility.init(0);
// Anti-chaos prayer: Hits things vulnerable to silver, or with chaotic spells/gods.
eligibility[RECITE_CHAOTIC] = mon->how_chaotic(true);
// Anti-impure prayer: Hits things that Zin hates in general.
// Don't look at the monster's god; that's what RECITE_HERETIC is for.
eligibility[RECITE_IMPURE] = mon->how_unclean(false);
// Anti-unholy prayer: Hits demons and incorporeal undead.
if (holiness & MH_UNDEAD && mon->is_insubstantial()
|| holiness & MH_DEMONIC)
{
eligibility[RECITE_UNHOLY]++;
}
// Anti-heretic prayer: Hits intelligent monsters, especially priests.
eligibility[RECITE_HERETIC] = _heretic_recite_weakness(mon);
#ifdef DEBUG_DIAGNOSTICS
if (!quiet)
{
string elig;
for (int i = 0; i < NUM_RECITE_TYPES; i++)
elig += '0' + eligibility[i];
dprf("Eligibility: %s", elig.c_str());
}
#else
UNUSED(quiet);
#endif
bool maybe_eligible = false;
// Checking to see whether they were eligible for anything at all.
for (int i = 0; i < NUM_RECITE_TYPES; i++)
if (eligibility[i] > 0)
maybe_eligible = true;
if (maybe_eligible)
{
// Too high HD to be affected at current power.
if (mon->get_hit_dice() >= zin_recite_power())
return RE_TOO_STRONG;
return RE_ELIGIBLE;
}
return RE_INELIGIBLE;
}
int zin_recite_power()
{
// Resistance is now based on HD.
// Anything at or above (30+30)/2 = 30 'power' (HD) is completely immune.
const int power_mult = 10;
const int invo_power = you.skill_rdiv(SK_INVOCATIONS, power_mult)
+ 3 * power_mult;
const int piety_power = you.piety * 3 / 2;
return (invo_power + piety_power) / 2 / power_mult;
}
bool zin_check_able_to_recite(bool quiet)
{
if (you.duration[DUR_RECITE])
{
if (!quiet)
mpr("Finish your current sermon first, please.");
return false;
}
if (you.duration[DUR_RECITE_COOLDOWN])
{
if (!quiet)
mpr("You're not ready to recite again yet.");
return false;
}
return true;
}
vector<coord_def> find_recite_targets()
{
vector<coord_def> result;
recite_counts eligibility;
for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
{
if (you.can_see(**mi)
&& zin_check_recite_to_single_monster(*mi, eligibility,
true) == RE_ELIGIBLE)
{
result.push_back((*mi)->pos());
}
}
return result;
}
/**
* Check whether there are monsters who might be influenced by Recite.
* If prayertype is null, we're just checking whether we can.
* Otherwise we're actually reciting, and may need to present a menu.
*
* @param quiet Whether to suppress messages.
* @return 0 if no eligible monsters were found.
* @return 1 if an eligible audience was found.
* @return -1 if the only monsters found cannot currently be affected (either
* due to lack of recite power, or already having been affected)
*
*/
int zin_check_recite_to_monsters(bool quiet)
{
bool found_temp_ineligible = false;
bool found_eligible = false;
for (radius_iterator ri(you.pos(), LOS_DEFAULT); ri; ++ri)
{
const monster *mon = monster_at(*ri);
if (!mon || !you.can_see(*mon))
continue;
recite_counts retval;
switch (zin_check_recite_to_single_monster(mon, retval, quiet))
{
case RE_TOO_STRONG:
case RE_RECITE_TIMER:
found_temp_ineligible = true;
// Intentional fallthrough
case RE_INELIGIBLE:
continue;
case RE_ELIGIBLE:
found_eligible = true;
}
}
if (!found_eligible && !found_temp_ineligible)
{
if (!quiet)
dprf("No audience found!");
return 0;
}
else if (!found_eligible && found_temp_ineligible)
{
if (!quiet)
dprf("No sensible audience found!");
return -1;
}
else
return 1; // We just recite against everything.
}
enum class zin_eff
{
nothing,
daze,
confuse,
paralyse,
smite,
blind,
silver_corona,
antimagic,
mute,
mad,
dumb,
ignite_chaos,
saltify,
holy_word,
};
bool zin_recite_to_single_monster(const coord_def& where)
{
// That's a pretty good sanity check, I guess.
ASSERT(you_worship(GOD_ZIN));
monster* mon = monster_at(where);
// Once you're already reciting, invis is ok.
if (!mon || !cell_see_cell(where, you.pos(), LOS_DEFAULT))
return false;
recite_counts eligibility;
bool affected = false;
if (zin_check_recite_to_single_monster(mon, eligibility) != RE_ELIGIBLE)
return false;
recite_type prayertype = RECITE_HERETIC;
for (int i = NUM_RECITE_TYPES - 1; i >= RECITE_HERETIC; i--)
{
if (eligibility[i] > 0)
{
prayertype = static_cast <recite_type>(i);
break;
}
}
// Second check: because this affects the whole screen over several turns,
// its effects are staggered. There's a 50% chance per monster, per turn,
// that nothing will happen - so the cumulative odds of nothing happening
// are one in eight, since you recite three times.
if (coinflip())
return false;
const int power = zin_recite_power();
// Old recite was mostly deterministic, which is bad.
const int resist = mon->get_hit_dice() + random2(6);
const int check = power - resist;
// We abort if we didn't *beat* their HD - but first we might get a cute message.
if (mon->can_speak() && one_chance_in(5))
{
if (check < -10)
simple_monster_message(*mon, " guffaws at your puny god.");
else if (check < -5)
simple_monster_message(*mon, " sneers at your recitation.");
}
if (check <= 0)
return false;
// To what degree are they eligible for this prayertype?
const int degree = eligibility[prayertype];
const bool minor = degree <= (prayertype == RECITE_HERETIC ? 2 : 1);
const int spellpower = power * 2 + degree * 20;
zin_eff effect = zin_eff::nothing;
switch (prayertype)
{
case RECITE_HERETIC:
if (degree == 1)
{
if (mon->asleep())
break;
// This is the path for 'conversion' effects.
// Their degree is only 1 if they weren't a priest,
// a worshiper of an evil or chaotic god, etc.
// Right now, it only has the 'failed conversion' effects, though.
// This branch can't hit sleeping monsters - until they wake up.
if (check < 5)
effect = zin_eff::daze;
else if (check < 10)
{
if (coinflip())
effect = zin_eff::confuse;
else
effect = zin_eff::daze;
}
else if (check < 15)
effect = zin_eff::confuse;
else
{
if (one_chance_in(3))
effect = zin_eff::paralyse;
else
effect = zin_eff::confuse;
}
}
else
{
// This is the path for 'smiting' effects.
// Their degree is only greater than 1 if
// they're unable to be redeemed.
if (check < 5)
{
if (coinflip())
effect = zin_eff::confuse;
else
effect = zin_eff::smite;
}
else if (check < 10)
{
if (one_chance_in(3))
effect = zin_eff::blind;
else if (mon->antimagic_susceptible())
effect = zin_eff::antimagic;
else
effect = zin_eff::silver_corona;
}
else if (check < 15)
{
if (one_chance_in(3))
effect = zin_eff::blind;
else if (coinflip())
effect = zin_eff::paralyse;
else
effect = zin_eff::mute;
}
else
{
if (coinflip())
effect = zin_eff::mad;
else
effect = zin_eff::dumb;
}
}
break;
case RECITE_CHAOTIC:
if (check < 5)
effect = zin_eff::smite;
else if (check < 10)
effect = zin_eff::silver_corona;
else if (check < 15)
effect = zin_eff::ignite_chaos;
else
effect = zin_eff::saltify;
break;
case RECITE_IMPURE:
if (check < 5)
effect = zin_eff::smite;
else if (check < 10)
effect = zin_eff::silver_corona;
else if (check < 15)
{