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