Fix save compatibility props initialization
[crawl.git] / crawl-ref / source / god-passive.cc
1 #include "AppHdr.h"
2
3 #include "god-passive.h"
4
5 #include <algorithm>
6 #include <cmath>
7
8 #include "act-iter.h"
9 #include "areas.h"
10 #include "artefact.h"
11 #include "art-enum.h"
12 #include "branch.h"
13 #include "chardump.h"
14 #include "cloud.h"
15 #include "coordit.h"
16 #include "directn.h"
17 #include "env.h"
18 #include "eq-type-flags.h"
19 #include "fight.h"
20 #include "files.h"
21 #include "fprop.h"
22 #include "god-abil.h"
23 #include "god-prayer.h"
24 #include "invent.h" // in_inventory
25 #include "item-name.h"
26 #include "item-prop.h"
27 #include "item-status-flag-type.h"
28 #include "items.h"
29 #include "libutil.h"
30 #include "map-knowledge.h"
31 #include "melee-attack.h"
32 #include "message.h"
33 #include "mon-cast.h"
34 #include "mon-place.h"
35 #include "mon-util.h"
36 #include "output.h"
37 #include "religion.h"
38 #include "shout.h"
39 #include "skills.h"
40 #include "spl-clouds.h"
41 #include "state.h"
42 #include "stringutil.h"
43 #include "tag-version.h"
44 #include "terrain.h"
45 #include "throw.h"
46 #include "unwind.h"
47
48 // TODO: template out the differences between this and god_power.
49 // TODO: use the display method rather than dummy powers in god_powers.
50 // TODO: finish using these for implementing passive abilities.
51 struct god_passive
52 {
53     // 1-6 means it unlocks at that many stars of piety;
54     // 0 means it is always present when in good standing with the god;
55     // -1 means it is present even under penance;
56     int rank;
57     passive_t pasv;
58     /** Message to be given on gain of this passive.
59      *
60      * If the string begins with an uppercase letter, it is treated as
61      * a complete sentence. Otherwise, if it contains the substring " NOW ",
62      * the string "You " is prepended. Otherwise, the string "You NOW " is
63      * prepended to the message, with the " NOW " then being replaced.
64      *
65      * The substring "GOD" is replaced with the name of the god.
66      * The substring " NOW " is replaced with " now " for messages about
67      * gaining the ability, or " " for messages about having the ability.
68      *
69      * Examples:
70      *   "have super powers"
71      *     => "You have super powers", "You now have super powers."
72      *   "are NOW powerful"
73      *     => "You are powerful", "You are now powerful."
74      *   "Your power is NOW great"
75      *     => "Your power is great", "Your power is now great"
76      *   "GOD NOW makes you powerful"
77      *     => "Moloch makes you powerful", "Moloch now makes you powerful"
78      *   "GOD grants you a boon"
79      *     => "Moloch grants you a boon" (in all situations)
80      *
81      * FIXME: This member is currently unused.
82      *
83      * @see god_passive::loss.
84      */
85     const char* gain;
86     /** Message to be given on loss of this passive.
87      *
88      * If empty, use the gain message. This makes sense only if the message
89      * contains " NOW ", either explicitly or implicitly through not
90      * beginning with a capital letter.
91      *
92      * The substring "GOD" is replaced with the name of the god.
93      * The substring " NOW " is replaced with " no longer ".
94      *
95      * Examples:
96      *   "have super powers"
97      *     => "You no longer have super powers"
98      *   "are NOW powerful"
99      *     => "You are no longer powerful"
100      *   "Your power is NOW great"
101      *     => "Your power is no longer great"
102      *   "GOD NOW makes you powerful"
103      *     => "Moloch no longer makes you powerful"
104      *   "GOD grants you a boon"
105      *     => "Moloch grants you a boon" (probably incorrect)
106      *
107      * FIXME: This member is currently unused.
108      *
109      * @see god_passive::gain.
110      */
111     const char* loss;
112
113     god_passive(int rank_, passive_t pasv_, const char* gain_,
114                 const char* loss_ = "")
115         : rank{rank_}, pasv{pasv_}, gain{gain_}, loss{*loss_ ? loss_ : gain_}
116     { }
117
118     god_passive(int rank_, const char* gain_, const char* loss_ = "")
119         : god_passive(rank_, passive_t::none, gain_, loss_)
120     { }
121
122     void display(bool gaining, const char* fmt) const
123     {
124         const char * const str = gaining ? gain : loss;
125         if (isupper(str[0]))
126             god_speaks(you.religion, str);
127         else
128             god_speaks(you.religion, make_stringf(fmt, str).c_str());
129     }
130 };
131
132 static const vector<god_passive> god_passives[] =
133 {
134     // no god
135     { },
136
137     // Zin
138     {
139         { -1, passive_t::protect_from_harm,
140               "GOD sometimes watches over you",
141               "GOD no longer watches over you"
142         },
143         { -1, passive_t::resist_mutation,
144               "GOD can shield you from mutations",
145               "GOD NOW shields you from mutations"
146         },
147         { -1, passive_t::resist_polymorph,
148               "GOD can protect you from unnatural transformations",
149               "GOD NOW protects you from unnatural transformations",
150         },
151         { -1, passive_t::resist_hell_effects,
152               "GOD can protect you from effects of Hell",
153               "GOD NOW protects you from effects of Hell"
154         },
155         { -1, passive_t::warn_shapeshifter,
156               "GOD will NOW warn you about shapeshifters"
157         },
158         {
159           6, passive_t::cleanse_mut_potions,
160               "GOD cleanses your potions of mutation",
161               "GOD no longer cleanses your potions of mutation",
162         }
163     },
164
165     // TSO
166     {
167         { -1, passive_t::protect_from_harm,
168               "GOD sometimes watches over you",
169               "GOD no longer watches over you"
170         },
171         { -1, passive_t::abjuration_protection_hd,
172               "GOD NOW protects your summons from abjuration" },
173         { -1, passive_t::bless_followers_vs_evil,
174               "GOD NOW blesses your followers when they kill evil beings" },
175         { -1, passive_t::restore_hp_mp_vs_evil,
176               "gain health and magic from killing evil beings" },
177         { -1, passive_t::no_stabbing,
178               "are NOW prevented from stabbing unaware creatures" },
179         {  0, passive_t::halo, "are NOW surrounded by divine halo" },
180     },
181
182     // Kikubaaqudgha
183     {
184         {  2, passive_t::miscast_protection_necromancy,
185               "GOD NOW protects you from necromancy miscasts"
186               " and mummy death curses"
187         },
188         {  4, passive_t::resist_torment,
189               "GOD NOW protects you from torment" },
190     },
191
192     // Yredelemnul
193     {
194         {  3, passive_t::nightvision, "can NOW see well in the dark" },
195     },
196
197     // Xom
198     { },
199
200     // Vehumet
201     {
202         { -1, passive_t::mp_on_kill,
203               "have a chance to gain magical power from killing" },
204         {  3, passive_t::spells_success,
205               "are NOW less likely to miscast destructive spells" },
206         {  4, passive_t::spells_range,
207               "can NOW cast destructive spells farther" },
208     },
209
210     // Okawaru
211     {
212         // None
213     },
214
215     // Makhleb
216     {
217         { -1, passive_t::restore_hp, "gain health from killing" },
218     },
219
220     // Sif Muna
221     { },
222
223     // Trog
224     {
225         { -1, passive_t::abjuration_protection,
226               "GOD NOW protects your allies from abjuration"
227         },
228         {  0, passive_t::extend_berserk,
229               "can NOW maintain berserk longer, and are less likely to pass out",
230               "can NOW maintain berserk as long, and are more likely to pass out"
231         },
232     },
233
234     // Nemelex
235     {
236         // None
237     },
238
239     // Elyvilon
240     {
241         { -1, passive_t::protect_from_harm,
242               "GOD sometimes watches over you",
243               "GOD no longer watches over you"
244         },
245         { -1, passive_t::protect_ally,
246               "GOD can protect the life of your allies",
247               "GOD NOW protects the life of your allies"
248         },
249     },
250
251     // Lugonu
252     {
253         { -1, passive_t::safe_distortion,
254               "are NOW protected from distortion unwield effects" },
255         { -1, passive_t::map_rot_res_abyss,
256               "remember the shape of the Abyss better" },
257         {  5, passive_t::attract_abyssal_rune,
258               "GOD will NOW help you find the Abyssal rune" },
259     },
260
261     // Beogh
262     {
263         { -1, passive_t::share_exp, "share experience with your followers" },
264         {  3, passive_t::convert_orcs, "inspire orcs to join your side" },
265         {  3, passive_t::bless_followers,
266               "GOD will bless your followers",
267               "GOD will no longer bless your followers"
268         },
269         {  5, passive_t::water_walk, "walk on water" },
270     },
271
272     // Jiyva
273     {
274         { -1, passive_t::neutral_slimes,
275               "Slimes and eye monsters are NOW neutral towards you" },
276         { -1, passive_t::jellies_army,
277               "GOD NOW summons jellies to protect you" },
278         { -1, passive_t::jelly_eating,
279               "GOD NOW allows jellies to devour items" },
280         { -1, passive_t::fluid_stats,
281               "GOD NOW adjusts your attributes periodically" },
282         {  0, passive_t::slime_wall_immune,
283               "are NOW immune to slime covered walls" },
284         {  2, passive_t::slime_feed,
285               "Your fellow slimes NOW consume items" },
286         {  3, passive_t::resist_corrosion,
287               "GOD NOW protects you from corrosion" },
288         {  4, passive_t::slime_mp,
289               "Items consumed by your fellow slimes NOW restore"
290               " your magical power"
291         },
292         {  5, passive_t::slime_hp,
293               "Items consumed by your fellow slimes NOW restore"
294               " your health"
295         },
296         {  6, passive_t::spawn_slimes_on_hit,
297               "spawn slimes when struck by massive blows" },
298         {  6, passive_t::unlock_slime_vaults,
299               "GOD NOW grants you access to the hidden treasures"
300               " of the Slime Pits"
301         },
302     },
303
304     // Fedhas
305     {
306         { -1, passive_t::pass_through_plants,
307               "can NOW walk through plants" },
308         { -1, passive_t::shoot_through_plants,
309               "can NOW safely fire through allied plants" },
310         {  0, passive_t::friendly_plants,
311               "Allied plants are NOW friendly towards you" },
312     },
313
314     // Cheibriados
315     {
316         { -1, passive_t::no_haste,
317               "are NOW protected from inadvertent hurry" },
318         { -1, passive_t::slowed, "move less quickly" },
319         {  0, passive_t::slow_orb_run,
320               "GOD will NOW aid your escape with the Orb of Zot",
321         },
322         {  0, passive_t::stat_boost,
323               "GOD NOW supports your attributes"
324         },
325         {  0, passive_t::slow_abyss,
326               "GOD will NOW slow the Abyss"
327         },
328         // TODO: this one should work regardless of penance, maybe?
329         {  0, passive_t::slow_zot,
330               "GOD will NOW slow Zot's hunt for you"
331         },
332         {  1, passive_t::slow_poison, "process poison slowly" },
333     },
334
335     // Ashenzari
336     {
337         { -1, passive_t::want_curses, "prefer cursed items" },
338         { -1, passive_t::detect_portals, "sense portals" },
339         { -1, passive_t::identify_items, "sense the properties of items" },
340         {  0, passive_t::auto_map, "have improved mapping abilities" },
341         {  0, passive_t::detect_montier, "sense threats" },
342         {  0, passive_t::detect_items, "sense items" },
343         {  0, passive_t::avoid_traps,
344               "avoid traps" },
345         {  0, passive_t::bondage_skill_boost,
346               "get a skill boost from cursed items" },
347         {  2, passive_t::sinv, "are NOW clear of vision" },
348         {  3, passive_t::clarity, "are NOW clear of mind" },
349         {  4, passive_t::xray_vision, "GOD NOW grants you astral sight" },
350     },
351
352     // Dithmenos
353     {
354         {  1, passive_t::nightvision, "can NOW see well in the dark" },
355         {  1, passive_t::umbra, "are NOW surrounded by an umbra" },
356         // TODO: this one should work regardless of penance.
357         {  3, passive_t::hit_smoke, "emit smoke when hit" },
358         {  4, passive_t::shadow_attacks,
359               "Your attacks are NOW mimicked by a shadow" },
360         {  4, passive_t::shadow_spells,
361               "Your attack spells are NOW mimicked by a shadow" },
362     },
363
364     // Gozag
365     {
366         { -1, passive_t::detect_gold, "detect gold" },
367         {  0, passive_t::goldify_corpses,
368               "GOD NOW turns all corpses to gold" },
369         {  0, passive_t::gold_aura, "have a gold aura" },
370     },
371
372     // Qazlal
373     {
374         {  0, passive_t::cloud_immunity, "and your divine allies are ADV immune to clouds" },
375         {  1, passive_t::storm_shield,
376               "generate elemental clouds to protect yourself" },
377         {  4, passive_t::upgraded_storm_shield,
378               "Your chances to be struck by projectiles are NOW reduced" },
379         {  5, passive_t::elemental_adaptation,
380               "Elemental attacks NOW leave you somewhat more resistant"
381               " to them"
382         }
383     },
384
385     // Ru
386     {
387         {  1, passive_t::aura_of_power,
388               "Your enemies will sometimes fail their attack or even hit themselves",
389               "Your enemies will NOW fail their attack or hit themselves"
390         },
391         {  2, passive_t::upgraded_aura_of_power,
392               "Enemies that inflict damage upon you will sometimes receive"
393               " a detrimental status effect",
394               "Enemies that inflict damage upon you will NOW receive"
395               " a detrimental status effect"
396         },
397     },
398
399 #if TAG_MAJOR_VERSION == 34
400     // Pakellas
401     {
402         { -1, passive_t::no_mp_regen,
403               "GOD NOW prevents you from regenerating your magical power" },
404         { -1, passive_t::mp_on_kill, "have a chance to gain magical power from"
405                                      " killing" },
406         {  1, passive_t::bottle_mp,
407               "GOD NOW collects and distills excess magic from your kills"
408         },
409     },
410 #endif
411
412     // Uskayaw
413     { },
414
415     // Hepliaklqana
416     {
417         { -1, passive_t::frail,
418               "GOD NOW siphons a part of your essence into your ancestor" },
419         {  5, passive_t::transfer_drain,
420               "drain nearby creatures when transferring your ancestor" },
421     },
422
423     // Wu Jian
424     {
425         { 0, passive_t::wu_jian_lunge, "perform damaging attacks by moving towards foes." },
426         { 1, passive_t::wu_jian_whirlwind, "lightly attack monsters by moving around them." },
427         { 2, passive_t::wu_jian_wall_jump, "perform airborne attacks in an area by jumping off a solid obstacle." },
428     },
429 };
430 COMPILE_CHECK(ARRAYSZ(god_passives) == NUM_GODS);
431
432 static bool _god_gives_passive_if(god_type god, passive_t passive,
433                                   function<bool(const god_passive&)> condition)
434 {
435     const auto &pasvec = god_passives[god];
436     return any_of(begin(pasvec), end(pasvec),
437                   [&] (const god_passive &p) -> bool
438                   { return p.pasv == passive && condition(p); });
439 }
440
441 bool god_gives_passive(god_type god, passive_t passive)
442 {
443   return _god_gives_passive_if(god, passive,
444                                [] (god_passive /*p*/) { return true; });
445 }
446
447 bool have_passive(passive_t passive)
448 {
449     return _god_gives_passive_if(you.religion, passive,
450                                  [] (god_passive p)
451                                  {
452                                      return piety_rank() >= p.rank
453                                          && (!player_under_penance()
454                                              || p.rank < 0);
455                                  });
456 }
457
458 bool will_have_passive(passive_t passive)
459 {
460   return _god_gives_passive_if(you.religion, passive,
461                                [] (god_passive/*p*/) { return true; });
462 }
463
464 // Returns a large number (10) if we will never get this passive.
465 int rank_for_passive(passive_t passive)
466 {
467     const auto &pasvec = god_passives[you.religion];
468     const auto found = find_if(begin(pasvec), end(pasvec),
469                               [passive] (const god_passive &p) -> bool
470                               {
471                                   return p.pasv == passive;
472                               });
473     return found == end(pasvec) ? 10 : found->rank;
474 }
475
476 int chei_stat_boost(int piety)
477 {
478     if (!have_passive(passive_t::stat_boost))
479         return 0;
480     if (piety < piety_breakpoint(0))  // Since you've already begun to slow down.
481         return 1;
482     if (piety >= piety_breakpoint(5))
483         return 15;
484     return (piety - 10) / 10;
485 }
486
487 // Eat from one random off-level item stack.
488 void jiyva_eat_offlevel_items()
489 {
490     // For wizard mode 'J' command
491     if (!have_passive(passive_t::jelly_eating))
492         return;
493
494     if (crawl_state.game_is_sprint())
495         return;
496
497     while (true)
498     {
499         if (one_chance_in(200))
500             break;
501
502         const int branch = random2(NUM_BRANCHES);
503
504         // Choose level based on main dungeon depth so that levels of
505         // short branches aren't picked more often.
506         ASSERT(brdepth[branch] <= MAX_BRANCH_DEPTH);
507         const int level = random2(MAX_BRANCH_DEPTH) + 1;
508
509         const level_id lid(static_cast<branch_type>(branch), level);
510
511         if (lid == level_id::current() || !you.level_visited(lid))
512             continue;
513
514         dprf("Checking %s", lid.describe().c_str());
515
516         level_excursion le;
517         le.go_to(lid);
518         while (true)
519         {
520             if (one_chance_in(200))
521                 break;
522
523             const coord_def p = random_in_bounds();
524
525             if (env.igrid(p) == NON_ITEM || testbits(env.pgrid(p), FPROP_NO_JIYVA))
526                 continue;
527
528             for (stack_iterator si(p); si; ++si)
529             {
530                 if (!item_is_jelly_edible(*si) || one_chance_in(4))
531                     continue;
532
533                 if (one_chance_in(4))
534                     break;
535
536                 dprf("Eating %s on %s",
537                      si->name(DESC_PLAIN).c_str(), lid.describe().c_str());
538
539                 // Needs a message now to explain possible hp or mp
540                 // gain from jiyva_slurp_bonus()
541                 mpr("You hear a distant slurping noise.");
542                 jiyva_slurp_item_stack(*si);
543                 item_was_destroyed(*si);
544                 destroy_item(si.index());
545             }
546             return;
547         }
548     }
549 }
550
551 int ash_scry_radius()
552 {
553     if (!have_passive(passive_t::xray_vision))
554         return 0;
555
556     // Radius 2 starting at 4* increasing to 4 at 6*
557     return piety_rank() - 2;
558 }
559
560 static bool _two_handed()
561 {
562     const item_def* wpn = you.slot_item(EQ_WEAPON, true);
563     if (!wpn)
564         return false;
565
566     hands_reqd_type wep_type = you.hands_reqd(*wpn, true);
567     return wep_type == HANDS_TWO;
568 }
569
570 static void _curse_boost_skills(const item_def &item)
571 {
572     if (!item.props.exists(CURSE_KNOWLEDGE_KEY))
573         return;
574
575     for (auto& curse : item.props[CURSE_KNOWLEDGE_KEY].get_vector())
576     {
577         skill_type sk = static_cast<skill_type>(curse.get_int());
578         if (you.skill_boost.count(sk))
579             you.skill_boost[sk]++;
580         else
581             you.skill_boost[sk] = 1;
582     }
583 }
584
585 // Checks bondage and sets ash piety
586 void ash_check_bondage()
587 {
588     if (!will_have_passive(passive_t::bondage_skill_boost))
589         return;
590
591 #if TAG_MAJOR_VERSION == 34
592     // Save compatibility for the new ash tag minor forgot to do this
593     initialize_ashenzari_props();
594 #endif
595
596     int num_cursed = 0, num_slots = 0;
597
598     you.skill_boost.clear();
599     for (int j = EQ_FIRST_EQUIP; j < NUM_EQUIP; j++)
600     {
601         const equipment_type i = static_cast<equipment_type>(j);
602         // Missing hands mean fewer rings
603         if (you.species != SP_OCTOPODE && i == EQ_LEFT_RING
604                  && you.get_mutation_level(MUT_MISSING_HAND))
605         {
606             continue;
607         }
608         // Octopodes don't count these slots:
609         else if (you.species == SP_OCTOPODE
610                  && ((i == EQ_LEFT_RING || i == EQ_RIGHT_RING)
611                      || (i == EQ_RING_EIGHT
612                          && you.get_mutation_level(MUT_MISSING_HAND))))
613         {
614             continue;
615         }
616         // *Only* octopodes count these slots:
617         else if (you.species != SP_OCTOPODE
618                  && i >= EQ_RING_ONE && i <= EQ_RING_EIGHT)
619         {
620             continue;
621         }
622         // The macabre finger necklace's extra slot does count if equipped.
623         else if (!player_equip_unrand(UNRAND_FINGER_AMULET)
624                  && i == EQ_RING_AMULET)
625         {
626             continue;
627         }
628
629         // transformed away slots are still considered to be possibly bound
630         if (you_can_wear(i))
631         {
632             num_slots++;
633             if (you.equip[i] != -1)
634             {
635                 const item_def& item = you.inv[you.equip[i]];
636                 if (item.cursed() && (i != EQ_WEAPON || is_weapon(item)))
637                 {
638                     if (i == EQ_WEAPON && _two_handed())
639                         num_cursed += 2;
640                     else
641                     {
642                         num_cursed++;
643                         if (i == EQ_BODY_ARMOUR && is_unrandom_artefact(item, UNRAND_LEAR))
644                             num_cursed += 3;
645                     }
646                     _curse_boost_skills(item);
647                 }
648             }
649         }
650     }
651
652     set_piety(ASHENZARI_BASE_PIETY
653               + (num_cursed * ASHENZARI_PIETY_SCALE) / num_slots);
654 }
655
656 bool god_id_item(item_def& item, bool silent)
657 {
658     iflags_t old_ided = item.flags & ISFLAG_IDENT_MASK;
659
660     if (have_passive(passive_t::identify_items))
661     {
662         // Don't identify runes or the orb, since this has no gameplay purpose
663         // and might mess up other things.
664         if (item.base_type == OBJ_RUNES || item_is_orb(item))
665             return false;
666
667         if ((item.base_type == OBJ_JEWELLERY || item.base_type == OBJ_STAVES)
668             && item_needs_autopickup(item))
669         {
670             item.props["needs_autopickup"] = true;
671         }
672         set_ident_type(item, true);
673         set_ident_flags(item, ISFLAG_IDENT_MASK);
674     }
675
676     iflags_t ided = item.flags & ISFLAG_IDENT_MASK;
677
678     if (ided & ~old_ided)
679     {
680         if (item.props.exists("needs_autopickup") && is_useless_item(item))
681             item.props.erase("needs_autopickup");
682
683         if (&item == you.weapon())
684             you.wield_change = true;
685
686         if (!silent)
687             mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str());
688
689         seen_item(item);
690         if (in_inventory(item))
691             auto_assign_item_slot(item);
692         return true;
693     }
694
695     // nothing new
696     return false;
697 }
698
699 static bool is_ash_portal(dungeon_feature_type feat)
700 {
701     if (feat_is_portal_entrance(feat))
702         return true;
703     switch (feat)
704     {
705     case DNGN_ENTER_HELL:
706     case DNGN_ENTER_ABYSS: // for completeness
707     case DNGN_EXIT_THROUGH_ABYSS:
708     case DNGN_EXIT_ABYSS:
709     case DNGN_ENTER_PANDEMONIUM:
710     case DNGN_EXIT_PANDEMONIUM:
711     // DNGN_TRANSIT_PANDEMONIUM is too mundane
712         return true;
713     default:
714         return false;
715     }
716 }
717
718 // Yay for rectangle_iterator and radius_iterator not sharing a base type
719 static bool _check_portal(coord_def where)
720 {
721     const dungeon_feature_type feat = env.grid(where);
722     if (feat != env.map_knowledge(where).feat() && is_ash_portal(feat))
723     {
724         env.map_knowledge(where).set_feature(feat);
725         set_terrain_mapped(where);
726
727         if (!testbits(env.pgrid(where), FPROP_SEEN_OR_NOEXP))
728         {
729             env.pgrid(where) |= FPROP_SEEN_OR_NOEXP;
730             if (!you.see_cell(where))
731                 return true;
732         }
733     }
734     return false;
735 }
736
737 int ash_detect_portals(bool all)
738 {
739     if (!have_passive(passive_t::detect_portals))
740         return 0;
741
742     int portals_found = 0;
743     const int map_radius = LOS_DEFAULT_RANGE + 1;
744
745     if (all)
746     {
747         for (rectangle_iterator ri(0); ri; ++ri)
748         {
749             if (_check_portal(*ri))
750                 portals_found++;
751         }
752     }
753     else
754     {
755         for (radius_iterator ri(you.pos(), map_radius, C_SQUARE); ri; ++ri)
756         {
757             if (_check_portal(*ri))
758                 portals_found++;
759         }
760     }
761
762     you.seen_portals += portals_found;
763     return portals_found;
764 }
765
766 monster_type ash_monster_tier(const monster *mon)
767 {
768     return monster_type(MONS_SENSED_TRIVIAL + monster_info(mon).threat);
769 }
770
771 /**
772  * Does the player have an ash skill boost for a particular skill?
773  */
774 bool ash_has_skill_boost(skill_type sk)
775 {
776     return have_passive(passive_t::bondage_skill_boost)
777            && you.skill_boost.count(sk) && you.skill_boost.find(sk)->second;
778 }
779
780 /**
781  * Calculate the ash skill point boost for skill sk.
782  *
783  * @param scaled_skill the skill level to calculate it for, scaled by 10.
784  *
785  * @return the skill point bonus to use.
786  */
787 unsigned int ash_skill_point_boost(skill_type sk, int scaled_skill)
788 {
789     unsigned int skill_points = 0;
790
791     skill_points += (you.skill_boost[sk] * 3 + 1) * (piety_rank() + 1)
792                     * max(scaled_skill, 1) * species_apt_factor(sk);
793     return skill_points;
794 }
795
796 int ash_skill_boost(skill_type sk, int scale)
797 {
798     // It gives a bonus to skill points. The formula is:
799     // ( curses * 3 + 1 ) * (piety_rank + 1) * skill_level
800
801     unsigned int skill_points = you.skill_points[sk]
802                   + get_crosstrain_points(sk)
803                   + ash_skill_point_boost(sk, you.skill(sk, 10, true));
804
805     int level = you.skills[sk];
806     while (level < MAX_SKILL_LEVEL && skill_points >= skill_exp_needed(level + 1, sk))
807         ++level;
808
809     level = level * scale + get_skill_progress(sk, level, skill_points, scale);
810
811     return min(level, MAX_SKILL_LEVEL * scale);
812 }
813
814 int gozag_gold_in_los(actor *whom)
815 {
816     if (!have_passive(passive_t::gold_aura))
817         return 0;
818
819     int gold_count = 0;
820
821     for (radius_iterator ri(whom->pos(), LOS_RADIUS, C_SQUARE, LOS_DEFAULT);
822          ri; ++ri)
823     {
824         for (stack_iterator j(*ri); j; ++j)
825         {
826             if (j->base_type == OBJ_GOLD)
827                 ++gold_count;
828         }
829     }
830
831     return gold_count;
832 }
833
834 void gozag_detect_level_gold(bool count)
835 {
836     ASSERT(you.on_current_level);
837     vector<item_def *> gold_piles;
838     vector<coord_def> gold_places;
839     int gold = 0;
840     for (rectangle_iterator ri(0); ri; ++ri)
841     {
842         for (stack_iterator j(*ri); j; ++j)
843         {
844             if (j->base_type == OBJ_GOLD && !(j->flags & ISFLAG_UNOBTAINABLE))
845             {
846                 gold += j->quantity;
847                 gold_piles.push_back(&(*j));
848                 gold_places.push_back(*ri);
849             }
850         }
851     }
852
853     if (!player_in_branch(BRANCH_ABYSS) && count)
854         you.attribute[ATTR_GOLD_GENERATED] += gold;
855
856     if (have_passive(passive_t::detect_gold))
857     {
858         for (unsigned int i = 0; i < gold_places.size(); i++)
859         {
860             int dummy = gold_piles[i]->index();
861             coord_def &pos = gold_places[i];
862             unlink_item(dummy);
863             move_item_to_grid(&dummy, pos, true);
864             if ((!env.map_knowledge(pos).item()
865                  || env.map_knowledge(pos).item()->base_type != OBJ_GOLD
866                  && you.visible_igrd(pos) != NON_ITEM))
867             {
868                 env.map_knowledge(pos).set_item(
869                         get_item_known_info(*gold_piles[i]),
870                         !!env.map_knowledge(pos).item());
871                 env.map_knowledge(pos).flags |= MAP_DETECTED_ITEM;
872 #ifdef USE_TILE
873                 // force an update for gold generated during Abyss shifts
874                 tiles.update_minimap(pos);
875 #endif
876             }
877         }
878     }
879 }
880
881 int qazlal_sh_boost(int piety)
882 {
883     if (!have_passive(passive_t::storm_shield))
884         return 0;
885
886     return min(piety, piety_breakpoint(5)) / 10;
887 }
888
889 // Not actually passive, but placing it here so that it can be easily compared
890 // with Qazlal's boost.
891 int tso_sh_boost()
892 {
893     if (!you.duration[DUR_DIVINE_SHIELD])
894         return 0;
895
896     return you.attribute[ATTR_DIVINE_SHIELD];
897 }
898
899 void qazlal_storm_clouds()
900 {
901     if (!have_passive(passive_t::storm_shield))
902         return;
903
904     // You are a *storm*. You are pretty loud!
905     noisy(min((int)you.piety, piety_breakpoint(5)) / 10, you.pos());
906
907     const int radius = you.piety >= piety_breakpoint(3) ? 2 : 1;
908
909     vector<coord_def> candidates;
910     for (radius_iterator ri(you.pos(), radius, C_SQUARE, LOS_SOLID, true);
911          ri; ++ri)
912     {
913         int count = 0;
914         if (cell_is_solid(*ri) || cloud_at(*ri))
915             continue;
916
917         // Don't create clouds over firewood
918         const monster * mon = monster_at(*ri);
919         if (mon != nullptr && mons_is_firewood(*mon))
920             continue;
921
922         // No clouds in corridors.
923         for (adjacent_iterator ai(*ri); ai; ++ai)
924             if (!cell_is_solid(*ai))
925                 count++;
926
927         if (count >= 5)
928             candidates.push_back(*ri);
929     }
930     const int count =
931         div_rand_round(min((int)you.piety, piety_breakpoint(5))
932                        * candidates.size() * you.time_taken,
933                        piety_breakpoint(5) * 7 * BASELINE_DELAY);
934     if (count < 0)
935         return;
936     shuffle_array(candidates);
937     int placed = 0;
938     for (unsigned int i = 0; placed < count && i < candidates.size(); i++)
939     {
940         bool water = false;
941         for (adjacent_iterator ai(candidates[i]); ai; ++ai)
942         {
943             if (feat_is_watery(env.grid(*ai)))
944                 water = true;
945         }
946
947         // No flame clouds over water to avoid steam generation.
948         cloud_type ctype;
949         do
950         {
951             ctype = random_choose(CLOUD_FIRE, CLOUD_COLD, CLOUD_STORM,
952                                   CLOUD_DUST);
953         } while (water && ctype == CLOUD_FIRE);
954
955         place_cloud(ctype, candidates[i], random_range(3, 5), &you);
956         placed++;
957     }
958 }
959
960 /**
961  * Handle Qazlal's elemental adaptation.
962  * This should be called (exactly once) for physical, fire, cold, and electrical damage.
963  * Right now, it is called only from expose_player_to_element. This may merit refactoring.
964  *
965  * @param flavour the beam type.
966  * @param strength The adaptations will trigger strength in (11 - piety_rank()) times. In practice, this is mostly called with a value of 2.
967  */
968 void qazlal_element_adapt(beam_type flavour, int strength)
969 {
970     if (strength <= 0
971         || !have_passive(passive_t::elemental_adaptation)
972         || !x_chance_in_y(strength, 11 - piety_rank()))
973     {
974         return;
975     }
976
977     beam_type what = BEAM_NONE;
978     duration_type dur = NUM_DURATIONS;
979     string descript = "";
980     switch (flavour)
981     {
982         case BEAM_FIRE:
983         case BEAM_LAVA:
984         case BEAM_STICKY_FLAME:
985         case BEAM_STEAM:
986             what = BEAM_FIRE;
987             dur = DUR_QAZLAL_FIRE_RES;
988             descript = "fire";
989             break;
990         case BEAM_COLD:
991         case BEAM_ICE:
992             what = BEAM_COLD;
993             dur = DUR_QAZLAL_COLD_RES;
994             descript = "cold";
995             break;
996         case BEAM_ELECTRICITY:
997             what = BEAM_ELECTRICITY;
998             dur = DUR_QAZLAL_ELEC_RES;
999             descript = "electricity";
1000             break;
1001         case BEAM_MMISSILE: // for LCS, iron shot
1002         case BEAM_MISSILE:
1003         case BEAM_FRAG:
1004             what = BEAM_MISSILE;
1005             dur = DUR_QAZLAL_AC;
1006             descript = "physical attacks";
1007             break;
1008         default:
1009             return;
1010     }
1011
1012     if (what != BEAM_FIRE && you.duration[DUR_QAZLAL_FIRE_RES])
1013     {
1014         mprf(MSGCH_DURATION, "Your resistance to fire fades away.");
1015         you.duration[DUR_QAZLAL_FIRE_RES] = 0;
1016     }
1017
1018     if (what != BEAM_COLD && you.duration[DUR_QAZLAL_COLD_RES])
1019     {
1020         mprf(MSGCH_DURATION, "Your resistance to cold fades away.");
1021         you.duration[DUR_QAZLAL_COLD_RES] = 0;
1022     }
1023
1024     if (what != BEAM_ELECTRICITY && you.duration[DUR_QAZLAL_ELEC_RES])
1025     {
1026         mprf(MSGCH_DURATION, "Your resistance to electricity fades away.");
1027         you.duration[DUR_QAZLAL_ELEC_RES] = 0;
1028     }
1029
1030     if (what != BEAM_MISSILE && you.duration[DUR_QAZLAL_AC])
1031     {
1032         mprf(MSGCH_DURATION, "Your resistance to physical damage fades away.");
1033         you.duration[DUR_QAZLAL_AC] = 0;
1034         you.redraw_armour_class = true;
1035     }
1036
1037     mprf(MSGCH_GOD, "You feel %sprotected from %s.",
1038          you.duration[dur] > 0 ? "more " : "", descript.c_str());
1039
1040     // was scaled by 10 * strength. But the strength parameter is used so inconsistently that
1041     // it seems like a constant would be better, based on the typical value of 2.
1042     you.increase_duration(dur, 20, 80);
1043
1044     if (what == BEAM_MISSILE)
1045         you.redraw_armour_class = true;
1046 }
1047
1048 /**
1049  * Determine whether a Ru worshipper will attempt to interfere with an attack
1050  * against the player.
1051  *
1052  * @return bool Whether or not whether the worshipper will attempt to interfere.
1053  */
1054 bool does_ru_wanna_redirect(monster* mon)
1055 {
1056     return have_passive(passive_t::aura_of_power)
1057             && !mon->friendly()
1058             && you.see_cell_no_trans(mon->pos())
1059             && !mons_is_firewood(*mon)
1060             && !mon->submerged()
1061             && !mons_is_projectile(mon->type);
1062 }
1063
1064 /**
1065  * Determine which, if any, action Ru takes on a possible attack.
1066  *
1067  * @return ru_interference
1068  */
1069 ru_interference get_ru_attack_interference_level()
1070 {
1071     int r = random2(100);
1072     int chance = div_rand_round(you.piety, 16);
1073
1074     // 10% chance of stopping any attack at max piety
1075     if (r < chance)
1076         return DO_BLOCK_ATTACK;
1077
1078     // 5% chance of redirect at max piety
1079     else if (r < chance + div_rand_round(chance, 2))
1080         return DO_REDIRECT_ATTACK;
1081
1082     else
1083         return DO_NOTHING;
1084 }
1085
1086 static bool _shadow_acts(bool spell)
1087 {
1088     const passive_t pasv = spell ? passive_t::shadow_spells
1089                                  : passive_t::shadow_attacks;
1090     if (!have_passive(pasv))
1091         return false;
1092
1093     const int minpiety = piety_breakpoint(rank_for_passive(pasv) - 1);
1094
1095     // 10% chance at minimum piety; 50% chance at 200 piety.
1096     const int range = MAX_PIETY - minpiety;
1097     const int min   = range / 5;
1098     return x_chance_in_y(min + ((range - min)
1099                                 * (you.piety - minpiety)
1100                                 / (MAX_PIETY - minpiety)),
1101                          2 * range);
1102 }
1103
1104 monster* shadow_monster(bool equip)
1105 {
1106     if (monster_at(you.pos()))
1107         return nullptr;
1108
1109     int wpn_index  = NON_ITEM;
1110
1111     // Do a basic clone of the weapon.
1112     item_def* wpn = you.weapon();
1113     if (equip
1114         && wpn
1115         && is_weapon(*wpn))
1116     {
1117         wpn_index = get_mitm_slot(10);
1118         if (wpn_index == NON_ITEM)
1119             return nullptr;
1120         item_def& new_item = env.item[wpn_index];
1121         if (wpn->base_type == OBJ_STAVES)
1122         {
1123             new_item.base_type = OBJ_WEAPONS;
1124             new_item.sub_type  = WPN_STAFF;
1125         }
1126         else
1127         {
1128             new_item.base_type = wpn->base_type;
1129             new_item.sub_type  = wpn->sub_type;
1130         }
1131         new_item.quantity = 1;
1132         new_item.rnd = 1;
1133         new_item.flags   |= ISFLAG_SUMMONED;
1134     }
1135
1136     monster* mon = get_free_monster();
1137     if (!mon)
1138     {
1139         if (wpn_index)
1140             destroy_item(wpn_index);
1141         return nullptr;
1142     }
1143
1144     mon->type       = MONS_PLAYER_SHADOW;
1145     mon->behaviour  = BEH_SEEK;
1146     mon->attitude   = ATT_FRIENDLY;
1147     mon->flags      = MF_NO_REWARD | MF_JUST_SUMMONED | MF_SEEN
1148                     | MF_WAS_IN_VIEW | MF_HARD_RESET;
1149     mon->hit_points = you.hp;
1150     mon->set_hit_dice(min(27, max(1,
1151                                   you.skill_rdiv(wpn_index != NON_ITEM
1152                                                  ? item_attack_skill(env.item[wpn_index])
1153                                                  : SK_UNARMED_COMBAT, 10, 20)
1154                                   + you.skill_rdiv(SK_FIGHTING, 10, 20))));
1155     mon->set_position(you.pos());
1156     mon->mid        = MID_PLAYER;
1157     mon->inv[MSLOT_WEAPON]  = wpn_index;
1158     mon->inv[MSLOT_MISSILE] = NON_ITEM;
1159
1160     env.mgrid(you.pos()) = mon->mindex();
1161
1162     return mon;
1163 }
1164
1165 void shadow_monster_reset(monster *mon)
1166 {
1167     if (mon->inv[MSLOT_WEAPON] != NON_ITEM)
1168         destroy_item(mon->inv[MSLOT_WEAPON]);
1169     // in case the shadow unwields for some reason, e.g. you clumsily bash with
1170     // a ranged weapon:
1171     if (mon->inv[MSLOT_ALT_WEAPON] != NON_ITEM)
1172         destroy_item(mon->inv[MSLOT_ALT_WEAPON]);
1173     if (mon->inv[MSLOT_MISSILE] != NON_ITEM)
1174         destroy_item(mon->inv[MSLOT_MISSILE]);
1175
1176     mon->reset();
1177 }
1178
1179 /**
1180  * Check if the player is in melee range of the target.
1181  *
1182  * Certain effects, e.g. distortion blink, can cause monsters to leave melee
1183  * range between the initial hit & the shadow mimic.
1184  *
1185  * XXX: refactor this with attack/fight code!
1186  *
1187  * @param target    The creature to be struck.
1188  * @return          Whether the player is melee range of the target, using
1189  *                  their current weapon.
1190  */
1191 static bool _in_melee_range(actor* target)
1192 {
1193     const int dist = (you.pos() - target->pos()).rdist();
1194     return dist <= you.reach_range();
1195 }
1196
1197 void dithmenos_shadow_melee(actor* target)
1198 {
1199     if (!target
1200         || !target->alive()
1201         || !_in_melee_range(target)
1202         || !_shadow_acts(false))
1203     {
1204         return;
1205     }
1206
1207     monster* mon = shadow_monster();
1208     if (!mon)
1209         return;
1210
1211     mon->target     = target->pos();
1212     mon->foe        = target->mindex();
1213
1214     fight_melee(mon, target);
1215
1216     shadow_monster_reset(mon);
1217 }
1218
1219 void dithmenos_shadow_throw(const dist &d, const item_def &item)
1220 {
1221     ASSERT(d.isValid);
1222     if (!_shadow_acts(false))
1223         return;
1224
1225     monster* mon = shadow_monster();
1226     if (!mon)
1227         return;
1228
1229     int ammo_index = get_mitm_slot(10);
1230     if (ammo_index != NON_ITEM)
1231     {
1232         item_def& new_item = env.item[ammo_index];
1233         new_item.base_type = item.base_type;
1234         new_item.sub_type  = item.sub_type;
1235         new_item.quantity  = 1;
1236         new_item.rnd = 1;
1237         new_item.flags    |= ISFLAG_SUMMONED;
1238         mon->inv[MSLOT_MISSILE] = ammo_index;
1239
1240         mon->target = clamp_in_bounds(d.target);
1241
1242         bolt beem;
1243         beem.set_target(d);
1244         setup_monster_throw_beam(mon, beem);
1245         beem.item = &env.item[mon->inv[MSLOT_MISSILE]];
1246         mons_throw(mon, beem, mon->inv[MSLOT_MISSILE]);
1247     }
1248
1249     shadow_monster_reset(mon);
1250 }
1251
1252 void dithmenos_shadow_spell(bolt* orig_beam, spell_type spell)
1253 {
1254     if (!orig_beam)
1255         return;
1256
1257     const coord_def target = orig_beam->target;
1258
1259     if (orig_beam->target.origin()
1260         || (orig_beam->is_enchantment() && !is_valid_mon_spell(spell))
1261         || orig_beam->flavour == BEAM_CHARM
1262            && monster_at(target) && monster_at(target)->friendly()
1263         || !_shadow_acts(true))
1264     {
1265         return;
1266     }
1267
1268     monster* mon = shadow_monster();
1269     if (!mon)
1270         return;
1271
1272     // Don't let shadow spells get too powerful.
1273     mon->set_hit_dice(max(1,
1274                           min(3 * spell_difficulty(spell),
1275                               you.experience_level) / 2));
1276
1277     mon->target = clamp_in_bounds(target);
1278     if (actor_at(target))
1279         mon->foe = actor_at(target)->mindex();
1280
1281     spell_type shadow_spell = spell;
1282     if (!orig_beam->is_enchantment())
1283     {
1284         shadow_spell = (orig_beam->pierce) ? SPELL_SHADOW_BOLT
1285                                            : SPELL_SHADOW_SHARD;
1286     }
1287
1288     bolt beem;
1289     beem.target = target;
1290     beem.aimed_at_spot = orig_beam->aimed_at_spot;
1291
1292     mprf(MSGCH_FRIEND_SPELL, "%s mimicks your spell!",
1293          mon->name(DESC_THE).c_str());
1294     mons_cast(mon, beem, shadow_spell, MON_SPELL_WIZARD, false);
1295
1296     shadow_monster_reset(mon);
1297 }
1298
1299 static void _wu_jian_trigger_serpents_lash(const coord_def& old_pos,
1300                                            bool wall_jump)
1301 {
1302     if (you.attribute[ATTR_SERPENTS_LASH] == 0)
1303        return;
1304
1305     if (wall_jump && you.attribute[ATTR_SERPENTS_LASH] == 1)
1306     {
1307         // No turn manipulation, since we are only refunding half a wall jump's
1308         // time (the walk speed modifier for this special case is already
1309         // factored in main.cc)
1310         you.attribute[ATTR_SERPENTS_LASH] = 0;
1311     }
1312     else
1313     {
1314         you.turn_is_over = false;
1315         you.elapsed_time_at_last_input = you.elapsed_time;
1316         you.attribute[ATTR_SERPENTS_LASH] -= wall_jump ? 2 : 1;
1317         you.redraw_status_lights = true;
1318         update_turn_count();
1319     }
1320
1321     if (you.attribute[ATTR_SERPENTS_LASH] == 0)
1322     {
1323         you.increase_duration(DUR_EXHAUSTED, 12 + random2(5));
1324         mpr("Your supernatural speed expires.");
1325     }
1326
1327     if (!cell_is_solid(old_pos))
1328         check_place_cloud(CLOUD_DUST, old_pos, 2 + random2(3) , &you, 1, -1);
1329 }
1330
1331 static void _wu_jian_increment_heavenly_storm()
1332 {
1333     int storm = you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int();
1334     if (storm < WU_JIAN_HEAVENLY_STORM_MAX)
1335         you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int()++;
1336 }
1337
1338 void wu_jian_heaven_tick()
1339 {
1340     for (radius_iterator ai(you.pos(), 2, C_SQUARE, LOS_SOLID); ai; ++ai)
1341         if (!cell_is_solid(*ai))
1342             place_cloud(CLOUD_GOLD_DUST, *ai, 5 + random2(5), &you);
1343
1344     noisy(15, you.pos());
1345 }
1346
1347 void wu_jian_decrement_heavenly_storm()
1348 {
1349     int storm = you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int();
1350
1351     if (storm > 1)
1352     {
1353         you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int()--;
1354         you.set_duration(DUR_HEAVENLY_STORM, random_range(2, 3));
1355     }
1356     else
1357         wu_jian_end_heavenly_storm();
1358 }
1359
1360 // TODO: why isn't this implemented as a duration end effect?
1361 void wu_jian_end_heavenly_storm()
1362 {
1363     you.props.erase(WU_JIAN_HEAVENLY_STORM_KEY);
1364     you.duration[DUR_HEAVENLY_STORM] = 0;
1365     invalidate_agrid(true);
1366     mprf(MSGCH_GOD, "The heavenly storm settles.");
1367 }
1368
1369 bool wu_jian_has_momentum(wu_jian_attack_type attack_type)
1370 {
1371     return you.attribute[ATTR_SERPENTS_LASH]
1372            && attack_type != WU_JIAN_ATTACK_NONE
1373            && attack_type != WU_JIAN_ATTACK_TRIGGERED_AUX;
1374 }
1375
1376 static bool _can_attack_martial(const monster* mons)
1377 {
1378     return !(mons->wont_attack()
1379              || mons_is_firewood(*mons)
1380              || mons_is_projectile(mons->type)
1381              || !you.can_see(*mons));
1382 }
1383
1384 // A mismatch between attack speed and move speed may cause any particular
1385 // martial attack to be doubled, tripled, or not happen at all. Given enough
1386 // time moving, you would have made the same amount of attacks as tabbing.
1387 static int _wu_jian_number_of_attacks(bool wall_jump)
1388 {
1389     // Under the effect of serpent's lash, move delay is normalized to
1390     // 10 aut for every character, to avoid punishing fast races.
1391     const int move_delay = you.attribute[ATTR_SERPENTS_LASH]
1392                            ? 100
1393                            : player_movement_speed() * player_speed();
1394
1395     int attack_delay;
1396
1397     {
1398         // attack_delay() is dependent on you.time_taken, which won't be set
1399         // appropriately during a movement turn. This temporarily resets
1400         // you.time_taken to the initial value (see `_prep_input`) used for
1401         // basic, simple, melee attacks.
1402         // TODO: can `attack_delay` be changed to not depend on you.time_taken?
1403         unwind_var<int> reset_speed(you.time_taken, player_speed());
1404         attack_delay = you.attack_delay().roll();
1405     }
1406
1407     return div_rand_round(wall_jump ? 2 * move_delay : move_delay,
1408                           attack_delay * BASELINE_DELAY);
1409 }
1410
1411 static bool _wu_jian_lunge(const coord_def& old_pos)
1412 {
1413     coord_def lunge_direction = (you.pos() - old_pos).sgn();
1414     coord_def potential_target = you.pos() + lunge_direction;
1415     monster* mons = monster_at(potential_target);
1416
1417     if (!mons || !_can_attack_martial(mons) || !mons->alive())
1418         return false;
1419
1420     if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1421         _wu_jian_increment_heavenly_storm();
1422
1423     you.apply_berserk_penalty = false;
1424
1425     const int number_of_attacks = _wu_jian_number_of_attacks(false);
1426
1427     if (number_of_attacks == 0)
1428     {
1429         mprf("You lunge at %s, but your attack speed is too slow for a blow "
1430              "to land.", mons->name(DESC_THE).c_str());
1431         return false;
1432     }
1433     else
1434     {
1435         mprf("You lunge%s at %s%s.",
1436              wu_jian_has_momentum(WU_JIAN_ATTACK_LUNGE) ?
1437                  " with incredible momentum" : "",
1438              mons->name(DESC_THE).c_str(),
1439              number_of_attacks > 1 ? ", in a flurry of attacks" : "");
1440     }
1441
1442     count_action(CACT_INVOKE, ABIL_WU_JIAN_LUNGE);
1443
1444     for (int i = 0; i < number_of_attacks; i++)
1445     {
1446         if (!mons->alive())
1447             break;
1448         melee_attack lunge(&you, mons);
1449         lunge.wu_jian_attack = WU_JIAN_ATTACK_LUNGE;
1450         lunge.attack();
1451     }
1452
1453     return true;
1454 }
1455
1456 // Monsters adjacent to the given pos that are valid targets for whirlwind.
1457 static vector<monster*> _get_whirlwind_targets(coord_def pos)
1458 {
1459     vector<monster*> targets;
1460     for (adjacent_iterator ai(pos, true); ai; ++ai)
1461         if (monster_at(*ai) && _can_attack_martial(monster_at(*ai)))
1462             targets.push_back(monster_at(*ai));
1463     sort(targets.begin(), targets.end());
1464     return targets;
1465 }
1466
1467 static bool _wu_jian_whirlwind(const coord_def& old_pos)
1468 {
1469     bool did_at_least_one_attack = false;
1470
1471     const vector<monster*> targets = _get_whirlwind_targets(you.pos());
1472     if (targets.empty())
1473         return did_at_least_one_attack;
1474
1475     const vector<monster*> old_targets = _get_whirlwind_targets(old_pos);
1476     vector<monster*> common_targets;
1477     set_intersection(targets.begin(), targets.end(),
1478                      old_targets.begin(), old_targets.end(),
1479                      back_inserter(common_targets));
1480
1481     for (auto mons : common_targets)
1482     {
1483         if (!mons->alive())
1484             continue;
1485
1486         if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1487             _wu_jian_increment_heavenly_storm();
1488
1489         you.apply_berserk_penalty = false;
1490
1491         const int number_of_attacks = _wu_jian_number_of_attacks(false);
1492         if (number_of_attacks == 0)
1493         {
1494             mprf("You spin to attack %s, but your attack speed is too slow for "
1495                  "a blow to land.", mons->name(DESC_THE).c_str());
1496             continue;
1497         }
1498         else
1499         {
1500             mprf("You spin and attack %s%s%s.",
1501                  mons->name(DESC_THE).c_str(),
1502                  number_of_attacks > 1 ? " repeatedly" : "",
1503                  wu_jian_has_momentum(WU_JIAN_ATTACK_WHIRLWIND) ?
1504                      ", with incredible momentum" : "");
1505         }
1506
1507         count_action(CACT_INVOKE, ABIL_WU_JIAN_WHIRLWIND);
1508
1509         for (int i = 0; i < number_of_attacks; i++)
1510         {
1511             if (!mons->alive())
1512                 break;
1513             melee_attack whirlwind(&you, mons);
1514             whirlwind.wu_jian_attack = WU_JIAN_ATTACK_WHIRLWIND;
1515             whirlwind.wu_jian_number_of_targets = common_targets.size();
1516             whirlwind.attack();
1517             if (!did_at_least_one_attack)
1518               did_at_least_one_attack = true;
1519         }
1520     }
1521
1522     return did_at_least_one_attack;
1523 }
1524
1525 static bool _wu_jian_trigger_martial_arts(const coord_def& old_pos)
1526 {
1527     bool did_wu_jian_attacks = false;
1528
1529     if (you.pos() == old_pos || you.duration[DUR_CONF])
1530         return did_wu_jian_attacks;
1531
1532     if (have_passive(passive_t::wu_jian_lunge))
1533         did_wu_jian_attacks = _wu_jian_lunge(old_pos);
1534
1535     if (have_passive(passive_t::wu_jian_whirlwind))
1536         did_wu_jian_attacks |= _wu_jian_whirlwind(old_pos);
1537
1538     return did_wu_jian_attacks;
1539 }
1540
1541 void wu_jian_wall_jump_effects()
1542 {
1543     vector<monster*> targets;
1544     for (adjacent_iterator ai(you.pos(), true); ai; ++ai)
1545     {
1546         monster* target = monster_at(*ai);
1547         if (target && _can_attack_martial(target) && target->alive())
1548             targets.push_back(target);
1549
1550         if (!cell_is_solid(*ai))
1551             check_place_cloud(CLOUD_DUST, *ai, 1 + random2(3) , &you, 0, -1);
1552     }
1553
1554     for (auto target : targets)
1555     {
1556         if (!target->alive())
1557             continue;
1558
1559         if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1560             _wu_jian_increment_heavenly_storm();
1561
1562         you.apply_berserk_penalty = false;
1563
1564         // Twice the attacks as Wall Jump spends twice the time
1565         const int number_of_attacks = _wu_jian_number_of_attacks(true);
1566         if (number_of_attacks == 0)
1567         {
1568             mprf("You attack %s from above, but your attack speed is too slow"
1569                  " for a blow to land.", target->name(DESC_THE).c_str());
1570             continue;
1571         }
1572         else
1573         {
1574             mprf("You %sattack %s from above%s.",
1575                  number_of_attacks > 1 ? "repeatedly " : "",
1576                  target->name(DESC_THE).c_str(),
1577                  wu_jian_has_momentum(WU_JIAN_ATTACK_WALL_JUMP) ?
1578                      ", with incredible momentum" : "");
1579         }
1580
1581         for (int i = 0; i < number_of_attacks; i++)
1582         {
1583             if (!target->alive())
1584                 break;
1585
1586             melee_attack aerial(&you, target);
1587             aerial.wu_jian_attack = WU_JIAN_ATTACK_WALL_JUMP;
1588             aerial.wu_jian_number_of_targets = targets.size();
1589             aerial.attack();
1590         }
1591     }
1592 }
1593
1594 bool wu_jian_post_move_effects(bool did_wall_jump,
1595                                const coord_def& initial_position)
1596 {
1597     bool did_wu_jian_attacks = false;
1598
1599     if (!did_wall_jump)
1600         did_wu_jian_attacks = _wu_jian_trigger_martial_arts(initial_position);
1601
1602     if (you.turn_is_over)
1603         _wu_jian_trigger_serpents_lash(initial_position, did_wall_jump);
1604
1605     return did_wu_jian_attacks;
1606 }
1607
1608 /**
1609  * check if the monster in this cell exists and is a valid target for Uskayaw
1610  */
1611 static int _check_for_uskayaw_targets(coord_def where)
1612 {
1613     if (!cell_has_valid_target(where))
1614         return 0;
1615     monster* mons = monster_at(where);
1616     ASSERT(mons);
1617
1618     if (mons_is_firewood(*mons))
1619         return 0;
1620
1621     return 1;
1622 }
1623
1624 /**
1625  * Paralyse the monster in this cell, assuming one exists.
1626  *
1627  * Duration increases with invocations and experience level, and decreases
1628  * with target HD. The duration is pretty low, maxing out at 40 AUT.
1629  */
1630 static int _prepare_audience(coord_def where)
1631 {
1632     if (!_check_for_uskayaw_targets(where))
1633         return 0;
1634
1635     monster* mons = monster_at(where);
1636
1637     int power =  max(1, random2(1 + you.skill(SK_INVOCATIONS, 2))
1638                  + you.experience_level - mons->get_hit_dice());
1639     int duration = min(max(10, 5 + power), 40);
1640     mons->add_ench(mon_enchant(ENCH_PARALYSIS, 1, &you, duration));
1641
1642     return 1;
1643 }
1644
1645 /**
1646  * On hitting *** piety, all the monsters are paralysed by their appreciation
1647  * for your dance.
1648  */
1649 void uskayaw_prepares_audience()
1650 {
1651     int count = apply_area_visible(_check_for_uskayaw_targets, you.pos());
1652     if (count > 0)
1653     {
1654         simple_god_message(" prepares the audience for your solo!");
1655         apply_area_visible(_prepare_audience, you.pos());
1656
1657         // Increment a delay timer to prevent players from spamming this ability
1658         // via piety loss and gain. Timer is in AUT.
1659         you.props[USKAYAW_AUDIENCE_TIMER] = 300 + random2(201);
1660     }
1661     else // Reset the timer because we didn't actually execute.
1662         you.props[USKAYAW_AUDIENCE_TIMER] = 0;
1663 }
1664
1665 /**
1666  * Apply pain bond to the monster in this cell.
1667  */
1668 static int _bond_audience(coord_def where)
1669 {
1670     if (!_check_for_uskayaw_targets(where))
1671         return 0;
1672
1673     monster* mons = monster_at(where);
1674
1675     // Don't pain bond monsters that aren't invested in fighting the player
1676     if (mons->wont_attack())
1677         return 0;
1678
1679     int power = you.skill(SK_INVOCATIONS, 7) + you.experience_level
1680                  - mons->get_hit_dice();
1681     int duration = 20 + random2avg(power, 2);
1682     mons->add_ench(mon_enchant(ENCH_PAIN_BOND, 1, &you, duration));
1683
1684     return 1;
1685 }
1686
1687 /**
1688  * On hitting **** piety, all the monsters are pain bonded.
1689  */
1690 void uskayaw_bonds_audience()
1691 {
1692     int count = apply_area_visible(_check_for_uskayaw_targets, you.pos());
1693     if (count > 1)
1694     {
1695         simple_god_message(" links your audience in an emotional bond!");
1696         apply_area_visible(_bond_audience, you.pos());
1697
1698         // Increment a delay timer to prevent players from spamming this ability
1699         // via piety loss and gain. Timer is in AUT.
1700         you.props[USKAYAW_BOND_TIMER] = 300 + random2(201);
1701     }
1702     else // Reset the timer because we didn't actually execute.
1703         you.props[USKAYAW_BOND_TIMER] = 0;
1704 }