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