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 = ∅ // 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 = ∅
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