1 #include "AppHdr.h"
2
3 #include "target.h"
4
5 #include <cmath>
6 #include <utility> // swap
7
8 #include "cloud.h"
9 #include "coord.h"
10 #include "coordit.h"
11 #include "directn.h"
12 #include "english.h"
13 #include "env.h"
14 #include "fight.h"
15 #include "god-abil.h"
16 #include "libutil.h"
17 #include "los-def.h"
18 #include "losglobal.h"
19 #include "mon-tentacle.h"
20 #include "religion.h"
21 #include "ray.h"
22 #include "spl-damage.h"
23 #include "spl-goditem.h" // player_is_debuffable
24 #include "spl-other.h"
25 #include "stringutil.h"
26 #include "terrain.h"
27
28 #define notify_fail(x) (why_not = (x), false)
29
_wallmsg(coord_def c)30 static string _wallmsg(coord_def c)
31 {
32 ASSERT(map_bounds(c)); // there'd be an information leak
33 const char *wall = feat_type_name(env.grid(c));
34 return "There is " + article_a(wall) + " there.";
35 }
36
_copy_explosion_map(explosion_map & source,explosion_map & dest)37 static void _copy_explosion_map(explosion_map &source, explosion_map &dest)
38 {
39 for (int i = 0; i < source.width(); i++)
40 for (int j = 0; j < source.height(); j++)
41 dest[i][j] = source[i][j];
42 }
43
set_aim(coord_def a)44 bool targeter::set_aim(coord_def a)
45 {
46 // This matches a condition in direction_chooser::move_is_ok().
47 if (agent && !can_affect_unseen() &&
48 !cell_see_cell(agent->pos(), a, LOS_NO_TRANS))
49 {
50 return false;
51 }
52
53 aim = a;
54 return true;
55 }
56
can_affect_outside_range()57 bool targeter::can_affect_outside_range()
58 {
59 return false;
60 }
61
can_affect_unseen()62 bool targeter::can_affect_unseen()
63 {
64 return false;
65 }
66
can_affect_walls()67 bool targeter::can_affect_walls()
68 {
69 return false;
70 }
71
anyone_there(coord_def loc)72 bool targeter::anyone_there(coord_def loc)
73 {
74 if (!map_bounds(loc))
75 return false;
76 if (agent && agent->is_player())
77 return env.map_knowledge(loc).monsterinfo();
78 return actor_at(loc);
79 }
80
affects_monster(const monster_info &)81 bool targeter::affects_monster(const monster_info& /*mon*/)
82 {
83 return true; //TODO: false
84 }
85
86 // Is the given location a valid endpoint for a Palentonga charge?
87 // That is, is there an enemy there which is visible to the player and
88 // is not firewood? If not, why not?
89 // Note that this does NOT handle checking the intervening path for
90 // obstacles or checking the distance from the player.
bad_charge_target(coord_def a)91 string bad_charge_target(coord_def a)
92 {
93 const monster* mons = monster_at(a);
94 // You can only charge at monsters.
95 // Specifically, monsters you can see. (No guessing!)
96 // You can't charge at plants you walk right through.
97 if (!mons || !you.can_see(*mons) || fedhas_passthrough(mons))
98 return "You can't see anything there to charge at.";
99
100 // You can't charge at friends. (Also, rude.)
101 // You can't charge at firewood. It's firewood.
102 if (mons_aligned(mons, &you) || mons_is_firewood(*mons))
103 return "Why would you want to do that?";
104 return "";
105 }
106
_ok_charge_target(coord_def a)107 static bool _ok_charge_target(coord_def a)
108 {
109 return bad_charge_target(a) == "";
110 }
111
112 // Can a player (for targeting purposes) charge through a given grid
113 // with a Palentonga rolling charge?
114 // We only check for monsters, not terrain.
can_charge_through_mons(coord_def a)115 bool can_charge_through_mons(coord_def a)
116 {
117 const monster* mons = monster_at(a);
118 return !mons
119 || !you.can_see(*mons)
120 || fedhas_passthrough(mons);
121 }
122
targeter_charge(const actor * act,int r)123 targeter_charge::targeter_charge(const actor *act, int r)
124 {
125 ASSERT(act);
126 ASSERT(r > 0);
127 agent = act;
128 range = r;
129 obeys_mesmerise = true;
130 }
131
valid_aim(coord_def a)132 bool targeter_charge::valid_aim(coord_def a)
133 {
134 if (agent->pos() == a)
135 return notify_fail("You can't charge at yourself.");
136 if (adjacent(agent->pos(), a))
137 return notify_fail("You're already next to there.");
138 if (grid_distance(agent->pos(), a) > range)
139 return notify_fail("That's out of range!");
140
141 ray_def ray;
142 if (!find_ray(agent->pos(), a, ray, opc_solid))
143 return notify_fail("There's something in the way.");
144 while (ray.advance())
145 {
146 if (ray.pos() == a)
147 {
148 const string bad = bad_charge_target(ray.pos());
149 if (bad != "")
150 return notify_fail(bad);
151 return true;
152 }
153 else if (is_feat_dangerous(env.grid(ray.pos())))
154 {
155 return notify_fail("There's "
156 + feature_description_at(ray.pos())
157 + " in the way.");
158 }
159 else if (!can_charge_through_mons(ray.pos()))
160 {
161 return notify_fail("There's "
162 + monster_at(ray.pos())->name(DESC_A)
163 + " in the way.");
164
165 }
166 }
167 die("Ray never reached the end?");
168 }
169
set_aim(coord_def a)170 bool targeter_charge::set_aim(coord_def a)
171 {
172 ray_def ray;
173 if (!find_ray(agent->pos(), a, ray, opc_solid))
174 return false;
175
176 path_taken.clear();
177 while (ray.advance())
178 {
179 path_taken.push_back(ray.pos());
180 if (grid_distance(agent->pos(), ray.pos()) >= range || ray.pos() == a)
181 break;
182
183 if (!can_charge_through_mons(ray.pos()))
184 break;
185 if (is_feat_dangerous(env.grid(ray.pos())))
186 return false;
187 }
188 return true;
189 }
190
is_affected(coord_def loc)191 aff_type targeter_charge::is_affected(coord_def loc)
192 {
193 bool in_path = false;
194 for (coord_def a : path_taken)
195 {
196 if (a == loc)
197 {
198 in_path = true;
199 break;
200 }
201 }
202 if (!in_path)
203 return AFF_NO;
204 if (_ok_charge_target(loc))
205 return AFF_MAYBE; // the target of the attack
206 if (path_taken.size() >= 2 && loc == path_taken[path_taken.size() - 2])
207 return AFF_LANDING;
208 return AFF_YES; // a movement space
209 }
210
targeter_beam(const actor * act,int r,zap_type zap,int pow,int min_ex_rad,int max_ex_rad)211 targeter_beam::targeter_beam(const actor *act, int r, zap_type zap,
212 int pow, int min_ex_rad, int max_ex_rad) :
213 min_expl_rad(min_ex_rad),
214 max_expl_rad(max_ex_rad),
215 range(r)
216 {
217 ASSERT(act);
218 ASSERT(min_ex_rad >= 0);
219 ASSERT(max_ex_rad >= 0);
220 ASSERT(max_ex_rad >= min_ex_rad);
221 agent = act;
222 beam.set_agent(act);
223 origin = aim = act->pos();
224 beam.attitude = ATT_FRIENDLY;
225 zappy(zap, pow, false, beam);
226 beam.is_tracer = true;
227 beam.is_targeting = true;
228 beam.range = range;
229 beam.source = origin;
230 beam.target = aim;
231 beam.dont_stop_player = true;
232 beam.friend_info.dont_stop = true;
233 beam.foe_info.dont_stop = true;
234 beam.ex_size = min_ex_rad;
235 beam.aimed_at_spot = true;
236
237 penetrates_targets = beam.pierce;
238 }
239
set_aim(coord_def a)240 bool targeter_beam::set_aim(coord_def a)
241 {
242 if (!targeter::set_aim(a))
243 return false;
244
245 bolt tempbeam = beam;
246
247 tempbeam.target = aim;
248 tempbeam.path_taken.clear();
249 tempbeam.fire();
250 path_taken = tempbeam.path_taken;
251
252 if (max_expl_rad > 0)
253 set_explosion_aim(beam);
254
255 return true;
256 }
257
set_explosion_aim(bolt tempbeam)258 void targeter_beam::set_explosion_aim(bolt tempbeam)
259 {
260 set_explosion_target(tempbeam);
261 tempbeam.use_target_as_pos = true;
262 exp_map_min.init(INT_MAX);
263 tempbeam.determine_affected_cells(exp_map_min, coord_def(), 0,
264 min_expl_rad, true, true);
265 if (max_expl_rad == min_expl_rad)
266 _copy_explosion_map(exp_map_min, exp_map_max);
267 else
268 {
269 exp_map_max.init(INT_MAX);
270 tempbeam.determine_affected_cells(exp_map_max, coord_def(), 0,
271 max_expl_rad, true, true);
272 }
273 }
274
set_explosion_target(bolt & tempbeam)275 void targeter_beam::set_explosion_target(bolt &tempbeam)
276 {
277 tempbeam.target = origin;
278 for (auto c : path_taken)
279 {
280 if (cell_is_solid(c) && !tempbeam.can_affect_wall(c))
281 break;
282 tempbeam.target = c;
283 if (anyone_there(c) && !tempbeam.ignores_monster(monster_at(c)))
284 break;
285 }
286 }
287
valid_aim(coord_def a)288 bool targeter_beam::valid_aim(coord_def a)
289 {
290 if (a != origin && !can_affect_unseen() &&
291 !cell_see_cell(origin, a, LOS_NO_TRANS))
292 {
293 if (agent->see_cell(a))
294 return notify_fail("There's something in the way.");
295 return notify_fail("You cannot see that place.");
296 }
297 if ((origin - a).rdist() > range)
298 return notify_fail("Out of range.");
299 return true;
300 }
301
can_affect_outside_range()302 bool targeter_beam::can_affect_outside_range()
303 {
304 // XXX is this everything?
305 return max_expl_rad > 0;
306 }
307
is_affected(coord_def loc)308 aff_type targeter_beam::is_affected(coord_def loc)
309 {
310 bool on_path = false;
311 int visit_count = 0;
312 coord_def c;
313 aff_type current = AFF_YES;
314 for (auto pc : path_taken)
315 {
316 if (cell_is_solid(pc)
317 && !beam.can_affect_wall(pc)
318 && max_expl_rad > 0)
319 {
320 break;
321 }
322
323 c = pc;
324 if (c == loc)
325 {
326 visit_count++;
327 if (max_expl_rad > 0)
328 on_path = true;
329 else if (cell_is_solid(pc))
330 {
331 bool res = beam.can_affect_wall(pc);
332 if (res)
333 return current;
334 else
335 return AFF_NO;
336
337 }
338 else
339 continue;
340 }
341 if (anyone_there(pc)
342 && !penetrates_targets
343 && !beam.ignores_monster(monster_at(pc)))
344 {
345 // We assume an exploding spell will always stop here.
346 if (max_expl_rad > 0)
347 break;
348 current = AFF_MAYBE;
349 }
350 }
351 if (max_expl_rad > 0)
352 {
353 if ((loc - c).rdist() <= 9)
354 {
355 bool aff_wall = beam.can_affect_wall(loc);
356 if (!cell_is_solid(loc) || aff_wall)
357 {
358 coord_def centre(9,9);
359 if (exp_map_min(loc - c + centre) < INT_MAX)
360 {
361 return (!cell_is_solid(loc) || aff_wall)
362 ? AFF_YES : AFF_MAYBE;
363 }
364 if (exp_map_max(loc - c + centre) < INT_MAX)
365 return AFF_MAYBE;
366 }
367 }
368 else
369 return on_path ? AFF_TRACER : AFF_NO;
370 }
371
372 return visit_count == 0 ? AFF_NO :
373 visit_count == 1 ? AFF_YES :
374 AFF_MULTIPLE;
375 }
376
affects_monster(const monster_info & mon)377 bool targeter_beam::affects_monster(const monster_info& mon)
378 {
379 //XXX: this is a disgusting hack that probably leaks information!
380 // bolt::is_harmless (and transitively, bolt::nasty_to) should
381 // take monster_infos instead.
382 const monster* m = monster_at(mon.pos);
383 if (!m)
384 return false;
385
386 if (beam.is_enchantment() && beam.has_saving_throw()
387 && mon.willpower() == WILL_INVULN)
388 {
389 return false;
390 }
391
392 // beckoning is useless against adjacent mons!
393 // XXX: this should probably be somewhere else
394 if (beam.flavour == BEAM_BECKONING)
395 return grid_distance(mon.pos, you.pos()) > 1;
396
397 return !beam.is_harmless(m) || beam.nice_to(mon)
398 // Inner flame affects allies without harming or helping them.
399 || beam.flavour == BEAM_INNER_FLAME;
400 }
401
targeter_unravelling(const actor * act,int r,int pow)402 targeter_unravelling::targeter_unravelling(const actor *act, int r, int pow)
403 : targeter_beam(act, r, ZAP_UNRAVELLING, pow, 1, 1)
404 {
405 }
406
407 /**
408 * Will a casting of Violent Unravelling explode a target at the given loc?
409 *
410 * @param c The location in question.
411 * @return Whether, to the player's knowledge, there's a valid target for
412 * Violent Unravelling at the given coordinate.
413 */
unravelling_explodes_at(const coord_def c)414 static bool unravelling_explodes_at(const coord_def c)
415 {
416 if (you.pos() == c && player_is_debuffable())
417 return true;
418
419 const monster_info* mi = env.map_knowledge(c).monsterinfo();
420 return mi && mi->debuffable();
421 }
422
set_aim(coord_def a)423 bool targeter_unravelling::set_aim(coord_def a)
424 {
425 if (!targeter::set_aim(a))
426 return false;
427
428 bolt tempbeam = beam;
429
430 tempbeam.target = aim;
431 tempbeam.path_taken.clear();
432 tempbeam.fire();
433 path_taken = tempbeam.path_taken;
434
435 bolt explosion_beam = beam;
436 set_explosion_target(beam);
437 if (unravelling_explodes_at(beam.target))
438 min_expl_rad = 1;
439 else
440 min_expl_rad = 0;
441
442 set_explosion_aim(beam);
443
444 return true;
445 }
446
targeter_view()447 targeter_view::targeter_view()
448 {
449 origin = aim = you.pos();
450 }
451
valid_aim(coord_def)452 bool targeter_view::valid_aim(coord_def /*a*/)
453 {
454 return true; // don't reveal map bounds
455 }
456
is_affected(coord_def loc)457 aff_type targeter_view::is_affected(coord_def loc)
458 {
459 if (loc == aim)
460 return AFF_YES;
461
462 return AFF_NO;
463 }
464
targeter_smite(const actor * act,int ran,int exp_min,int exp_max,bool wall_ok,bool (* affects_pos_func)(const coord_def &))465 targeter_smite::targeter_smite(const actor* act, int ran,
466 int exp_min, int exp_max, bool wall_ok,
467 bool (*affects_pos_func)(const coord_def &)):
468 exp_range_min(exp_min), exp_range_max(exp_max), range(ran),
469 affects_walls(wall_ok), affects_pos(affects_pos_func)
470 {
471 ASSERT(act);
472 ASSERT(exp_min >= 0);
473 ASSERT(exp_max >= 0);
474 ASSERT(exp_min <= exp_max);
475 agent = act;
476 origin = aim = act->pos();
477 }
478
valid_aim(coord_def a)479 bool targeter_smite::valid_aim(coord_def a)
480 {
481 if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
482 {
483 // Scrying/glass/tree/grate.
484 if (agent && agent->see_cell(a))
485 return notify_fail("There's something in the way.");
486 return notify_fail("You cannot see that place.");
487 }
488 if ((origin - a).rdist() > range)
489 return notify_fail("Out of range.");
490 if (!affects_walls && cell_is_solid(a))
491 return notify_fail(_wallmsg(a));
492 return true;
493 }
494
set_aim(coord_def a)495 bool targeter_smite::set_aim(coord_def a)
496 {
497 if (!targeter::set_aim(a))
498 return false;
499
500 if (exp_range_max > 0)
501 {
502 bolt beam;
503 beam.target = a;
504 beam.use_target_as_pos = true;
505 exp_map_min.init(INT_MAX);
506 beam.determine_affected_cells(exp_map_min, coord_def(), 0,
507 exp_range_min, true, true);
508 if (exp_range_min == exp_range_max)
509 _copy_explosion_map(exp_map_min, exp_map_max);
510 else
511 {
512 exp_map_max.init(INT_MAX);
513 beam.determine_affected_cells(exp_map_max, coord_def(), 0,
514 exp_range_max, true, true);
515 }
516 }
517 return true;
518 }
519
can_affect_outside_range()520 bool targeter_smite::can_affect_outside_range()
521 {
522 // XXX is this everything?
523 return exp_range_max > 0;
524 }
525
can_affect_walls()526 bool targeter_smite::can_affect_walls()
527 {
528 return affects_walls;
529 }
530
is_affected(coord_def loc)531 aff_type targeter_smite::is_affected(coord_def loc)
532 {
533 if (!valid_aim(aim))
534 return AFF_NO;
535
536 if (affects_pos && !affects_pos(loc))
537 return AFF_NO;
538
539 if (loc == aim)
540 return AFF_YES;
541
542 if (exp_range_max <= 0)
543 return AFF_NO;
544
545 if ((loc - aim).rdist() > 9)
546 return AFF_NO;
547 coord_def centre(9,9);
548 if (exp_map_min(loc - aim + centre) < INT_MAX)
549 return AFF_YES;
550 if (exp_map_max(loc - aim + centre) < INT_MAX)
551 return AFF_MAYBE;
552
553 return AFF_NO;
554 }
555
targeter_walljump()556 targeter_walljump::targeter_walljump() :
557 targeter_smite(&you, LOS_RADIUS, 1, 1, false, nullptr)
558 {
559 }
560
valid_aim(coord_def a)561 bool targeter_walljump::valid_aim(coord_def a)
562 {
563 return wu_jian_can_wall_jump(a, why_not);
564 }
565
is_affected(coord_def loc)566 aff_type targeter_walljump::is_affected(coord_def loc)
567 {
568 if (!valid_aim(aim))
569 return AFF_NO;
570
571 auto wall_jump_direction = (you.pos() - aim).sgn();
572 auto wall_jump_landing_spot = (you.pos() + wall_jump_direction
573 + wall_jump_direction);
574 if (loc == wall_jump_landing_spot)
575 return AFF_YES;
576
577 if (loc.distance_from(wall_jump_landing_spot) == 1 && monster_at(loc))
578 return AFF_YES;
579
580 return AFF_NO;
581 }
582
targeter_passwall(int max_range)583 targeter_passwall::targeter_passwall(int max_range) :
584 targeter_smite(&you, max_range, 1, 1, true, nullptr)
585 {
586 }
587
valid_aim(coord_def a)588 bool targeter_passwall::valid_aim(coord_def a)
589 {
590 passwall_path tmp_path(you, a - you.pos(), range);
591 string failmsg;
592 tmp_path.is_valid(&failmsg);
593 if (!tmp_path.spell_succeeds())
594 return notify_fail(failmsg);
595 return true;
596 }
597
set_aim(coord_def a)598 bool targeter_passwall::set_aim(coord_def a)
599 {
600 cur_path = make_unique<passwall_path>(you, a - you.pos(), range);
601 return true;
602 }
603
is_affected(coord_def loc)604 aff_type targeter_passwall::is_affected(coord_def loc)
605 {
606 if (!cur_path)
607 return AFF_NO;
608 // not very efficient...
609 for (auto p : cur_path->path)
610 if (p == loc)
611 return AFF_YES;
612 return AFF_NO;
613 }
614
can_affect_outside_range()615 bool targeter_passwall::can_affect_outside_range()
616 {
617 return true;
618 }
619
can_affect_unseen()620 bool targeter_passwall::can_affect_unseen()
621 {
622 return true;
623 }
624
affects_monster(const monster_info &)625 bool targeter_passwall::affects_monster(const monster_info& /*mon*/)
626 {
627 return false;
628 }
629
targeter_dig(int max_range)630 targeter_dig::targeter_dig(int max_range) :
631 targeter_beam(&you, max_range, ZAP_DIG, 0, 0, 0)
632 {
633 }
634
valid_aim(coord_def a)635 bool targeter_dig::valid_aim(coord_def a)
636 {
637 if (a == origin)
638 return notify_fail("Please select a direction to dig.");
639 if ((origin - a).rdist() > range || !in_bounds(a))
640 return notify_fail("Out of range.");
641 int possible_squares_affected;
642 if (aim_test_cache.count(a))
643 possible_squares_affected = aim_test_cache[a];
644 else
645 {
646 // TODO: maybe shouldn't use set_aim? ugly side-effect, but it does take
647 // care of all the beam management.
648 if (!set_aim(a))
649 possible_squares_affected = -1; // can't happen?
650 else
651 {
652 possible_squares_affected = 0;
653 for (auto p : path_taken)
654 if (beam.can_affect_wall(p) ||
655 in_bounds(p) && env.map_knowledge(p).feat() == DNGN_UNSEEN)
656 {
657 possible_squares_affected++;
658 }
659 }
660 aim_test_cache[a] = possible_squares_affected;
661 }
662 if (possible_squares_affected == 0)
663 return notify_fail("Digging in that direction won't affect any walls.");
664 else if (possible_squares_affected < 0)
665 return false;
666 else
667 return true;
668 }
669
can_affect_unseen()670 bool targeter_dig::can_affect_unseen()
671 {
672 return true;
673 }
674
affects_monster(const monster_info &)675 bool targeter_dig::affects_monster(const monster_info& /*mon*/)
676 {
677 return false;
678 }
679
can_affect_walls()680 bool targeter_dig::can_affect_walls()
681 {
682 return true;
683 }
684
is_affected(coord_def loc)685 aff_type targeter_dig::is_affected(coord_def loc)
686 {
687 aff_type current = AFF_YES;
688 bool hit_barrier = false;
689 for (auto pc : path_taken)
690 {
691 if (hit_barrier)
692 return AFF_NO; // some previous iteration hit a barrier
693 current = AFF_YES;
694 // uses comparison to DNGN_UNSEEN so that this works sensibly with magic
695 // mapping etc. TODO: console tracers use the same symbol/color as
696 // mmapped walls.
697 if (in_bounds(pc) && env.map_knowledge(pc).feat() != DNGN_UNSEEN)
698 {
699 if (!cell_is_solid(pc))
700 current = AFF_TRACER;
701 else if (!beam.can_affect_wall(pc))
702 {
703 current = AFF_TRACER; // show tracer at the barrier cell
704 hit_barrier = true;
705 }
706 // otherwise, default to AFF_YES
707 }
708 // unseen squares default to AFF_YES
709 if (pc == loc)
710 return current;
711 }
712 // path never intersected loc at all
713 return AFF_NO;
714 }
715
targeter_transference(const actor * act,int aoe)716 targeter_transference::targeter_transference(const actor* act, int aoe) :
717 targeter_smite(act, LOS_RADIUS, aoe, aoe, false, nullptr)
718 {
719 }
720
valid_aim(coord_def a)721 bool targeter_transference::valid_aim(coord_def a)
722 {
723 if (!targeter_smite::valid_aim(a))
724 return false;
725
726 const actor *victim = actor_at(a);
727 if (victim && you.can_see(*victim))
728 {
729 if (mons_is_hepliaklqana_ancestor(victim->type))
730 {
731 return notify_fail("You can't transfer your ancestor with "
732 + victim->pronoun(PRONOUN_REFLEXIVE) + ".");
733 }
734 if (mons_is_tentacle_or_tentacle_segment(victim->type)
735 || victim->is_stationary())
736 {
737 return notify_fail("You can't transfer that.");
738 }
739 }
740 return true;
741 }
742
targeter_fragment(const actor * act,int power,int ran)743 targeter_fragment::targeter_fragment(const actor* act, int power, int ran) :
744 targeter_smite(act, ran, 1, 1, true, nullptr),
745 pow(power)
746 {
747 }
748
valid_aim(coord_def a)749 bool targeter_fragment::valid_aim(coord_def a)
750 {
751 if (!targeter_smite::valid_aim(a))
752 return false;
753
754 bolt tempbeam;
755 bool temp;
756 if (!setup_fragmentation_beam(tempbeam, pow, agent, a, true, nullptr, temp))
757 return notify_fail("You cannot affect that.");
758 return true;
759 }
760
set_aim(coord_def a)761 bool targeter_fragment::set_aim(coord_def a)
762 {
763 if (!targeter::set_aim(a))
764 return false;
765
766 bolt tempbeam;
767 bool temp;
768
769 if (setup_fragmentation_beam(tempbeam, pow, agent, a, true, nullptr, temp))
770 {
771 exp_range_min = tempbeam.ex_size;
772 exp_range_max = tempbeam.ex_size;
773 }
774 else
775 {
776 exp_range_min = exp_range_max = 0;
777 return false;
778 }
779
780 tempbeam.use_target_as_pos = true;
781 exp_map_min.init(INT_MAX);
782 tempbeam.determine_affected_cells(exp_map_min, coord_def(), 0,
783 exp_range_min, true, true);
784
785 // Min and max ranges are always identical.
786 _copy_explosion_map(exp_map_min, exp_map_max);
787
788 return true;
789 }
790
targeter_reach(const actor * act,reach_type ran)791 targeter_reach::targeter_reach(const actor* act, reach_type ran) :
792 range(ran)
793 {
794 ASSERT(act);
795 agent = act;
796 origin = aim = act->pos();
797 }
798
valid_aim(coord_def a)799 bool targeter_reach::valid_aim(coord_def a)
800 {
801 if (!cell_see_cell(origin, a, LOS_DEFAULT))
802 return notify_fail("You cannot see that place.");
803 if (!agent->see_cell_no_trans(a))
804 return notify_fail("You can't get through.");
805
806 int dist = (origin - a).rdist();
807
808 if (dist > range)
809 return notify_fail("Your weapon can't reach that far!");
810
811 return true;
812 }
813
is_affected(coord_def loc)814 aff_type targeter_reach::is_affected(coord_def loc)
815 {
816 if (!valid_aim(loc))
817 return AFF_NO;
818
819 if (loc == aim)
820 return AFF_YES;
821
822 // hacks: REACH_THREE entails smite targeting, because it exists entirely
823 // for the sake of UNRAND_RIFT. So, don't show the tracer.
824 if (range == REACH_TWO
825 && ((loc - origin) * 2 - (aim - origin)).abs() < 1
826 && feat_is_reachable_past(env.grid(loc)))
827 {
828 return AFF_TRACER;
829 }
830
831 return AFF_NO;
832 }
833
targeter_cleave(const actor * act,coord_def target)834 targeter_cleave::targeter_cleave(const actor* act, coord_def target)
835 {
836 ASSERT(act);
837 agent = act;
838 origin = act->pos();
839 set_aim(target);
840 }
841
valid_aim(coord_def a)842 bool targeter_cleave::valid_aim(coord_def a)
843 {
844 if ((origin - a).rdist() > 1)
845 return notify_fail("Your weapon can't reach that far!");
846 return true;
847 }
848
849
set_aim(coord_def target)850 bool targeter_cleave::set_aim(coord_def target)
851 {
852 aim = target;
853 targets.clear();
854 list<actor*> act_targets;
855 get_cleave_targets(*agent, target, act_targets);
856 while (!act_targets.empty())
857 {
858 actor *potential_target = act_targets.front();
859 if (agent->can_see(*potential_target))
860 targets.insert(potential_target->pos());
861 act_targets.pop_front();
862 }
863 return true;
864 }
865
is_affected(coord_def loc)866 aff_type targeter_cleave::is_affected(coord_def loc)
867 {
868 return targets.count(loc) ? AFF_YES : AFF_NO;
869 }
870
targeter_cloud(const actor * act,int r,int count_min,int count_max)871 targeter_cloud::targeter_cloud(const actor* act, int r,
872 int count_min, int count_max) :
873 range(r), cnt_min(count_min), cnt_max(count_max), avoid_clouds(true)
874 {
875 ASSERT(cnt_min > 0);
876 ASSERT(cnt_max > 0);
877 ASSERT(cnt_min <= cnt_max);
878 agent = act;
879 if (agent)
880 origin = aim = act->pos();
881 }
882
_cloudable(coord_def loc,bool avoid_clouds)883 static bool _cloudable(coord_def loc, bool avoid_clouds)
884 {
885 return in_bounds(loc)
886 && !cell_is_solid(loc)
887 && !(avoid_clouds && cloud_at(loc)
888 && !is_sanctuary(loc));
889 }
890
valid_aim(coord_def a)891 bool targeter_cloud::valid_aim(coord_def a)
892 {
893 if (agent && (origin - a).rdist() > range)
894 return notify_fail("Out of range.");
895 if (!map_bounds(a)
896 || agent
897 && origin != a
898 && !cell_see_cell(origin, a, LOS_NO_TRANS))
899 {
900 // Scrying/glass/tree/grate.
901 if (agent && agent->see_cell(a))
902 return notify_fail("There's something in the way.");
903 return notify_fail("You cannot see that place.");
904 }
905 if (cell_is_solid(a))
906 return notify_fail(_wallmsg(a));
907 if (agent)
908 {
909 if (cloud_at(a) && avoid_clouds)
910 return notify_fail("There's already a cloud there.");
911 else if (is_sanctuary(a))
912 return notify_fail("You can't place clouds in a sanctuary.");
913 ASSERT(_cloudable(a, avoid_clouds));
914 }
915 return true;
916 }
917
set_aim(coord_def a)918 bool targeter_cloud::set_aim(coord_def a)
919 {
920 if (!targeter::set_aim(a))
921 return false;
922
923 seen.clear();
924 queue.clear();
925 queue.emplace_back();
926
927 int placed = 0;
928 queue[0].push_back(a);
929
930 for (unsigned int d1 = 0; d1 < queue.size() && placed < cnt_max; d1++)
931 {
932 placed += queue[d1].size();
933
934 for (coord_def c : queue[d1])
935 {
936 for (adjacent_iterator ai(c); ai; ++ai)
937 if (_cloudable(*ai, avoid_clouds) && !seen.count(*ai))
938 {
939 unsigned int d2 = d1 + 1;
940 if (d2 >= queue.size())
941 queue.resize(d2 + 1);
942 queue[d2].push_back(*ai);
943 seen[*ai] = AFF_TRACER;
944 }
945
946 seen[c] = placed <= cnt_min ? AFF_YES : AFF_MAYBE;
947 }
948 }
949
950 return true;
951 }
952
can_affect_outside_range()953 bool targeter_cloud::can_affect_outside_range()
954 {
955 return true;
956 }
957
is_affected(coord_def loc)958 aff_type targeter_cloud::is_affected(coord_def loc)
959 {
960 if (!valid_aim(aim))
961 return AFF_NO;
962
963 if (aff_type *aff = map_find(seen, loc))
964 {
965 if (*aff > 0) // AFF_TRACER is used privately
966 return *aff;
967 }
968 return AFF_NO;
969 }
970
targeter_splash(const actor * act,int ran)971 targeter_splash::targeter_splash(const actor* act, int ran)
972 : range(ran)
973 {
974 ASSERT(act);
975 agent = act;
976 origin = aim = act->pos();
977 }
978
valid_aim(coord_def a)979 bool targeter_splash::valid_aim(coord_def a)
980 {
981 if (agent && grid_distance(origin, a) > range)
982 return notify_fail("Out of range.");
983 return true;
984 }
985
is_affected(coord_def loc)986 aff_type targeter_splash::is_affected(coord_def loc)
987 {
988 if (!valid_aim(aim) || !valid_aim(loc))
989 return AFF_NO;
990
991 if (loc == aim)
992 return AFF_YES;
993
994 // self-spit currently doesn't splash
995 if (aim == origin)
996 return AFF_NO;
997
998 // it splashes around only upon hitting someone
999 if (!anyone_there(aim))
1000 return AFF_NO;
1001
1002 if (grid_distance(loc, aim) > 1)
1003 return AFF_NO;
1004
1005 // you're safe from being splashed by own spit
1006 if (loc == origin)
1007 return AFF_NO;
1008
1009 return anyone_there(loc) ? AFF_YES : AFF_MAYBE;
1010 }
1011
targeter_radius(const actor * act,los_type _los,int ran,int ran_max,int ran_min)1012 targeter_radius::targeter_radius(const actor *act, los_type _los,
1013 int ran, int ran_max, int ran_min):
1014 range(ran), range_max(ran_max), range_min(ran_min)
1015 {
1016 ASSERT(act);
1017 agent = act;
1018 origin = aim = act->pos();
1019 los = _los;
1020 if (!range_max)
1021 range_max = range;
1022 ASSERT(range_max >= range);
1023 }
1024
valid_aim(coord_def a)1025 bool targeter_radius::valid_aim(coord_def a)
1026 {
1027 if ((a - origin).rdist() > range_max || (a - origin).rdist() < range_min)
1028 return notify_fail("Out of range.");
1029 // If this message ever becomes used, please improve it. I did not
1030 // bother adding complexity just for monsters and "hit allies" prompts
1031 // which don't need it.
1032 if (!is_affected(a))
1033 return notify_fail("The effect is blocked.");
1034 return true;
1035 }
1036
is_affected(coord_def loc)1037 aff_type targeter_radius::is_affected(coord_def loc)
1038 {
1039 if (loc == aim)
1040 return AFF_YES;
1041
1042 if ((loc - origin).rdist() > range_max || (loc - origin).rdist() < range_min)
1043 return AFF_NO;
1044
1045 if (!cell_see_cell(loc, origin, los))
1046 return AFF_NO;
1047
1048 return AFF_YES;
1049 }
1050
is_affected(coord_def loc)1051 aff_type targeter_shatter::is_affected(coord_def loc)
1052 {
1053 if (loc == origin)
1054 return AFF_NO; // Shatter doesn't affect the caster.
1055
1056 if (!cell_see_cell(loc, origin, LOS_ARENA))
1057 return AFF_NO; // No shattering through glass... without work.
1058
1059 monster* mons = monster_at(loc);
1060 if (!mons || !you.can_see(*mons))
1061 {
1062 const int terrain_chance = terrain_shatter_chance(loc, you);
1063 if (terrain_chance == 100)
1064 return AFF_YES;
1065 else if (terrain_chance > 0)
1066 return AFF_MAYBE;
1067 return AFF_NO;
1068 }
1069
1070 const dice_def dam = shatter_damage(200, mons);
1071 const int dice = dam.num;
1072 if (dice == DEFAULT_SHATTER_DICE)
1073 return AFF_YES;
1074 if (dice > DEFAULT_SHATTER_DICE)
1075 return AFF_MULTIPLE;
1076 return AFF_MAYBE;
1077 }
1078
targeter_thunderbolt(const actor * act,int r,coord_def _prev)1079 targeter_thunderbolt::targeter_thunderbolt(const actor *act, int r,
1080 coord_def _prev)
1081 {
1082 ASSERT(act);
1083 agent = act;
1084 origin = act->pos();
1085 prev = _prev;
1086 ASSERT(prev != origin);
1087 aim = prev.origin() ? origin : prev;
1088 ASSERT_RANGE(r, 1 + 1, you.current_vision + 1);
1089 range = r;
1090 }
1091
valid_aim(coord_def a)1092 bool targeter_thunderbolt::valid_aim(coord_def a)
1093 {
1094 if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
1095 {
1096 // Scrying/glass/tree/grate.
1097 if (agent->see_cell(a))
1098 return notify_fail("There's something in the way.");
1099 return notify_fail("You cannot see that place.");
1100 }
1101 if ((origin - a).rdist() > range)
1102 return notify_fail("Out of range.");
1103 return true;
1104 }
1105
_make_ray(ray_def & ray,coord_def a,coord_def b)1106 static void _make_ray(ray_def &ray, coord_def a, coord_def b)
1107 {
1108 // Like beams, we need to allow picking the "better" ray if one is blocked
1109 // by a wall.
1110 if (!find_ray(a, b, ray, opc_solid_see))
1111 ray = ray_def(geom::ray(a.x + 0.5, a.y + 0.5, b.x - a.x, b.y - a.y));
1112 }
1113
left_of(coord_def a,coord_def b)1114 static bool left_of(coord_def a, coord_def b)
1115 {
1116 return a.x * b.y > a.y * b.x;
1117 }
1118
set_aim(coord_def a)1119 bool targeter_thunderbolt::set_aim(coord_def a)
1120 {
1121 aim = a;
1122 zapped.clear();
1123
1124 if (a == origin)
1125 return false;
1126
1127 arc_length.init(0);
1128
1129 ray_def ray;
1130 coord_def p; // ray.pos() does lots of processing, cache it
1131
1132 // For consistency with beams, we need to
1133 _make_ray(ray, origin, aim);
1134 while ((origin - (p = ray.pos())).rdist() <= range
1135 && map_bounds(p) && opc_solid_see(p) < OPC_OPAQUE)
1136 {
1137 if (p != origin && zapped[p] <= 0)
1138 {
1139 zapped[p] = AFF_YES;
1140 arc_length[origin.distance_from(p)]++;
1141 }
1142 ray.advance();
1143 }
1144
1145 if (prev.origin())
1146 return true;
1147
1148 _make_ray(ray, origin, prev);
1149 while ((origin - (p = ray.pos())).rdist() <= range
1150 && map_bounds(p) && opc_solid_see(p) < OPC_OPAQUE)
1151 {
1152 if (p != origin && zapped[p] <= 0)
1153 {
1154 zapped[p] = AFF_MAYBE; // fully affected, we just want to highlight cur
1155 arc_length[origin.distance_from(p)]++;
1156 }
1157 ray.advance();
1158 }
1159
1160 coord_def a1 = prev - origin;
1161 coord_def a2 = aim - origin;
1162 if (left_of(a2, a1))
1163 swap(a1, a2);
1164
1165 for (int x = -LOS_RADIUS; x <= LOS_RADIUS; ++x)
1166 for (int y = -LOS_RADIUS; y <= LOS_RADIUS; ++y)
1167 {
1168 if (max(abs(x), abs(y)) > range)
1169 continue;
1170 coord_def r(x, y);
1171 if (left_of(a1, r) && left_of(r, a2))
1172 {
1173 (p = r) += origin;
1174 if (!zapped.count(p))
1175 arc_length[r.rdist()]++;
1176 if (zapped[p] <= 0 && cell_see_cell(origin, p, LOS_NO_TRANS))
1177 zapped[p] = AFF_MAYBE;
1178 }
1179 }
1180
1181 zapped[origin] = AFF_NO;
1182
1183 return true;
1184 }
1185
is_affected(coord_def loc)1186 aff_type targeter_thunderbolt::is_affected(coord_def loc)
1187 {
1188 if (loc == aim)
1189 return zapped[loc] ? AFF_YES : AFF_TRACER;
1190
1191 if ((loc - origin).rdist() > range)
1192 return AFF_NO;
1193
1194 return zapped[loc];
1195 }
1196
targeter_shadow_step(const actor * act,int r)1197 targeter_shadow_step::targeter_shadow_step(const actor* act, int r) :
1198 range(r)
1199 {
1200 ASSERT(act);
1201 agent = act;
1202 origin = act->pos();
1203 step_is_blocked = false;
1204 }
1205
valid_aim(coord_def a)1206 bool targeter_shadow_step::valid_aim(coord_def a)
1207 {
1208 ray_def ray;
1209
1210 if (origin == a)
1211 return notify_fail("You cannot target yourself.");
1212 else if ((origin - a).rdist() > range)
1213 return notify_fail("Out of range.");
1214 else if (!cell_see_cell(origin, a, LOS_NO_TRANS))
1215 {
1216 if (agent->see_cell(a))
1217 return notify_fail("There's something in the way.");
1218 else
1219 return notify_fail("You cannot see that place.");
1220 }
1221 else if (cell_is_solid(a))
1222 return notify_fail("There's something in the way.");
1223 else if (!find_ray(agent->pos(), a, ray, opc_solid_see))
1224 return notify_fail("There's something in the way.");
1225 else if (!has_additional_sites(a))
1226 {
1227 switch (no_landing_reason)
1228 {
1229 case shadow_step_blocked::move:
1230 case shadow_step_blocked::occupied:
1231 return notify_fail("There is no safe place near that"
1232 " location.");
1233 case shadow_step_blocked::path:
1234 return notify_fail("There's something in the way.");
1235 case shadow_step_blocked::no_target:
1236 return notify_fail("There isn't a shadow there.");
1237 case shadow_step_blocked::none:
1238 die("buggy no_landing_reason");
1239 }
1240 }
1241 return true;
1242 }
1243
valid_landing(coord_def a,bool check_invis)1244 bool targeter_shadow_step::valid_landing(coord_def a, bool check_invis)
1245 {
1246 actor *act;
1247 ray_def ray;
1248
1249 if (!agent->is_habitable(a))
1250 {
1251 blocked_landing_reason = shadow_step_blocked::move;
1252 return false;
1253 }
1254 if (agent->is_player())
1255 {
1256 monster* beholder = you.get_beholder(a);
1257 if (beholder)
1258 {
1259 blocked_landing_reason = shadow_step_blocked::move;
1260 return false;
1261 }
1262
1263 monster* fearmonger = you.get_fearmonger(a);
1264 if (fearmonger)
1265 {
1266 blocked_landing_reason = shadow_step_blocked::move;
1267 return false;
1268 }
1269 }
1270 if (!find_ray(agent->pos(), a, ray, opc_solid_see))
1271 {
1272 blocked_landing_reason = shadow_step_blocked::path;
1273 return false;
1274 }
1275
1276 // Check if a landing site is invalid due to a visible monster obstructing
1277 // the path.
1278 ray.advance();
1279 while (map_bounds(ray.pos()))
1280 {
1281 act = actor_at(ray.pos());
1282 if (ray.pos() == a)
1283 {
1284 if (act && (!check_invis || agent->can_see(*act)))
1285 {
1286 blocked_landing_reason = shadow_step_blocked::occupied;
1287 return false;
1288 }
1289 break;
1290 }
1291 ray.advance();
1292 }
1293 return true;
1294 }
1295
is_affected(coord_def loc)1296 aff_type targeter_shadow_step::is_affected(coord_def loc)
1297 {
1298 aff_type aff = AFF_NO;
1299
1300 if (loc == aim)
1301 aff = AFF_YES;
1302 else if (additional_sites.count(loc))
1303 aff = AFF_LANDING;
1304 return aff;
1305 }
1306
1307 // If something unseen either occupies the aim position or blocks the
1308 // shadow_step path, indicate that with step_is_blocked, but still return true
1309 // so long there is at least one valid landing position from the player's
1310 // perspective.
set_aim(coord_def a)1311 bool targeter_shadow_step::set_aim(coord_def a)
1312 {
1313 if (a == origin)
1314 return false;
1315 if (!targeter::set_aim(a))
1316 return false;
1317
1318 step_is_blocked = false;
1319
1320 // Find our set of landing sites, choose one at random to be the
1321 // destination and see if it's actually blocked.
1322 set_additional_sites(aim);
1323 if (additional_sites.size())
1324 {
1325 auto it = random_iterator(additional_sites);
1326 landing_site = *it;
1327 if (!valid_landing(landing_site, false))
1328 step_is_blocked = true;
1329 return true;
1330 }
1331 return false;
1332 }
1333
1334 // Determine the set of valid landing sites
set_additional_sites(coord_def a)1335 void targeter_shadow_step::set_additional_sites(coord_def a)
1336 {
1337 get_additional_sites(a);
1338 additional_sites = temp_sites;
1339 }
1340
1341 // Determine the set of valid landing sites for the target, putting the results
1342 // in the private set variable temp_sites. This uses valid_aim(), so it looks
1343 // for uninhabited squares that are habitable by the player, but doesn't check
1344 // against e.g. harmful clouds
get_additional_sites(coord_def a)1345 void targeter_shadow_step::get_additional_sites(coord_def a)
1346 {
1347 bool agent_adjacent = a.distance_from(agent->pos()) == 1;
1348 temp_sites.clear();
1349
1350 const actor *victim = actor_at(a);
1351 if (!victim || !victim->as_monster()
1352 || mons_is_firewood(*victim->as_monster())
1353 || victim->as_monster()->friendly()
1354 || !agent->can_see(*victim)
1355 || !victim->umbraed())
1356 {
1357 no_landing_reason = shadow_step_blocked::no_target;
1358 return;
1359 }
1360
1361 no_landing_reason = shadow_step_blocked::none;
1362 for (adjacent_iterator ai(a, false); ai; ++ai)
1363 {
1364 // See if site is valid, record a putative reason for why no sites were
1365 // found.
1366 if (!agent_adjacent || agent->pos().distance_from(*ai) > 1)
1367 {
1368 if (valid_landing(*ai))
1369 {
1370 temp_sites.insert(*ai);
1371 no_landing_reason = shadow_step_blocked::none;
1372 }
1373 else
1374 no_landing_reason = blocked_landing_reason;
1375 }
1376 }
1377 }
1378
1379 // See if we can find at least one valid landing position for the given monster.
has_additional_sites(coord_def a)1380 bool targeter_shadow_step::has_additional_sites(coord_def a)
1381 {
1382 get_additional_sites(a);
1383 return temp_sites.size();
1384 }
1385
targeter_cone(const actor * act,int r)1386 targeter_cone::targeter_cone(const actor *act, int r)
1387 {
1388 ASSERT(act);
1389 agent = act;
1390 origin = act->pos();
1391 aim = origin;
1392 ASSERT_RANGE(r, 1 + 1, you.current_vision + 1);
1393 range = r;
1394 }
1395
valid_aim(coord_def a)1396 bool targeter_cone::valid_aim(coord_def a)
1397 {
1398 if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
1399 {
1400 // Scrying/glass/tree/grate.
1401 if (agent->see_cell(a))
1402 return notify_fail("There's something in the way.");
1403 return notify_fail("You cannot see that place.");
1404 }
1405 if ((origin - a).rdist() > range)
1406 return notify_fail("Out of range.");
1407 return true;
1408 }
1409
left_of_eq(coord_def a,coord_def b)1410 static bool left_of_eq(coord_def a, coord_def b)
1411 {
1412 return a.x * b.y >= a.y * b.x;
1413 }
1414
1415 // Ripped off from targeter_thunderbolt::set_aim.
set_aim(coord_def a)1416 bool targeter_cone::set_aim(coord_def a)
1417 {
1418 aim = a;
1419 zapped.clear();
1420 for (int i = 0; i < LOS_RADIUS + 1; i++)
1421 sweep[i].clear();
1422
1423 if (a == origin)
1424 return false;
1425
1426 const coord_def delta = a - origin;
1427 const double arc = PI/4;
1428 coord_def l, r;
1429 l.x = origin.x + (cos(-arc) * delta.x - sin(-arc) * delta.y + 0.5);
1430 l.y = origin.y + (sin(-arc) * delta.x + cos(-arc) * delta.y + 0.5);
1431 r.x = origin.x + (cos( arc) * delta.x - sin( arc) * delta.y + 0.5);
1432 r.y = origin.y + (sin( arc) * delta.x + cos( arc) * delta.y + 0.5);
1433
1434 coord_def p;
1435
1436 coord_def a1 = l - origin;
1437 coord_def a2 = r - origin;
1438 if (left_of(a2, a1))
1439 swap(a1, a2);
1440
1441 for (int x = -LOS_RADIUS; x <= LOS_RADIUS; ++x)
1442 for (int y = -LOS_RADIUS; y <= LOS_RADIUS; ++y)
1443 {
1444 if (max(abs(x), abs(y)) > range)
1445 continue;
1446 coord_def q(x, y);
1447 if (left_of_eq(a1, q) && left_of_eq(q, a2))
1448 {
1449 (p = q) += origin;
1450 if (zapped[p] <= 0
1451 && map_bounds(p)
1452 && opc_solid_see(p) < OPC_OPAQUE
1453 && cell_see_cell(origin, p, LOS_NO_TRANS))
1454 {
1455 zapped[p] = AFF_YES;
1456 sweep[(origin - p).rdist()][p] = AFF_YES;
1457 }
1458 }
1459 }
1460
1461 zapped[origin] = AFF_NO;
1462 sweep[0].clear();
1463
1464 return true;
1465 }
1466
is_affected(coord_def loc)1467 aff_type targeter_cone::is_affected(coord_def loc)
1468 {
1469 if (loc == aim)
1470 return zapped[loc] ? AFF_YES : AFF_TRACER;
1471
1472 if ((loc - origin).rdist() > range)
1473 return AFF_NO;
1474
1475 return zapped[loc];
1476 }
1477
targeter_shotgun(const actor * act,size_t beam_count,int r,bool clouds)1478 targeter_shotgun::targeter_shotgun(const actor* act, size_t beam_count,
1479 int r, bool clouds)
1480 {
1481 ASSERT(act);
1482 agent = act;
1483 origin = act->pos();
1484 num_beams = beam_count;
1485 for (size_t i = 0; i < num_beams; i++)
1486 rays.emplace_back();
1487 range = r;
1488 uses_clouds = clouds;
1489 }
1490
valid_aim(coord_def a)1491 bool targeter_shotgun::valid_aim(coord_def a)
1492 {
1493 if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
1494 {
1495 if (agent->see_cell(a))
1496 return notify_fail("There's something in the way.");
1497 return notify_fail("You cannot see that place.");
1498 }
1499 if ((origin - a).rdist() > range)
1500 return notify_fail("Out of range.");
1501 return true;
1502 }
1503
set_aim(coord_def a)1504 bool targeter_shotgun::set_aim(coord_def a)
1505 {
1506 zapped.clear();
1507
1508 // confused monster targeting might be fuzzed across a wall, so
1509 // skip the validation in the parent function and set aim directly.
1510 // N.B. We assume this targeter can actually handle an invalid aim
1511 // (not all targeters can).
1512 if (!agent || agent->is_monster())
1513 aim = a;
1514 // ... but for UI consistency, players should be restricted to LOS.
1515 else if (!targeter::set_aim(a))
1516 return false;
1517
1518 if (a == origin)
1519 return false;
1520
1521 ray_def orig_ray;
1522 _make_ray(orig_ray, origin, a);
1523 coord_def p;
1524
1525 const double spread_range = (double)(num_beams - 1) * PI / 40.0;
1526 for (size_t i = 0; i < num_beams; i++)
1527 {
1528 double spread = (num_beams == 1)
1529 ? 0.0
1530 : -(spread_range / 2.0)
1531 + (spread_range * (double)i)
1532 / (double)(num_beams - 1);
1533 rays[i] = ray_def();
1534 rays[i].r.start = orig_ray.r.start;
1535 rays[i].r.dir.x =
1536 orig_ray.r.dir.x * cos(spread) + orig_ray.r.dir.y * sin(spread);
1537 rays[i].r.dir.y =
1538 -orig_ray.r.dir.x * sin(spread) + orig_ray.r.dir.y * cos(spread);
1539 ray_def tempray = rays[i];
1540 p = tempray.pos();
1541 while ((origin - (p = tempray.pos())).rdist() <= range
1542 && map_bounds(p) && opc_solid_see(p) < OPC_OPAQUE)
1543 {
1544 if (p != origin
1545 && (!uses_clouds || !cloud_at(p) && !is_sanctuary(p)))
1546 {
1547 zapped[p] = zapped[p] + 1;
1548 }
1549 tempray.advance();
1550 }
1551 }
1552
1553 zapped[origin] = 0;
1554 return true;
1555 }
1556
is_affected(coord_def loc)1557 aff_type targeter_shotgun::is_affected(coord_def loc)
1558 {
1559 if ((loc - origin).rdist() > range
1560 || uses_clouds && (cloud_at(loc) || is_sanctuary(loc)))
1561 {
1562 return AFF_NO;
1563 }
1564
1565 return (zapped[loc] >= num_beams) ? AFF_YES :
1566 (zapped[loc] > 0) ? AFF_MAYBE
1567 : AFF_NO;
1568 }
1569
targeter_monster_sequence(const actor * act,int pow,int r)1570 targeter_monster_sequence::targeter_monster_sequence(const actor *act, int pow, int r) :
1571 targeter_beam(act, r, ZAP_DEBUGGING_RAY, pow, 0, 0)
1572 {
1573 // for `path_taken` to be set properly, the beam needs to be piercing, and
1574 // ZAP_DEBUGGING_RAY is not.
1575 beam.pierce = true;
1576 }
1577
set_aim(coord_def a)1578 bool targeter_monster_sequence::set_aim(coord_def a)
1579 {
1580 if (!targeter_beam::set_aim(a))
1581 return false;
1582
1583 bolt tempbeam = beam;
1584 tempbeam.target = origin;
1585 bool last_cell_has_mons = true;
1586 bool passed_through_mons = false;
1587 for (auto c : path_taken)
1588 {
1589 if (!last_cell_has_mons)
1590 return false; // we must have an uninterrupted chain of monsters
1591
1592 if (cell_is_solid(c))
1593 break;
1594
1595 tempbeam.target = c;
1596 if (anyone_there(c))
1597 {
1598 passed_through_mons = true;
1599 tempbeam.use_target_as_pos = true;
1600 exp_map.init(INT_MAX);
1601 tempbeam.determine_affected_cells(exp_map, coord_def(), 0,
1602 0, true, true);
1603 }
1604 else
1605 last_cell_has_mons = false;
1606 }
1607
1608 return passed_through_mons;
1609 }
1610
valid_aim(coord_def a)1611 bool targeter_monster_sequence::valid_aim(coord_def a)
1612 {
1613 if (!targeter_beam::set_aim(a))
1614 return false;
1615
1616 bool last_cell_has_mons = true;
1617 bool passed_through_mons = false;
1618 for (auto c : path_taken)
1619 {
1620 if (!last_cell_has_mons)
1621 return false; // we must have an uninterrupted chain of monsters
1622
1623 if (cell_is_solid(c))
1624 return false;
1625
1626 if (!anyone_there(c))
1627 last_cell_has_mons = false;
1628 else
1629 passed_through_mons = true;
1630 }
1631
1632 return passed_through_mons;
1633 }
1634
is_affected(coord_def loc)1635 aff_type targeter_monster_sequence::is_affected(coord_def loc)
1636 {
1637 bool on_path = false;
1638 for (auto c : path_taken)
1639 {
1640 if (cell_is_solid(c))
1641 break;
1642 if (c == loc)
1643 on_path = true;
1644 if (anyone_there(c)
1645 && !beam.ignores_monster(monster_at(c))
1646 && (loc - c).rdist() <= 9)
1647 {
1648 coord_def centre(9,9);
1649 if (exp_map(loc - c + centre) < INT_MAX
1650 && !cell_is_solid(loc))
1651 {
1652 return AFF_YES;
1653 }
1654 }
1655 }
1656
1657 return on_path ? AFF_TRACER : AFF_NO;
1658 }
1659
targeter_overgrow()1660 targeter_overgrow::targeter_overgrow()
1661 {
1662 agent = &you;
1663 origin = you.pos();
1664 }
1665
overgrow_affects_pos(const coord_def & p)1666 bool targeter_overgrow::overgrow_affects_pos(const coord_def &p)
1667 {
1668 if (!in_bounds(p))
1669 return false;
1670 if (env.markers.property_at(p, MAT_ANY, "veto_destroy") == "veto")
1671 return false;
1672
1673 const dungeon_feature_type feat = env.grid(p);
1674 if (feat_is_open_door(feat))
1675 {
1676 const monster* const mons = monster_at(p);
1677 if (mons && agent && agent->can_see(*mons))
1678 return false;
1679
1680 return true;
1681 }
1682
1683 return feat_is_diggable(feat)
1684 || feat_is_closed_door(feat)
1685 || feat_is_tree(feat)
1686 || (feat_is_wall(feat) && !feat_is_permarock(feat));
1687 }
1688
is_affected(coord_def loc)1689 aff_type targeter_overgrow::is_affected(coord_def loc)
1690 {
1691 if (affected_positions.count(loc))
1692 return AFF_YES;
1693
1694 return AFF_NO;
1695 }
1696
valid_aim(coord_def a)1697 bool targeter_overgrow::valid_aim(coord_def a)
1698 {
1699 if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS))
1700 {
1701 if (agent && agent->see_cell(a))
1702 return notify_fail("There's something in the way.");
1703 return notify_fail("You cannot see that place.");
1704 }
1705
1706 if (!overgrow_affects_pos(a))
1707 return notify_fail("You cannot grow anything here.");
1708
1709 return true;
1710 }
1711
set_aim(coord_def a)1712 bool targeter_overgrow::set_aim(coord_def a)
1713 {
1714 affected_positions.clear();
1715
1716 if (!targeter::set_aim(a))
1717 return false;
1718
1719 if (overgrow_affects_pos(a))
1720 affected_positions.insert(a);
1721 else
1722 return false;
1723
1724 for (adjacent_iterator ai(a, true); ai; ++ai)
1725 {
1726 if (overgrow_affects_pos(*ai) && you.see_cell_no_trans(*ai))
1727 affected_positions.insert(*ai);
1728 }
1729
1730 return affected_positions.size();
1731 }
1732
targeter_multiposition(const actor * a,vector<coord_def> seeds,aff_type _positive)1733 targeter_multiposition::targeter_multiposition(const actor *a,
1734 vector<coord_def> seeds, aff_type _positive)
1735 : targeter(), positive(_positive)
1736 {
1737 agent = a;
1738 for (auto &c : seeds)
1739 affected_positions.insert(c);
1740 }
1741
targeter_multiposition(const actor * a,vector<monster * > seeds,aff_type _positive)1742 targeter_multiposition::targeter_multiposition(const actor *a,
1743 vector<monster *> seeds, aff_type _positive)
1744 : targeter(), positive(_positive)
1745 {
1746 agent = a;
1747 for (monster *m : seeds)
1748 if (m)
1749 affected_positions.insert(m->pos());
1750 }
1751
1752 // sigh, necessary to allow empty initializer lists with the above two
1753 // constructors
targeter_multiposition(const actor * a,initializer_list<coord_def> seeds,aff_type _positive)1754 targeter_multiposition::targeter_multiposition(const actor *a,
1755 initializer_list<coord_def> seeds, aff_type _positive)
1756 : targeter_multiposition(a, vector<coord_def>(seeds.begin(), seeds.end()),
1757 _positive)
1758 {
1759 }
1760
is_affected(coord_def loc)1761 aff_type targeter_multiposition::is_affected(coord_def loc)
1762 {
1763 if ((cell_is_solid(loc) && !can_affect_walls())
1764 || !cell_see_cell(agent->pos(), loc, LOS_NO_TRANS))
1765 {
1766 return AFF_NO;
1767 }
1768
1769 // is this better with maybe or yes?
1770 return affected_positions.count(loc) > 0 ? positive : AFF_NO;
1771 }
1772
targeter_chain_lightning()1773 targeter_chain_lightning::targeter_chain_lightning()
1774 {
1775 vector<coord_def> target_set = chain_lightning_targets();
1776 int min_dist = 999;
1777 for (coord_def pos : target_set)
1778 {
1779 potential_victims.insert(pos);
1780 const int dist = grid_distance(pos, you.pos());
1781 if (dist && dist < min_dist)
1782 min_dist = dist;
1783 }
1784
1785 for (coord_def pos : target_set)
1786 {
1787 const int dist = grid_distance(pos, you.pos());
1788 if (dist && dist <= min_dist)
1789 closest_victims.insert(pos);
1790 }
1791 }
1792
is_affected(coord_def loc)1793 aff_type targeter_chain_lightning::is_affected(coord_def loc)
1794 {
1795 if (closest_victims.find(loc) != closest_victims.end())
1796 return AFF_YES;
1797 if (potential_victims.find(loc) != potential_victims.end())
1798 return AFF_MAYBE;
1799 return AFF_NO;
1800 }
1801
targeter_maxwells_coupling()1802 targeter_maxwells_coupling::targeter_maxwells_coupling()
1803 : targeter_multiposition(&you, find_maxwells_possibles())
1804 {
1805 if (affected_positions.size() == 1)
1806 positive = AFF_YES;
1807 }
1808
targeter_multifireball(const actor * a,vector<coord_def> seeds)1809 targeter_multifireball::targeter_multifireball(const actor *a, vector<coord_def> seeds)
1810 : targeter_multiposition(a, seeds)
1811 {
1812 vector <coord_def> bursts;
1813 for (auto &c : seeds)
1814 {
1815 if (affected_positions.count(c)) // did the parent constructor like this pos?
1816 for (adjacent_iterator ai(c); ai; ++ai)
1817 bursts.push_back(*ai);
1818 }
1819
1820 for (auto &c : bursts)
1821 {
1822 actor * act = actor_at(c);
1823 if (act && mons_aligned(agent, act))
1824 continue;
1825 affected_positions.insert(c);
1826 }
1827 }
1828
targeter_ramparts(const actor * a)1829 targeter_ramparts::targeter_ramparts(const actor *a)
1830 : targeter_multiposition(a, { })
1831 {
1832 auto seeds = find_ramparts_walls(a->pos());
1833 for (auto &c : seeds)
1834 {
1835 affected_positions.insert(c);
1836 for (adjacent_iterator ai(c); ai; ++ai)
1837 if (!cell_is_solid(*ai)) // don't add any walls not in `seeds`
1838 affected_positions.insert(*ai);
1839 }
1840 }
1841
is_affected(coord_def loc)1842 aff_type targeter_ramparts::is_affected(coord_def loc)
1843 {
1844 if (!affected_positions.count(loc)
1845 || !cell_see_cell(agent->pos(), loc, LOS_NO_TRANS))
1846 {
1847 return AFF_NO;
1848 }
1849
1850 return cell_is_solid(loc) ? AFF_YES : AFF_MAYBE;
1851 }
1852
1853 // note: starburst is not in spell_to_zap
targeter_starburst_beam(const actor * a,int _range,int pow,const coord_def & offset)1854 targeter_starburst_beam::targeter_starburst_beam(const actor *a, int _range, int pow, const coord_def &offset)
1855 : targeter_beam(a, _range, ZAP_BOLT_OF_FIRE, pow, 0, 0)
1856 {
1857 set_aim(a->pos() + offset);
1858 }
1859
targeter_starburst(const actor * a,int range,int pow)1860 targeter_starburst::targeter_starburst(const actor *a, int range, int pow)
1861 : targeter()
1862 {
1863 agent = a ? a : &you;
1864 // XX code duplication with cast_starburst
1865 const vector<coord_def> offsets = { coord_def(range, 0),
1866 coord_def(range, range),
1867 coord_def(0, range),
1868 coord_def(-range, range),
1869 coord_def(-range, 0),
1870 coord_def(-range, -range),
1871 coord_def(0, -range),
1872 coord_def(range, -range) };
1873
1874 // extremely brute force...
1875 for (auto &o : offsets)
1876 beams.push_back(targeter_starburst_beam(agent, range, pow, o));
1877 }
1878
is_affected(coord_def loc)1879 aff_type targeter_starburst::is_affected(coord_def loc)
1880 {
1881 for (auto &t : beams)
1882 if (auto r = t.is_affected(loc))
1883 return r;
1884 return AFF_NO;
1885 }
1886
targeter_bog(const actor * a,int pow)1887 targeter_bog::targeter_bog(const actor *a, int pow)
1888 : targeter_multiposition(a, { })
1889 {
1890 auto seeds = find_bog_locations(a->pos(), pow);
1891 for (auto &c : seeds)
1892 affected_positions.insert(c);
1893 }
1894
targeter_ignite_poison(actor * a)1895 targeter_ignite_poison::targeter_ignite_poison(actor *a)
1896 : targeter_multiposition(a, { })
1897 {
1898 for (radius_iterator ri(a->pos(), LOS_SOLID_SEE); ri; ++ri)
1899 if (ignite_poison_affects_cell(*ri, a))
1900 affected_positions.insert(*ri);
1901 }
1902
targeter_multimonster(const actor * a)1903 targeter_multimonster::targeter_multimonster(const actor *a)
1904 : targeter()
1905 {
1906 agent = a;
1907 }
1908
is_affected(coord_def loc)1909 aff_type targeter_multimonster::is_affected(coord_def loc)
1910 {
1911 if ((cell_is_solid(loc) && !can_affect_walls())
1912 || !cell_see_cell(agent->pos(), loc, LOS_NO_TRANS))
1913 {
1914 return AFF_NO;
1915 }
1916
1917 //if (agent && act && !agent->can_see(*act))
1918 // return AFF_NO;
1919
1920 // Any special checks from our inheritors
1921 const monster_info *mon = env.map_knowledge(loc).monsterinfo();
1922 if (!mon || !affects_monster(*mon))
1923 return AFF_NO;
1924
1925 return AFF_YES;
1926 }
1927
targeter_drain_life()1928 targeter_drain_life::targeter_drain_life()
1929 : targeter_multimonster(&you)
1930 {
1931 }
1932
affects_monster(const monster_info & mon)1933 bool targeter_drain_life::affects_monster(const monster_info& mon)
1934 {
1935 return get_resist(mon.resists(), MR_RES_NEG) < 3
1936 && !mons_atts_aligned(agent->temp_attitude(), mon.attitude);
1937 }
1938
targeter_discord()1939 targeter_discord::targeter_discord()
1940 : targeter_multimonster(&you)
1941 {
1942 }
1943
affects_monster(const monster_info & mon)1944 bool targeter_discord::affects_monster(const monster_info& mon)
1945 {
1946 return mon.willpower() != WILL_INVULN && mon.can_go_frenzy;
1947 }
1948
targeter_englaciate()1949 targeter_englaciate::targeter_englaciate()
1950 : targeter_multimonster(&you)
1951 {
1952 }
1953
affects_monster(const monster_info & mon)1954 bool targeter_englaciate::affects_monster(const monster_info& mon)
1955 {
1956 return get_resist(mon.resists(), MR_RES_COLD) <= 0
1957 && !mons_class_flag(mon.type, M_STATIONARY);
1958 }
1959
targeter_fear()1960 targeter_fear::targeter_fear()
1961 : targeter_multimonster(&you)
1962 {
1963 }
1964
affects_monster(const monster_info & mon)1965 bool targeter_fear::affects_monster(const monster_info& mon)
1966 {
1967 return mon.willpower() != WILL_INVULN
1968 && mon.can_feel_fear
1969 && !mons_atts_aligned(agent->temp_attitude(), mon.attitude);
1970 }
1971
targeter_intoxicate()1972 targeter_intoxicate::targeter_intoxicate()
1973 : targeter_multimonster(&you)
1974 {
1975 }
1976
affects_monster(const monster_info & mon)1977 bool targeter_intoxicate::affects_monster(const monster_info& mon)
1978 {
1979 return !(mon.mintel < I_HUMAN
1980 || !(mon.holi & MH_NATURAL)
1981 || get_resist(mon.resists(), MR_RES_POISON) >= 3);
1982 }
1983