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 "fight.h"
19 #include "files.h"
20 #include "fprop.h"
21 #include "god-abil.h"
22 #include "god-prayer.h"
23 #include "invent.h" // in_inventory
24 #include "item-name.h"
25 #include "item-prop.h"
26 #include "item-status-flag-type.h"
27 #include "items.h"
28 #include "libutil.h"
29 #include "los.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 "player.h"
38 #include "religion.h"
39 #include "shout.h"
40 #include "skills.h"
41 #include "spl-clouds.h"
42 #include "state.h"
43 #include "stringutil.h"
44 #include "tag-version.h"
45 #include "terrain.h"
46 #include "throw.h"
47 #include "unwind.h"
48 
49 // TODO: template out the differences between this and god_power.
50 // TODO: use the display method rather than dummy powers in god_powers.
51 // TODO: finish using these for implementing passive abilities.
52 struct god_passive
53 {
54     // 1-6 means it unlocks at that many stars of piety;
55     // 0 means it is always present when in good standing with the god;
56     // -1 means it is present even under penance;
57     int rank;
58     passive_t pasv;
59     /** Message to be given on gain of this passive.
60      *
61      * If the string begins with an uppercase letter, it is treated as
62      * a complete sentence. Otherwise, if it contains the substring " NOW ",
63      * the string "You " is prepended. Otherwise, the string "You NOW " is
64      * prepended to the message, with the " NOW " then being replaced.
65      *
66      * The substring "GOD" is replaced with the name of the god.
67      * The substring " NOW " is replaced with " now " for messages about
68      * gaining the ability, or " " for messages about having the ability.
69      *
70      * Examples:
71      *   "have super powers"
72      *     => "You have super powers", "You now have super powers."
73      *   "are NOW powerful"
74      *     => "You are powerful", "You are now powerful."
75      *   "Your power is NOW great"
76      *     => "Your power is great", "Your power is now great"
77      *   "GOD NOW makes you powerful"
78      *     => "Moloch makes you powerful", "Moloch now makes you powerful"
79      *   "GOD grants you a boon"
80      *     => "Moloch grants you a boon" (in all situations)
81      *
82      * FIXME: This member is currently unused.
83      *
84      * @see god_passive::loss.
85      */
86     const char* gain;
87     /** Message to be given on loss of this passive.
88      *
89      * If empty, use the gain message. This makes sense only if the message
90      * contains " NOW ", either explicitly or implicitly through not
91      * beginning with a capital letter.
92      *
93      * The substring "GOD" is replaced with the name of the god.
94      * The substring " NOW " is replaced with " no longer ".
95      *
96      * Examples:
97      *   "have super powers"
98      *     => "You no longer have super powers"
99      *   "are NOW powerful"
100      *     => "You are no longer powerful"
101      *   "Your power is NOW great"
102      *     => "Your power is no longer great"
103      *   "GOD NOW makes you powerful"
104      *     => "Moloch no longer makes you powerful"
105      *   "GOD grants you a boon"
106      *     => "Moloch grants you a boon" (probably incorrect)
107      *
108      * FIXME: This member is currently unused.
109      *
110      * @see god_passive::gain.
111      */
112     const char* loss;
113 
god_passivegod_passive114     god_passive(int rank_, passive_t pasv_, const char* gain_,
115                 const char* loss_ = "")
116         : rank{rank_}, pasv{pasv_}, gain{gain_}, loss{*loss_ ? loss_ : gain_}
117     { }
118 
god_passivegod_passive119     god_passive(int rank_, const char* gain_, const char* loss_ = "")
120         : god_passive(rank_, passive_t::none, gain_, loss_)
121     { }
122 
displaygod_passive123     void display(bool gaining, const char* fmt) const
124     {
125         const char * const str = gaining ? gain : loss;
126         if (isupper(str[0]))
127             god_speaks(you.religion, str);
128         else
129             god_speaks(you.religion, make_stringf(fmt, str).c_str());
130     }
131 };
132 
133 static const vector<god_passive> god_passives[] =
134 {
135     // no god
136     { },
137 
138     // Zin
139     {
140         { -1, passive_t::protect_from_harm,
141               "GOD sometimes watches over you",
142               "GOD no longer watches over you"
143         },
144         { -1, passive_t::resist_mutation,
145               "GOD can shield you from mutations",
146               "GOD NOW shields you from mutations"
147         },
148         { -1, passive_t::resist_polymorph,
149               "GOD can protect you from unnatural transformations",
150               "GOD NOW protects you from unnatural transformations",
151         },
152         { -1, passive_t::resist_hell_effects,
153               "GOD can protect you from effects of Hell",
154               "GOD NOW protects you from effects of Hell"
155         },
156         { -1, passive_t::warn_shapeshifter,
157               "GOD will NOW warn you about shapeshifters"
158         },
159         {
160           6, passive_t::cleanse_mut_potions,
161               "GOD cleanses your potions of mutation",
162               "GOD no longer cleanses your potions of mutation",
163         }
164     },
165 
166     // TSO
167     {
168         { -1, passive_t::protect_from_harm,
169               "GOD sometimes watches over you",
170               "GOD no longer watches over you"
171         },
172         { -1, passive_t::abjuration_protection_hd,
173               "GOD NOW protects your summons from abjuration" },
174         { -1, passive_t::bless_followers_vs_evil,
175               "GOD NOW blesses your followers when they kill evil beings" },
176         { -1, passive_t::restore_hp_mp_vs_evil,
177               "gain health and magic from killing evil beings" },
178         { -1, passive_t::no_stabbing,
179               "are NOW prevented from stabbing unaware creatures" },
180         {  0, passive_t::halo, "are NOW surrounded by divine halo" },
181     },
182 
183     // Kikubaaqudgha
184     {
185         {  2, passive_t::miscast_protection_necromancy,
186               "GOD NOW protects you from necromancy miscasts"
187               " and mummy death curses"
188         },
189         {  4, passive_t::resist_torment,
190               "GOD NOW protects you from torment" },
191     },
192 
193     // Yredelemnul
194     {
195         {  3, passive_t::nightvision, "can NOW see well in the dark" },
196     },
197 
198     // Xom
199     { },
200 
201     // Vehumet
202     {
203         { -1, passive_t::mp_on_kill,
204               "have a chance to gain magical power from killing" },
205         {  3, passive_t::spells_success,
206               "are NOW less likely to miscast destructive spells" },
207         {  4, passive_t::spells_range,
208               "can NOW cast destructive spells farther" },
209     },
210 
211     // Okawaru
212     {
213         // None
214     },
215 
216     // Makhleb
217     {
218         { -1, passive_t::restore_hp, "gain health from killing" },
219     },
220 
221     // Sif Muna
222     { },
223 
224     // Trog
225     {
226         { -1, passive_t::abjuration_protection,
227               "GOD NOW protects your allies from abjuration"
228         },
229         {  0, passive_t::extend_berserk,
230               "GOD NOW extends your berserk rage on killing"
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 
_god_gives_passive_if(god_type god,passive_t passive,function<bool (const god_passive &)> condition)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 
god_gives_passive(god_type god,passive_t passive)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 
have_passive(passive_t passive)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 
will_have_passive(passive_t passive)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.
rank_for_passive(passive_t 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 
chei_stat_boost(int piety)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.
jiyva_eat_offlevel_items()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 
ash_scry_radius()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 min(piety_rank() - 2, get_los_radius());
558 }
559 
_two_handed()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 
_curse_boost_skills(const item_def & item)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         for (skill_type sk : curse_skills(curse))
578         {
579             if (you.skill_boost.count(sk))
580                 you.skill_boost[sk]++;
581             else
582                 you.skill_boost[sk] = 1;
583         }
584     }
585 }
586 
587 // Checks bondage and sets ash piety
ash_check_bondage()588 void ash_check_bondage()
589 {
590     if (!will_have_passive(passive_t::bondage_skill_boost))
591         return;
592 
593 #if TAG_MAJOR_VERSION == 34
594     // Save compatibility for the new ash tag minor forgot to do this
595     initialize_ashenzari_props();
596 #endif
597 
598     int num_cursed = 0, num_slots = 0;
599 
600     you.skill_boost.clear();
601     for (int j = EQ_FIRST_EQUIP; j < NUM_EQUIP; j++)
602     {
603         const equipment_type i = static_cast<equipment_type>(j);
604 
605         // handles missing hand, octopode ring slots, finger necklace, species
606         // armour restrictions, etc. Finger necklace slot counts.
607         if (you_can_wear(i) == MB_FALSE)
608             continue;
609 
610         // transformed away slots are still considered to be possibly bound
611         num_slots++;
612         if (you.equip[i] != -1)
613         {
614             const item_def& item = you.inv[you.equip[i]];
615             if (item.cursed() && (i != EQ_WEAPON || is_weapon(item)))
616             {
617                 if (i == EQ_WEAPON && _two_handed())
618                     num_cursed += 2;
619                 else
620                 {
621                     num_cursed++;
622                     if (i == EQ_BODY_ARMOUR
623                         && is_unrandom_artefact(item, UNRAND_LEAR))
624                     {
625                         num_cursed += 3;
626                     }
627                 }
628                 if (!item_is_melded(item))
629                     _curse_boost_skills(item);
630             }
631         }
632     }
633 
634     set_piety(ASHENZARI_BASE_PIETY
635               + (num_cursed * ASHENZARI_PIETY_SCALE) / num_slots);
636 
637     calc_hp(true);
638     calc_mp(true);
639 }
640 
641 // XXX: If this is called on an item in inventory, then auto_assign_item_slot
642 // needs to be called subsequently. However, moving an item in inventory
643 // invalidates its reference, which is a different behavior than for floor
644 // items, so we don't do it in this function.
god_id_item(item_def & item,bool silent)645 bool god_id_item(item_def& item, bool silent)
646 {
647     iflags_t old_ided = item.flags & ISFLAG_IDENT_MASK;
648 
649     if (have_passive(passive_t::identify_items))
650     {
651         // Don't identify runes or the orb, since this has no gameplay purpose
652         // and might mess up other things.
653         if (item.base_type == OBJ_RUNES || item_is_orb(item))
654             return false;
655 
656         if ((item.base_type == OBJ_JEWELLERY || item.base_type == OBJ_STAVES)
657             && item_needs_autopickup(item))
658         {
659             item.props["needs_autopickup"] = true;
660         }
661         set_ident_type(item, true);
662         set_ident_flags(item, ISFLAG_IDENT_MASK);
663     }
664 
665     iflags_t ided = item.flags & ISFLAG_IDENT_MASK;
666 
667     if (ided & ~old_ided)
668     {
669         if (item.props.exists("needs_autopickup") && is_useless_item(item))
670             item.props.erase("needs_autopickup");
671 
672         if (&item == you.weapon())
673             you.wield_change = true;
674 
675         if (!silent)
676             mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str());
677 
678         seen_item(item);
679         return true;
680     }
681 
682     // nothing new
683     return false;
684 }
685 
is_ash_portal(dungeon_feature_type feat)686 static bool is_ash_portal(dungeon_feature_type feat)
687 {
688     if (feat_is_portal_entrance(feat))
689         return true;
690     switch (feat)
691     {
692     case DNGN_ENTER_HELL:
693     case DNGN_ENTER_ABYSS: // for completeness
694     case DNGN_EXIT_THROUGH_ABYSS:
695     case DNGN_EXIT_ABYSS:
696     case DNGN_ENTER_PANDEMONIUM:
697     case DNGN_EXIT_PANDEMONIUM:
698     // DNGN_TRANSIT_PANDEMONIUM is too mundane
699         return true;
700     default:
701         return false;
702     }
703 }
704 
705 // Yay for rectangle_iterator and radius_iterator not sharing a base type
_check_portal(coord_def where)706 static bool _check_portal(coord_def where)
707 {
708     const dungeon_feature_type feat = env.grid(where);
709     if (feat != env.map_knowledge(where).feat() && is_ash_portal(feat))
710     {
711         env.map_knowledge(where).set_feature(feat);
712         set_terrain_mapped(where);
713 
714         if (!testbits(env.pgrid(where), FPROP_SEEN_OR_NOEXP))
715         {
716             env.pgrid(where) |= FPROP_SEEN_OR_NOEXP;
717             if (!you.see_cell(where))
718                 return true;
719         }
720     }
721     return false;
722 }
723 
ash_detect_portals(bool all)724 int ash_detect_portals(bool all)
725 {
726     if (!have_passive(passive_t::detect_portals))
727         return 0;
728 
729     int portals_found = 0;
730     const int map_radius = LOS_DEFAULT_RANGE + 1;
731 
732     if (all)
733     {
734         for (rectangle_iterator ri(0); ri; ++ri)
735         {
736             if (_check_portal(*ri))
737                 portals_found++;
738         }
739     }
740     else
741     {
742         for (radius_iterator ri(you.pos(), map_radius, C_SQUARE); ri; ++ri)
743         {
744             if (_check_portal(*ri))
745                 portals_found++;
746         }
747     }
748 
749     you.seen_portals += portals_found;
750     return portals_found;
751 }
752 
ash_monster_tier(const monster * mon)753 monster_type ash_monster_tier(const monster *mon)
754 {
755     return monster_type(MONS_SENSED_TRIVIAL + monster_info(mon).threat);
756 }
757 
758 /**
759  * Does the player have an ash skill boost for a particular skill?
760  */
ash_has_skill_boost(skill_type sk)761 bool ash_has_skill_boost(skill_type sk)
762 {
763     return have_passive(passive_t::bondage_skill_boost)
764            && you.skill_boost.count(sk) && you.skill_boost.find(sk)->second;
765 }
766 
767 /**
768  * Calculate the ash skill point boost for skill sk.
769  *
770  * @param scaled_skill the skill level to calculate it for, scaled by 10.
771  *
772  * @return the skill point bonus to use.
773  */
ash_skill_point_boost(skill_type sk,int scaled_skill)774 unsigned int ash_skill_point_boost(skill_type sk, int scaled_skill)
775 {
776     unsigned int skill_points = 0;
777 
778     skill_points += (you.skill_boost[sk] * 2 + 1) * (piety_rank() + 1)
779                     * max(scaled_skill, 1) * species_apt_factor(sk);
780     return skill_points;
781 }
782 
ash_skill_boost(skill_type sk,int scale)783 int ash_skill_boost(skill_type sk, int scale)
784 {
785     // It gives a bonus to skill points. The formula is:
786     // ( curses * 2 + 1 ) * (piety_rank + 1) * skill_level
787 
788     unsigned int skill_points = you.skill_points[sk]
789                   + get_crosstrain_points(sk)
790                   + ash_skill_point_boost(sk, you.skill(sk, 10, true));
791 
792     int level = you.skills[sk];
793     while (level < MAX_SKILL_LEVEL && skill_points >= skill_exp_needed(level + 1, sk))
794         ++level;
795 
796     level = level * scale + get_skill_progress(sk, level, skill_points, scale);
797 
798     return min(level, MAX_SKILL_LEVEL * scale);
799 }
800 
gozag_gold_in_los(actor * whom)801 int gozag_gold_in_los(actor *whom)
802 {
803     if (!have_passive(passive_t::gold_aura))
804         return 0;
805 
806     int gold_count = 0;
807 
808     for (radius_iterator ri(whom->pos(), LOS_RADIUS, C_SQUARE, LOS_DEFAULT);
809          ri; ++ri)
810     {
811         for (stack_iterator j(*ri); j; ++j)
812         {
813             if (j->base_type == OBJ_GOLD)
814                 ++gold_count;
815         }
816     }
817 
818     return gold_count;
819 }
820 
gozag_detect_level_gold(bool count)821 void gozag_detect_level_gold(bool count)
822 {
823     ASSERT(you.on_current_level);
824     vector<item_def *> gold_piles;
825     vector<coord_def> gold_places;
826     int gold = 0;
827     for (rectangle_iterator ri(0); ri; ++ri)
828     {
829         for (stack_iterator j(*ri); j; ++j)
830         {
831             if (j->base_type == OBJ_GOLD && !(j->flags & ISFLAG_UNOBTAINABLE))
832             {
833                 gold += j->quantity;
834                 gold_piles.push_back(&(*j));
835                 gold_places.push_back(*ri);
836             }
837         }
838     }
839 
840     if (!player_in_branch(BRANCH_ABYSS) && count)
841         you.attribute[ATTR_GOLD_GENERATED] += gold;
842 
843     if (have_passive(passive_t::detect_gold))
844     {
845         for (unsigned int i = 0; i < gold_places.size(); i++)
846         {
847             int dummy = gold_piles[i]->index();
848             coord_def &pos = gold_places[i];
849             unlink_item(dummy);
850             move_item_to_grid(&dummy, pos, true);
851             if ((!env.map_knowledge(pos).item()
852                  || env.map_knowledge(pos).item()->base_type != OBJ_GOLD
853                  && you.visible_igrd(pos) != NON_ITEM))
854             {
855                 env.map_knowledge(pos).set_item(
856                         get_item_known_info(*gold_piles[i]),
857                         !!env.map_knowledge(pos).item());
858                 env.map_knowledge(pos).flags |= MAP_DETECTED_ITEM;
859 #ifdef USE_TILE
860                 // force an update for gold generated during Abyss shifts
861                 tiles.update_minimap(pos);
862 #endif
863             }
864         }
865     }
866 }
867 
qazlal_sh_boost(int piety)868 int qazlal_sh_boost(int piety)
869 {
870     if (!have_passive(passive_t::storm_shield))
871         return 0;
872 
873     return min(piety, piety_breakpoint(5)) / 10;
874 }
875 
876 // Not actually passive, but placing it here so that it can be easily compared
877 // with Qazlal's boost.
tso_sh_boost()878 int tso_sh_boost()
879 {
880     if (!you.duration[DUR_DIVINE_SHIELD])
881         return 0;
882 
883     return you.attribute[ATTR_DIVINE_SHIELD];
884 }
885 
qazlal_storm_clouds()886 void qazlal_storm_clouds()
887 {
888     if (!have_passive(passive_t::storm_shield))
889         return;
890 
891     // You are a *storm*. You are pretty loud!
892     noisy(min((int)you.piety, piety_breakpoint(5)) / 10, you.pos());
893 
894     const int radius = you.piety >= piety_breakpoint(3) ? 2 : 1;
895 
896     vector<coord_def> candidates;
897     for (radius_iterator ri(you.pos(), radius, C_SQUARE, LOS_SOLID, true);
898          ri; ++ri)
899     {
900         int count = 0;
901         if (cell_is_solid(*ri) || cloud_at(*ri))
902             continue;
903 
904         // Don't create clouds over firewood
905         const monster * mon = monster_at(*ri);
906         if (mon != nullptr && mons_is_firewood(*mon))
907             continue;
908 
909         // No clouds in corridors.
910         for (adjacent_iterator ai(*ri); ai; ++ai)
911             if (!cell_is_solid(*ai))
912                 count++;
913 
914         if (count >= 5)
915             candidates.push_back(*ri);
916     }
917     const int count =
918         div_rand_round(min((int)you.piety, piety_breakpoint(5))
919                        * candidates.size() * you.time_taken,
920                        piety_breakpoint(5) * 7 * BASELINE_DELAY);
921     if (count < 0)
922         return;
923     shuffle_array(candidates);
924     int placed = 0;
925     for (unsigned int i = 0; placed < count && i < candidates.size(); i++)
926     {
927         bool water = false;
928         for (adjacent_iterator ai(candidates[i]); ai; ++ai)
929         {
930             if (feat_is_watery(env.grid(*ai)))
931                 water = true;
932         }
933 
934         // No flame clouds over water to avoid steam generation.
935         cloud_type ctype;
936         do
937         {
938             ctype = random_choose(CLOUD_FIRE, CLOUD_COLD, CLOUD_STORM,
939                                   CLOUD_DUST);
940         } while (water && ctype == CLOUD_FIRE);
941 
942         place_cloud(ctype, candidates[i], random_range(3, 5), &you);
943         placed++;
944     }
945 }
946 
947 /**
948  * Handle Qazlal's elemental adaptation.
949  * This should be called (exactly once) for physical, fire, cold, and electrical damage.
950  * Right now, it is called only from expose_player_to_element. This may merit refactoring.
951  *
952  * @param flavour the beam type.
953  * @param strength The adaptations will trigger strength in (11 - piety_rank()) times. In practice, this is mostly called with a value of 2.
954  */
qazlal_element_adapt(beam_type flavour,int strength)955 void qazlal_element_adapt(beam_type flavour, int strength)
956 {
957     if (strength <= 0
958         || !have_passive(passive_t::elemental_adaptation)
959         || !x_chance_in_y(strength, 11 - piety_rank()))
960     {
961         return;
962     }
963 
964     beam_type what = BEAM_NONE;
965     duration_type dur = NUM_DURATIONS;
966     string descript = "";
967     switch (flavour)
968     {
969         case BEAM_FIRE:
970         case BEAM_LAVA:
971         case BEAM_STICKY_FLAME:
972         case BEAM_STEAM:
973             what = BEAM_FIRE;
974             dur = DUR_QAZLAL_FIRE_RES;
975             descript = "fire";
976             break;
977         case BEAM_COLD:
978         case BEAM_ICE:
979             what = BEAM_COLD;
980             dur = DUR_QAZLAL_COLD_RES;
981             descript = "cold";
982             break;
983         case BEAM_ELECTRICITY:
984         case BEAM_THUNDER:
985             what = BEAM_ELECTRICITY;
986             dur = DUR_QAZLAL_ELEC_RES;
987             descript = "electricity";
988             break;
989         case BEAM_MMISSILE: // for LCS, iron shot
990         case BEAM_MISSILE:
991         case BEAM_FRAG:
992             what = BEAM_MISSILE;
993             dur = DUR_QAZLAL_AC;
994             descript = "physical attacks";
995             break;
996         default:
997             return;
998     }
999 
1000     if (what != BEAM_FIRE && you.duration[DUR_QAZLAL_FIRE_RES])
1001     {
1002         mprf(MSGCH_DURATION, "Your resistance to fire fades away.");
1003         you.duration[DUR_QAZLAL_FIRE_RES] = 0;
1004     }
1005 
1006     if (what != BEAM_COLD && you.duration[DUR_QAZLAL_COLD_RES])
1007     {
1008         mprf(MSGCH_DURATION, "Your resistance to cold fades away.");
1009         you.duration[DUR_QAZLAL_COLD_RES] = 0;
1010     }
1011 
1012     if (what != BEAM_ELECTRICITY && you.duration[DUR_QAZLAL_ELEC_RES])
1013     {
1014         mprf(MSGCH_DURATION, "Your resistance to electricity fades away.");
1015         you.duration[DUR_QAZLAL_ELEC_RES] = 0;
1016     }
1017 
1018     if (what != BEAM_MISSILE && you.duration[DUR_QAZLAL_AC])
1019     {
1020         mprf(MSGCH_DURATION, "Your resistance to physical damage fades away.");
1021         you.duration[DUR_QAZLAL_AC] = 0;
1022         you.redraw_armour_class = true;
1023     }
1024 
1025     mprf(MSGCH_GOD, "You feel %sprotected from %s.",
1026          you.duration[dur] > 0 ? "more " : "", descript.c_str());
1027 
1028     // was scaled by 10 * strength. But the strength parameter is used so inconsistently that
1029     // it seems like a constant would be better, based on the typical value of 2.
1030     you.increase_duration(dur, 20, 80);
1031 
1032     if (what == BEAM_MISSILE)
1033         you.redraw_armour_class = true;
1034 }
1035 
1036 /**
1037  * Determine whether a Ru worshipper will attempt to interfere with an attack
1038  * against the player.
1039  *
1040  * @return bool Whether or not whether the worshipper will attempt to interfere.
1041  */
does_ru_wanna_redirect(monster * mon)1042 bool does_ru_wanna_redirect(monster* mon)
1043 {
1044     return have_passive(passive_t::aura_of_power)
1045             && !mon->friendly()
1046             && you.see_cell_no_trans(mon->pos())
1047             && !mons_is_firewood(*mon)
1048             && !mon->submerged()
1049             && !mons_is_projectile(mon->type);
1050 }
1051 
1052 /**
1053  * Determine which, if any, action Ru takes on a possible attack.
1054  *
1055  * @return ru_interference
1056  */
get_ru_attack_interference_level()1057 ru_interference get_ru_attack_interference_level()
1058 {
1059     int r = random2(100);
1060     int chance = div_rand_round(you.piety, 16);
1061 
1062     // 10% chance of stopping any attack at max piety
1063     if (r < chance)
1064         return DO_BLOCK_ATTACK;
1065 
1066     // 5% chance of redirect at max piety
1067     else if (r < chance + div_rand_round(chance, 2))
1068         return DO_REDIRECT_ATTACK;
1069 
1070     else
1071         return DO_NOTHING;
1072 }
1073 
_shadow_acts(bool spell)1074 static bool _shadow_acts(bool spell)
1075 {
1076     const passive_t pasv = spell ? passive_t::shadow_spells
1077                                  : passive_t::shadow_attacks;
1078     if (!have_passive(pasv))
1079         return false;
1080 
1081     const int minpiety = piety_breakpoint(rank_for_passive(pasv) - 1);
1082 
1083     // 10% chance at minimum piety; 50% chance at 200 piety.
1084     const int range = MAX_PIETY - minpiety;
1085     const int min   = range / 5;
1086     return x_chance_in_y(min + ((range - min)
1087                                 * (you.piety - minpiety)
1088                                 / (MAX_PIETY - minpiety)),
1089                          2 * range);
1090 }
1091 
shadow_monster(bool equip)1092 monster* shadow_monster(bool equip)
1093 {
1094     if (monster_at(you.pos()))
1095         return nullptr;
1096 
1097     int wpn_index  = NON_ITEM;
1098 
1099     // Do a basic clone of the weapon.
1100     item_def* wpn = you.weapon();
1101     if (equip
1102         && wpn
1103         && is_weapon(*wpn))
1104     {
1105         wpn_index = get_mitm_slot(10);
1106         if (wpn_index == NON_ITEM)
1107             return nullptr;
1108         item_def& new_item = env.item[wpn_index];
1109         if (wpn->base_type == OBJ_STAVES)
1110         {
1111             new_item.base_type = OBJ_WEAPONS;
1112             new_item.sub_type  = WPN_STAFF;
1113         }
1114         else
1115         {
1116             new_item.base_type = wpn->base_type;
1117             new_item.sub_type  = wpn->sub_type;
1118         }
1119         new_item.quantity = 1;
1120         new_item.rnd = 1;
1121         new_item.flags   |= ISFLAG_SUMMONED;
1122     }
1123 
1124     monster* mon = get_free_monster();
1125     if (!mon)
1126     {
1127         if (wpn_index)
1128             destroy_item(wpn_index);
1129         return nullptr;
1130     }
1131 
1132     mon->type       = MONS_PLAYER_SHADOW;
1133     mon->behaviour  = BEH_SEEK;
1134     mon->attitude   = ATT_FRIENDLY;
1135     mon->flags      = MF_NO_REWARD | MF_JUST_SUMMONED | MF_SEEN
1136                     | MF_WAS_IN_VIEW | MF_HARD_RESET;
1137     mon->hit_points = you.hp;
1138     mon->set_hit_dice(min(27, max(1,
1139                                   you.skill_rdiv(wpn_index != NON_ITEM
1140                                                  ? item_attack_skill(env.item[wpn_index])
1141                                                  : SK_UNARMED_COMBAT, 10, 20)
1142                                   + you.skill_rdiv(SK_FIGHTING, 10, 20))));
1143     mon->set_position(you.pos());
1144     mon->mid        = MID_PLAYER;
1145     mon->inv[MSLOT_WEAPON]  = wpn_index;
1146     mon->inv[MSLOT_MISSILE] = NON_ITEM;
1147 
1148     env.mgrid(you.pos()) = mon->mindex();
1149 
1150     return mon;
1151 }
1152 
shadow_monster_reset(monster * mon)1153 void shadow_monster_reset(monster *mon)
1154 {
1155     if (mon->inv[MSLOT_WEAPON] != NON_ITEM)
1156         destroy_item(mon->inv[MSLOT_WEAPON]);
1157     // in case the shadow unwields for some reason, e.g. you clumsily bash with
1158     // a ranged weapon:
1159     if (mon->inv[MSLOT_ALT_WEAPON] != NON_ITEM)
1160         destroy_item(mon->inv[MSLOT_ALT_WEAPON]);
1161     if (mon->inv[MSLOT_MISSILE] != NON_ITEM)
1162         destroy_item(mon->inv[MSLOT_MISSILE]);
1163 
1164     mon->reset();
1165 }
1166 
1167 /**
1168  * Check if the player is in melee range of the target.
1169  *
1170  * Certain effects, e.g. distortion blink, can cause monsters to leave melee
1171  * range between the initial hit & the shadow mimic.
1172  *
1173  * XXX: refactor this with attack/fight code!
1174  *
1175  * @param target    The creature to be struck.
1176  * @return          Whether the player is melee range of the target, using
1177  *                  their current weapon.
1178  */
_in_melee_range(actor * target)1179 static bool _in_melee_range(actor* target)
1180 {
1181     const int dist = (you.pos() - target->pos()).rdist();
1182     return dist <= you.reach_range();
1183 }
1184 
dithmenos_shadow_melee(actor * target)1185 void dithmenos_shadow_melee(actor* target)
1186 {
1187     if (!target
1188         || !target->alive()
1189         || !_in_melee_range(target)
1190         || !_shadow_acts(false))
1191     {
1192         return;
1193     }
1194 
1195     monster* mon = shadow_monster();
1196     if (!mon)
1197         return;
1198 
1199     mon->target     = target->pos();
1200     mon->foe        = target->mindex();
1201 
1202     fight_melee(mon, target);
1203 
1204     shadow_monster_reset(mon);
1205 }
1206 
dithmenos_shadow_throw(const dist & d,const item_def & item)1207 void dithmenos_shadow_throw(const dist &d, const item_def &item)
1208 {
1209     ASSERT(d.isValid);
1210     if (!_shadow_acts(false))
1211         return;
1212 
1213     monster* mon = shadow_monster();
1214     if (!mon)
1215         return;
1216 
1217     int ammo_index = get_mitm_slot(10);
1218     if (ammo_index != NON_ITEM)
1219     {
1220         item_def& new_item = env.item[ammo_index];
1221         new_item.base_type = item.base_type;
1222         new_item.sub_type  = item.sub_type;
1223         new_item.quantity  = 1;
1224         new_item.rnd = 1;
1225         new_item.flags    |= ISFLAG_SUMMONED;
1226         mon->inv[MSLOT_MISSILE] = ammo_index;
1227 
1228         mon->target = clamp_in_bounds(d.target);
1229 
1230         bolt beem;
1231         beem.set_target(d);
1232         setup_monster_throw_beam(mon, beem);
1233         beem.item = &env.item[mon->inv[MSLOT_MISSILE]];
1234         mons_throw(mon, beem, mon->inv[MSLOT_MISSILE]);
1235     }
1236 
1237     shadow_monster_reset(mon);
1238 }
1239 
dithmenos_shadow_spell(bolt * orig_beam,spell_type spell)1240 void dithmenos_shadow_spell(bolt* orig_beam, spell_type spell)
1241 {
1242     if (!orig_beam)
1243         return;
1244 
1245     const coord_def target = orig_beam->target;
1246 
1247     if (orig_beam->target.origin()
1248         || (orig_beam->is_enchantment() && !is_valid_mon_spell(spell))
1249         || orig_beam->flavour == BEAM_CHARM
1250            && monster_at(target) && monster_at(target)->friendly()
1251         || !_shadow_acts(true))
1252     {
1253         return;
1254     }
1255 
1256     monster* mon = shadow_monster();
1257     if (!mon)
1258         return;
1259 
1260     // Don't let shadow spells get too powerful.
1261     mon->set_hit_dice(max(1,
1262                           min(3 * spell_difficulty(spell),
1263                               you.experience_level) / 2));
1264 
1265     mon->target = clamp_in_bounds(target);
1266     if (actor_at(target))
1267         mon->foe = actor_at(target)->mindex();
1268 
1269     spell_type shadow_spell = spell;
1270     if (!orig_beam->is_enchantment())
1271     {
1272         shadow_spell = (orig_beam->pierce) ? SPELL_SHADOW_BOLT
1273                                            : SPELL_SHADOW_SHARD;
1274     }
1275 
1276     bolt beem;
1277     beem.target = target;
1278     beem.aimed_at_spot = orig_beam->aimed_at_spot;
1279 
1280     mprf(MSGCH_FRIEND_SPELL, "%s mimicks your spell!",
1281          mon->name(DESC_THE).c_str());
1282     mons_cast(mon, beem, shadow_spell, MON_SPELL_WIZARD, false);
1283 
1284     shadow_monster_reset(mon);
1285 }
1286 
_wu_jian_trigger_serpents_lash(const coord_def & old_pos,bool wall_jump)1287 static void _wu_jian_trigger_serpents_lash(const coord_def& old_pos,
1288                                            bool wall_jump)
1289 {
1290     if (you.attribute[ATTR_SERPENTS_LASH] == 0)
1291        return;
1292 
1293     if (wall_jump && you.attribute[ATTR_SERPENTS_LASH] == 1)
1294     {
1295         // No turn manipulation, since we are only refunding half a wall jump's
1296         // time (the walk speed modifier for this special case is already
1297         // factored in main.cc)
1298         you.attribute[ATTR_SERPENTS_LASH] = 0;
1299     }
1300     else
1301     {
1302         you.turn_is_over = false;
1303         you.elapsed_time_at_last_input = you.elapsed_time;
1304         you.attribute[ATTR_SERPENTS_LASH] -= wall_jump ? 2 : 1;
1305         you.redraw_status_lights = true;
1306         update_turn_count();
1307     }
1308 
1309     if (you.attribute[ATTR_SERPENTS_LASH] == 0)
1310     {
1311         you.increase_duration(DUR_EXHAUSTED, 12 + random2(5));
1312         mpr("Your supernatural speed expires.");
1313     }
1314 
1315     if (!cell_is_solid(old_pos))
1316         check_place_cloud(CLOUD_DUST, old_pos, 2 + random2(3) , &you, 1, -1);
1317 }
1318 
_wu_jian_increment_heavenly_storm()1319 static void _wu_jian_increment_heavenly_storm()
1320 {
1321     int storm = you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int();
1322     if (storm < WU_JIAN_HEAVENLY_STORM_MAX)
1323     {
1324         you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int()++;
1325         you.redraw_evasion = true;
1326     }
1327 }
1328 
wu_jian_heaven_tick()1329 void wu_jian_heaven_tick()
1330 {
1331     for (radius_iterator ai(you.pos(), 2, C_SQUARE, LOS_SOLID); ai; ++ai)
1332         if (!cell_is_solid(*ai))
1333             place_cloud(CLOUD_GOLD_DUST, *ai, 5 + random2(5), &you);
1334 
1335     noisy(12, you.pos());
1336 }
1337 
wu_jian_decrement_heavenly_storm()1338 void wu_jian_decrement_heavenly_storm()
1339 {
1340     int storm = you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int();
1341 
1342     if (storm > 1)
1343     {
1344         you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int()--;
1345         you.set_duration(DUR_HEAVENLY_STORM, random_range(2, 3));
1346         you.redraw_evasion = true;
1347     }
1348     else
1349         wu_jian_end_heavenly_storm();
1350 }
1351 
1352 // TODO: why isn't this implemented as a duration end effect?
wu_jian_end_heavenly_storm()1353 void wu_jian_end_heavenly_storm()
1354 {
1355     you.props.erase(WU_JIAN_HEAVENLY_STORM_KEY);
1356     you.duration[DUR_HEAVENLY_STORM] = 0;
1357     you.redraw_evasion = true;
1358     invalidate_agrid(true);
1359     mprf(MSGCH_GOD, "The heavenly storm settles.");
1360 }
1361 
wu_jian_has_momentum(wu_jian_attack_type attack_type)1362 bool wu_jian_has_momentum(wu_jian_attack_type attack_type)
1363 {
1364     return you.attribute[ATTR_SERPENTS_LASH]
1365            && attack_type != WU_JIAN_ATTACK_NONE
1366            && attack_type != WU_JIAN_ATTACK_TRIGGERED_AUX;
1367 }
1368 
_can_attack_martial(const monster * mons)1369 static bool _can_attack_martial(const monster* mons)
1370 {
1371     return !(mons->wont_attack()
1372              || mons_is_firewood(*mons)
1373              || mons_is_projectile(mons->type)
1374              || !you.can_see(*mons));
1375 }
1376 
1377 // A mismatch between attack speed and move speed may cause any particular
1378 // martial attack to be doubled, tripled, or not happen at all. Given enough
1379 // time moving, you would have made the same amount of attacks as tabbing.
_wu_jian_number_of_attacks(bool wall_jump)1380 static int _wu_jian_number_of_attacks(bool wall_jump)
1381 {
1382     // Under the effect of serpent's lash, move delay is normalized to
1383     // 10 aut for every character, to avoid punishing fast races.
1384     const int move_delay = you.attribute[ATTR_SERPENTS_LASH]
1385                            ? 100
1386                            : player_movement_speed() * player_speed();
1387 
1388     int attack_delay;
1389 
1390     {
1391         // attack_delay() is dependent on you.time_taken, which won't be set
1392         // appropriately during a movement turn. This temporarily resets
1393         // you.time_taken to the initial value (see `_prep_input`) used for
1394         // basic, simple, melee attacks.
1395         // TODO: can `attack_delay` be changed to not depend on you.time_taken?
1396         unwind_var<int> reset_speed(you.time_taken, player_speed());
1397         attack_delay = you.attack_delay().roll();
1398     }
1399 
1400     return div_rand_round(wall_jump ? 2 * move_delay : move_delay,
1401                           attack_delay * BASELINE_DELAY);
1402 }
1403 
_wu_jian_lunge(const coord_def & old_pos)1404 static bool _wu_jian_lunge(const coord_def& old_pos)
1405 {
1406     coord_def lunge_direction = (you.pos() - old_pos).sgn();
1407     coord_def potential_target = you.pos() + lunge_direction;
1408     monster* mons = monster_at(potential_target);
1409 
1410     if (!mons || !_can_attack_martial(mons) || !mons->alive())
1411         return false;
1412 
1413     if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1414         _wu_jian_increment_heavenly_storm();
1415 
1416     you.apply_berserk_penalty = false;
1417 
1418     const int number_of_attacks = _wu_jian_number_of_attacks(false);
1419 
1420     if (number_of_attacks == 0)
1421     {
1422         mprf("You lunge at %s, but your attack speed is too slow for a blow "
1423              "to land.", mons->name(DESC_THE).c_str());
1424         return false;
1425     }
1426     else
1427     {
1428         mprf("You lunge%s at %s%s.",
1429              wu_jian_has_momentum(WU_JIAN_ATTACK_LUNGE) ?
1430                  " with incredible momentum" : "",
1431              mons->name(DESC_THE).c_str(),
1432              number_of_attacks > 1 ? ", in a flurry of attacks" : "");
1433     }
1434 
1435     count_action(CACT_INVOKE, ABIL_WU_JIAN_LUNGE);
1436 
1437     for (int i = 0; i < number_of_attacks; i++)
1438     {
1439         if (!mons->alive())
1440             break;
1441         melee_attack lunge(&you, mons);
1442         lunge.wu_jian_attack = WU_JIAN_ATTACK_LUNGE;
1443         lunge.attack();
1444     }
1445 
1446     return true;
1447 }
1448 
1449 // Monsters adjacent to the given pos that are valid targets for whirlwind.
_get_whirlwind_targets(coord_def pos)1450 static vector<monster*> _get_whirlwind_targets(coord_def pos)
1451 {
1452     vector<monster*> targets;
1453     for (adjacent_iterator ai(pos, true); ai; ++ai)
1454         if (monster_at(*ai) && _can_attack_martial(monster_at(*ai)))
1455             targets.push_back(monster_at(*ai));
1456     sort(targets.begin(), targets.end());
1457     return targets;
1458 }
1459 
_wu_jian_whirlwind(const coord_def & old_pos)1460 static bool _wu_jian_whirlwind(const coord_def& old_pos)
1461 {
1462     bool did_at_least_one_attack = false;
1463 
1464     const vector<monster*> targets = _get_whirlwind_targets(you.pos());
1465     if (targets.empty())
1466         return did_at_least_one_attack;
1467 
1468     const vector<monster*> old_targets = _get_whirlwind_targets(old_pos);
1469     vector<monster*> common_targets;
1470     set_intersection(targets.begin(), targets.end(),
1471                      old_targets.begin(), old_targets.end(),
1472                      back_inserter(common_targets));
1473 
1474     for (auto mons : common_targets)
1475     {
1476         if (!mons->alive())
1477             continue;
1478 
1479         if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1480             _wu_jian_increment_heavenly_storm();
1481 
1482         you.apply_berserk_penalty = false;
1483 
1484         const int number_of_attacks = _wu_jian_number_of_attacks(false);
1485         if (number_of_attacks == 0)
1486         {
1487             mprf("You spin to attack %s, but your attack speed is too slow for "
1488                  "a blow to land.", mons->name(DESC_THE).c_str());
1489             continue;
1490         }
1491         else
1492         {
1493             mprf("You spin and attack %s%s%s.",
1494                  mons->name(DESC_THE).c_str(),
1495                  number_of_attacks > 1 ? " repeatedly" : "",
1496                  wu_jian_has_momentum(WU_JIAN_ATTACK_WHIRLWIND) ?
1497                      ", with incredible momentum" : "");
1498         }
1499 
1500         count_action(CACT_INVOKE, ABIL_WU_JIAN_WHIRLWIND);
1501 
1502         for (int i = 0; i < number_of_attacks; i++)
1503         {
1504             if (!mons->alive())
1505                 break;
1506             melee_attack whirlwind(&you, mons);
1507             whirlwind.wu_jian_attack = WU_JIAN_ATTACK_WHIRLWIND;
1508             whirlwind.wu_jian_number_of_targets = common_targets.size();
1509             whirlwind.attack();
1510             if (!did_at_least_one_attack)
1511               did_at_least_one_attack = true;
1512         }
1513     }
1514 
1515     return did_at_least_one_attack;
1516 }
1517 
_wu_jian_trigger_martial_arts(const coord_def & old_pos)1518 static bool _wu_jian_trigger_martial_arts(const coord_def& old_pos)
1519 {
1520     bool did_wu_jian_attacks = false;
1521 
1522     if (you.pos() == old_pos
1523         || you.duration[DUR_CONF]
1524         || you.weapon() && !is_melee_weapon(*you.weapon()))
1525     {
1526         return did_wu_jian_attacks;
1527     }
1528 
1529     if (have_passive(passive_t::wu_jian_lunge))
1530         did_wu_jian_attacks = _wu_jian_lunge(old_pos);
1531 
1532     if (have_passive(passive_t::wu_jian_whirlwind))
1533         did_wu_jian_attacks |= _wu_jian_whirlwind(old_pos);
1534 
1535     return did_wu_jian_attacks;
1536 }
1537 
wu_jian_wall_jump_effects()1538 void wu_jian_wall_jump_effects()
1539 {
1540     vector<monster*> targets;
1541     for (adjacent_iterator ai(you.pos(), true); ai; ++ai)
1542     {
1543         monster* target = monster_at(*ai);
1544         if (target && _can_attack_martial(target) && target->alive())
1545             targets.push_back(target);
1546 
1547         if (!cell_is_solid(*ai))
1548             check_place_cloud(CLOUD_DUST, *ai, 1 + random2(3) , &you, 0, -1);
1549     }
1550 
1551     for (auto target : targets)
1552     {
1553         if (!target->alive())
1554             continue;
1555 
1556         if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
1557             _wu_jian_increment_heavenly_storm();
1558 
1559         you.apply_berserk_penalty = false;
1560 
1561         // Twice the attacks as Wall Jump spends twice the time
1562         const int number_of_attacks = _wu_jian_number_of_attacks(true);
1563         if (number_of_attacks == 0)
1564         {
1565             mprf("You attack %s from above, but your attack speed is too slow"
1566                  " for a blow to land.", target->name(DESC_THE).c_str());
1567             continue;
1568         }
1569         else
1570         {
1571             mprf("You %sattack %s from above%s.",
1572                  number_of_attacks > 1 ? "repeatedly " : "",
1573                  target->name(DESC_THE).c_str(),
1574                  wu_jian_has_momentum(WU_JIAN_ATTACK_WALL_JUMP) ?
1575                      ", with incredible momentum" : "");
1576         }
1577 
1578         for (int i = 0; i < number_of_attacks; i++)
1579         {
1580             if (!target->alive())
1581                 break;
1582 
1583             melee_attack aerial(&you, target);
1584             aerial.wu_jian_attack = WU_JIAN_ATTACK_WALL_JUMP;
1585             aerial.wu_jian_number_of_targets = targets.size();
1586             aerial.attack();
1587         }
1588     }
1589 }
1590 
wu_jian_post_move_effects(bool did_wall_jump,const coord_def & initial_position)1591 bool wu_jian_post_move_effects(bool did_wall_jump,
1592                                const coord_def& initial_position)
1593 {
1594     bool did_wu_jian_attacks = false;
1595 
1596     if (!did_wall_jump)
1597         did_wu_jian_attacks = _wu_jian_trigger_martial_arts(initial_position);
1598 
1599     if (you.turn_is_over)
1600         _wu_jian_trigger_serpents_lash(initial_position, did_wall_jump);
1601 
1602     return did_wu_jian_attacks;
1603 }
1604 
1605 /**
1606  * check if the monster in this cell exists and is a valid target for Uskayaw
1607  */
_check_for_uskayaw_targets(coord_def where)1608 static int _check_for_uskayaw_targets(coord_def where)
1609 {
1610     if (!cell_has_valid_target(where))
1611         return 0;
1612     monster* mons = monster_at(where);
1613     ASSERT(mons);
1614 
1615     if (mons_is_firewood(*mons))
1616         return 0;
1617 
1618     return 1;
1619 }
1620 
1621 /**
1622  * Paralyse the monster in this cell, assuming one exists.
1623  *
1624  * Duration increases with invocations and experience level, and decreases
1625  * with target HD. The duration is pretty low, maxing out at 40 AUT.
1626  */
_prepare_audience(coord_def where)1627 static int _prepare_audience(coord_def where)
1628 {
1629     if (!_check_for_uskayaw_targets(where))
1630         return 0;
1631 
1632     monster* mons = monster_at(where);
1633 
1634     int power =  max(1, random2(1 + you.skill(SK_INVOCATIONS, 2))
1635                  + you.experience_level - mons->get_hit_dice());
1636     int duration = min(max(10, 5 + power), 40);
1637     mons->add_ench(mon_enchant(ENCH_PARALYSIS, 1, &you, duration));
1638 
1639     return 1;
1640 }
1641 
1642 /**
1643  * On hitting *** piety, all the monsters are paralysed by their appreciation
1644  * for your dance.
1645  */
uskayaw_prepares_audience()1646 void uskayaw_prepares_audience()
1647 {
1648     int count = apply_area_visible(_check_for_uskayaw_targets, you.pos());
1649     if (count > 0)
1650     {
1651         simple_god_message(" prepares the audience for your solo!");
1652         apply_area_visible(_prepare_audience, you.pos());
1653 
1654         // Increment a delay timer to prevent players from spamming this ability
1655         // via piety loss and gain. Timer is in AUT.
1656         you.props[USKAYAW_AUDIENCE_TIMER] = 300 + random2(201);
1657     }
1658     else // Reset the timer because we didn't actually execute.
1659         you.props[USKAYAW_AUDIENCE_TIMER] = 0;
1660 }
1661 
1662 /**
1663  * Apply pain bond to the monster in this cell.
1664  */
_bond_audience(coord_def where)1665 static int _bond_audience(coord_def where)
1666 {
1667     if (!_check_for_uskayaw_targets(where))
1668         return 0;
1669 
1670     monster* mons = monster_at(where);
1671 
1672     // Don't pain bond monsters that aren't invested in fighting the player
1673     if (mons->wont_attack())
1674         return 0;
1675 
1676     int power = you.skill(SK_INVOCATIONS, 7) + you.experience_level
1677                  - mons->get_hit_dice();
1678     int duration = 20 + random2avg(power, 2);
1679     mons->add_ench(mon_enchant(ENCH_PAIN_BOND, 1, &you, duration));
1680 
1681     return 1;
1682 }
1683 
1684 /**
1685  * On hitting **** piety, all the monsters are pain bonded.
1686  */
uskayaw_bonds_audience()1687 void uskayaw_bonds_audience()
1688 {
1689     int count = apply_area_visible(_check_for_uskayaw_targets, you.pos());
1690     if (count > 1)
1691     {
1692         simple_god_message(" links your audience in an emotional bond!");
1693         apply_area_visible(_bond_audience, you.pos());
1694 
1695         // Increment a delay timer to prevent players from spamming this ability
1696         // via piety loss and gain. Timer is in AUT.
1697         you.props[USKAYAW_BOND_TIMER] = 300 + random2(201);
1698     }
1699     else // Reset the timer because we didn't actually execute.
1700         you.props[USKAYAW_BOND_TIMER] = 0;
1701 }
1702