1 /**
2  * @file
3  * @brief Translocation spells.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "spl-transloc.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <vector>
13 
14 #include "abyss.h"
15 #include "act-iter.h"
16 #include "areas.h"
17 #include "art-enum.h"
18 #include "artefact.h"
19 #include "cloud.h"
20 #include "coordit.h"
21 #include "delay.h"
22 #include "directn.h"
23 #include "dungeon.h"
24 #include "english.h"
25 #include "god-abil.h" // fedhas_passthrough for palentonga charge
26 #include "item-prop.h"
27 #include "items.h"
28 #include "level-state-type.h"
29 #include "libutil.h"
30 #include "los.h"
31 #include "losglobal.h"
32 #include "losparam.h"
33 #include "melee-attack.h" // palentonga charge
34 #include "message.h"
35 #include "mon-behv.h"
36 #include "mon-tentacle.h"
37 #include "mon-util.h"
38 #include "movement.h" // palentonga charge
39 #include "nearby-danger.h"
40 #include "orb.h"
41 #include "output.h"
42 #include "prompt.h"
43 #include "religion.h"
44 #include "shout.h"
45 #include "spl-damage.h" // cancel_polar_vortex
46 #include "spl-monench.h"
47 #include "spl-util.h"
48 #include "stash.h"
49 #include "state.h"
50 #include "stringutil.h"
51 #include "target.h"
52 #include "teleport.h"
53 #include "terrain.h"
54 #include "tiledoll.h"
55 #include "traps.h"
56 #include "view.h"
57 #include "viewmap.h"
58 #include "xom.h"
59 
60 /**
61  * Place a cloud of translocational energy at a player's previous location,
62  * to make it easier for players to tell what just happened.
63  *
64  * @param origin    The player's previous location.
65  */
_place_tloc_cloud(const coord_def & origin)66 static void _place_tloc_cloud(const coord_def &origin)
67 {
68     if (!cell_is_solid(origin))
69         place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), &you);
70 }
71 
cast_disjunction(int pow,bool fail)72 spret cast_disjunction(int pow, bool fail)
73 {
74     fail_check();
75     int rand = random_range(35, 45) + random2(pow / 12);
76     you.duration[DUR_DISJUNCTION] = min(90 + pow / 12,
77         max(you.duration[DUR_DISJUNCTION] + rand,
78         30 + rand));
79     contaminate_player(750 + random2(500), true);
80     disjunction_spell();
81     return spret::success;
82 }
83 
disjunction_spell()84 void disjunction_spell()
85 {
86     int steps = you.time_taken;
87     invalidate_agrid(true);
88     for (int step = 0; step < steps; ++step)
89     {
90         vector<monster*> mvec;
91         for (radius_iterator ri(you.pos(), LOS_RADIUS, C_SQUARE); ri; ++ri)
92         {
93             monster* mons = monster_at(*ri);
94             if (!mons || !you.see_cell(*ri))
95                 continue;
96             mvec.push_back(mons);
97         }
98         if (mvec.empty())
99             return;
100         // blink should be isotropic
101         shuffle_array(mvec);
102         for (auto mons : mvec)
103         {
104             if (!mons->alive() || mons->no_tele())
105                 continue;
106             coord_def p = mons->pos();
107             if (!disjunction_haloed(p))
108                 continue;
109 
110             int dist = grid_distance(you.pos(), p);
111             int decay = max(1, (dist - 1) * (dist + 1));
112             int chance = pow(0.8, 1.0 / decay) * 1000;
113             if (!x_chance_in_y(chance, 1000))
114                 blink_away(mons, &you, false);
115         }
116     }
117 }
118 
119 /**
120  * Attempt to blink the player to a random nearby tile.
121  *
122  * @param override_stasis       Whether to blink even if the player is under
123  *                              stasis (& thus normally unable to).
124  */
uncontrolled_blink(bool override_stasis)125 void uncontrolled_blink(bool override_stasis)
126 {
127     if (you.no_tele(true, true, true) && !override_stasis)
128     {
129         canned_msg(MSG_STRANGE_STASIS);
130         return;
131     }
132 
133     coord_def target;
134     // First try to find a random square not adjacent to the player,
135     // then one adjacent if that fails.
136     if (!random_near_space(&you, you.pos(), target)
137         && !random_near_space(&you, you.pos(), target, true))
138     {
139         mpr("You feel jittery for a moment.");
140         return;
141     }
142 
143     if (!you.attempt_escape(2)) // prints its own messages
144         return;
145 
146     canned_msg(MSG_YOU_BLINK);
147     const coord_def origin = you.pos();
148     move_player_to_grid(target, false);
149     _place_tloc_cloud(origin);
150 }
151 
152 /**
153  * Let the player choose a destination for their controlled blink or similar
154  * effect.
155  *
156  * @param target[out]   The target found, if any.
157  * @param safe_cancel   Whether it's OK to let the player cancel the control
158  *                      of the blink (or whether there should be a prompt -
159  *                      for e.g. read-identified ?blink)
160  * @param verb          What kind of movement is this, exactly?
161  *                      (E.g. 'blink', 'hop'.)
162  * @param hitfunc       A hitfunc passed to the direction_chooser.
163  * @return              True if a target was found; false if the player aborted.
164  */
_find_cblink_target(dist & target,bool safe_cancel,string verb,targeter * hitfunc=nullptr)165 static bool _find_cblink_target(dist &target, bool safe_cancel,
166                                 string verb, targeter *hitfunc = nullptr)
167 {
168     while (true)
169     {
170         // query for location {dlb}:
171         direction_chooser_args args;
172         args.restricts = DIR_TARGET;
173         args.needs_path = false;
174         args.top_prompt = uppercase_first(verb) + " to where?";
175         args.hitfunc = hitfunc;
176         direction(target, args);
177 
178         if (crawl_state.seen_hups)
179         {
180             mprf("Cancelling %s due to HUP.", verb.c_str());
181             return false;
182         }
183 
184         if (!target.isValid || target.target == you.pos())
185         {
186             const string prompt =
187                 "Are you sure you want to cancel this " + verb + "?";
188             if (!safe_cancel && !yesno(prompt.c_str(), false, 'n'))
189             {
190                 clear_messages();
191                 continue;
192             }
193 
194             canned_msg(MSG_OK);
195             return false;
196         }
197 
198         const monster* beholder = you.get_beholder(target.target);
199         if (beholder)
200         {
201             mprf("You cannot %s away from %s!",
202                  verb.c_str(),
203                  beholder->name(DESC_THE, true).c_str());
204             continue;
205         }
206 
207         const monster* fearmonger = you.get_fearmonger(target.target);
208         if (fearmonger)
209         {
210             mprf("You cannot %s closer to %s!",
211                  verb.c_str(),
212                  fearmonger->name(DESC_THE, true).c_str());
213             continue;
214         }
215 
216         if (cell_is_solid(target.target))
217         {
218             clear_messages();
219             mprf("You can't %s into that!", verb.c_str());
220             continue;
221         }
222 
223         monster* target_mons = monster_at(target.target);
224         if (target_mons && you.can_see(*target_mons))
225         {
226             mprf("You can't %s onto %s!", verb.c_str(),
227                  target_mons->name(DESC_THE).c_str());
228             continue;
229         }
230 
231         if (!check_moveto(target.target, verb, false))
232         {
233             continue;
234             // try again (messages handled by check_moveto)
235         }
236 
237         if (!you.see_cell_no_trans(target.target))
238         {
239             clear_messages();
240             if (you.trans_wall_blocking(target.target))
241                 canned_msg(MSG_SOMETHING_IN_WAY);
242             else
243                 canned_msg(MSG_CANNOT_SEE);
244             continue;
245         }
246 
247         if (cancel_harmful_move(false))
248         {
249             clear_messages();
250             continue;
251         }
252 
253         return true;
254     }
255 }
256 
wizard_blink()257 void wizard_blink()
258 {
259     // query for location {dlb}:
260     direction_chooser_args args;
261     args.restricts = DIR_TARGET;
262     args.needs_path = false;
263     targeter_smite tgt(&you, LOS_RADIUS);
264     tgt.obeys_mesmerise = false;
265     args.hitfunc = &tgt;
266 
267     args.top_prompt = "Blink to where?";
268     dist beam;
269     direction(beam, args);
270 
271     if (!beam.isValid || beam.target == you.pos())
272     {
273         canned_msg(MSG_OK);
274         return;
275     }
276 
277     if (!in_bounds(beam.target))
278     {
279         clear_messages();
280         mpr("Please don't blink into the map border.");
281         return wizard_blink();
282     }
283 
284     if (monster_at(beam.target))
285     {
286         clear_messages();
287         mpr("Please don't try to blink into monsters.");
288         return wizard_blink();
289     }
290 
291     if (!check_moveto(beam.target, "blink", false))
292     {
293         return wizard_blink();
294         // try again (messages handled by check_moveto)
295     }
296 
297     // Allow wizard blink to send player into walls, in case the
298     // user wants to alter that grid to something else.
299     if (cell_is_solid(beam.target))
300         env.grid(beam.target) = DNGN_FLOOR;
301 
302     move_player_to_grid(beam.target, false);
303 }
304 
305 static const int HOP_FUZZ_RADIUS = 2;
306 
307 /**
308  * Randomly choose one of the spaces near the given target for the player's hop
309  * to land on.
310  *
311  * @param target    The tile the player wants to land on.
312  * @return          A nearby, unoccupied, inhabitable tile.
313  */
_fuzz_hop_destination(coord_def target)314 static coord_def _fuzz_hop_destination(coord_def target)
315 {
316     coord_def chosen;
317     int seen = 0;
318     for (radius_iterator ri(target, HOP_FUZZ_RADIUS, C_SQUARE, LOS_NO_TRANS);
319          ri; ++ri)
320     {
321         if (valid_blink_destination(&you, *ri) && one_chance_in(++seen))
322             chosen = *ri;
323     }
324     return chosen;
325 }
326 
327 /**
328  * Attempt to hop the player to a space near a tile of their choosing.
329  *
330  * @param fail          Whether this came from a mis-invoked ability (& should
331  *                      therefore fail after selecting a target)
332  * @return              Whether the hop succeeded, aborted, or was miscast.
333  */
frog_hop(bool fail,dist * target)334 spret frog_hop(bool fail, dist *target)
335 {
336     dist empty;
337     if (!target)
338         target = &empty; // XX just convert some of these fn signatures to take dist &
339     const int hop_range = 2 + you.get_mutation_level(MUT_HOP) * 2; // 4-6
340     targeter_smite tgt(&you, hop_range, 0, HOP_FUZZ_RADIUS);
341     tgt.obeys_mesmerise = true;
342 
343     if (cancel_harmful_move())
344         return spret::abort;
345 
346     while (true)
347     {
348         if (!_find_cblink_target(*target, true, "hop", &tgt))
349             return spret::abort;
350 
351         if (grid_distance(you.pos(), target->target) > hop_range)
352         {
353             mpr("That's out of range!"); // ! targeting
354             continue;
355         }
356         break;
357     }
358     target->target = _fuzz_hop_destination(target->target);
359 
360     fail_check();
361 
362     if (!you.attempt_escape(2)) // XXX: 1?
363         return spret::success; // of a sort
364 
365     // invisible monster that the targeter didn't know to avoid, or similar
366     if (target->target.origin())
367     {
368         mpr("You tried to hop, but there was no room to land!");
369         // TODO: what to do here?
370         return spret::success; // of a sort
371     }
372 
373     if (!cell_is_solid(you.pos())) // should be safe.....
374         place_cloud(CLOUD_DUST, you.pos(), 2 + random2(3), &you);
375     move_player_to_grid(target->target, false);
376     crawl_state.cancel_cmd_again();
377     crawl_state.cancel_cmd_repeat();
378     mpr("Boing!");
379     you.increase_duration(DUR_NO_HOP, 12 + random2(13));
380     apply_barbs_damage();
381 
382     return spret::success; // TODO
383 }
384 
_check_charge_through(coord_def pos)385 static bool _check_charge_through(coord_def pos)
386 {
387     if (!you.can_pass_through_feat(env.grid(pos)))
388     {
389         clear_messages();
390         mprf("You can't roll into that!");
391         return false;
392     }
393 
394     return true;
395 }
396 
_find_charge_target(vector<coord_def> & target_path,int max_range,targeter * hitfunc,dist * target)397 static bool _find_charge_target(vector<coord_def> &target_path, int max_range,
398                                 targeter *hitfunc, dist *target)
399 {
400     // Check for unholy weapons, breadswinging, etc
401     if (!wielded_weapon_check(you.weapon(), "roll"))
402         return false;
403 
404     const bool interactive = target && target->interactive;
405     dist targ_local;
406     if (!target)
407         target = &targ_local;
408 
409     // TODO: can't this all be done within a single direction call?
410     while (true)
411     {
412         // query for location {dlb}:
413         direction_chooser_args args;
414         args.restricts = DIR_TARGET;
415         args.mode = TARG_HOSTILE;
416         args.prefer_farthest = true;
417         args.top_prompt = "Roll where?";
418         args.hitfunc = hitfunc;
419         direction(*target, args);
420 
421         // TODO: deduplicate with _find_cblink_target
422         if (crawl_state.seen_hups)
423         {
424             mpr("Cancelling rolling charge due to HUP.");
425             return false;
426         }
427 
428         if (!target->isValid || target->target == you.pos())
429         {
430             canned_msg(MSG_OK);
431             return false;
432         }
433 
434         const monster* beholder = you.get_beholder(target->target);
435         if (beholder)
436         {
437             mprf("You cannot roll away from %s!",
438                 beholder->name(DESC_THE, true).c_str());
439             if (interactive)
440                 continue;
441             else
442                 return false;
443         }
444 
445         const monster* fearmonger = you.get_fearmonger(target->target);
446         if (fearmonger)
447         {
448             mprf("You cannot roll closer to %s!",
449                 fearmonger->name(DESC_THE, true).c_str());
450             if (interactive)
451                 continue;
452             else
453                 return false;
454         }
455 
456         if (!you.see_cell_no_trans(target->target))
457         {
458             clear_messages();
459             if (you.trans_wall_blocking(target->target))
460                 canned_msg(MSG_SOMETHING_IN_WAY);
461             else
462                 canned_msg(MSG_CANNOT_SEE);
463             if (interactive)
464                 continue;
465             else
466                 return false;
467         }
468 
469         if (grid_distance(you.pos(), target->target) > max_range)
470         {
471             mpr("That's out of range!"); // ! targeting
472             if (interactive)
473                 continue;
474             else
475                 return false;
476         }
477 
478         ray_def ray;
479         if (!find_ray(you.pos(), target->target, ray, opc_solid))
480         {
481             mpr("You can't roll through that!");
482             if (interactive)
483                 continue;
484             else
485                 return false;
486         }
487 
488         // done with hard vetos; now we're on a mix of prompts and vetos.
489         // (Ideally we'd like to split these up and do all the vetos before
490         // the prompts, but...)
491 
492         target_path.clear();
493         bool ok = true;
494         while (ray.advance())
495         {
496             target_path.push_back(ray.pos());
497             if (!can_charge_through_mons(ray.pos()))
498                 break;
499             ok = _check_charge_through(ray.pos());
500             if (ray.pos() == target->target || !ok)
501                 break;
502         }
503         if (!ok)
504         {
505             if (interactive)
506                 continue;
507             else
508                 return false;
509         }
510 
511         // DON'T use beam.target here - we might have used ! targeting to
512         // target something behind another known monster
513         const monster* target_mons = monster_at(ray.pos());
514         const string bad_charge = bad_charge_target(ray.pos());
515         if (bad_charge != "")
516         {
517             mpr(bad_charge.c_str());
518             return false;
519         }
520 
521         if (adjacent(you.pos(), ray.pos()))
522         {
523             mprf("You're already next to %s!",
524                  target_mons->name(DESC_THE).c_str());
525             return false;
526         }
527 
528         // prompt to make sure the player really wants to attack the monster
529         // (if extant and not hostile)
530         // Intentionally don't use the real attack position here - that's only
531         // used for sanctuary,
532         // so it's more accurate if we use our current pos, since sanctuary
533         // should move with us.
534         if (stop_attack_prompt(target_mons, false, target_mons->pos()))
535             return false;
536 
537         ray.regress();
538         // confirm movement for the final square only
539         if (!check_moveto(ray.pos(), "charge"))
540             return false;
541 
542         return true;
543     }
544 }
545 
_charge_cloud_trail(const coord_def pos)546 static void _charge_cloud_trail(const coord_def pos)
547 {
548     if (!apply_cloud_trail(pos))
549         place_cloud(CLOUD_DUST, pos, 2 + random2(3), &you);
550 }
551 
palentonga_charge_possible(bool quiet,bool allow_safe_monsters)552 bool palentonga_charge_possible(bool quiet, bool allow_safe_monsters)
553 {
554     // general movement conditions are checked in ability.cc:_check_ability_possible
555     targeter_charge tgt(&you, palentonga_charge_range());
556     for (monster_near_iterator mi(&you); mi; ++mi)
557         if (tgt.valid_aim(mi->pos())
558             && (allow_safe_monsters || !mons_is_safe(*mi, false) || mons_class_is_test(mi->type)))
559         {
560             return true;
561         }
562     if (!quiet)
563         mpr("There's nothing you can charge at!");
564     return false;
565 }
566 
palentonga_charge_range()567 int palentonga_charge_range()
568 {
569     return 3 + you.get_mutation_level(MUT_ROLL);
570 }
571 
572 /**
573  * Attempt to charge the player to a target of their choosing.
574  *
575  * @param fail          Whether this came from a mis-invoked ability (& should
576  *                      therefore fail after selecting a target)
577  * @return              Whether the charge succeeded, aborted, or was miscast.
578  */
palentonga_charge(bool fail,dist * target)579 spret palentonga_charge(bool fail, dist *target)
580 {
581     const coord_def initial_pos = you.pos();
582 
583     vector<coord_def> target_path;
584     targeter_charge tgt(&you, palentonga_charge_range());
585     if (!_find_charge_target(target_path, palentonga_charge_range(), &tgt, target))
586         return spret::abort;
587 
588     fail_check();
589 
590     if (!you.attempt_escape(1)) // prints its own messages
591         return spret::success;
592 
593     const coord_def target_pos = target_path.back();
594     monster* target_mons = monster_at(target_pos);
595     if (fedhas_passthrough(target_mons))
596         target_mons = nullptr;
597     ASSERT(target_mons != nullptr);
598     // Are you actually moving forward?
599     if (grid_distance(you.pos(), target_pos) > 1 || !target_mons)
600         mpr("You roll forward with a clatter of scales!");
601 
602     crawl_state.cancel_cmd_again();
603     crawl_state.cancel_cmd_repeat();
604 
605     const coord_def orig_pos = you.pos();
606     for (coord_def pos : target_path)
607     {
608         monster* sneaky_mons = monster_at(pos);
609         if (sneaky_mons && !fedhas_passthrough(sneaky_mons))
610         {
611             target_mons = sneaky_mons;
612             break;
613         }
614     }
615     const coord_def dest_pos = target_path.at(target_path.size() - 2);
616 
617     remove_water_hold();
618     move_player_to_grid(dest_pos, true);
619     noisy(12, you.pos());
620     apply_barbs_damage();
621     _charge_cloud_trail(orig_pos);
622     for (auto it = target_path.begin(); it != target_path.end() - 2; ++it)
623         _charge_cloud_trail(*it);
624 
625     if (you.pos() != dest_pos) // polar vortex and trap nonsense
626         return spret::success; // of a sort
627 
628     // Maybe we hit a trap and something weird happened.
629     if (!target_mons->alive() || !adjacent(you.pos(), target_mons->pos()))
630         return spret::success;
631 
632     // manually apply noise
633     // this silence check feels kludgy - perhaps could check along the whole route..?
634     if (!silenced(target_pos))
635         behaviour_event(target_mons, ME_ALERT, &you, you.pos()); // shout + set you as foe
636 
637     // We got webbed/netted at the destination, bail on the attack.
638     if (you.attribute[ATTR_HELD])
639         return spret::success;
640 
641     const int base_delay =
642         div_rand_round(you.time_taken * player_movement_speed(), 10);
643 
644     melee_attack charge_atk(&you, target_mons);
645     charge_atk.roll_dist = grid_distance(initial_pos, you.pos());
646     charge_atk.attack();
647 
648     // Normally this is 10 aut (times haste, chei etc), but slow weapons
649     // take longer. Most relevant for low-skill players and Dark Maul.
650     you.time_taken = max(you.time_taken, base_delay);
651 
652     return spret::success;
653 }
654 
655 /**
656  * Attempt to blink the player to a nearby tile of their choosing. Doesn't
657  * handle you.no_tele().
658  *
659  * @param safe_cancel   Whether it's OK to let the player cancel the control
660  *                      of the blink (or whether there should be a prompt -
661  *                      for e.g. read-identified ?blink)
662  * @return              Whether the blink succeeded, aborted, or was miscast.
663  */
controlled_blink(bool safe_cancel,dist * target)664 spret controlled_blink(bool safe_cancel, dist *target)
665 {
666     if (crawl_state.is_replaying_keys())
667     {
668         crawl_state.cancel_cmd_all("You can't repeat controlled blinks.");
669         return spret::abort;
670     }
671 
672     dist empty;
673     if (!target)
674         target = &empty;
675 
676     targeter_smite tgt(&you, LOS_RADIUS);
677     tgt.obeys_mesmerise = true;
678     if (!_find_cblink_target(*target, safe_cancel, "blink", &tgt))
679         return spret::abort;
680 
681     if (!you.attempt_escape(2))
682         return spret::success; // of a sort
683 
684     // invisible monster that the targeter didn't know to avoid
685     if (monster_at(target->target))
686     {
687         mpr("Oops! There was something there already!");
688         uncontrolled_blink();
689         return spret::success; // of a sort
690     }
691 
692     _place_tloc_cloud(you.pos());
693     move_player_to_grid(target->target, false);
694 
695     crawl_state.cancel_cmd_again();
696     crawl_state.cancel_cmd_repeat();
697 
698     return spret::success;
699 }
700 
701 /**
702  * Cast the player spell Blink.
703  *
704  * @param fail              Whether the player miscast the spell.
705  * @return                  Whether the spell was successfully cast, aborted,
706  *                          or miscast.
707  */
cast_blink(bool fail)708 spret cast_blink(bool fail)
709 {
710     // effects that cast the spell through the player, I guess (e.g. xom)
711     if (you.no_tele(false, false, true))
712         return fail ? spret::fail : spret::success; // probably always SUCCESS
713 
714     if (cancel_harmful_move(false))
715         return spret::abort;
716 
717     fail_check();
718     uncontrolled_blink();
719     return spret::success;
720 }
721 
you_teleport()722 void you_teleport()
723 {
724     // [Cha] here we block teleportation, which will save the player from
725     // death from read-id'ing scrolls (in sprint)
726     if (you.no_tele(true, true))
727         canned_msg(MSG_STRANGE_STASIS);
728     else if (you.duration[DUR_TELEPORT])
729     {
730         mpr("You feel strangely stable.");
731         you.duration[DUR_TELEPORT] = 0;
732     }
733     else
734     {
735         mpr("You feel strangely unstable.");
736 
737         int teleport_delay = 3 + random2(3);
738 
739         if (player_in_branch(BRANCH_ABYSS))
740         {
741             mpr("You feel the power of the Abyss delaying your translocation!");
742             teleport_delay += 5 + random2(10);
743         }
744         else if (orb_limits_translocation())
745         {
746             mprf(MSGCH_ORB, "You feel the Orb delaying your translocation!");
747             teleport_delay += 5 + random2(5);
748         }
749 
750         you.set_duration(DUR_TELEPORT, teleport_delay);
751     }
752 }
753 
754 // Should return true if we don't want anyone to teleport here.
_cell_vetoes_teleport(const coord_def cell,bool check_monsters=true,bool wizard_tele=false)755 static bool _cell_vetoes_teleport(const coord_def cell, bool check_monsters = true,
756                                   bool wizard_tele = false)
757 {
758     // Monsters always veto teleport.
759     if (monster_at(cell) && check_monsters)
760         return true;
761 
762     // As do all clouds; this may change.
763     if (cloud_at(cell) && !wizard_tele)
764         return true;
765 
766     if (cell_is_solid(cell))
767         return true;
768 
769     return is_feat_dangerous(env.grid(cell), true) && !wizard_tele;
770 }
771 
_handle_teleport_update(bool large_change,const coord_def old_pos)772 static void _handle_teleport_update(bool large_change, const coord_def old_pos)
773 {
774     if (large_change)
775     {
776         viewwindow();
777         update_screen();
778         for (monster_iterator mi; mi; ++mi)
779         {
780             const bool see_cell = you.see_cell(mi->pos());
781 
782             if (mi->foe == MHITYOU && !see_cell && !you.penance[GOD_ASHENZARI])
783             {
784                 mi->foe_memory = 0;
785                 behaviour_event(*mi, ME_EVAL);
786             }
787             else if (see_cell)
788                 behaviour_event(*mi, ME_EVAL);
789         }
790     }
791 
792 #ifdef USE_TILE
793     if (you.has_innate_mutation(MUT_MERTAIL))
794     {
795         const dungeon_feature_type new_grid = env.grid(you.pos());
796         const dungeon_feature_type old_grid = env.grid(old_pos);
797         if (feat_is_water(old_grid) && !feat_is_water(new_grid)
798             || !feat_is_water(old_grid) && feat_is_water(new_grid))
799         {
800             init_player_doll();
801         }
802     }
803 #else
804     UNUSED(old_pos);
805 #endif
806 }
807 
_teleport_player(bool wizard_tele,bool teleportitis,string reason="")808 static bool _teleport_player(bool wizard_tele, bool teleportitis,
809                              string reason="")
810 {
811     if (!wizard_tele && !teleportitis
812         && (crawl_state.game_is_sprint() || you.no_tele())
813             && !player_in_branch(BRANCH_ABYSS))
814     {
815         if (!reason.empty())
816             mpr(reason);
817         canned_msg(MSG_STRANGE_STASIS);
818         return false;
819     }
820 
821     // After this point, we're guaranteed to teleport. Kill the appropriate
822     // delays. Teleportitis needs to check the target square first, though.
823     if (!teleportitis)
824         interrupt_activity(activity_interrupt::teleport);
825 
826     // Update what we can see at the current location as well as its stash,
827     // in case something happened in the exact turn that we teleported
828     // (like picking up/dropping an item).
829     viewwindow();
830     update_screen();
831     StashTrack.update_stash(you.pos());
832 
833     if (player_in_branch(BRANCH_ABYSS) && !wizard_tele)
834     {
835         if (teleportitis)
836             return false;
837 
838         if (!reason.empty())
839             mpr(reason);
840         abyss_teleport();
841         if (you.pet_target != MHITYOU)
842             you.pet_target = MHITNOT;
843 
844         return true;
845     }
846 
847     coord_def pos(1, 0);
848     const coord_def old_pos = you.pos();
849     bool      large_change  = false;
850 
851     if (wizard_tele)
852     {
853         while (true)
854         {
855             level_pos lpos;
856             bool chose = show_map(lpos, false, false);
857             pos = lpos.pos;
858             redraw_screen();
859             update_screen();
860 
861             // If we've received a HUP signal then the user can't choose a
862             // location, so cancel the teleport.
863             if (crawl_state.seen_hups)
864             {
865                 mprf(MSGCH_ERROR, "Controlled teleport interrupted by HUP signal, "
866                                   "cancelling teleport.");
867                 return false;
868             }
869 
870             dprf("Target square (%d,%d)", pos.x, pos.y);
871 
872             if (!chose || pos == you.pos())
873                 return false;
874 
875             break;
876         }
877 
878         if (!you.see_cell(pos))
879             large_change = true;
880 
881         if (_cell_vetoes_teleport(pos, true, wizard_tele))
882         {
883             mprf(MSGCH_WARN, "Even you can't go there right now. Sorry!");
884             return false;
885         }
886         else
887             move_player_to_grid(pos, false);
888     }
889     else
890     {
891         coord_def newpos;
892         int tries = 500;
893         do
894         {
895             newpos = random_in_bounds();
896         }
897         while (--tries > 0
898                && (_cell_vetoes_teleport(newpos)
899                    || testbits(env.pgrid(newpos), FPROP_NO_TELE_INTO)));
900 
901         // Running out of tries shouldn't happen; no message. Return false so
902         // it doesn't count as a random teleport for Xom purposes.
903         if (tries == 0)
904             return false;
905         // Teleportitis requires a monster in LOS of the new location, else
906         // it silently fails.
907         else if (teleportitis)
908         {
909             int mons_near_target = 0;
910             for (monster_near_iterator mi(newpos, LOS_NO_TRANS); mi; ++mi)
911                 if (mons_is_threatening(**mi) && mons_attitude(**mi) == ATT_HOSTILE)
912                     mons_near_target++;
913             if (!mons_near_target)
914             {
915                 dprf("teleportitis: no monster near target");
916                 return false;
917             }
918             else if (you.no_tele())
919             {
920                 if (!reason.empty())
921                     mpr(reason);
922                 canned_msg(MSG_STRANGE_STASIS);
923                 return false;
924             }
925             else
926             {
927                 interrupt_activity(activity_interrupt::teleport);
928                 if (!reason.empty())
929                     mpr(reason);
930                 mprf("You are suddenly yanked towards %s nearby monster%s!",
931                      mons_near_target > 1 ? "some" : "a",
932                      mons_near_target > 1 ? "s" : "");
933             }
934         }
935 
936         if (newpos == old_pos)
937             mpr("Your surroundings flicker for a moment.");
938         else if (you.see_cell(newpos))
939             mpr("Your surroundings seem slightly different.");
940         else
941         {
942             mpr("Your surroundings suddenly seem different.");
943             large_change = true;
944         }
945 
946         cancel_polar_vortex(true);
947         // Leave a purple cloud.
948         _place_tloc_cloud(old_pos);
949 
950         move_player_to_grid(newpos, false);
951         stop_delay(true);
952     }
953 
954     _handle_teleport_update(large_change, old_pos);
955     return !wizard_tele;
956 }
957 
you_teleport_to(const coord_def where_to,bool move_monsters)958 bool you_teleport_to(const coord_def where_to, bool move_monsters)
959 {
960     // Attempts to teleport the player from their current location to 'where'.
961     // Follows this line of reasoning:
962     //   1. Check the location (against _cell_vetoes_teleport), if valid,
963     //      teleport the player there.
964     //   2. If not because of a monster, and move_monster, teleport that
965     //      monster out of the way, then teleport the player there.
966     //   3. Otherwise, iterate over adjacent squares. If a sutiable position is
967     //      found (or a monster can be moved out of the way, with move_monster)
968     //      then teleport the player there.
969     //   4. If not, give up and return false.
970 
971     const coord_def old_pos = you.pos();
972     coord_def where = where_to;
973     coord_def old_where = where_to;
974 
975     // Don't bother to calculate a possible new position if it's out of bounds.
976     if (!in_bounds(where))
977         return false;
978 
979     if (_cell_vetoes_teleport(where))
980     {
981         if (monster_at(where) && move_monsters && !_cell_vetoes_teleport(where, false))
982         {
983             // dlua only, don't heed no_tele
984             monster* mons = monster_at(where);
985             mons->teleport(true);
986         }
987         else
988         {
989             for (adjacent_iterator ai(where); ai; ++ai)
990             {
991                 if (!_cell_vetoes_teleport(*ai))
992                 {
993                     where = *ai;
994                     break;
995                 }
996                 else
997                 {
998                     if (monster_at(*ai) && move_monsters
999                             && !_cell_vetoes_teleport(*ai, false))
1000                     {
1001                         monster* mons = monster_at(*ai);
1002                         mons->teleport(true);
1003                         where = *ai;
1004                         break;
1005                     }
1006                 }
1007             }
1008             // Give up, we can't find a suitable spot.
1009             if (where == old_where)
1010                 return false;
1011         }
1012     }
1013 
1014     // If we got this far, we're teleporting the player.
1015     _place_tloc_cloud(old_pos);
1016 
1017     bool large_change = you.see_cell(where);
1018 
1019     move_player_to_grid(where, false);
1020 
1021     _handle_teleport_update(large_change, old_pos);
1022     return true;
1023 }
1024 
you_teleport_now(bool wizard_tele,bool teleportitis,string reason)1025 void you_teleport_now(bool wizard_tele, bool teleportitis, string reason)
1026 {
1027     const bool randtele = _teleport_player(wizard_tele, teleportitis, reason);
1028 
1029     // Xom is amused by teleports that land you in a dangerous place, unless
1030     // the player is in the Abyss and teleported to escape from all the
1031     // monsters chasing him/her, since in that case the new dangerous area is
1032     // almost certainly *less* dangerous than the old dangerous area.
1033     if (randtele && !player_in_branch(BRANCH_ABYSS)
1034         && player_in_a_dangerous_place())
1035     {
1036         xom_is_stimulated(200);
1037     }
1038 }
1039 
cast_portal_projectile(int pow,bool fail)1040 spret cast_portal_projectile(int pow, bool fail)
1041 {
1042     fail_check();
1043     if (!you.duration[DUR_PORTAL_PROJECTILE])
1044         mpr("You begin teleporting projectiles to their destination.");
1045     else
1046         mpr("You renew your portal.");
1047     // Calculate the accuracy bonus based on current spellpower.
1048     you.attribute[ATTR_PORTAL_PROJECTILE] = pow;
1049     you.increase_duration(DUR_PORTAL_PROJECTILE, 3 + random2(pow / 2) + random2(pow / 5), 50);
1050     return spret::success;
1051 }
1052 
weapon_unprojectability_reason()1053 string weapon_unprojectability_reason()
1054 {
1055     if (!you.weapon())
1056         return "";
1057     const item_def &it = *you.weapon();
1058     // These all cause attack prompts, which are awkward to handle.
1059     // TODO: support these!
1060     static const vector<int> forbidden_unrands = {
1061         UNRAND_POWER,
1062         UNRAND_DEVASTATOR,
1063         UNRAND_VARIABILITY,
1064         UNRAND_SINGING_SWORD,
1065         UNRAND_TORMENT,
1066         UNRAND_ARC_BLADE,
1067     };
1068     for (int urand : forbidden_unrands)
1069     {
1070         if (is_unrandom_artefact(it, urand))
1071         {
1072             return make_stringf("%s would react catastrophically with paradoxical space!",
1073                                 you.weapon()->name(DESC_THE, false, false, false, false, ISFLAG_KNOW_PLUSES).c_str());
1074         }
1075     }
1076     return "";
1077 }
1078 
cast_manifold_assault(int pow,bool fail,bool real)1079 spret cast_manifold_assault(int pow, bool fail, bool real)
1080 {
1081     vector<monster*> targets;
1082     for (monster_near_iterator mi(&you, LOS_NO_TRANS); mi; ++mi)
1083     {
1084         if (mi->friendly() || mi->neutral())
1085             continue; // this should be enough to avoid penance?
1086         if (mons_is_firewood(**mi) || mons_is_projectile(**mi))
1087             continue;
1088         if (!you.can_see(**mi))
1089             continue;
1090         targets.emplace_back(*mi);
1091     }
1092 
1093     if (targets.empty())
1094     {
1095         if (real)
1096             mpr("You can't see anything to attack.");
1097         return spret::abort;
1098     }
1099 
1100     if (real)
1101     {
1102         const string unproj_reason = weapon_unprojectability_reason();
1103         if (unproj_reason != "")
1104         {
1105             mprf("%s", unproj_reason.c_str());
1106             return spret::abort;
1107         }
1108     }
1109 
1110     if (!real)
1111         return spret::success;
1112 
1113     if (!wielded_weapon_check(you.weapon()))
1114         return spret::abort;
1115 
1116     fail_check();
1117 
1118     mpr("Space momentarily warps into an impossible shape!");
1119 
1120     const int initial_time = you.time_taken;
1121 
1122     shuffle_array(targets);
1123     const size_t max_targets = 2 + div_rand_round(pow, 50);
1124     for (size_t i = 0; i < max_targets && i < targets.size(); i++)
1125     {
1126         // Somewhat hacky: reset attack delay before each attack so that only the final
1127         // attack ends up actually setting time taken. (No quadratic effects.)
1128         you.time_taken = initial_time;
1129 
1130         melee_attack atk(&you, targets[i]);
1131         atk.is_projected = true;
1132         atk.attack();
1133 
1134         if (you.hp <= 0 || you.pending_revival)
1135             break;
1136     }
1137 
1138     return spret::success;
1139 }
1140 
cast_apportation(int pow,bolt & beam,bool fail)1141 spret cast_apportation(int pow, bolt& beam, bool fail)
1142 {
1143     const coord_def where = beam.target;
1144 
1145     if (!cell_see_cell(you.pos(), where, LOS_SOLID))
1146     {
1147         canned_msg(MSG_SOMETHING_IN_WAY);
1148         return spret::abort;
1149     }
1150 
1151     // Let's look at the top item in that square...
1152     // And don't allow apporting from shop inventories.
1153     // Using visible_igrd takes care of deep water/lava where appropriate.
1154     const int item_idx = you.visible_igrd(where);
1155     if (item_idx == NON_ITEM || !in_bounds(where))
1156     {
1157         mpr("You don't see anything to apport there.");
1158         return spret::abort;
1159     }
1160 
1161     item_def& item = env.item[item_idx];
1162 
1163     // Nets can be apported when they have a victim trapped.
1164     if (item_is_stationary(item) && !item_is_stationary_net(item))
1165     {
1166         mpr("You cannot apport that!");
1167         return spret::abort;
1168     }
1169 
1170     fail_check();
1171 
1172     // We need to modify the item *before* we move it, because
1173     // move_top_item() might change the location, or merge
1174     // with something at our position.
1175     if (item_is_orb(item))
1176     {
1177         fake_noisy(30, where);
1178 
1179         // There's also a 1-in-3 flat chance of apport failing.
1180         if (one_chance_in(3))
1181         {
1182             orb_pickup_noise(where, 30,
1183                 "The Orb shrieks and becomes a dead weight against your magic!",
1184                 "The Orb lets out a furious burst of light and becomes "
1185                     "a dead weight against your magic!");
1186             return spret::success;
1187         }
1188         else // Otherwise it's just a noisy little shiny thing
1189         {
1190             orb_pickup_noise(where, 30,
1191                 "The Orb shrieks as your magic touches it!",
1192                 "The Orb lets out a furious burst of light as your magic touches it!");
1193             start_orb_run(CHAPTER_ANGERED_PANDEMONIUM, "Now pick up the Orb and get out of here!");
1194         }
1195     }
1196 
1197     // If we apport a net, free the monster under it.
1198     if (item_is_stationary_net(item))
1199     {
1200         free_stationary_net(item_idx);
1201         if (monster* mons = monster_at(where))
1202             mons->del_ench(ENCH_HELD, true);
1203     }
1204 
1205     beam.is_tracer = true;
1206     beam.aimed_at_spot = true;
1207     beam.affects_nothing = true;
1208     beam.fire();
1209 
1210     // The item's current location is not part of the apportion path
1211     beam.path_taken.pop_back();
1212 
1213     // The actual number of squares it needs to traverse to get to you.
1214     int dist = beam.path_taken.size();
1215 
1216     // The maximum number of squares the item will actually move, always
1217     // at least one square. Always has a chance to move the entirety of default
1218     // LOS (7), but only becomes certain at max power (50).
1219     int max_dist = max(1, min(LOS_RADIUS, random2(8) + div_rand_round(pow, 7)));
1220 
1221     dprf("Apport dist=%d, max_dist=%d", dist, max_dist);
1222 
1223     // path_taken does not include the player's position either, but we do want
1224     // to check that. Treat -1 as the player's pos; 0 is 1 away from player.
1225     int location_on_path = max(-1, dist - max_dist);
1226     coord_def new_spot = (location_on_path < 0)
1227                                         ? you.pos()
1228                                         : beam.path_taken[location_on_path];
1229 
1230     // Try to find safe terrain for the item, including the player's position
1231     // if max_dist < 0. At this point, location_on_path is guaranteed to be
1232     // less than dist.
1233     while (location_on_path < dist)
1234     {
1235         if (!feat_eliminates_items(env.grid(new_spot)))
1236             break;
1237         location_on_path++;
1238         if (location_on_path == dist)
1239         {
1240             // we've checked every position in beam.path_taken within max_dist
1241             mpr("Not with that terrain in the way!");
1242             return spret::success; // of a sort
1243         }
1244         new_spot = beam.path_taken[location_on_path];
1245     }
1246     dprf("Apport: new spot is %d/%d", new_spot.x, new_spot.y);
1247 
1248     // Actually move the item.
1249     mprf("Yoink! You pull the item%s towards yourself.",
1250          (item.quantity > 1) ? "s" : "");
1251 
1252     move_top_item(where, new_spot);
1253 
1254     // Mark the item as found now.
1255     origin_set(new_spot);
1256 
1257     return spret::success;
1258 }
1259 
cast_golubrias_passage(const coord_def & where,bool fail)1260 spret cast_golubrias_passage(const coord_def& where, bool fail)
1261 {
1262     if (player_in_branch(BRANCH_GAUNTLET))
1263     {
1264         mprf(MSGCH_ORB, "A magic seal in the Gauntlet prevents you from "
1265                 "opening a passage!");
1266         return spret::abort;
1267     }
1268 
1269     // randomize position a bit to make it not as useful to use on monsters
1270     // chasing you, as well as to not give away hidden trap positions
1271     int tries = 0;
1272     int tries2 = 0;
1273     // Less accurate when the orb is interfering.
1274     const int range = orb_limits_translocation() ? 4 : 2;
1275     coord_def randomized_where = where;
1276     coord_def randomized_here = you.pos();
1277     do
1278     {
1279         tries++;
1280         randomized_where = where;
1281         randomized_where.x += random_range(-range, range);
1282         randomized_where.y += random_range(-range, range);
1283     }
1284     while ((!in_bounds(randomized_where)
1285             || env.grid(randomized_where) != DNGN_FLOOR
1286             || monster_at(randomized_where)
1287             || !you.see_cell(randomized_where)
1288             || you.trans_wall_blocking(randomized_where)
1289             || randomized_where == you.pos())
1290            && tries < 100);
1291 
1292     do
1293     {
1294         tries2++;
1295         randomized_here = you.pos();
1296         randomized_here.x += random_range(-range, range);
1297         randomized_here.y += random_range(-range, range);
1298     }
1299     while ((!in_bounds(randomized_here)
1300             || env.grid(randomized_here) != DNGN_FLOOR
1301             || monster_at(randomized_here)
1302             || !you.see_cell(randomized_here)
1303             || you.trans_wall_blocking(randomized_here)
1304             || randomized_here == randomized_where)
1305            && tries2 < 100);
1306 
1307     if (tries >= 100 || tries2 >= 100)
1308     {
1309         if (you.trans_wall_blocking(randomized_where))
1310             mpr("You cannot create a passage on the other side of the transparent wall.");
1311         else
1312             // XXX: bleh, dumb message
1313             mpr("Creating a passage of Golubria requires sufficient empty space.");
1314         return spret::abort;
1315     }
1316 
1317     fail_check();
1318     place_specific_trap(randomized_where, TRAP_GOLUBRIA);
1319     place_specific_trap(randomized_here, TRAP_GOLUBRIA);
1320     env.level_state |= LSTATE_GOLUBRIA;
1321 
1322     trap_def *trap = trap_at(randomized_where);
1323     trap_def *trap2 = trap_at(randomized_here);
1324     if (!trap || !trap2)
1325     {
1326         mpr("Something buggy happened.");
1327         return spret::abort;
1328     }
1329 
1330     if (orb_limits_translocation())
1331         mprf(MSGCH_ORB, "The Orb disrupts the stability of your passage!");
1332 
1333     trap->reveal();
1334     trap2->reveal();
1335 
1336     return spret::success;
1337 }
1338 
_disperse_monster(monster & mon,int pow)1339 static int _disperse_monster(monster& mon, int pow)
1340 {
1341     if (mon.no_tele())
1342         return false;
1343 
1344     if (mon.check_willpower(pow) > 0)
1345         monster_blink(&mon);
1346     else
1347         monster_teleport(&mon, true);
1348 
1349     // Moving the monster may have killed it in apply_location_effects.
1350     if (mon.alive() && mon.check_willpower(pow) <= 0)
1351         mon.confuse(&you, 1 + random2avg(pow / 10, 2));
1352 
1353     return true;
1354 }
1355 
cast_dispersal(int pow,bool fail)1356 spret cast_dispersal(int pow, bool fail)
1357 {
1358     fail_check();
1359     const int radius = spell_range(SPELL_DISPERSAL, pow);
1360     if (!apply_monsters_around_square([pow] (monster& mon) {
1361             return _disperse_monster(mon, pow);
1362         }, you.pos(), radius))
1363     {
1364         mpr("The air shimmers briefly around you.");
1365     }
1366     return spret::success;
1367 }
1368 
gravitas_range(int pow)1369 int gravitas_range(int pow)
1370 {
1371     return pow >= 80 ? 3 : 2;
1372 }
1373 
1374 
1375 #define GRAVITY "by gravitational forces"
1376 
_attract_actor(const actor * agent,actor * victim,const coord_def pos,int pow,int strength)1377 static void _attract_actor(const actor* agent, actor* victim,
1378                            const coord_def pos, int pow, int strength)
1379 {
1380     ASSERT(victim); // XXX: change to actor &victim
1381     const bool fedhas_prot = victim->is_monster()
1382                                 && god_protects(agent, victim->as_monster());
1383 
1384     ray_def ray;
1385     if (!find_ray(victim->pos(), pos, ray, opc_solid))
1386     {
1387         // This probably shouldn't ever happen, but just in case:
1388         if (you.can_see(*victim))
1389         {
1390             mprf("%s violently %s moving!",
1391                  victim->name(DESC_THE).c_str(),
1392                  victim->conj_verb("stop").c_str());
1393         }
1394         if (fedhas_prot)
1395         {
1396             simple_god_message(
1397                 make_stringf(" protects %s from harm.",
1398                     agent->is_player() ? "your" : "a").c_str(), GOD_FEDHAS);
1399         }
1400         else
1401         {
1402             victim->hurt(agent, roll_dice(strength / 2, pow / 20),
1403                          BEAM_MMISSILE, KILLED_BY_BEAM, "", GRAVITY);
1404         }
1405         return;
1406     }
1407 
1408     const coord_def starting_pos = victim->pos();
1409     for (int i = 0; i < strength; i++)
1410     {
1411         ray.advance();
1412         const coord_def newpos = ray.pos();
1413 
1414         if (!victim->can_pass_through_feat(env.grid(newpos)))
1415         {
1416             victim->collide(newpos, agent, pow);
1417             break;
1418         }
1419         else if (actor* act_at_space = actor_at(newpos))
1420         {
1421             if (victim != act_at_space)
1422                 victim->collide(newpos, agent, pow);
1423             break;
1424         }
1425         else if (!victim->is_habitable(newpos))
1426             break;
1427         else
1428             victim->move_to_pos(newpos);
1429 
1430         if (victim->is_monster() && !fedhas_prot)
1431         {
1432             behaviour_event(victim->as_monster(),
1433                             ME_ANNOY, agent, agent ? agent->pos()
1434                                                    : coord_def(0, 0));
1435         }
1436 
1437         if (victim->pos() == pos)
1438             break;
1439     }
1440     if (starting_pos != victim->pos())
1441     {
1442         victim->apply_location_effects(starting_pos);
1443         if (victim->is_monster())
1444             mons_relocated(victim->as_monster());
1445     }
1446 }
1447 
fatal_attraction(const coord_def & pos,const actor * agent,int pow)1448 bool fatal_attraction(const coord_def& pos, const actor *agent, int pow)
1449 {
1450     vector <actor *> victims;
1451 
1452     for (actor_near_iterator ai(pos, LOS_SOLID); ai; ++ai)
1453     {
1454         if (*ai == agent || ai->is_stationary() || ai->pos() == pos)
1455             continue;
1456 
1457         const int range = (pos - ai->pos()).rdist();
1458         if (range > gravitas_range(pow))
1459             continue;
1460 
1461         victims.push_back(*ai);
1462     }
1463 
1464     if (victims.empty())
1465         return false;
1466 
1467     near_to_far_sorter sorter = {you.pos()};
1468     sort(victims.begin(), victims.end(), sorter);
1469 
1470     for (actor * ai : victims)
1471     {
1472         const int range = (pos - ai->pos()).rdist();
1473         const int strength = ((pow + 100) / 20) / (range*range);
1474 
1475         _attract_actor(agent, ai, pos, pow, strength);
1476     }
1477 
1478     return true;
1479 }
1480 
cast_gravitas(int pow,const coord_def & where,bool fail)1481 spret cast_gravitas(int pow, const coord_def& where, bool fail)
1482 {
1483     if (cell_is_solid(where))
1484     {
1485         canned_msg(MSG_UNTHINKING_ACT);
1486         return spret::abort;
1487     }
1488 
1489     fail_check();
1490 
1491     monster* mons = monster_at(where);
1492 
1493     mprf("Gravity reorients around %s.",
1494          mons                      ? mons->name(DESC_THE).c_str() :
1495          feat_is_solid(env.grid(where)) ? feature_description(env.grid(where),
1496                                                          NUM_TRAPS, "",
1497                                                          DESC_THE)
1498                                                          .c_str()
1499                                    : "empty space");
1500     fatal_attraction(where, &you, pow);
1501     return spret::success;
1502 }
1503 
_can_beckon(const actor & beckoned)1504 static bool _can_beckon(const actor &beckoned)
1505 {
1506     return !beckoned.is_stationary()  // don't move statues, etc
1507         && !mons_is_tentacle_or_tentacle_segment(beckoned.type); // a mess...
1508 }
1509 
1510 /**
1511  * Where is the closest point along the given path to its source that the given
1512  * actor can be moved to?
1513  *
1514  * @param beckoned      The actor to be moved.
1515  * @param path          The path for the actor to be moved along
1516  * @return              The closest point for the actor to be moved to;
1517  *                      guaranteed to be on the path or its original location.
1518  */
_beckon_destination(const actor & beckoned,const bolt & path)1519 static coord_def _beckon_destination(const actor &beckoned, const bolt &path)
1520 {
1521     if (!_can_beckon(beckoned))
1522         return beckoned.pos();
1523 
1524     for (coord_def pos : path.path_taken)
1525     {
1526         if (actor_at(pos) || !beckoned.is_habitable(pos))
1527             continue; // actor could be caster, or a bush
1528 
1529         return pos;
1530     }
1531 
1532     return beckoned.pos(); // failed to find any point along the path
1533 }
1534 
1535 /**
1536  * Attempt to move the beckoned creature to the spot on the path closest to its
1537  * beginning (that is, to the caster of the effect). Also handles some
1538  * messaging.
1539  *
1540  * @param beckoned  The creature being moved.
1541  * @param path      The path to move the creature along.
1542  * @return          Whether the beckoned creature actually moved.
1543  */
beckon(actor & beckoned,const bolt & path)1544 bool beckon(actor &beckoned, const bolt &path)
1545 {
1546     const coord_def dest = _beckon_destination(beckoned, path);
1547     if (dest == beckoned.pos())
1548         return false;
1549 
1550     const coord_def old_pos = beckoned.pos();
1551     if (!beckoned.move_to_pos(dest))
1552         return false;
1553 
1554     mprf("%s %s suddenly forward!",
1555          beckoned.name(DESC_THE).c_str(),
1556          beckoned.conj_verb("hurl").c_str());
1557 
1558     beckoned.apply_location_effects(old_pos); // traps, etc.
1559     if (beckoned.is_monster())
1560         mons_relocated(beckoned.as_monster()); // cleanup tentacle segments
1561 
1562     return true;
1563 }
1564 
_can_move_mons_to(const monster & mons,coord_def pos)1565 static bool _can_move_mons_to(const monster &mons, coord_def pos)
1566 {
1567     return mons.can_pass_through_feat(env.grid(pos))
1568            && !actor_at(pos)
1569            && mons.is_habitable(pos);
1570 }
1571 
1572 /**
1573   * Attempt to pull nearby monsters toward the player.
1574  */
attract_monsters()1575 void attract_monsters()
1576 {
1577     vector<monster *> targets;
1578     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1579         if (!mi->friendly() && _can_beckon(**mi))
1580             targets.push_back(*mi);
1581 
1582     near_to_far_sorter sorter = {you.pos()};
1583     sort(targets.begin(), targets.end(), sorter);
1584 
1585     for (monster *mi : targets) {
1586         const int orig_dist = grid_distance(you.pos(), mi->pos());
1587         if (orig_dist <= 1)
1588             continue;
1589 
1590         ray_def ray;
1591         if (!find_ray(mi->pos(), you.pos(), ray, opc_solid))
1592             continue;
1593 
1594         const int max_move = 3;
1595         for (int i = 0; i < max_move && i < orig_dist - 1; i++)
1596             ray.advance();
1597 
1598         while (!_can_move_mons_to(*mi, ray.pos()) && ray.pos() != mi->pos())
1599             ray.regress();
1600 
1601         if (ray.pos() == mi->pos())
1602             continue;
1603 
1604         const coord_def old_pos = mi->pos();
1605         if (!mi->move_to_pos(ray.pos()))
1606             continue;
1607 
1608         mprf("%s is pulled toward you!", mi->name(DESC_THE).c_str());
1609 
1610         mi->apply_location_effects(old_pos);
1611         mons_relocated(mi);
1612     }
1613 }
1614 
word_of_chaos(int pow,bool fail)1615 spret word_of_chaos(int pow, bool fail)
1616 {
1617     vector<monster *> visible_targets;
1618     vector<monster *> targets;
1619     for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
1620     {
1621         if (!mons_is_tentacle_or_tentacle_segment(mi->type)
1622             && !mons_class_is_stationary(mi->type)
1623             && !mons_is_conjured(mi->type)
1624             && !mi->friendly())
1625         {
1626             if (you.can_see(**mi))
1627                 visible_targets.push_back(*mi);
1628             targets.push_back(*mi);
1629         }
1630     }
1631 
1632     if (visible_targets.empty())
1633     {
1634         if (!yesno("You cannot see any enemies that you can affect. Speak a "
1635                    "word of chaos anyway?", true, 'n'))
1636         {
1637             canned_msg(MSG_OK);
1638             return spret::abort;
1639         }
1640     }
1641 
1642     fail_check();
1643     shuffle_array(targets);
1644 
1645     mprf("You speak a word of chaos!");
1646     for (auto mons : targets)
1647     {
1648         if (mons->no_tele())
1649             continue;
1650 
1651         blink_away(mons, &you, false);
1652         if (x_chance_in_y(pow, 500))
1653             ensnare(mons);
1654         if (x_chance_in_y(pow, 500))
1655             do_slow_monster(*mons, &you, 20 + random2(pow));
1656         if (x_chance_in_y(pow, 500))
1657         {
1658             mons->add_ench(mon_enchant(ENCH_FEAR, 0, &you));
1659             behaviour_event(mons, ME_SCARE, &you);
1660         }
1661     }
1662 
1663     you.increase_duration(DUR_WORD_OF_CHAOS_COOLDOWN, 15 + random2(10));
1664     drain_player(50, false, true);
1665     return spret::success;
1666 }
1667 
blinkbolt(int power,bolt & beam,bool fail)1668 spret blinkbolt(int power, bolt &beam, bool fail)
1669 {
1670     if (cell_is_solid(beam.target))
1671     {
1672         canned_msg(MSG_UNTHINKING_ACT);
1673         return spret::abort;
1674     }
1675 
1676     monster* mons = monster_at(beam.target);
1677     if (!mons || !you.can_see(*mons))
1678     {
1679         mpr("You see nothing there to target!");
1680         return spret::abort;
1681     }
1682 
1683     if (!player_tracer(ZAP_BLINKBOLT, power, beam))
1684         return spret::abort;
1685 
1686     fail_check();
1687 
1688     beam.thrower = KILL_YOU_MISSILE;
1689     zappy(ZAP_BLINKBOLT, power, false, beam);
1690     beam.name = "shock of your passage";
1691     beam.fire();
1692     you.duration[DUR_BLINKBOLT_COOLDOWN] = 50 + random2(150);
1693 
1694     return spret::success;
1695 }
1696