1 /**
2  * @file
3  * @brief Functions used when picking squares.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "directn.h"
9 
10 #include <algorithm>
11 #include <cstdarg>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <cstring>
15 #include <functional>
16 #include <sstream>
17 
18 #include "act-iter.h"
19 #include "areas.h"
20 #include "artefact.h"
21 #include "attitude-change.h"
22 #include "cloud.h"
23 #include "colour.h"
24 #include "command.h"
25 #include "coordit.h"
26 #include "describe.h"
27 #include "dungeon.h"
28 #include "english.h"
29 #include "externs.h" // INVALID_COORD
30 #include "fight.h" // melee_confuse_chance
31 #include "god-abil.h"
32 #include "hints.h"
33 #include "invent.h"
34 #include "item-prop.h"
35 #include "item-status-flag-type.h"
36 #include "items.h"
37 #include "libutil.h"
38 #include "losglobal.h"
39 #include "macro.h"
40 #include "mapmark.h"
41 #include "message.h"
42 #include "misc.h"
43 #include "mon-death.h"
44 #include "mon-tentacle.h"
45 #include "nearby-danger.h"
46 #include "output.h"
47 #include "prompt.h"
48 #include "showsymb.h"
49 #include "spl-goditem.h"
50 #include "stash.h"
51 #include "state.h"
52 #include "stringutil.h"
53 #include "tag-version.h"
54 #include "target.h"
55 #include "terrain.h"
56 #ifdef USE_TILE
57  #include "tileview.h"
58  #include "tile-flags.h"
59 #endif
60 #include "traps.h"
61 #include "travel.h"
62 #include "viewchar.h"
63 #include "view.h"
64 #include "viewmap.h"
65 #include "wiz-dgn.h"
66 #include "wiz-mon.h"
67 
68 enum LOSSelect
69 {
70     LS_ANY      = 0x00,
71 
72     // Check only visible squares
73     LS_VISIBLE  = 0x01,
74 
75     // Check only hidden squares
76     LS_HIDDEN   = 0x02,
77 
78     LS_VISMASK  = 0x03,
79 
80     // Flip from visible to hidden when going forward,
81     // or hidden to visible when going backwards.
82     LS_FLIPVH   = 0x20,
83 
84     // Flip from hidden to visible when going forward,
85     // or visible to hidden when going backwards.
86     LS_FLIPHV   = 0x40,
87 
88     LS_NONE     = 0xFFFF,
89 };
90 
91 #ifdef WIZARD
92 static void _wizard_make_friendly(monster* m);
93 #endif
94 static dist _look_around_target(const coord_def &whence);
95 static void _describe_oos_feature(const coord_def& where);
96 static void _describe_cell(const coord_def& where, bool in_range = true);
97 static bool _print_cloud_desc(const coord_def where);
98 static bool _print_item_desc(const coord_def where);
99 
100 static bool _blocked_ray(const coord_def &where);
101 static bool _want_target_monster(const monster *mon, targ_mode_type mode,
102                                  targeter* hitfunc);
103 static bool _find_monster(const coord_def& where, targ_mode_type mode,
104                           bool need_path, int range, targeter *hitfunc);
105 static bool _find_monster_expl(const coord_def& where, targ_mode_type mode,
106                                bool need_path, int range, targeter *hitfunc,
107                                aff_type mon_aff, aff_type allowed_self_aff);
108 static bool _find_shadow_step_mons(const coord_def& where, targ_mode_type mode,
109                                    bool need_path, int range,
110                                    targeter *hitfunc);
111 static bool _find_object(const coord_def& where, bool need_path, int range,
112                          targeter *hitfunc);
113 static bool _find_autopickup_object(const coord_def& where, bool need_path,
114                                     int range, targeter *hitfunc);
115 
116 typedef function<bool (const coord_def& where)> target_checker;
117 static bool _find_square_wrapper(coord_def &mfp, int direction,
118                                  target_checker find_targ, targeter *hitfunc,
119                                  LOSSelect los = LS_ANY);
120 
121 static int  _targeting_cmd_to_compass(command_type command);
122 static void _describe_oos_square(const coord_def& where);
123 static void _extend_move_to_edge(dist &moves);
124 static vector<string> _get_monster_desc_vector(const monster_info& mi);
125 static string _get_monster_desc(const monster_info& mi);
126 
127 #ifdef DEBUG_DIAGNOSTICS
128 static void _debug_describe_feature_at(const coord_def &where);
129 #endif
130 
131 #ifdef WIZARD
_wizard_make_friendly(monster * m)132 static void _wizard_make_friendly(monster* m)
133 {
134     if (m == nullptr)
135         return;
136 
137     mon_attitude_type att = m->attitude;
138 
139     // During arena mode, skip directly from friendly to hostile.
140     if (crawl_state.arena_suspended && att == ATT_FRIENDLY)
141         att = ATT_NEUTRAL;
142 
143     switch (att)
144     {
145     case ATT_FRIENDLY:
146         m->attitude = ATT_GOOD_NEUTRAL;
147         m->flags &= ~MF_NO_REWARD;
148         m->flags |= MF_WAS_NEUTRAL;
149         break;
150     case ATT_GOOD_NEUTRAL:
151         m->attitude = ATT_STRICT_NEUTRAL;
152         break;
153     case ATT_STRICT_NEUTRAL:
154         m->attitude = ATT_NEUTRAL;
155         break;
156     case ATT_NEUTRAL:
157         m->attitude = ATT_HOSTILE;
158         m->flags &= ~MF_WAS_NEUTRAL;
159         break;
160     case ATT_HOSTILE:
161         m->attitude = ATT_FRIENDLY;
162         m->flags |= MF_NO_REWARD;
163         break;
164     }
165     mons_att_changed(m);
166 }
167 #endif
168 
dist()169 dist::dist()
170     : isValid(false), isTarget(false), isEndpoint(false), isCancel(false),
171       choseRay(false), interactive(false), target(), delta(), ray(),
172        find_target(false), fire_context(nullptr), cmd_result(CMD_NO_CMD)
173 {
174 }
175 
isMe() const176 bool dist::isMe() const
177 {
178     // We hack the decision as to whether to use delta or target by
179     // assuming that we use delta only if target hasn't been touched.
180     return isValid && !isCancel
181            && (target == you.pos()
182                || (target.origin() && delta.origin()));
183 }
184 
185 /**
186  * Does this dist object need `target` to be filled in somehow, e.g. with
187  * manual/interactive targeting? Affected by the following three dist fields:
188  *
189  *  `interactive`: force interactive targeting, overrides the other two.
190  *  `find_target`: requests to use the direction chooser default target, allows
191  *                 non-interactive mode. Overrides a value supplied by `target`.
192  *  `target`: supplying a target coord directly allows non-interactive mode.
193  */
needs_targeting() const194 bool dist::needs_targeting() const
195 {
196     return interactive || !in_bounds(target) && !find_target;
197 }
198 
_targeting_cmd_to_compass(command_type command)199 static int _targeting_cmd_to_compass(command_type command)
200 {
201     switch (command)
202     {
203     case CMD_TARGET_UP:         case CMD_TARGET_DIR_UP:
204         return 0;
205     case CMD_TARGET_UP_RIGHT:   case CMD_TARGET_DIR_UP_RIGHT:
206         return 1;
207     case CMD_TARGET_RIGHT:      case CMD_TARGET_DIR_RIGHT:
208         return 2;
209     case CMD_TARGET_DOWN_RIGHT: case CMD_TARGET_DIR_DOWN_RIGHT:
210         return 3;
211     case CMD_TARGET_DOWN:       case CMD_TARGET_DIR_DOWN:
212         return 4;
213     case CMD_TARGET_DOWN_LEFT:  case CMD_TARGET_DIR_DOWN_LEFT:
214         return 5;
215     case CMD_TARGET_LEFT:       case CMD_TARGET_DIR_LEFT:
216         return 6;
217     case CMD_TARGET_UP_LEFT:    case CMD_TARGET_DIR_UP_LEFT:
218         return 7;
219     default:
220         return -1;
221     }
222 }
223 
shift_direction(command_type cmd)224 static command_type shift_direction(command_type cmd)
225 {
226     switch (cmd)
227     {
228     case CMD_TARGET_DOWN_LEFT:  return CMD_TARGET_DIR_DOWN_LEFT;
229     case CMD_TARGET_LEFT:       return CMD_TARGET_DIR_LEFT;
230     case CMD_TARGET_DOWN:       return CMD_TARGET_DIR_DOWN;
231     case CMD_TARGET_UP:         return CMD_TARGET_DIR_UP;
232     case CMD_TARGET_RIGHT:      return CMD_TARGET_DIR_RIGHT;
233     case CMD_TARGET_DOWN_RIGHT: return CMD_TARGET_DIR_DOWN_RIGHT;
234     case CMD_TARGET_UP_RIGHT:   return CMD_TARGET_DIR_UP_RIGHT;
235     case CMD_TARGET_UP_LEFT:    return CMD_TARGET_DIR_UP_LEFT;
236     default: return cmd;
237     }
238 }
239 
targeted_actor() const240 actor* direction_chooser::targeted_actor() const
241 {
242     if (target() == you.pos())
243         return &you;
244     else
245         return targeted_monster();
246 }
247 
targeted_monster() const248 monster* direction_chooser::targeted_monster() const
249 {
250     monster* m = monster_at(target());
251     if (m && you.can_see(*m))
252         return m;
253     else
254         return nullptr;
255 }
256 
257 // Return your target, if it still exists and is visible to you.
_get_current_target()258 static monster* _get_current_target()
259 {
260     if (invalid_monster_index(you.prev_targ))
261         return nullptr;
262 
263     monster* mon = &env.mons[you.prev_targ];
264     ASSERT(mon);
265     if (mon->alive() && you.can_see(*mon))
266         return mon;
267     else
268         return nullptr;
269 }
270 
build_targeting_hint_string() const271 string direction_chooser::build_targeting_hint_string() const
272 {
273     string hint_string;
274 
275     // Hint for 'p' - previous target, and for 'f' - current cell, if
276     // applicable.
277     // TODO: currently 'f' works for non-actor targets (features, apport) but
278     // shows nothing here
279     const actor*   f_target = targeted_actor();
280     const monster* p_target = _get_current_target();
281 
282     if (f_target && f_target == p_target)
283         hint_string = ", f/p - " + f_target->name(DESC_PLAIN);
284     else
285     {
286         if (f_target)
287             hint_string += ", f - " + f_target->name(DESC_PLAIN);
288         if (p_target)
289             hint_string += ", p - " + p_target->name(DESC_PLAIN);
290     }
291 
292     return hint_string;
293 }
294 
print_top_prompt() const295 void direction_chooser::print_top_prompt() const
296 {
297     if (!top_prompt.empty())
298         mprf(MSGCH_PROMPT, "%s", top_prompt.c_str());
299     else if (moves.fire_context)
300     {
301         // TODO: consolidate top prompt construction more
302         mprf(MSGCH_PROMPT, "%s",
303             moves.fire_context->get()->quiver_description().tostring().c_str());
304     }
305 }
306 
print_key_hints() const307 void direction_chooser::print_key_hints() const
308 {
309     // TODO: build this as a vector and insert ,s and \ns in a smarter way
310     string prompt = "Press: ? - help";
311 
312     if (just_looking)
313     {
314         if (you.see_cell(target()))
315             prompt += ", v - describe";
316         prompt += ", . - travel";
317         if (in_bounds(target()) && env.map_knowledge(target()).item())
318             prompt += ", g - get item";
319     }
320     else
321     {
322         if (moves.fire_context)
323         {
324             const string hint = moves.fire_context->fire_key_hints();
325             if (!hint.empty())
326                 prompt += hint + "\n";
327         }
328         string direction_hint = "";
329         if (!behaviour->targeted())
330             direction_hint = "Dir - look around, f - activate";
331         else
332         {
333             switch (restricts)
334             {
335             case DIR_NONE:
336                 direction_hint = "Shift-Dir - straight line";
337                 break;
338             case DIR_TARGET:
339             case DIR_SHADOW_STEP:
340             case DIR_LEAP:
341                 direction_hint = "Dir - move target";
342                 break;
343             }
344         }
345 
346         if (direction_hint.size())
347         {
348             if (prompt[prompt.size() - 1] != '\n')
349                 prompt += ", ";
350             prompt += direction_hint;
351         }
352         if (behaviour->targeted())
353             prompt += build_targeting_hint_string();
354     }
355 
356     // Display the prompt.
357     mprf(MSGCH_PROMPT, "%s", prompt.c_str());
358 }
359 
targets_objects() const360 bool direction_chooser::targets_objects() const
361 {
362     return mode == TARG_MOVABLE_OBJECT;
363 }
364 
365 /// Are we looking for enemies?
targets_enemies() const366 bool direction_chooser::targets_enemies() const
367 {
368     switch (mode)
369     {
370         case TARG_HOSTILE:
371         case TARG_HOSTILE_SUBMERGED:
372             return true;
373         default:
374             return false;
375     }
376 }
377 
describe_cell() const378 void direction_chooser::describe_cell() const
379 {
380     print_top_prompt();
381     print_key_hints();
382 
383     if (!you.see_cell(target()))
384     {
385         // FIXME: make this better integrated.
386         _describe_oos_square(target());
387     }
388     else
389     {
390         bool did_cloud = false;
391         print_target_description(did_cloud);
392         if (just_looking)
393             print_items_description();
394         if (just_looking || show_floor_desc)
395         {
396             print_floor_description(show_boring_feats);
397             if (!did_cloud)
398                 _print_cloud_desc(target());
399         }
400     }
401 
402     flush_prev_message();
403 }
404 
405 #ifndef USE_TILE_LOCAL
_get_ray_glyph(const coord_def & pos,int colour,int glych,int mcol)406 static cglyph_t _get_ray_glyph(const coord_def& pos, int colour, int glych,
407                                int mcol)
408 {
409     if (const monster* mons = monster_at(pos))
410     {
411         if (mons->alive() && mons->visible_to(&you))
412         {
413             glych = get_cell_glyph(pos).ch;
414             colour = mcol;
415         }
416     }
417     if (pos == you.pos())
418     {
419         glych = mons_char(you.symbol);
420         colour = mcol;
421     }
422     return {static_cast<char32_t>(glych),
423             static_cast<unsigned short>(real_colour(colour))};
424 }
425 #endif
426 
427 // Unseen monsters in shallow water show a "strange disturbance".
428 // (Unless flying!)
429 // These should match tests in show.cc's _update_monster
_mon_exposed_in_water(const monster * mon)430 static bool _mon_exposed_in_water(const monster* mon)
431 {
432     return env.grid(mon->pos()) == DNGN_SHALLOW_WATER && !mon->airborne()
433            && !mon->submerged() && !cloud_at(mon->pos());
434 }
435 
_mon_exposed_in_cloud(const monster * mon)436 static bool _mon_exposed_in_cloud(const monster* mon)
437 {
438     return cloud_at(mon->pos())
439            && is_opaque_cloud(cloud_at(mon->pos())->type)
440            && !mon->submerged() && !mon->is_insubstantial();
441 }
442 
_mon_exposed(const monster * mon)443 static bool _mon_exposed(const monster* mon)
444 {
445     if (!mon || !you.see_cell(mon->pos()) || mon->visible_to(&you))
446         return false;
447 
448     return _mon_exposed_in_water(mon) || _mon_exposed_in_cloud(mon);
449 }
450 
_is_target_in_range(const coord_def & where,int range,targeter * hitfunc)451 static bool _is_target_in_range(const coord_def& where, int range,
452                                 targeter *hitfunc)
453 {
454     if (hitfunc)
455         return hitfunc->valid_aim(where);
456     // range == -1 means that range doesn't matter.
457     return range == -1 || grid_distance(you.pos(), where) <= range;
458 }
459 
460 targeting_behaviour direction_chooser::stock_behaviour;
461 
direction(dist & moves,const direction_chooser_args & args)462 void direction(dist &moves, const direction_chooser_args& args)
463 {
464     // TODO this might break pre-chosen delta targeting, if that ever happens
465     // (currently it looks like isTarget is always set to true)
466     moves.interactive = moves.needs_targeting();
467 
468     if (moves.interactive)
469         direction_chooser(moves, args).choose_direction();
470     else
471         direction_chooser(moves, args).noninteractive();
472 }
473 
direction_chooser(dist & moves_,const direction_chooser_args & args)474 direction_chooser::direction_chooser(dist& moves_,
475                                      const direction_chooser_args& args) :
476     moves(moves_),
477     restricts(args.restricts),
478     mode(args.mode),
479     range(args.range),
480     just_looking(args.just_looking),
481     prefer_farthest(args.prefer_farthest),
482     allow_shift_dir(args.allow_shift_dir),
483     self(args.self),
484     target_prefix(args.target_prefix),
485     top_prompt(args.top_prompt),
486     behaviour(args.behaviour),
487     show_floor_desc(args.show_floor_desc),
488     show_boring_feats(args.show_boring_feats),
489     hitfunc(args.hitfunc),
490     default_place(args.default_place),
491     renderer(*this),
492     unrestricted(args.unrestricted),
493     needs_path(args.needs_path)
494 {
495     if (!behaviour)
496         behaviour = &stock_behaviour;
497 
498     behaviour->just_looking = just_looking;
499     behaviour->get_desc_func = args.get_desc_func;
500     if (unrestricted)
501     {
502         needs_path = false;
503         behaviour->needs_path = MB_MAYBE;
504     }
505     else if (hitfunc)
506     {
507         needs_path = true;
508         behaviour->needs_path = MB_MAYBE; // TODO: can this be relaxed?
509     }
510     if (behaviour->needs_path != MB_MAYBE)
511         needs_path = tobool(behaviour->needs_path, true);
512 
513     show_beam = !just_looking && needs_path;
514     need_viewport_redraw = show_beam;
515     have_beam = false;
516 
517     need_text_redraw = true;
518     need_cursor_redraw = true;
519     need_all_redraw = false;
520 }
521 
522 class view_desc_proc
523 {
524 public:
view_desc_proc()525     view_desc_proc()
526     {
527         // This thing seems to be starting off 1 line above where it
528         // should be. -cao
529         nextline();
530     }
width()531     int width() { return crawl_view.msgsz.x; }
height()532     int height() { return crawl_view.msgsz.y; }
print(const string & str)533     void print(const string &str) { cprintf("%s", str.c_str()); }
nextline()534     void nextline() { cgotoxy(1, wherey() + 1); }
535 };
536 
_full_describe_menu(vector<monster_info> const & list_mons,vector<item_def> const & list_items,vector<coord_def> const & list_features,string selectverb,bool examine_only=false,string title="")537 static coord_def _full_describe_menu(vector<monster_info> const &list_mons,
538                                      vector<item_def> const &list_items,
539                                      vector<coord_def> const &list_features,
540                                      string selectverb,
541                                      bool examine_only = false,
542                                      string title = "")
543 {
544     InvMenu desc_menu(MF_SINGLESELECT | MF_ANYPRINTABLE
545                         | MF_ALLOW_FORMATTING | MF_SELECT_BY_PAGE);
546 
547     string title_secondary;
548 
549     if (title.empty())
550     {
551         if (!list_mons.empty())
552             title  = "Monsters";
553         if (!list_items.empty())
554         {
555             if (!title.empty())
556                 title += "/";
557             title += "Items";
558         }
559         if (!list_features.empty())
560         {
561             if (!title.empty())
562                 title += "/";
563             title += "Features";
564         }
565         title = "Visible " + title;
566         if (examine_only)
567             title += " (select to examine)";
568         else
569         {
570             title_secondary = title + " (select to examine, '!' to "
571               + selectverb + "):";
572             title += " (select to " + selectverb + ", '!' to examine):";
573         }
574     }
575 
576     desc_menu.set_tag("pickup");
577     // necessary for sorting of the item submenu
578     desc_menu.set_type(menu_type::pickup);
579     desc_menu.set_title(new MenuEntry(title, MEL_TITLE));
580     if (examine_only)
581     {
582         desc_menu.action_cycle = Menu::CYCLE_NONE;
583         desc_menu.menu_action = InvMenu::ACT_EXAMINE;
584     }
585     else
586     {
587         desc_menu.action_cycle = Menu::CYCLE_TOGGLE;
588         desc_menu.menu_action = InvMenu::ACT_EXECUTE;
589         desc_menu.set_title(new MenuEntry(title_secondary, MEL_TITLE), false);
590     }
591 
592     // Start with hotkey 'a' and count from there.
593     menu_letter hotkey;
594     // Build menu entries for monsters.
595     if (!list_mons.empty())
596     {
597         desc_menu.add_entry(new MenuEntry("Monsters", MEL_SUBTITLE));
598         for (const monster_info &mi : list_mons)
599         {
600             // List monsters in the form
601             // (A) An angel (neutral), wielding a glowing long sword
602 
603             ostringstream prefix;
604 #ifndef USE_TILE_LOCAL
605             cglyph_t g = get_mons_glyph(mi);
606             const string col_string = colour_to_str(g.col);
607             prefix << "(<" << col_string << ">"
608                      << (g.ch == '<' ? "<<" : stringize_glyph(g.ch))
609                      << "</" << col_string << ">) ";
610 #endif
611             if (Options.monster_item_view_coordinates)
612             {
613                 const coord_def relpos = mi.pos - you.pos();
614                 prefix << "(" << relpos.x << ", " << -relpos.y << ") ";
615             }
616 
617             string str = get_monster_equipment_desc(mi, DESC_FULL, DESC_A, true);
618             if (mi.dam != MDAM_OKAY)
619                 str += ", " + mi.damage_desc();
620 
621             string consinfo = mi.constriction_description();
622             if (!consinfo.empty())
623                 str += ", " + consinfo;
624 
625 #ifndef USE_TILE_LOCAL
626             // Wraparound if the description is longer than allowed.
627             linebreak_string(str, get_number_of_cols() - 9);
628 #endif
629             vector<formatted_string> fss;
630             formatted_string::parse_string_to_multiple(str, fss);
631             MenuEntry *me = nullptr;
632             for (unsigned int j = 0; j < fss.size(); ++j)
633             {
634                 if (j == 0)
635                     me = new MonsterMenuEntry(prefix.str() + fss[j].tostring(), &mi, hotkey++);
636 #ifndef USE_TILE_LOCAL
637                 else
638                 {
639                     str = "         " + fss[j].tostring();
640                     me = new MenuEntry(str, MEL_ITEM, 1);
641                 }
642 #endif
643                 desc_menu.add_entry(me);
644             }
645         }
646     }
647 
648     // Build menu entries for items.
649     if (!list_items.empty())
650     {
651         vector<InvEntry*> all_items;
652         for (const item_def &item : list_items)
653             all_items.push_back(new InvEntry(item));
654 
655         const menu_sort_condition *cond = desc_menu.find_menu_sort_condition();
656         desc_menu.sort_menu(all_items, cond);
657 
658         desc_menu.add_entry(new MenuEntry("Items", MEL_SUBTITLE));
659         for (InvEntry *me : all_items)
660         {
661 #ifndef USE_TILE_LOCAL
662             // Show glyphs only for ASCII.
663             me->set_show_glyph(true);
664 #endif
665             me->set_show_coordinates(Options.monster_item_view_coordinates);
666             me->tag = "pickup";
667             me->hotkeys[0] = hotkey;
668             me->quantity = 2; // Hack to make items selectable.
669 
670             desc_menu.add_entry(me);
671             ++hotkey;
672         }
673     }
674 
675     if (!list_features.empty())
676     {
677         desc_menu.add_entry(new MenuEntry("Features", MEL_SUBTITLE));
678         for (const coord_def &c : list_features)
679         {
680             ostringstream desc;
681 #ifndef USE_TILE_LOCAL
682             cglyph_t g = get_cell_glyph(c, true);
683             const string colour_str = colour_to_str(g.col);
684             desc << "(<" << colour_str << ">";
685             desc << (g.ch == '<' ? "<<" : stringize_glyph(g.ch));
686 
687             desc << "</" << colour_str << ">) ";
688 #endif
689             if (Options.monster_item_view_coordinates)
690             {
691                 const coord_def relpos = c - you.pos();
692                 desc << "(" << relpos.x << ", " << -relpos.y << ") ";
693             }
694 
695             desc << feature_description_at(c, false, DESC_A);
696             if (is_unknown_stair(c) || is_unknown_transporter(c))
697                 desc << " (not visited)";
698             FeatureMenuEntry *me = new FeatureMenuEntry(desc.str(), c, hotkey);
699             me->tag        = "description";
700             // Hack to make features selectable.
701             me->quantity   = c.x*100 + c.y + 3;
702             desc_menu.add_entry(me);
703             ++hotkey;
704         }
705     }
706 
707     // Select an item to read its full description, or a monster to read its
708     // e'x'amine description. Toggle with '!' to return the coordinates to
709     // the caller so that it may perform the promised selectverb
710 
711     coord_def target(-1, -1);
712 
713     desc_menu.on_single_selection = [&desc_menu, &target](const MenuEntry& sel)
714     {
715         target = coord_def(-1, -1);
716         // HACK: quantity == 1: monsters, quantity == 2: items
717         const int quant = sel.quantity;
718         if (quant == 1)
719         {
720             // Get selected monster.
721             monster_info* m = static_cast<monster_info* >(sel.data);
722 
723 #ifdef USE_TILE
724             // Highlight selected monster on the screen.
725             const coord_def gc(m->pos);
726             tiles.place_cursor(CURSOR_TUTORIAL, gc);
727             const string &desc = get_terse_square_desc(gc);
728             tiles.clear_text_tags(TAG_TUTORIAL);
729             tiles.add_text_tag(TAG_TUTORIAL, desc, gc);
730 #endif
731 
732             if (desc_menu.menu_action == InvMenu::ACT_EXAMINE)
733             {
734                 // View database entry.
735                 describe_monsters(*m);
736                 redraw_screen();
737                 update_screen();
738                 clear_messages();
739             }
740             else // ACT_EXECUTE -> view/travel
741                 target = m->pos;
742         }
743         else if (quant == 2)
744         {
745             // Get selected item.
746             const item_def* i = static_cast<item_def*>(sel.data);
747             if (desc_menu.menu_action == InvMenu::ACT_EXAMINE)
748                 describe_item_popup(*i);
749             else // ACT_EXECUTE -> view/travel
750                 target = i->pos;
751         }
752         else
753         {
754             const int num = quant - 3;
755             const int y = num % 100;
756             const int x = (num - y)/100;
757             coord_def c(x,y);
758 
759             if (desc_menu.menu_action == InvMenu::ACT_EXAMINE)
760                 describe_feature_wide(c);
761             else // ACT_EXECUTE -> view/travel
762                 target = c;
763         }
764         return desc_menu.menu_action == InvMenu::ACT_EXAMINE;
765     };
766     desc_menu.show();
767     redraw_screen();
768     update_screen();
769 
770 
771 #ifndef USE_TILE_LOCAL
772     if (!list_items.empty())
773     {
774         // Unset show_glyph for other menus.
775         InvEntry me(list_items[0]);
776         me.set_show_glyph(false);
777     }
778 #endif
779 #ifdef USE_TILE
780     // Clear cursor placement.
781     tiles.place_cursor(CURSOR_TUTORIAL, NO_CURSOR);
782     tiles.clear_text_tags(TAG_TUTORIAL);
783 #endif
784     return target;
785 }
786 
_get_nearby_items(vector<item_def> & list_items,bool need_path,int range,targeter * hitfunc)787 static void _get_nearby_items(vector<item_def> &list_items,
788                                 bool need_path, int range, targeter *hitfunc)
789 {
790     // Grab all items known (or thought) to be in the stashes in view.
791     for (vision_iterator ri(you); ri; ++ri)
792     {
793         if (!_is_target_in_range(*ri, range, hitfunc))
794             continue;
795 
796         if (need_path && _blocked_ray(*ri))
797             continue;
798 
799         const int oid = you.visible_igrd(*ri);
800         if (oid == NON_ITEM)
801             continue;
802 
803         const vector<const item_def *> items = item_list_on_square(
804                                                    you.visible_igrd(*ri));
805 
806         for (const item_def * item : items)
807             list_items.push_back(*item);
808     }
809 }
810 
_get_nearby_features(vector<coord_def> & list_features,bool need_path,int range,targeter * hitfunc)811 static void _get_nearby_features(vector<coord_def> &list_features,
812                           bool need_path, int range, targeter *hitfunc)
813 {
814     vector <text_pattern> &filters = Options.monster_item_view_features;
815     for (vision_iterator ri(you); ri; ++ri)
816     {
817         if (!_is_target_in_range(*ri, range, hitfunc))
818             continue;
819 
820         if (need_path && _blocked_ray(*ri))
821             continue;
822 
823         // Do we want to aim at this because its the feature, not because
824         // of a monster. This is a bit of a hack since hitfunc->valid_aim
825         // is monster-aware and monster targets are listed in a different
826         // place.
827         if (hitfunc && !monster_at(*ri))
828             list_features.push_back(*ri);
829         // Not using a targeter, list features according to user preferences.
830         else if (!hitfunc)
831         {
832             if (!filters.empty())
833             {
834                 for (const text_pattern &pattern : filters)
835                 {
836                     if (pattern.matches(feature_description(env.grid(*ri)))
837                         || feat_stair_direction(env.grid(*ri)) != CMD_NO_CMD
838                            && pattern.matches("stair")
839                         || feat_is_trap(env.grid(*ri))
840                            && pattern.matches("trap"))
841                     {
842                         list_features.push_back(*ri);
843                         break;
844                     }
845                 }
846             }
847         }
848     }
849 }
850 
851 // Lists monsters, items, and some interesting features in the player's view.
852 // TODO: Allow sorting of items lists.
full_describe_view()853 void full_describe_view()
854 {
855     vector<monster_info> list_mons;
856     vector<item_def> list_items;
857     vector<coord_def> list_features;
858     // Get monsters via the monster_info, sorted by difficulty.
859     get_monster_info(list_mons);
860     _get_nearby_items(list_items, false, get_los_radius(), nullptr);
861     _get_nearby_features(list_features, false, get_los_radius(), nullptr);
862 
863     if (list_mons.empty() && list_items.empty() && list_features.empty())
864     {
865         mpr("No monsters, items or features are visible.");
866         return;
867     }
868 
869     coord_def target = _full_describe_menu(list_mons, list_items,
870                                            list_features, "target/travel");
871 
872     // need to do this after the menu has been closed on console,
873     // since do_look_around() runs its own loop
874     if (target != coord_def(-1, -1))
875         do_look_around(target);
876 }
877 
do_look_around(const coord_def & whence)878 void do_look_around(const coord_def &whence)
879 {
880     dist lmove = _look_around_target(you.pos() + whence);
881     if (lmove.isValid && lmove.isTarget && !lmove.isCancel
882         && !crawl_state.arena_suspended)
883     {
884         start_travel(lmove.target);
885     }
886 }
887 
get_look_position(coord_def * c)888 bool get_look_position(coord_def *c)
889 {
890     dist lmove = _look_around_target(you.pos());
891     if (lmove.isCancel)
892         return false;
893     *c = lmove.target;
894     return true;
895 }
896 
_look_around_target(const coord_def & whence)897 static dist _look_around_target(const coord_def &whence)
898 {
899     dist lmove;   // Will be initialised by direction().
900     direction_chooser_args args;
901     args.restricts = DIR_TARGET;
902     args.just_looking = true;
903     args.needs_path = false;
904     args.target_prefix = "Here";
905     args.default_place = whence - you.pos();
906     direction(lmove, args);
907     return lmove;
908 }
909 
range_view_annotator(targeter * range)910 range_view_annotator::range_view_annotator(targeter *range)
911 {
912     if (range && Options.darken_beyond_range)
913         crawl_state.darken_range = range;
914 }
915 
~range_view_annotator()916 range_view_annotator::~range_view_annotator()
917 {
918     crawl_state.darken_range = nullptr;
919 }
920 
monster_view_annotator(vector<monster * > * monsters)921 monster_view_annotator::monster_view_annotator(vector<monster *> *monsters)
922 {
923     if ((Options.use_animations & UA_MONSTER_IN_SIGHT) && monsters->size())
924     {
925         crawl_state.flash_monsters = monsters;
926         viewwindow(false);
927         update_screen();
928     }
929 }
930 
~monster_view_annotator()931 monster_view_annotator::~monster_view_annotator()
932 {
933     if ((Options.use_animations & UA_MONSTER_IN_SIGHT)
934         && crawl_state.flash_monsters)
935     {
936         crawl_state.flash_monsters = nullptr;
937         viewwindow(false);
938         update_screen();
939     }
940 }
941 
move_is_ok() const942 bool direction_chooser::move_is_ok() const
943 {
944     if (unrestricted || !behaviour->targeted())
945         return true;
946     if (!moves.isCancel && moves.isTarget)
947     {
948         if (!cell_see_cell(you.pos(), target(), LOS_NO_TRANS))
949         {
950             if (hitfunc && hitfunc->can_affect_unseen())
951                 return true; // is this too broad?
952             if (you.see_cell(target()))
953                 mprf(MSGCH_EXAMINE_FILTER, "There's something in the way.");
954             else
955                 mprf(MSGCH_EXAMINE_FILTER, "You can't see that place.");
956             return false;
957         }
958 
959         if (looking_at_you())
960         {
961             if (!targets_objects() && targets_enemies())
962             {
963                 if (self == confirm_prompt_type::cancel
964                     || self == confirm_prompt_type::prompt
965                        && Options.allow_self_target
966                               == confirm_prompt_type::cancel)
967                 {
968                     if (moves.interactive)
969                         mprf(MSGCH_EXAMINE_FILTER, "That would be overly suicidal.");
970                     return false;
971                 }
972                 else if (self != confirm_prompt_type::none
973                          && Options.allow_self_target
974                                 != confirm_prompt_type::none)
975                 {
976                     // if it needs to be asked, simply disallow it when
977                     // calling in non-interactive mode
978                     if (!moves.interactive)
979                         return false;
980                     return yesno("Really target yourself?", false, 'n',
981                                  true, true, false, nullptr, false);
982                 }
983             }
984 
985             if (self == confirm_prompt_type::cancel)
986             {
987                 // avoid printing this message when autotargeting -- it doesn't
988                 // make much sense
989                 if (moves.interactive)
990                     mprf(MSGCH_EXAMINE_FILTER, "Sorry, you can't target yourself.");
991                 return false;
992             }
993         }
994     }
995 
996     // Some odd cases
997     if (!moves.isValid && !moves.isCancel)
998         return yesno("Are you sure you want to fizzle?", false, 'n');
999 
1000     return true;
1001 }
1002 
1003 // Assuming the target is in view, is line-of-fire blocked?
_blocked_ray(const coord_def & where)1004 static bool _blocked_ray(const coord_def &where)
1005 {
1006     return !exists_ray(you.pos(), where, opc_solid_see);
1007 }
1008 
1009 // Try to find an enemy monster to target
find_default_monster_target(coord_def & result) const1010 bool direction_chooser::find_default_monster_target(coord_def& result) const
1011 {
1012     // First try to pick our previous monster target.
1013     const monster* mons_target = _get_current_target();
1014     if (mons_target != nullptr
1015         && _want_target_monster(mons_target, mode, hitfunc)
1016         && in_range(mons_target->pos()))
1017     {
1018         result = mons_target->pos();
1019         return true;
1020     }
1021     // If the previous targetted position is at all useful, use it.
1022     if (!Options.simple_targeting && hitfunc && !prefer_farthest
1023         && _find_monster_expl(you.prev_grd_targ, mode, needs_path,
1024                               range, hitfunc, AFF_YES, AFF_MULTIPLE))
1025     {
1026         result = you.prev_grd_targ;
1027         return true;
1028     }
1029     // The previous target is no good. Try to find one from scratch.
1030     bool success = false;
1031 
1032     if (Options.simple_targeting)
1033     {
1034         success = _find_square_wrapper(result, 1,
1035                                        bind(_find_monster, placeholders::_1,
1036                                             mode, needs_path, range, hitfunc),
1037                                        hitfunc);
1038     }
1039     else
1040     {
1041         success = hitfunc && _find_square_wrapper(result, 1,
1042                                                   bind(_find_monster_expl,
1043                                                        placeholders::_1, mode,
1044                                                        needs_path, range,
1045                                                        hitfunc,
1046                                                        // First try to bizap
1047                                                        AFF_MULTIPLE, AFF_YES),
1048                                                   hitfunc)
1049                   || _find_square_wrapper(result, 1,
1050                                           bind(restricts == DIR_SHADOW_STEP ?
1051                                                _find_shadow_step_mons :
1052                                                _find_monster,
1053                                                placeholders::_1, mode,
1054                                                needs_path, range, hitfunc),
1055                                           hitfunc);
1056     }
1057 
1058     // This is used for three things:
1059     // * For all LRD targetting
1060     // * To aim explosions so they try to miss you
1061     // * To hit monsters in LOS that are outside of normal range, but
1062     //   inside explosion/cloud range
1063     if (!Options.simple_targeting && hitfunc
1064         && hitfunc->can_affect_outside_range()
1065         && (!hitfunc->set_aim(result)
1066             || hitfunc->is_affected(result) < AFF_YES
1067             || hitfunc->is_affected(you.pos()) > AFF_NO))
1068     {
1069         coord_def old_result;
1070         if (success)
1071             old_result = result;
1072         for (aff_type mon_aff : { AFF_YES, AFF_MAYBE })
1073         {
1074             for (aff_type allowed_self_aff : { AFF_NO, AFF_MAYBE, AFF_YES })
1075             {
1076                 success = _find_square_wrapper(result, 1,
1077                                        bind(_find_monster_expl,
1078                                             placeholders::_1, mode,
1079                                             needs_path, range, hitfunc,
1080                                             mon_aff, allowed_self_aff),
1081                                        hitfunc);
1082                 if (success)
1083                 {
1084                     // If we're hitting ourselves anyway, just target the
1085                     // monster's position (this looks less strange).
1086                     if (allowed_self_aff == AFF_YES && !old_result.origin())
1087                         result = old_result;
1088                     break;
1089                 }
1090             }
1091             if (success)
1092                 break;
1093         }
1094     }
1095     if (success)
1096         return true;
1097 
1098     // If we couldn't, maybe it was because of line-of-fire issues.
1099     // Check if that's happening, and inform the user (because it's
1100     // pretty confusing.)
1101     if (needs_path
1102         && _find_square_wrapper(result, 1,
1103                                 bind(restricts == DIR_SHADOW_STEP ?
1104                                      _find_shadow_step_mons : _find_monster,
1105                                      placeholders::_1, mode, false,
1106                                      range, hitfunc),
1107                                hitfunc))
1108     {
1109         // Special colouring in tutorial or hints mode.
1110         const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE];
1111         // TODO: this seems to trigger when there are no monsters in range
1112         // of the hitfunc, regardless of what's in the way, and it shouldn't.
1113         mprf(need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT,
1114             "All monsters which could be auto-targeted are covered by "
1115             "a wall or statue which interrupts your line of fire, even "
1116             "though it doesn't interrupt your line of sight.");
1117 
1118         if (need_hint)
1119         {
1120             mprf(MSGCH_TUTORIAL, "To return to the main mode, press <w>Escape</w>.");
1121             Hints.hints_events[HINT_TARGET_NO_FOE] = false;
1122         }
1123     }
1124     return false;
1125 }
1126 
1127 // Find a good square to start targeting from.
find_default_target() const1128 coord_def direction_chooser::find_default_target() const
1129 {
1130     coord_def result = you.pos();
1131     bool success = false;
1132 
1133     if (targets_objects())
1134     {
1135         // First, try to find a particularly relevant item (autopickup).
1136         // Barring that, just try anything.
1137         success = _find_square_wrapper(result, 1,
1138                                        bind(_find_autopickup_object,
1139                                             placeholders::_1,
1140                                             needs_path, range, hitfunc),
1141                                        hitfunc,
1142                                        LS_FLIPVH)
1143                || _find_square_wrapper(result, 1,
1144                                        bind(_find_object, placeholders::_1,
1145                                             needs_path, range, hitfunc),
1146                                        hitfunc,
1147                                        LS_FLIPVH);
1148     }
1149     else if ((mode != TARG_ANY && mode != TARG_FRIEND)
1150              || self == confirm_prompt_type::cancel)
1151     {
1152         success = find_default_monster_target(result);
1153     }
1154 
1155     if (!success)
1156         result = you.pos();
1157 
1158     return result;
1159 }
1160 
target() const1161 const coord_def& direction_chooser::target() const
1162 {
1163     return moves.target;
1164 }
1165 
set_target(const coord_def & new_target)1166 void direction_chooser::set_target(const coord_def& new_target)
1167 {
1168     moves.target = new_target;
1169 }
1170 
1171 #ifdef USE_TILE
_tileidx_aff_type(aff_type aff)1172 static tileidx_t _tileidx_aff_type(aff_type aff)
1173 {
1174     if (aff < AFF_YES)
1175         return TILE_RAY_OUT_OF_RANGE;
1176     else if (aff == AFF_YES)
1177         return TILE_RAY;
1178     else if (aff == AFF_LANDING)
1179         return TILE_LANDING;
1180     else if (aff == AFF_MULTIPLE)
1181         return TILE_RAY_MULTI;
1182     else
1183         return 0;
1184 }
1185 #endif
1186 
1187 #ifndef USE_TILE_LOCAL
_colour_aff_type(aff_type aff,bool target)1188 static colour_t _colour_aff_type(aff_type aff, bool target)
1189 {
1190     if (aff < 0)
1191         return DARKGREY;
1192     else if (aff < AFF_YES)
1193         return target ? RED : MAGENTA;
1194     else if (aff == AFF_YES)
1195         return target ? LIGHTRED : LIGHTMAGENTA;
1196     else if (aff == AFF_LANDING)
1197         return target ? LIGHTGREEN : GREEN;
1198     else if (aff == AFF_MULTIPLE)
1199         return target ? LIGHTCYAN : CYAN;
1200     else
1201         die("unhandled aff %d", aff);
1202 }
1203 #endif
1204 
_draw_ray_cell(screen_cell_t & cell,coord_def p,bool on_target,aff_type aff)1205 static void _draw_ray_cell(screen_cell_t& cell, coord_def p, bool on_target,
1206                            aff_type aff)
1207 {
1208 #ifdef USE_TILE
1209     UNUSED(on_target, p);
1210     cell.tile.dngn_overlay[cell.tile.num_dngn_overlay++] =
1211         _tileidx_aff_type(aff);
1212 #endif
1213 #ifndef USE_TILE_LOCAL
1214     const auto bcol = _colour_aff_type(aff, on_target);
1215     const auto mbcol = on_target ? bcol : bcol | COLFLAG_REVERSE;
1216     const auto cglyph = _get_ray_glyph(p, bcol, '*', mbcol);
1217     cell.glyph = cglyph.ch;
1218     cell.colour = cglyph.col;
1219 #endif
1220 }
1221 
render(crawl_view_buffer & vbuf)1222 void direction_chooser_renderer::render(crawl_view_buffer& vbuf)
1223 {
1224     if (crawl_state.invisible_targeting)
1225         return;
1226     m_directn.draw_beam(vbuf);
1227     m_directn.highlight_summoner(vbuf);
1228 }
1229 
draw_beam(crawl_view_buffer & vbuf)1230 void direction_chooser::draw_beam(crawl_view_buffer &vbuf)
1231 {
1232     if (!show_beam)
1233         return;
1234 
1235     // Use the new API if implemented.
1236     if (hitfunc)
1237     {
1238         if (behaviour->targeted() && !hitfunc->set_aim(target()))
1239             return;
1240         const los_type los = hitfunc->can_affect_unseen()
1241                                             ? LOS_NONE : LOS_DEFAULT;
1242         for (radius_iterator ri(you.pos(), los); ri; ++ri)
1243         {
1244             aff_type aff = hitfunc->is_affected(*ri);
1245             if (aff
1246                 && (!feat_is_solid(env.grid(*ri)) || hitfunc->can_affect_walls()))
1247             {
1248                 auto& cell = vbuf(grid2view(*ri) - 1);
1249                 _draw_ray_cell(cell, *ri, *ri == target(), aff);
1250             }
1251         }
1252 
1253         return;
1254     }
1255 
1256     // If we don't have a new beam to show, we're done.
1257     if (!have_beam)
1258         return;
1259 
1260     // We shouldn't ever get a beam to an out-of-LOS target.
1261     ASSERT(you.see_cell(target()));
1262 
1263     // Work with a copy in order not to mangle anything.
1264     ray_def ray = beam;
1265 
1266     // Draw the new ray with magenta '*'s, not including your square
1267     // or the target square. Out-of-range cells get grey '*'s instead.
1268     for (; ray.pos() != target(); ray.advance())
1269     {
1270         const coord_def p = ray.pos();
1271         ASSERT(you.see_cell(p));
1272 
1273         if (p == you.pos())
1274             continue;
1275 
1276         const bool inrange = in_range(p);
1277         auto& cell = vbuf(grid2view(p) - 1);
1278 #ifdef USE_TILE
1279         cell.tile.dngn_overlay[cell.tile.num_dngn_overlay++] =
1280             inrange ? TILE_RAY : TILE_RAY_OUT_OF_RANGE;
1281 #endif
1282 #ifndef USE_TILE_LOCAL
1283         const auto bcol = inrange ? MAGENTA : DARKGREY;
1284         const auto cglyph = _get_ray_glyph(p, bcol, '*', bcol| COLFLAG_REVERSE);
1285         cell.glyph = cglyph.ch;
1286         cell.colour = cglyph.col;
1287 #endif
1288     }
1289     textcolour(LIGHTGREY);
1290 
1291     // Only draw the ray over the target on tiles.
1292 #ifdef USE_TILE
1293     auto& cell = vbuf(grid2view(target()) - 1);
1294     cell.tile.dngn_overlay[cell.tile.num_dngn_overlay++] =
1295         in_range(ray.pos()) ? TILE_RAY : TILE_RAY_OUT_OF_RANGE;
1296 #endif
1297 }
1298 
in_range(const coord_def & p) const1299 bool direction_chooser::in_range(const coord_def& p) const
1300 {
1301     if (!behaviour->targeted())
1302         return true;
1303     if (hitfunc)
1304         return hitfunc->valid_aim(p);
1305     return range < 0 || grid_distance(p, you.pos()) <= range;
1306 }
1307 
1308 // Cycle to either the next (dir == 1) or previous (dir == -1) object
1309 // and update output accordingly if successful.
object_cycle(int dir)1310 void direction_chooser::object_cycle(int dir)
1311 {
1312     if (_find_square_wrapper(objfind_pos, dir,
1313                              bind(_find_object, placeholders::_1, needs_path,
1314                                   range, hitfunc),
1315                              hitfunc,
1316                              dir > 0 ? LS_FLIPVH : LS_FLIPHV))
1317     {
1318         set_target(objfind_pos);
1319     }
1320     else
1321         flush_input_buffer(FLUSH_ON_FAILURE);
1322 }
1323 
monster_cycle(int dir)1324 void direction_chooser::monster_cycle(int dir)
1325 {
1326     if (prefer_farthest)
1327         dir = -dir; // cycle from furthest to closest
1328     if (_find_square_wrapper(monsfind_pos, dir,
1329                              bind(_find_monster, placeholders::_1, mode,
1330                                   needs_path, range, hitfunc),
1331                              hitfunc))
1332     {
1333         set_target(monsfind_pos);
1334     }
1335     else
1336         flush_input_buffer(FLUSH_ON_FAILURE);
1337 }
1338 
feature_cycle_forward(int feature)1339 void direction_chooser::feature_cycle_forward(int feature)
1340 {
1341     if (_find_square_wrapper(objfind_pos, 1,
1342                              [feature](const coord_def& where)
1343                              {
1344                                  return map_bounds(where)
1345                                         && (you.see_cell(where)
1346                                             || env.map_knowledge(where).seen())
1347                                         && is_feature(feature, where);
1348                              },
1349                              hitfunc,
1350                              LS_FLIPVH))
1351     {
1352         set_target(objfind_pos);
1353     }
1354     else
1355         flush_input_buffer(FLUSH_ON_FAILURE);
1356 }
1357 
update_previous_target() const1358 void direction_chooser::update_previous_target() const
1359 {
1360     you.prev_targ = MHITNOT;
1361     you.prev_grd_targ.reset();
1362 
1363     // Maybe we should except just_looking here?
1364     const monster* m = monster_at(target());
1365     if (m && you.can_see(*m))
1366         you.prev_targ = m->mindex();
1367     else if (looking_at_you())
1368         you.prev_targ = MHITYOU;
1369     else
1370         you.prev_grd_targ = target();
1371 }
1372 
select(bool allow_out_of_range,bool endpoint)1373 bool direction_chooser::select(bool allow_out_of_range, bool endpoint)
1374 {
1375     const monster* mons = monster_at(target());
1376 
1377     if (restricts == DIR_SHADOW_STEP)
1378     {
1379         targeter_shadow_step &tgt =
1380             *static_cast<targeter_shadow_step*>(hitfunc);
1381         if (!tgt.has_additional_sites(target()))
1382             return false;
1383     }
1384 
1385     // leap and shadow step never allow selecting from past the target point
1386     if ((restricts == DIR_LEAP
1387          || restricts == DIR_SHADOW_STEP
1388          || !allow_out_of_range)
1389         && !in_range(target()))
1390     {
1391         return false;
1392     }
1393     moves.isEndpoint = endpoint || (mons && _mon_exposed(mons));
1394     moves.isValid  = true;
1395     moves.isTarget = true;
1396     update_previous_target();
1397     return true;
1398 }
1399 
pickup_item()1400 bool direction_chooser::pickup_item()
1401 {
1402     item_def *ii = nullptr;
1403     if (in_bounds(target()))
1404         ii = env.map_knowledge(target()).item();
1405     if (!ii || !ii->is_valid(true))
1406     {
1407         mprf(MSGCH_EXAMINE_FILTER, "You can't see any item there.");
1408         return false;
1409     }
1410     ii->flags |= ISFLAG_THROWN; // make autoexplore greedy
1411 
1412     // From this point, if there's no item, we'll fake one. False info means
1413     // it's out of bounds and taken, or a mimic.
1414     item_def *item = 0;
1415     unsigned short it = env.igrid(target());
1416     if (it != NON_ITEM)
1417     {
1418         item = &env.item[it];
1419         // Check if it appears to be the same item.
1420         if (!item->is_valid()
1421             || ii->base_type != item->base_type
1422             || ii->sub_type != item->sub_type
1423                // TODO: check for different unidentified items of the same base type
1424                && (!item_type_has_unidentified(item->base_type)
1425                    || ii->sub_type == get_max_subtype(item->base_type))
1426             || ii->get_colour() != item->get_colour())
1427         {
1428             item = 0;
1429         }
1430     }
1431     if (item)
1432         item->flags |= ISFLAG_THROWN;
1433 
1434     if (!just_looking) // firing/casting prompt
1435     {
1436         mprf(MSGCH_EXAMINE_FILTER, "Marked for pickup.");
1437         return false;
1438     }
1439 
1440     moves.isValid  = true;
1441     moves.isTarget = true;
1442     update_previous_target();
1443     return true;
1444 }
1445 
handle_signals()1446 bool direction_chooser::handle_signals()
1447 {
1448     // If we've received a HUP signal then the user can't choose a
1449     // target.
1450     if (crawl_state.seen_hups)
1451     {
1452         moves.isValid  = false;
1453         moves.isCancel = true;
1454         moves.cmd_result = CMD_NO_CMD;
1455 
1456         mprf(MSGCH_ERROR, "Targeting interrupted by HUP signal.");
1457         return true;
1458     }
1459     return false;
1460 }
1461 
1462 // Print out the initial prompt when targeting starts.
1463 // Prompts might get scrolled off if you have too few lines available;
1464 // we'll live with that.
show_initial_prompt()1465 void direction_chooser::show_initial_prompt()
1466 {
1467     if (crawl_state.invisible_targeting)
1468         return;
1469     behaviour->update_top_prompt(&top_prompt);
1470     describe_cell();
1471     const string err = behaviour ? behaviour->get_error() : "";
1472     if (!err.empty())
1473         mprf(MSGCH_PROMPT, "%s", err.c_str()); // can this push the prompt one line too tall?
1474 }
1475 
print_target_description(bool & did_cloud) const1476 void direction_chooser::print_target_description(bool &did_cloud) const
1477 {
1478     if (targets_objects())
1479         print_target_object_description();
1480     else
1481         print_target_monster_description(did_cloud);
1482 
1483     if (!in_range(target()))
1484     {
1485         mprf(MSGCH_EXAMINE_FILTER, "%s",
1486              hitfunc ? hitfunc->why_not.c_str() : "Out of range.");
1487     }
1488 }
1489 
target_interesting_terrain_description() const1490 string direction_chooser::target_interesting_terrain_description() const
1491 {
1492     const dungeon_feature_type feature = env.grid(target());
1493 
1494     // Only features which can make you lose the item are interesting.
1495     // FIXME: extract the naming logic from here and use
1496     // feat_has_solid_floor().
1497     switch (feature)
1498     {
1499     case DNGN_DEEP_WATER: return "water";
1500     case DNGN_LAVA:       return "lava";
1501     default:              return "";
1502     }
1503 }
1504 
target_cloud_description() const1505 string direction_chooser::target_cloud_description() const
1506 {
1507     if (cloud_struct* cloud = cloud_at(target()))
1508         return cloud->cloud_name(true);
1509     else
1510         return "";
1511 }
1512 
1513 template<typename C1, typename C2>
_append_container(C1 & container_base,const C2 & container_append)1514 static void _append_container(C1& container_base, const C2& container_append)
1515 {
1516     container_base.insert(container_base.end(),
1517                           container_append.begin(), container_append.end());
1518 }
1519 
target_sanctuary_description() const1520 string direction_chooser::target_sanctuary_description() const
1521 {
1522     return is_sanctuary(target()) ? "sanctuary" : "";
1523 }
1524 
target_silence_description() const1525 string direction_chooser::target_silence_description() const
1526 {
1527     return silenced(target()) ? "silenced" : "";
1528 }
1529 
_push_back_if_nonempty(const string & str,vector<string> * vec)1530 static void _push_back_if_nonempty(const string& str, vector<string>* vec)
1531 {
1532     if (!str.empty())
1533         vec->push_back(str);
1534 }
1535 
print_target_monster_description(bool & did_cloud) const1536 void direction_chooser::print_target_monster_description(bool &did_cloud) const
1537 {
1538     // Do we see anything?
1539     const monster* mon = monster_at(target());
1540     if (!mon)
1541         return;
1542 
1543     const bool visible = you.can_see(*mon);
1544     const bool exposed = _mon_exposed(mon);
1545     if (!visible && !exposed)
1546         return;
1547 
1548     // OK, now we know that we have something to describe.
1549     vector<string> suffixes;
1550     string text;
1551     // Cell features go first.
1552     _append_container(suffixes, target_cell_description_suffixes());
1553     if (visible)
1554     {
1555         monster_info mi(mon);
1556         // Only describe the monster if you can actually see it.
1557         _append_container(suffixes, monster_description_suffixes(mi));
1558         text = get_monster_equipment_desc(mi);
1559     }
1560     else
1561         text = "Disturbance";
1562 
1563     // Build the final description string.
1564     if (!suffixes.empty())
1565     {
1566         text += " ("
1567             + comma_separated_line(suffixes.begin(), suffixes.end(), ", ")
1568             + ")";
1569     }
1570 
1571     mprf(MSGCH_PROMPT, "%s: <lightgrey>%s</lightgrey>",
1572          target_prefix ? target_prefix : !behaviour->targeted() ? "Look" : "Aim",
1573          text.c_str());
1574 
1575     // If there's a cloud here, it's been described.
1576     did_cloud = true;
1577 }
1578 
1579 // FIXME: this should really take a cell as argument.
target_cell_description_suffixes() const1580 vector<string> direction_chooser::target_cell_description_suffixes() const
1581 {
1582     vector<string> suffixes;
1583     // Things which describe the cell.
1584     _push_back_if_nonempty(target_cloud_description(), &suffixes);
1585     _push_back_if_nonempty(target_sanctuary_description(), &suffixes);
1586     _push_back_if_nonempty(target_silence_description(), &suffixes);
1587     _push_back_if_nonempty(target_interesting_terrain_description(), &suffixes);
1588 
1589     return suffixes;
1590 }
1591 
monster_description_suffixes(const monster_info & mi) const1592 vector<string> direction_chooser::monster_description_suffixes(
1593     const monster_info& mi) const
1594 {
1595     vector<string> suffixes;
1596 
1597     _push_back_if_nonempty(mi.wounds_description(true), &suffixes);
1598     _push_back_if_nonempty(mi.constriction_description(), &suffixes);
1599     _append_container(suffixes, mi.attributes());
1600     _append_container(suffixes, _get_monster_desc_vector(mi));
1601     _append_container(suffixes, behaviour->get_monster_desc(mi));
1602 
1603     return suffixes;
1604 }
1605 
print_target_object_description() const1606 void direction_chooser::print_target_object_description() const
1607 {
1608     if (!you.see_cell(target()))
1609         return;
1610 
1611     const item_def* item = top_item_at(target());
1612     if (!item)
1613         return;
1614 
1615     // FIXME: remove the duplication with print_items_description().
1616     mprf(MSGCH_PROMPT, "%s: %s",
1617          target_prefix ? target_prefix : "Aim",
1618          menu_colour_item_name(*item, DESC_A).c_str());
1619 }
1620 
print_items_description() const1621 void direction_chooser::print_items_description() const
1622 {
1623     if (!in_bounds(target()))
1624         return;
1625 
1626     auto items = item_list_on_square(you.visible_igrd(target()));
1627 
1628     if (items.empty())
1629         return;
1630 
1631     if (items.size() == 1)
1632     {
1633         mprf(MSGCH_FLOOR_ITEMS, "<cyan>Item here:</cyan> %s.",
1634              menu_colour_item_name(*items[0], DESC_A).c_str());
1635     }
1636     else
1637         mprf(MSGCH_FLOOR_ITEMS, "<cyan>Items here: </cyan> %s.", item_message(items).c_str());
1638 }
1639 
print_floor_description(bool boring_too) const1640 void direction_chooser::print_floor_description(bool boring_too) const
1641 {
1642     const dungeon_feature_type feat = env.grid(target());
1643     if (!boring_too && feat == DNGN_FLOOR)
1644         return;
1645 
1646 #ifdef DEBUG_DIAGNOSTICS
1647     // [ds] Be more verbose in debug mode.
1648     if (you.wizard)
1649         _debug_describe_feature_at(target());
1650     else
1651 #endif
1652     mprf(MSGCH_EXAMINE_FILTER, "%s.",
1653          feature_description_at(target(), true).c_str());
1654 }
1655 
reinitialize_move_flags()1656 void direction_chooser::reinitialize_move_flags()
1657 {
1658     moves.isValid    = false;
1659     moves.isTarget   = false;
1660     moves.isCancel   = false;
1661     moves.isEndpoint = false;
1662     moves.choseRay   = false;
1663 }
1664 
1665 // Returns true if we've completed targeting.
select_compass_direction(const coord_def & delta)1666 bool direction_chooser::select_compass_direction(const coord_def& delta)
1667 {
1668     if (restricts != DIR_TARGET && restricts != DIR_SHADOW_STEP)
1669     {
1670         // A direction is allowed, and we've selected it.
1671         moves.delta    = delta;
1672         // Needed for now...eventually shouldn't be necessary
1673         set_target(you.pos() + moves.delta);
1674         moves.isValid  = true;
1675         moves.isTarget = false;
1676         have_beam      = false;
1677         show_beam      = false;
1678         moves.choseRay = false;
1679         return true;
1680     }
1681     else
1682     {
1683         // Direction not allowed, so just move in that direction.
1684         // Maybe make this a bigger jump?
1685         set_target(target() + delta * 3);
1686         return false;
1687     }
1688 }
1689 
toggle_beam()1690 void direction_chooser::toggle_beam()
1691 {
1692     if (!needs_path)
1693     {
1694         mprf(MSGCH_EXAMINE_FILTER, "This spell doesn't need a beam path.");
1695         return;
1696     }
1697 
1698     show_beam = !show_beam;
1699     need_viewport_redraw = true;
1700 
1701     if (show_beam)
1702     {
1703         have_beam = find_ray(you.pos(), target(), beam,
1704                              opc_solid_see, you.current_vision);
1705     }
1706 }
1707 
select_previous_target()1708 bool direction_chooser::select_previous_target()
1709 {
1710     if (const monster* mon_target = _get_current_target())
1711     {
1712         // We have all the information we need.
1713         moves.isValid  = true;
1714         moves.isTarget = true;
1715         set_target(mon_target->pos());
1716         if (!just_looking)
1717             have_beam = false;
1718 
1719         return !just_looking;
1720     }
1721     else
1722     {
1723         mprf(MSGCH_EXAMINE_FILTER, "Your target is gone.");
1724         flush_prev_message();
1725         return false;
1726     }
1727 }
1728 
looking_at_you() const1729 bool direction_chooser::looking_at_you() const
1730 {
1731     return target() == you.pos();
1732 }
1733 
handle_movement_key(command_type key_command,bool * loop_done)1734 void direction_chooser::handle_movement_key(command_type key_command,
1735                                             bool* loop_done)
1736 {
1737     const int compass_idx = _targeting_cmd_to_compass(key_command);
1738     if (compass_idx != -1)
1739     {
1740         const coord_def& delta = Compass[compass_idx];
1741         const bool unshifted = (shift_direction(key_command) != key_command);
1742         if (unshifted)
1743             set_target(target() + delta);
1744         else if (!allow_shift_dir)
1745             mpr("You can't do that.");
1746         else
1747             *loop_done = select_compass_direction(delta);
1748     }
1749 }
1750 
handle_wizard_command(command_type key_command,bool * loop_done)1751 void direction_chooser::handle_wizard_command(command_type key_command,
1752                                               bool* loop_done)
1753 {
1754 #ifdef WIZARD
1755     if (!you.wizard)
1756         return;
1757 
1758     monster* const m = monster_at(target());
1759     string marker_result = "";
1760 
1761     // These commands do something even if there's no monster there.
1762     switch (key_command)
1763     {
1764     case CMD_TARGET_WIZARD_MOVE:
1765         wizard_move_player_or_monster(target());
1766         *loop_done = true;
1767         return;
1768 
1769     case CMD_TARGET_WIZARD_MISCAST:
1770         if (m)
1771             debug_miscast(m->mindex());
1772         else if (looking_at_you())
1773             debug_miscast(NON_MONSTER);
1774         return;
1775 
1776     // Note that this is a wizard-only command.
1777     case CMD_TARGET_CYCLE_BEAM:
1778         show_beam = true;
1779         have_beam = find_ray(you.pos(), target(), beam,
1780                              opc_solid_see, you.current_vision, show_beam);
1781         need_viewport_redraw = true;
1782         return;
1783 
1784     case CMD_TARGET_WIZARD_DEBUG_PORTAL:
1785         mprf(MSGCH_DIAGNOSTICS, "Trying to run portal debug at %d/%d...",
1786             target().x, target().y);
1787 
1788         marker_result =
1789             env.markers.property_at(target(), MAT_ANY, "portal_debug");
1790 
1791         mprf(MSGCH_DIAGNOSTICS, "Got result: %s!",
1792             marker_result.empty() ? "nothing" : marker_result.c_str());
1793 
1794         return;
1795 
1796     case CMD_TARGET_WIZARD_HURT_MONSTER:
1797         if (looking_at_you())
1798         {
1799             set_hp(1);
1800             print_stats();
1801             update_screen();
1802         }
1803         break;
1804 
1805     case CMD_TARGET_WIZARD_CREATE_MIMIC:
1806         if (target() != you.pos())
1807         {
1808             wizard_create_feature(target());
1809             need_viewport_redraw = true;
1810         }
1811         return;
1812 
1813     default:
1814         break;
1815     }
1816 
1817     // Everything below here doesn't work if there's no monster.
1818     if (!m)
1819         return;
1820 
1821     const int mid = m->mindex();
1822 
1823     switch (key_command)
1824     {
1825     case CMD_TARGET_WIZARD_PATHFIND:      debug_pathfind(mid);      break;
1826     case CMD_TARGET_WIZARD_DEBUG_MONSTER: debug_stethoscope(mid);   break;
1827     case CMD_TARGET_WIZARD_MAKE_SHOUT: debug_make_monster_shout(m); break;
1828     case CMD_TARGET_WIZARD_MAKE_FRIENDLY:
1829         _wizard_make_friendly(m);
1830         need_text_redraw = true;
1831         break;
1832 
1833     case CMD_TARGET_WIZARD_GIVE_ITEM:  wizard_give_monster_item(m); break;
1834     case CMD_TARGET_WIZARD_POLYMORPH:  wizard_polymorph_monster(m); break;
1835 
1836     case CMD_TARGET_WIZARD_GAIN_LEVEL:
1837         wizard_gain_monster_level(m);
1838         break;
1839 
1840     case CMD_TARGET_WIZARD_BLESS_MONSTER:
1841         wizard_apply_monster_blessing(m);
1842         break;
1843 
1844     case CMD_TARGET_WIZARD_MAKE_SUMMONED:
1845         wizard_make_monster_summoned(m);
1846         break;
1847 
1848     case CMD_TARGET_WIZARD_HEAL_MONSTER:
1849         if (m->hit_points < m->max_hit_points)
1850         {
1851             m->hit_points = m->max_hit_points;
1852             need_all_redraw = true;
1853         }
1854         break;
1855 
1856     case CMD_TARGET_WIZARD_HURT_MONSTER:
1857         m->hit_points = 1;
1858         mpr("Brought monster down to 1 HP.");
1859         flush_prev_message();
1860         break;
1861 
1862     case CMD_TARGET_WIZARD_BANISH_MONSTER:
1863         m->banish(&you, "", 0, true);
1864         break;
1865 
1866     case CMD_TARGET_WIZARD_KILL_MONSTER:
1867         monster_die(*m, KILL_YOU, NON_MONSTER);
1868         break;
1869 
1870     default:
1871         return;
1872     }
1873     redraw_screen();
1874     update_screen();
1875 #endif
1876 }
1877 
do_redraws()1878 void direction_chooser::do_redraws()
1879 {
1880     if (crawl_state.invisible_targeting)
1881         return;
1882 
1883     // Check if our targeting behaviour forces a redraw.
1884     if (behaviour->should_redraw())
1885     {
1886         need_all_redraw = true;
1887         behaviour->clear_redraw();
1888     }
1889 
1890     if (need_all_redraw)
1891     {
1892         need_viewport_redraw = true;
1893         need_text_redraw = true;
1894         need_cursor_redraw = true;
1895         need_all_redraw = false;
1896     }
1897 
1898     if (need_viewport_redraw)
1899     {
1900         viewwindow(false, false, nullptr, &renderer);
1901         need_viewport_redraw = false;
1902     }
1903 
1904     if (need_text_redraw)
1905     {
1906         msgwin_clear_temporary();
1907         describe_cell();
1908         need_text_redraw = false;
1909     }
1910 
1911     if (need_cursor_redraw || Options.use_fake_cursor)
1912     {
1913         cursorxy(crawl_view.grid2screen(target()));
1914 #ifdef USE_TILE_WEB
1915         // cursorxy doesn't place the cursor in Webtiles, we do it manually here
1916         // This is by design, since we don't want to use the mouse cursor for
1917         // the overview map.
1918         tiles.place_cursor(CURSOR_MOUSE, target());
1919 #endif
1920         need_cursor_redraw = false;
1921     }
1922 }
1923 
find_summoner()1924 coord_def direction_chooser::find_summoner()
1925 {
1926     const monster* mon = monster_at(target());
1927 
1928     if (mon && mon->is_summoned()
1929         // Don't leak information about rakshasa mirrored illusions.
1930         && !mon->has_ench(ENCH_PHANTOM_MIRROR)
1931         // Don't leak information about invisible or out-of-los summons.
1932         && you.can_see(*mon))
1933     {
1934         const monster *summ = monster_by_mid(mon->summoner);
1935         // Don't leak information about invisible summoners.
1936         if (summ && you.can_see(*summ))
1937             return summ->pos();
1938     }
1939     return INVALID_COORD;
1940 }
1941 
highlight_summoner(crawl_view_buffer & vbuf)1942 void direction_chooser::highlight_summoner(crawl_view_buffer &vbuf)
1943 {
1944     const coord_def summ_loc = find_summoner();
1945 
1946     if (summ_loc == INVALID_COORD || !you.see_cell(summ_loc))
1947         return;
1948 
1949     auto& cell = vbuf(grid2view(summ_loc) - 1);
1950 #ifdef USE_TILE
1951     cell.tile.is_highlighted_summoner = true;
1952 #endif
1953 #if defined(USE_TILE_WEB) || !defined(USE_TILE)
1954     cell.colour = CYAN | COLFLAG_REVERSE;
1955 #endif
1956 }
1957 
tiles_update_target()1958 bool direction_chooser::tiles_update_target()
1959 {
1960 #ifdef USE_TILE
1961     const coord_def& gc = tiles.get_cursor();
1962     if (gc != NO_CURSOR && map_bounds(gc))
1963     {
1964         set_target(gc);
1965         return true;
1966     }
1967 #endif
1968     return false;
1969 }
1970 
move_to_you()1971 void direction_chooser::move_to_you()
1972 {
1973     moves.isValid  = true;
1974     moves.isTarget = true;
1975     set_target(you.pos());
1976     moves.delta.reset();
1977 }
1978 
full_describe()1979 void direction_chooser::full_describe()
1980 {
1981     vector <monster_info> list_mons;
1982     vector<item_def> list_items;
1983     vector<coord_def> list_features;
1984 
1985     vector <monster *> nearby_mons = get_nearby_monsters(true, false, false,
1986                                                          false, true, true,
1987                                                          range);
1988     for (auto m : nearby_mons)
1989         if (_want_target_monster(m, mode, hitfunc))
1990             list_mons.push_back(monster_info(m));
1991 
1992     if (targets_objects() || just_looking)
1993         _get_nearby_items(list_items, needs_path, range, hitfunc);
1994 
1995     if (hitfunc && hitfunc->can_affect_walls() || just_looking)
1996         _get_nearby_features(list_features, needs_path, range, hitfunc);
1997 
1998     if (list_mons.empty() && list_items.empty() && list_features.empty())
1999     {
2000         mprf(MSGCH_EXAMINE_FILTER, "There are no valid targets to list.");
2001         flush_prev_message();
2002         return;
2003     }
2004 
2005     const coord_def choice =
2006         _full_describe_menu(list_mons, list_items, list_features,
2007                             just_looking ? "target/travel" : "target");
2008 
2009     if (choice != coord_def(-1, -1))
2010         set_target(choice);
2011     need_all_redraw = true;
2012 }
2013 
describe_target()2014 void direction_chooser::describe_target()
2015 {
2016     if (!map_bounds(target()) || !env.map_knowledge(target()).known())
2017         return;
2018     full_describe_square(target(), false);
2019     need_all_redraw = true;
2020 }
2021 
show_help()2022 void direction_chooser::show_help()
2023 {
2024     show_targeting_help();
2025     redraw_screen();
2026     update_screen();
2027     clear_messages(true);
2028     need_all_redraw = true;
2029 }
2030 
process_command(command_type command)2031 bool direction_chooser::process_command(command_type command)
2032 {
2033     bool loop_done = false;
2034 
2035     // Move flags are volitile, reset them to defaults before each command
2036     reinitialize_move_flags();
2037 
2038     switch (command)
2039     {
2040     case CMD_TARGET_SHOW_PROMPT: describe_cell(); break;
2041 
2042     case CMD_TARGET_TOGGLE_BEAM:
2043         if (!just_looking)
2044             toggle_beam();
2045         break;
2046 
2047     case CMD_TARGET_EXCLUDE:
2048         if (!just_looking)
2049             break;
2050 
2051         if (!is_map_persistent())
2052             mpr("You cannot set exclusions on this level.");
2053         else
2054         {
2055             const bool was_excluded = is_exclude_root(target());
2056             cycle_exclude_radius(target());
2057             need_viewport_redraw   = true;
2058             const bool is_excluded = is_exclude_root(target());
2059             if (!was_excluded && is_excluded)
2060                 mpr("Placed new exclusion.");
2061             else if (was_excluded && !is_excluded)
2062                 mpr("Removed exclusion.");
2063             else
2064                 mpr("Reduced exclusion size to a single square.");
2065         }
2066 
2067         need_cursor_redraw = true;
2068         break;
2069 
2070     case CMD_TARGET_FIND_YOU:       move_to_you(); break;
2071     case CMD_TARGET_FIND_TRAP:      feature_cycle_forward('^');  break;
2072     case CMD_TARGET_FIND_PORTAL:    feature_cycle_forward('\\'); break;
2073     case CMD_TARGET_FIND_ALTAR:     feature_cycle_forward('_');  break;
2074     case CMD_TARGET_FIND_UPSTAIR:   feature_cycle_forward('<');  break;
2075     case CMD_TARGET_FIND_DOWNSTAIR: feature_cycle_forward('>');  break;
2076 
2077     case CMD_TARGET_MAYBE_PREV_TARGET:
2078         loop_done = looking_at_you() ? select_previous_target()
2079                                      : select(false, false);
2080         break;
2081 
2082     case CMD_TARGET_PREV_TARGET: loop_done = select_previous_target(); break;
2083 
2084     // some modifiers to the basic selection command
2085     case CMD_TARGET_SELECT:          loop_done = select(false, false); break;
2086     case CMD_TARGET_SELECT_FORCE:    loop_done = select(true,  false); break;
2087     case CMD_TARGET_SELECT_ENDPOINT: loop_done = select(false, true);  break;
2088     case CMD_TARGET_SELECT_FORCE_ENDPOINT: loop_done = select(true,true); break;
2089 
2090 #ifdef USE_TILE
2091     case CMD_TARGET_MOUSE_SELECT:
2092         if (tiles_update_target())
2093             loop_done = select(false, false);
2094         break;
2095 
2096     case CMD_TARGET_MOUSE_MOVE: tiles_update_target(); break;
2097 #endif
2098 
2099     case CMD_TARGET_GET:             loop_done = pickup_item(); break;
2100 
2101     case CMD_TARGET_CYCLE_BACK:
2102         if (!targets_objects())
2103             monster_cycle(-1);
2104         else
2105             object_cycle(-1);
2106         break;
2107 
2108     case CMD_TARGET_OBJ_CYCLE_BACK:
2109         object_cycle(-1);
2110         break;
2111 
2112     case CMD_TARGET_CYCLE_FORWARD:
2113         if (!targets_objects())
2114             monster_cycle(1);
2115         else
2116             object_cycle(1);
2117         break;
2118 
2119     case CMD_TARGET_OBJ_CYCLE_FORWARD:
2120         object_cycle(1);
2121         break;
2122 
2123     case CMD_TARGET_CANCEL:
2124         loop_done = true;
2125         moves.isCancel = true;
2126         break;
2127 
2128     case CMD_TARGET_FULL_DESCRIBE: full_describe(); break;
2129     case CMD_TARGET_DESCRIBE: describe_target(); break;
2130     case CMD_TARGET_HELP:     show_help();       break;
2131 
2132     default:
2133         if (moves.fire_context
2134             && moves.fire_context->targeter_handles_key(command))
2135         {
2136             // because of the somewhat convoluted way in which action selection
2137             // is handled, some commands can only be handled if the direction
2138             // chooser has been called via a quiver::action. Otherwise, we
2139             // ignore these commands.
2140             moves.isValid = false;
2141             moves.isCancel = true;
2142             moves.cmd_result = static_cast<int>(command);
2143             loop_done = true;
2144             break;
2145         }
2146         // Some blocks of keys with similar handling.
2147         handle_movement_key(command, &loop_done);
2148         handle_wizard_command(command, &loop_done);
2149         break;
2150     }
2151 
2152     return loop_done;
2153 }
2154 
finalize_moves()2155 void direction_chooser::finalize_moves()
2156 {
2157     moves.choseRay = have_beam;
2158     moves.ray = beam;
2159 
2160     // We need this for directional explosions, otherwise they'll explode one
2161     // square away from the player.
2162     _extend_move_to_edge(moves);
2163 
2164 #ifdef USE_TILE
2165     tiles.place_cursor(CURSOR_MOUSE, NO_CURSOR);
2166 #endif
2167 }
2168 
2169 class UIDirectionChooserView
2170 #ifdef USE_TILE_LOCAL
2171     : public ui::Widget
2172 #else
2173     : public ui::OverlayWidget
2174 #endif
2175 {
2176 public:
UIDirectionChooserView(direction_chooser & dc)2177     UIDirectionChooserView(direction_chooser& dc) :
2178         m_dc(dc), old_target(dc.target())
2179     {
2180     }
~UIDirectionChooserView()2181     ~UIDirectionChooserView() {}
2182 
_render()2183     void _render() override
2184     {
2185         if (crawl_state.invisible_targeting)
2186             return;
2187 
2188 #ifndef USE_TILE_LOCAL
2189         // This call ensures that the hud will get redrawn in console any time
2190         // need_all_redraw is set. Minimally, this needs to happen on the
2191         // initial call, as well as after any popups on top of this widget.
2192         if (m_dc.need_all_redraw)
2193             redraw_screen(false);
2194 #endif
2195 
2196 #ifdef USE_TILE_LOCAL
2197         // We always have to redraw the viewport, because ui::redraw() will call
2198         // redraw_screen in case the window has been resized.
2199         m_dc.need_viewport_redraw = true;
2200 #endif
2201         m_dc.do_redraws();
2202 
2203 #ifdef USE_TILE_LOCAL
2204         tiles.render_current_regions();
2205         glmanager->reset_transform();
2206 #endif
2207     }
2208 
_allocate_region()2209     void _allocate_region() override
2210     {
2211         m_dc.need_all_redraw = true;
2212         _expose();
2213     }
2214 
on_event(const ui::Event & ev)2215     bool on_event(const ui::Event& ev) override
2216     {
2217         if (ev.type() == ui::Event::Type::KeyDown)
2218         {
2219             auto key = static_cast<const ui::KeyEvent&>(ev).key();
2220             key = unmangle_direction_keys(key, KMC_TARGETING, false);
2221 
2222             const auto command = m_dc.behaviour->get_command(key);
2223             // XX a bit ugly to do this here..
2224             if (m_dc.behaviour->needs_path != MB_MAYBE)
2225             {
2226                 m_dc.needs_path = tobool(m_dc.behaviour->needs_path, true);
2227                 m_dc.show_beam = !m_dc.just_looking && m_dc.needs_path;
2228                 // XX code duplication
2229                 m_dc.have_beam = m_dc.show_beam
2230                                  && find_ray(you.pos(), m_dc.target(), m_dc.beam,
2231                                              opc_solid_see, you.current_vision);
2232                 m_dc.need_text_redraw = true;
2233                 m_dc.need_viewport_redraw = true;
2234                 m_dc.need_cursor_redraw = true;
2235             }
2236 
2237             string top_prompt = m_dc.top_prompt;
2238             m_dc.behaviour->update_top_prompt(&top_prompt);
2239 
2240             if (m_dc.top_prompt != top_prompt)
2241             {
2242                 _expose();
2243                 m_dc.top_prompt = top_prompt;
2244             }
2245 
2246             process_command(command);
2247 
2248             // Flush the input buffer before the next command.
2249             if (!crawl_state.is_replaying_keys())
2250                 flush_input_buffer(FLUSH_BEFORE_COMMAND);
2251             return true;
2252         }
2253 
2254 #ifdef USE_TILE_LOCAL
2255         if (ev.type() == ui::Event::Type::MouseMove
2256             || ev.type() == ui::Event::Type::MouseDown)
2257         {
2258             auto wm_event = to_wm_event(static_cast<const ui::MouseEvent&>(ev));
2259             tiles.handle_mouse(wm_event);
2260             process_command(ev.type() == ui::Event::Type::MouseMove ?
2261                                 CMD_TARGET_MOUSE_MOVE :
2262                                 CMD_TARGET_MOUSE_SELECT);
2263             return true;
2264         }
2265 #endif
2266 
2267         return false;
2268     }
2269 
process_command(command_type cmd)2270     void process_command(command_type cmd)
2271     {
2272         // the chooser needs a cursor, but we need to hide it here
2273         cursor_control cc(false);
2274         bool loop_done = m_dc.process_command(cmd);
2275 
2276         // Don't allow going out of bounds.
2277         if (!crawl_view.in_viewport_g(m_dc.target()))
2278             m_dc.set_target(old_target);
2279 
2280         if (loop_done)
2281         {
2282             m_is_alive = false;
2283 
2284             if (!m_dc.just_looking && !m_dc.move_is_ok())
2285             {
2286                 m_dc.moves.isCancel = true;
2287                 m_dc.moves.isValid = false;
2288             }
2289             return;
2290         }
2291 
2292         // Redraw whatever is necessary.
2293         if (old_target != m_dc.target())
2294         {
2295             m_dc.have_beam = m_dc.show_beam
2296                              && find_ray(you.pos(), m_dc.target(), m_dc.beam,
2297                                          opc_solid_see, you.current_vision);
2298             m_dc.need_text_redraw = true;
2299             m_dc.need_viewport_redraw = true;
2300             m_dc.need_cursor_redraw = true;
2301         }
2302 
2303         if (m_dc.need_viewport_redraw || m_dc.need_cursor_redraw
2304             || m_dc.need_text_redraw || m_dc.need_all_redraw)
2305         {
2306             _expose();
2307         }
2308 
2309         old_target = m_dc.target();
2310     }
2311 
is_alive()2312     bool is_alive()
2313     {
2314         return m_is_alive;
2315     }
2316 
2317 private:
2318     direction_chooser& m_dc;
2319     coord_def old_target;
2320     bool m_is_alive = true;
2321 };
2322 
update_validity()2323 void direction_chooser::update_validity()
2324 {
2325     if (!select(false, moves.isEndpoint) || !move_is_ok())
2326     {
2327         moves.isCancel = true;
2328         moves.isValid = false;
2329         return;
2330     }
2331     // select() should handle setting bools appropriately on success
2332 }
2333 
noninteractive()2334 bool direction_chooser::noninteractive()
2335 {
2336     // if target is unset, this will find previous or closest target; if
2337     // target is set this will adjust targeting depending on custom
2338     // behavior
2339     if (moves.find_target)
2340         set_target(find_default_target());
2341 
2342     update_validity();
2343     finalize_moves();
2344     moves.cmd_result = moves.isValid && !moves.isCancel ? CMD_FIRE : CMD_NO_CMD;
2345     return moves.cmd_result == CMD_FIRE;
2346 }
2347 
choose_direction()2348 bool direction_chooser::choose_direction()
2349 {
2350 #ifdef USE_TILE
2351     ui::cutoff_point ui_cutoff_point;
2352 #endif
2353 
2354 #ifndef USE_TILE_LOCAL
2355     cursor_control ccon(!Options.use_fake_cursor);
2356 #endif
2357 #ifdef DGAMELAUNCH
2358     suppress_dgl_clrscr no_blinking;
2359 #endif
2360 
2361     mouse_control mc(needs_path && !just_looking ? MOUSE_MODE_TARGET_PATH
2362                                                  : MOUSE_MODE_TARGET);
2363     targeter_smite legacy_range(&you, range, 0, 0, true);
2364     range_view_annotator rva(hitfunc ? hitfunc :
2365                              (range >= 0) ? &legacy_range : nullptr);
2366 
2367     // init
2368     moves.delta.reset();
2369 
2370     // Find a default target.
2371     set_target(!default_place.origin() ? default_place
2372                                        : find_default_target());
2373 
2374     objfind_pos = monsfind_pos = target();
2375     if (prefer_farthest && moves.target != you.pos())
2376         monster_cycle(1);
2377 
2378     // If requested, show the beam on startup.
2379     if (show_beam)
2380     {
2381         have_beam = find_ray(you.pos(), target(), beam,
2382                              opc_solid_see, you.current_vision);
2383         need_viewport_redraw = have_beam;
2384     }
2385     if (hitfunc)
2386         need_viewport_redraw = true;
2387 
2388     clear_messages();
2389     msgwin_temporary_mode tmp;
2390 
2391     unwind_bool save_more(crawl_state.show_more_prompt, false);
2392     show_initial_prompt();
2393     need_text_redraw = false;
2394 
2395     auto directn_view = make_shared<UIDirectionChooserView>(*this);
2396 
2397 #ifdef USE_TILE_LOCAL
2398     unwind_bool inhibit_rendering(ui::should_render_current_regions, false);
2399 #endif
2400 
2401     // TODO: ideally crawl_state.invisible_targeting would suppress the redraws
2402     // associated with these ui calls, but I'm not sure of a clean way to make
2403     // that work
2404     ui::push_layout(directn_view, KMC_TARGETING);
2405     directn_view->_queue_allocation();
2406     while (directn_view->is_alive() && !handle_signals())
2407         ui::pump_events();
2408     ui::pop_layout();
2409 
2410     finalize_moves();
2411     if (moves.isValid && !moves.isCancel)
2412         moves.cmd_result = CMD_FIRE;
2413     return moves.isValid;
2414 }
2415 
get_terse_square_desc(const coord_def & gc)2416 string get_terse_square_desc(const coord_def &gc)
2417 {
2418     string desc = "";
2419     const char *unseen_desc = "[unseen terrain]";
2420 
2421     if (gc == you.pos())
2422         desc = you.your_name;
2423     else if (!map_bounds(gc))
2424         desc = unseen_desc;
2425     else if (!you.see_cell(gc))
2426     {
2427         if (env.map_knowledge(gc).seen())
2428         {
2429             desc = "[" + feature_description_at(gc, false, DESC_PLAIN)
2430                        + "]";
2431         }
2432         else
2433             desc = unseen_desc;
2434     }
2435     else if (monster_at(gc) && you.can_see(*monster_at(gc)))
2436             desc = monster_at(gc)->full_name(DESC_PLAIN);
2437     else if (you.visible_igrd(gc) != NON_ITEM)
2438     {
2439         if (env.item[you.visible_igrd(gc)].defined())
2440             desc = env.item[you.visible_igrd(gc)].name(DESC_PLAIN);
2441     }
2442     else
2443         desc = feature_description_at(gc, false, DESC_PLAIN);
2444 
2445     return desc;
2446 }
2447 
terse_describe_square(const coord_def & c,bool in_range)2448 void terse_describe_square(const coord_def &c, bool in_range)
2449 {
2450     if (!you.see_cell(c))
2451         _describe_oos_square(c);
2452     else if (in_bounds(c))
2453         _describe_cell(c, in_range);
2454 }
2455 
2456 // Get description of the "top" thing in a square; for mouseover text.
get_square_desc(const coord_def & c,describe_info & inf)2457 void get_square_desc(const coord_def &c, describe_info &inf)
2458 {
2459     const dungeon_feature_type feat = env.map_knowledge(c).feat();
2460     const cloud_type cloud = env.map_knowledge(c).cloud();
2461 
2462     if (const monster_info *mi = env.map_knowledge(c).monsterinfo())
2463     {
2464         // First priority: monsters.
2465         string desc = uppercase_first(get_monster_equipment_desc(*mi))
2466                     + ".\n";
2467         const string wounds = mi->wounds_description_sentence();
2468         if (!wounds.empty())
2469             desc += uppercase_first(wounds) + "\n";
2470         const string constrictions = mi->constriction_description();
2471         if (!constrictions.empty())
2472             desc += "It is " + constrictions + ".\n";
2473         desc += _get_monster_desc(*mi);
2474 
2475         inf.title = desc;
2476 
2477         bool temp = false;
2478         get_monster_db_desc(*mi, inf, temp);
2479     }
2480     else if (const item_def *obj = env.map_knowledge(c).item())
2481     {
2482         // Second priority: objects.
2483         get_item_desc(*obj, inf);
2484     }
2485     else if (feat != DNGN_UNSEEN && feat != DNGN_FLOOR
2486              && !feat_is_wall(feat) && !feat_is_tree(feat))
2487     {
2488         // Third priority: features.
2489         get_feature_desc(c, inf);
2490     }
2491     else // Fourth priority: clouds.
2492         inf.body << get_cloud_desc(cloud);
2493 }
2494 
2495 // Show a description of the only thing on a square, or a selection menu with
2496 // visible things on the square if there are many. For x-v and similar contexts.
2497 // Used for both in- and out-of-los cells.
full_describe_square(const coord_def & c,bool cleanup)2498 void full_describe_square(const coord_def &c, bool cleanup)
2499 {
2500     vector<monster_info> list_mons;
2501     vector<item_def> list_items;
2502     vector<coord_def> list_features;
2503     int quantity = 0;
2504 
2505     const monster_info *mi = env.map_knowledge(c).monsterinfo();
2506     item_def *obj = env.map_knowledge(c).item();
2507     const dungeon_feature_type feat = env.map_knowledge(c).feat();
2508 
2509     if (mi)
2510     {
2511         list_mons.emplace_back(*mi);
2512         ++quantity;
2513     }
2514     if (obj)
2515     {
2516         list_items = item_list_in_stash(c);
2517         quantity += list_items.size();
2518     }
2519     // I'm not sure if features should be included. But it seems reasonable to
2520     // at least include what full_describe_view shows
2521     if (feat_stair_direction(feat) != CMD_NO_CMD || feat_is_trap(feat))
2522     {
2523         list_features.push_back(c);
2524         ++quantity;
2525     }
2526 
2527     if (quantity > 1)
2528     {
2529         _full_describe_menu(list_mons, list_items, list_features, "", true,
2530                             you.see_cell(c) ? "What do you want to examine?"
2531                                             : "What do you want to remember?");
2532     }
2533     else if (quantity == 1)
2534     {
2535         if (mi)
2536             describe_monsters(*mi);
2537         else if (list_items.size())
2538             describe_item(*obj);
2539         else
2540             describe_feature_wide(c);
2541     }
2542     else
2543         describe_feature_wide(c);
2544 
2545     if (cleanup)
2546     {
2547         redraw_screen();
2548         update_screen();
2549         clear_messages();
2550     }
2551 }
2552 
_extend_move_to_edge(dist & moves)2553 static void _extend_move_to_edge(dist &moves)
2554 {
2555     if (moves.delta.origin())
2556         return;
2557 
2558     // Now the tricky bit - extend the target x,y out to map edge.
2559     int mx = 0, my = 0;
2560 
2561     if (moves.delta.x > 0)
2562         mx = (GXM - 1) - you.pos().x;
2563     if (moves.delta.x < 0)
2564         mx = you.pos().x;
2565 
2566     if (moves.delta.y > 0)
2567         my = (GYM - 1) - you.pos().y;
2568     if (moves.delta.y < 0)
2569         my = you.pos().y;
2570 
2571     if (mx != 0 && my != 0)
2572         mx = my = min(mx, my);
2573 
2574     moves.target.x = you.pos().x + moves.delta.x * mx;
2575     moves.target.y = you.pos().y + moves.delta.y * my;
2576 }
2577 
2578 // Attempts to describe a square that's not in line-of-sight. If
2579 // there's a stash on the square, announces the top item and number
2580 // of items, otherwise, if there's a stair that's in the travel
2581 // cache and noted in the Dungeon (O)verview, names the stair.
_describe_oos_square(const coord_def & where)2582 static void _describe_oos_square(const coord_def& where)
2583 {
2584     mprf(MSGCH_EXAMINE_FILTER, "You can't see that place.");
2585 
2586     if (!in_bounds(where) || !env.map_knowledge(where).seen())
2587     {
2588 #ifdef DEBUG_DIAGNOSTICS
2589         if (!in_bounds(where))
2590             dprf("(out of bounds)");
2591         else
2592             dprf("(map: %x)", env.map_knowledge(where).flags);
2593 #endif
2594         return;
2595     }
2596 
2597     describe_stash(where);
2598     _describe_oos_feature(where);
2599 #ifdef DEBUG_DIAGNOSTICS
2600     _debug_describe_feature_at(where);
2601 #endif
2602 }
2603 
_mons_is_valid_target(const monster * mon,targ_mode_type mode,int range)2604 static bool _mons_is_valid_target(const monster* mon, targ_mode_type mode,
2605                                   int range)
2606 {
2607     // Monsters that are no threat to you don't count as monsters.
2608     if (!mons_is_threatening(*mon) && !mons_class_is_test(mon->type))
2609         return false;
2610 
2611     // Don't target submerged monsters.
2612     if (mode != TARG_HOSTILE_SUBMERGED && mon->submerged())
2613         return false;
2614 
2615     // Don't usually target unseen monsters...
2616     if (!mon->visible_to(&you))
2617     {
2618         // ...unless it creates a "disturbance in the water".
2619         // Since you can't see the monster, assume it's not a friend.
2620         return mode != TARG_FRIEND
2621                && _mon_exposed(mon)
2622                && i_feel_safe(false, false, true, true, range);
2623     }
2624 
2625     return true;
2626 }
2627 
_want_target_monster(const monster * mon,targ_mode_type mode,targeter * hitfunc)2628 static bool _want_target_monster(const monster *mon, targ_mode_type mode,
2629                                  targeter* hitfunc)
2630 {
2631     if (hitfunc && !hitfunc->affects_monster(monster_info(mon)))
2632         return false;
2633     switch (mode)
2634     {
2635     case TARG_ANY:
2636         return true;
2637     case TARG_HOSTILE:
2638     case TARG_HOSTILE_SUBMERGED:
2639         return mons_attitude(*mon) == ATT_HOSTILE;
2640     case TARG_FRIEND:
2641         return mon->friendly();
2642     case TARG_INJURED_FRIEND:
2643         if (mon->friendly() && mons_get_damage_level(*mon) > MDAM_OKAY)
2644             return true;
2645         return !mon->wont_attack() && !mon->neutral()
2646             && unpacifiable_reason(*mon).empty();
2647     case TARG_BEOGH_GIFTABLE:
2648         return beogh_can_gift_items_to(mon);
2649     case TARG_MOVABLE_OBJECT:
2650         return false;
2651     case TARG_MOBILE_MONSTER:
2652         return !(mons_is_tentacle_or_tentacle_segment(mon->type)
2653                  || mon->is_stationary());
2654     case TARG_NUM_MODES:
2655         break;
2656     // intentionally no default
2657     }
2658     die("Unknown targeting mode!");
2659 }
2660 
_tobool(maybe_bool mb)2661 static bool _tobool(maybe_bool mb)
2662 {
2663     ASSERT(mb != MB_MAYBE);
2664     return mb == MB_TRUE;
2665 }
2666 
_find_monster(const coord_def & where,targ_mode_type mode,bool need_path,int range,targeter * hitfunc)2667 static bool _find_monster(const coord_def& where, targ_mode_type mode,
2668                           bool need_path, int range, targeter *hitfunc)
2669 {
2670     {
2671         coord_def dp = grid2player(where);
2672         // We could pass more info here.
2673         maybe_bool x = clua.callmbooleanfn("ch_target_monster", "dd",
2674                                            dp.x, dp.y);
2675         if (x != MB_MAYBE)
2676             return _tobool(x);
2677     }
2678 
2679     // Target the player for friendly and general spells.
2680     if ((mode == TARG_FRIEND || mode == TARG_ANY) && where == you.pos())
2681         return true;
2682 
2683     // Don't target out of range
2684     if (!_is_target_in_range(where, range, hitfunc))
2685         return false;
2686 
2687     const monster* mon = monster_at(where);
2688 
2689     // No monster or outside LOS.
2690     if (!mon || !cell_see_cell(you.pos(), where, LOS_DEFAULT))
2691         return false;
2692 
2693     // Monster in LOS but only via glass walls, so no direct path.
2694     if (!you.see_cell_no_trans(where))
2695         return false;
2696 
2697     if (!_mons_is_valid_target(mon, mode, range))
2698         return false;
2699 
2700     if (need_path && _blocked_ray(mon->pos()))
2701         return false;
2702 
2703     return _want_target_monster(mon, mode, hitfunc);
2704 }
2705 
_find_shadow_step_mons(const coord_def & where,targ_mode_type mode,bool need_path,int range,targeter * hitfunc)2706 static bool _find_shadow_step_mons(const coord_def& where, targ_mode_type mode,
2707                                    bool need_path, int range,
2708                                    targeter *hitfunc)
2709 {
2710     {
2711         coord_def dp = grid2player(where);
2712         // We could pass more info here.
2713         maybe_bool x = clua.callmbooleanfn("ch_target_shadow_step", "dd",
2714                                            dp.x, dp.y);
2715         if (x != MB_MAYBE)
2716             return _tobool(x);
2717     }
2718 
2719     // Need a monster to attack; this checks that the monster is a valid target.
2720     if (!_find_monster(where, mode, need_path, range, hitfunc))
2721         return false;
2722     // Can't step on yourself
2723     if (where == you.pos())
2724         return false;
2725 
2726     targeter_shadow_step &tgt = *static_cast<targeter_shadow_step*>(hitfunc);
2727     return tgt.has_additional_sites(where);
2728 }
2729 
_find_monster_expl(const coord_def & where,targ_mode_type mode,bool need_path,int range,targeter * hitfunc,aff_type mon_aff,aff_type allowed_self_aff)2730 static bool _find_monster_expl(const coord_def& where, targ_mode_type mode,
2731                                bool need_path, int range, targeter *hitfunc,
2732                                aff_type mon_aff, aff_type allowed_self_aff)
2733 {
2734     ASSERT(hitfunc);
2735 
2736     {
2737         coord_def dp = grid2player(where);
2738         // We could pass more info here.
2739         maybe_bool x = clua.callmbooleanfn("ch_target_monster_expl", "dd",
2740                                            dp.x, dp.y);
2741         if (x != MB_MAYBE)
2742             return _tobool(x);
2743     }
2744 
2745     if (!hitfunc->valid_aim(where))
2746         return false;
2747 
2748     // Target is blocked by something
2749     if (need_path && _blocked_ray(where))
2750         return false;
2751 
2752     if (hitfunc->set_aim(where))
2753     {
2754         if (hitfunc->is_affected(you.pos()) > allowed_self_aff)
2755             return false;
2756         for (monster_near_iterator mi(&you); mi; ++mi)
2757         {
2758             if (hitfunc->is_affected(mi->pos()) == mon_aff
2759                 && _mons_is_valid_target(*mi, mode, range)
2760                 && _want_target_monster(*mi, mode, hitfunc))
2761             {
2762                     return true;
2763             }
2764         }
2765     }
2766     return false;
2767 }
2768 
_item_at(const coord_def & where)2769 static const item_def* _item_at(const coord_def &where)
2770 {
2771     // XXX: are we ever interacting with unseen items, anyway?
2772     return you.see_cell(where)
2773             ? top_item_at(where)
2774             : env.map_knowledge(where).item();
2775 }
2776 
_find_autopickup_object(const coord_def & where,bool need_path,int range,targeter * hitfunc)2777 static bool _find_autopickup_object(const coord_def& where, bool need_path,
2778                                     int range, targeter *hitfunc)
2779 {
2780     if (!_find_object(where, need_path, range, hitfunc))
2781         return false;
2782 
2783     const item_def * const item = _item_at(where);
2784     ASSERT(item);
2785     return item_needs_autopickup(*item);
2786 }
2787 
_find_object(const coord_def & where,bool need_path,int range,targeter * hitfunc)2788 static bool _find_object(const coord_def& where, bool need_path, int range,
2789                          targeter *hitfunc)
2790 {
2791     // Don't target out of range.
2792     if (!_is_target_in_range(where, range, hitfunc))
2793         return false;
2794 
2795     if (need_path && (!you.see_cell(where) || _blocked_ray(where)))
2796         return false;
2797 
2798     const item_def * const item = _item_at(where);
2799     return item && !item_is_stationary(*item);
2800 }
2801 
_next_los(int dir,int los,bool wrap)2802 static int _next_los(int dir, int los, bool wrap)
2803 {
2804     if (los == LS_ANY)
2805         return wrap? los : LS_NONE;
2806 
2807     bool vis    = los & LS_VISIBLE;
2808     bool hidden = los & LS_HIDDEN;
2809     bool flipvh = los & LS_FLIPVH;
2810     bool fliphv = los & LS_FLIPHV;
2811 
2812     if (!vis && !hidden)
2813         vis = true;
2814 
2815     if (wrap)
2816     {
2817         if (!flipvh && !fliphv)
2818             return los;
2819 
2820         // We have to invert flipvh and fliphv if we're wrapping. Here's
2821         // why:
2822         //
2823         //  * Say the cursor is on the last item in LOS, there are no
2824         //    items outside LOS, and wrap == true. flipvh is true.
2825         //  * We set wrap false and flip from visible to hidden, but there
2826         //    are no hidden items. So now we need to flip back to visible
2827         //    so we can go back to the first item in LOS. Unless we set
2828         //    fliphv, we can't flip from hidden to visible.
2829         //
2830         los = flipvh ? LS_FLIPHV : LS_FLIPVH;
2831     }
2832     else
2833     {
2834         if (!flipvh && !fliphv)
2835             return LS_NONE;
2836 
2837         if (flipvh && vis != (dir == 1))
2838             return LS_NONE;
2839 
2840         if (fliphv && vis == (dir == 1))
2841             return LS_NONE;
2842     }
2843 
2844     return (los & ~LS_VISMASK) | (vis ? LS_HIDDEN : LS_VISIBLE);
2845 }
2846 
2847 //---------------------------------------------------------------
2848 //
2849 // find_square
2850 //
2851 // Finds the next monster/object/whatever (moving in a spiral
2852 // outwards from the player, so closer targets are chosen first;
2853 // starts to player's left) and puts its coordinates in mfp.
2854 // Returns 1 if it found something, zero otherwise. If direction
2855 // is -1, goes backwards.
2856 //
2857 //---------------------------------------------------------------
_find_square(coord_def & mfp,int direction,target_checker find_targ,targeter * hitfunc,bool wrap,int los)2858 static bool _find_square(coord_def &mfp, int direction,
2859                          target_checker find_targ, targeter *hitfunc,
2860                          bool wrap, int los)
2861 {
2862     int temp_xps = mfp.x;
2863     int temp_yps = mfp.y;
2864     int x_change = 0;
2865     int y_change = 0;
2866 
2867     bool onlyVis = false, onlyHidden = false;
2868 
2869     int i, j;
2870 
2871     if (los == LS_NONE)
2872         return false;
2873 
2874     if (los == LS_FLIPVH || los == LS_FLIPHV)
2875     {
2876         if (in_los_bounds_v(mfp))
2877         {
2878             // We've been told to flip between visible/hidden, so we
2879             // need to find what we're currently on.
2880             const bool vis = you.see_cell(view2grid(mfp));
2881 
2882             if (wrap && (vis != (los == LS_FLIPVH)) == (direction == 1))
2883             {
2884                 // We've already flipped over into the other direction,
2885                 // so correct the flip direction if we're wrapping.
2886                 los = (los == LS_FLIPHV ? LS_FLIPVH : LS_FLIPHV);
2887             }
2888 
2889             los = (los & ~LS_VISMASK) | (vis ? LS_VISIBLE : LS_HIDDEN);
2890         }
2891         else
2892         {
2893             if (wrap)
2894                 los = LS_HIDDEN | (direction > 0 ? LS_FLIPHV : LS_FLIPVH);
2895             else
2896                 los |= LS_HIDDEN;
2897         }
2898     }
2899 
2900     onlyVis     = (los & LS_VISIBLE);
2901     onlyHidden  = (los & LS_HIDDEN);
2902 
2903     int radius = 0;
2904     if (crawl_view.viewsz.x > crawl_view.viewsz.y)
2905         radius = crawl_view.viewsz.x - LOS_RADIUS - 1;
2906     else
2907         radius = crawl_view.viewsz.y - LOS_RADIUS - 1;
2908 
2909     const coord_def vyou = grid2view(you.pos());
2910 
2911     const int minx = vyou.x - radius, maxx = vyou.x + radius,
2912               miny = vyou.y - radius, maxy = vyou.y + radius,
2913               ctrx = vyou.x, ctry = vyou.y;
2914 
2915     while (temp_xps >= minx - 1 && temp_xps <= maxx
2916            && temp_yps <= maxy && temp_yps >= miny - 1)
2917     {
2918         if (direction == 1 && temp_xps == minx && temp_yps == maxy)
2919         {
2920             mfp = vyou;
2921             if (find_targ(you.pos()))
2922                 return true;
2923             return _find_square(mfp, direction,
2924                                 find_targ, hitfunc,
2925                                 false, _next_los(direction, los, wrap));
2926         }
2927         if (direction == -1 && temp_xps == ctrx && temp_yps == ctry)
2928         {
2929             mfp = coord_def(minx, maxy);
2930             return _find_square(mfp, direction,
2931                                 find_targ, hitfunc,
2932                                 false, _next_los(direction, los, wrap));
2933         }
2934 
2935         if (direction == 1)
2936         {
2937             if (temp_xps == minx - 1)
2938             {
2939                 x_change = 0;
2940                 y_change = -1;
2941             }
2942             else if (temp_xps == ctrx && temp_yps == ctry)
2943             {
2944                 x_change = -1;
2945                 y_change = 0;
2946             }
2947             else if (abs(temp_xps - ctrx) <= abs(temp_yps - ctry))
2948             {
2949                 if (temp_xps - ctrx >= 0 && temp_yps - ctry <= 0)
2950                 {
2951                     if (abs(temp_xps - ctrx) > abs(temp_yps - ctry + 1))
2952                     {
2953                         x_change = 0;
2954                         y_change = -1;
2955                         if (temp_xps - ctrx > 0)
2956                             y_change = 1;
2957                         goto finished_spiralling;
2958                     }
2959                 }
2960                 x_change = -1;
2961                 if (temp_yps - ctry < 0)
2962                     x_change = 1;
2963                 y_change = 0;
2964             }
2965             else
2966             {
2967                 x_change = 0;
2968                 y_change = -1;
2969                 if (temp_xps - ctrx > 0)
2970                     y_change = 1;
2971             }
2972         }                       // end if (direction == 1)
2973         else
2974         {
2975             // This part checks all eight surrounding squares to find the
2976             // one that leads on to the present square.
2977             for (i = -1; i < 2; ++i)
2978                 for (j = -1; j < 2; ++j)
2979                 {
2980                     if (i == 0 && j == 0)
2981                         continue;
2982 
2983                     if (temp_xps + i == minx - 1)
2984                     {
2985                         x_change = 0;
2986                         y_change = -1;
2987                     }
2988                     else if (temp_xps + i - ctrx == 0
2989                              && temp_yps + j - ctry == 0)
2990                     {
2991                         x_change = -1;
2992                         y_change = 0;
2993                     }
2994                     else if (abs(temp_xps + i - ctrx) <= abs(temp_yps + j - ctry))
2995                     {
2996                         const int xi = temp_xps + i - ctrx;
2997                         const int yj = temp_yps + j - ctry;
2998 
2999                         if (xi >= 0 && yj <= 0
3000                             && abs(xi) > abs(yj + 1))
3001                         {
3002                             x_change = 0;
3003                             y_change = -1;
3004                             if (xi > 0)
3005                                 y_change = 1;
3006                             goto finished_spiralling;
3007                         }
3008 
3009                         x_change = -1;
3010                         if (yj < 0)
3011                             x_change = 1;
3012                         y_change = 0;
3013                     }
3014                     else
3015                     {
3016                         x_change = 0;
3017                         y_change = -1;
3018                         if (temp_xps + i - ctrx > 0)
3019                             y_change = 1;
3020                     }
3021 
3022                     if (temp_xps + i + x_change == temp_xps
3023                         && temp_yps + j + y_change == temp_yps)
3024                     {
3025                         goto finished_spiralling;
3026                     }
3027                 }
3028         }                       // end else
3029 
3030       finished_spiralling:
3031         x_change *= direction;
3032         y_change *= direction;
3033 
3034         temp_xps += x_change;
3035         if (temp_yps + y_change <= maxy)  // it can wrap, unfortunately
3036             temp_yps += y_change;
3037 
3038         const int targ_x = you.pos().x + temp_xps - ctrx;
3039         const int targ_y = you.pos().y + temp_yps - ctry;
3040         const coord_def targ(targ_x, targ_y);
3041 
3042         if (!crawl_view.in_viewport_g(targ))
3043             continue;
3044 
3045         if (!map_bounds(targ))
3046             continue;
3047 
3048         if ((onlyVis || onlyHidden) && onlyVis != you.see_cell(targ))
3049             continue;
3050 
3051         if (find_targ(targ))
3052         {
3053             mfp.set(temp_xps, temp_yps);
3054             return true;
3055         }
3056     }
3057 
3058     mfp = (direction > 0 ? coord_def(ctrx, ctry) : coord_def(minx, maxy));
3059     return _find_square(mfp, direction,
3060                         find_targ, hitfunc,
3061                         false, _next_los(direction, los, wrap));
3062 }
3063 
3064 // XXX Unbelievably hacky. And to think that my goal was to clean up the code.
3065 // Identical to _find_square, except that mfp is in grid coordinates
3066 // rather than view coordinates.
_find_square_wrapper(coord_def & mfp,int direction,target_checker find_targ,targeter * hitfunc,LOSSelect los)3067 static bool _find_square_wrapper(coord_def &mfp, int direction,
3068                                  target_checker find_targ, targeter *hitfunc,
3069                                  LOSSelect los)
3070 {
3071     mfp = grid2view(mfp);
3072     const bool r =  _find_square(mfp, direction, find_targ, hitfunc, true, los);
3073     mfp = view2grid(mfp);
3074     return r;
3075 }
3076 
_describe_oos_feature(const coord_def & where)3077 static void _describe_oos_feature(const coord_def& where)
3078 {
3079     if (!env.map_knowledge(where).seen())
3080         return;
3081 
3082     string desc = feature_description(env.map_knowledge(where).feat()) + ".";
3083 
3084     if (!desc.empty())
3085         mprf(MSGCH_EXAMINE_FILTER, "[%s]", desc.c_str());
3086 }
3087 
3088 // Returns a vector of features matching the given pattern.
features_by_desc(const base_pattern & pattern)3089 vector<dungeon_feature_type> features_by_desc(const base_pattern &pattern)
3090 {
3091     vector<dungeon_feature_type> features;
3092 
3093     if (pattern.valid())
3094     {
3095         for (int i = 0; i < NUM_FEATURES; ++i)
3096         {
3097             string fdesc =
3098                 feature_description(static_cast<dungeon_feature_type>(i)) + ".";
3099 
3100             if (pattern.matches(fdesc))
3101                 features.push_back(dungeon_feature_type(i));
3102         }
3103     }
3104     return features;
3105 }
3106 
describe_floor()3107 void describe_floor()
3108 {
3109     dungeon_feature_type grid = env.map_knowledge(you.pos()).feat();
3110 
3111     const char* prefix = "There is ";
3112     string feat;
3113 
3114     switch (grid)
3115     {
3116     case DNGN_FLOOR:
3117         return;
3118 
3119     case DNGN_ENTER_SHOP:
3120         prefix = "There is an entrance to ";
3121         break;
3122 
3123     default:
3124         break;
3125     }
3126 
3127     feat = feature_description_at(you.pos(), true, DESC_A);
3128     if (feat.empty())
3129         return;
3130 
3131     msg_channel_type channel = MSGCH_EXAMINE;
3132 
3133     // Messages for water/lava are too spammy use a status light instead.
3134     if (feat_is_water(grid) || feat_is_lava(grid))
3135         return;
3136 
3137     mprf(channel, "%s%s here.", prefix, feat.c_str());
3138     if (grid == DNGN_ENTER_GAUNTLET)
3139         mprf(MSGCH_EXAMINE, "Beware, the minotaur awaits!");
3140 }
3141 
_base_feature_desc(dungeon_feature_type grid,trap_type trap)3142 static string _base_feature_desc(dungeon_feature_type grid, trap_type trap)
3143 {
3144     if (feat_is_trap(grid) && trap != NUM_TRAPS)
3145         return full_trap_name(trap);
3146 
3147     if (grid == DNGN_ROCK_WALL && player_in_branch(BRANCH_PANDEMONIUM))
3148         return "wall of the weird stuff which makes up Pandemonium";
3149     else if (!is_valid_feature_type(grid))
3150         return "";
3151     else
3152         return get_feature_def(grid).name;
3153 
3154 }
3155 
feature_description(dungeon_feature_type grid,trap_type trap,const string & cover_desc,description_level_type dtype)3156 string feature_description(dungeon_feature_type grid, trap_type trap,
3157                            const string & cover_desc,
3158                            description_level_type dtype)
3159 {
3160     string desc = _base_feature_desc(grid, trap);
3161     desc += cover_desc;
3162 
3163     if (grid == DNGN_FLOOR && dtype == DESC_A)
3164         dtype = DESC_THE;
3165 
3166     bool ignore_case = false;
3167     if (grid == DNGN_TRAP_ZOT)
3168         ignore_case = true;
3169 
3170     return thing_do_grammar(dtype, desc, ignore_case);
3171 }
3172 
raw_feature_description(const coord_def & where)3173 string raw_feature_description(const coord_def &where)
3174 {
3175     dungeon_feature_type feat = env.grid(where);
3176 
3177     int mapi = env.level_map_ids(where);
3178     if (mapi != INVALID_MAP_INDEX)
3179     {
3180         const auto &renames = env.level_vaults[mapi]->map.feat_renames;
3181         if (const string *rename = map_find(renames, feat))
3182             return *rename;
3183     }
3184 
3185     return _base_feature_desc(feat, get_trap_type(where));
3186 }
3187 
3188 #ifndef DEBUG_DIAGNOSTICS
3189 // Is a feature interesting enough to 'v'iew it, even if a player normally
3190 // doesn't care about descriptions, i.e. does the description hold important
3191 // information? (Yes, this is entirely subjective. --jpeg)
_interesting_feature(dungeon_feature_type feat)3192 static bool _interesting_feature(dungeon_feature_type feat)
3193 {
3194     return get_feature_def(feat).flags & FFT_EXAMINE_HINT;
3195 }
3196 #endif
3197 
feature_description_at(const coord_def & where,bool covering,description_level_type dtype)3198 string feature_description_at(const coord_def& where, bool covering,
3199                               description_level_type dtype)
3200 {
3201     dungeon_feature_type grid = env.map_knowledge(where).feat();
3202     trap_type trap = env.map_knowledge(where).trap();
3203 
3204     string marker_desc = env.markers.property_at(where, MAT_ANY,
3205                                                  "feature_description");
3206 
3207     string covering_description = "";
3208 
3209     if (covering && you.see_cell(where))
3210     {
3211         if (feat_is_tree(grid) && env.forest_awoken_until)
3212             covering_description += ", awoken";
3213 
3214         if (is_icecovered(where))
3215             covering_description = ", covered with ice";
3216 
3217         if (is_temp_terrain(where))
3218             covering_description = ", summoned";
3219 
3220         if (is_bloodcovered(where))
3221             covering_description += ", spattered with blood";
3222     }
3223 
3224     // FIXME: remove desc markers completely; only Zin walls are left.
3225     // They suffer, among other problems, from an information leak.
3226     if (!marker_desc.empty())
3227     {
3228         marker_desc += covering_description;
3229 
3230         return thing_do_grammar(dtype, marker_desc);
3231     }
3232 
3233     if (feat_is_door(grid))
3234     {
3235         const string door_desc_prefix =
3236             env.markers.property_at(where, MAT_ANY,
3237                                     "door_description_prefix");
3238         const string door_desc_suffix =
3239             env.markers.property_at(where, MAT_ANY,
3240                                     "door_description_suffix");
3241         const string door_desc_noun =
3242             env.markers.property_at(where, MAT_ANY,
3243                                     "door_description_noun");
3244         const string door_desc_adj  =
3245             env.markers.property_at(where, MAT_ANY,
3246                                     "door_description_adjective");
3247         const string door_desc_veto =
3248             env.markers.property_at(where, MAT_ANY,
3249                                     "door_description_veto");
3250 
3251         set<coord_def> all_door;
3252         find_connected_identical(where, all_door);
3253         const char *adj, *noun;
3254         get_door_description(all_door.size(), &adj, &noun);
3255 
3256         string desc;
3257         if (!door_desc_adj.empty())
3258             desc += door_desc_adj;
3259         else
3260             desc += adj;
3261 
3262         if (door_desc_veto.empty() || door_desc_veto != "veto")
3263         {
3264             if (grid == DNGN_OPEN_DOOR)
3265                 desc += "open ";
3266             else if (grid == DNGN_CLOSED_CLEAR_DOOR)
3267                 desc += "closed translucent ";
3268             else if (grid == DNGN_OPEN_CLEAR_DOOR)
3269                 desc += "open translucent ";
3270             else if (grid == DNGN_RUNED_DOOR)
3271                 desc += "runed ";
3272             else if (grid == DNGN_RUNED_CLEAR_DOOR)
3273                 desc += "runed translucent ";
3274             else if (grid == DNGN_SEALED_DOOR)
3275                 desc += "sealed ";
3276             else if (grid == DNGN_SEALED_CLEAR_DOOR)
3277                 desc += "sealed translucent ";
3278             else
3279                 desc += "closed ";
3280         }
3281 
3282         desc += door_desc_prefix;
3283 
3284         if (!door_desc_noun.empty())
3285             desc += door_desc_noun;
3286         else
3287             desc += noun;
3288 
3289         desc += door_desc_suffix;
3290 
3291         desc += covering_description;
3292 
3293         return thing_do_grammar(dtype, desc);
3294     }
3295 
3296     bool ignore_case = false;
3297     if (grid == DNGN_TRAP_ZOT)
3298         ignore_case = true;
3299 
3300     switch (grid)
3301     {
3302 #if TAG_MAJOR_VERSION == 34
3303     case DNGN_TRAP_MECHANICAL:
3304         return feature_description(grid, trap, covering_description, dtype);
3305 
3306     case DNGN_ENTER_PORTAL_VAULT:
3307         // Should have been handled at the top of the function.
3308         return thing_do_grammar(dtype, "UNAMED PORTAL VAULT ENTRY");
3309 #endif
3310     case DNGN_ENTER_SHOP:
3311         return shop_name(*shop_at(where));
3312 
3313     case DNGN_FLOOR:
3314         if (dtype == DESC_A)
3315             dtype = DESC_THE;
3316         // fallthrough
3317     default:
3318         const string featdesc = grid == env.grid(where)
3319                               ? raw_feature_description(where)
3320                               : _base_feature_desc(grid, trap);
3321         return thing_do_grammar(dtype, featdesc + covering_description,
3322                 ignore_case);
3323     }
3324 }
3325 
_describe_monster_weapon(const monster_info & mi,bool ident)3326 static string _describe_monster_weapon(const monster_info& mi, bool ident)
3327 {
3328     string desc = "";
3329     string name1, name2;
3330     const item_def *weap = mi.inv[MSLOT_WEAPON].get();
3331     const item_def *alt  = mi.inv[MSLOT_ALT_WEAPON].get();
3332 
3333     if (weap && (!ident || item_type_known(*weap)))
3334         name1 = weap->name(DESC_A, false, false, true, false);
3335     if (alt && (!ident || item_type_known(*alt)) && mi.wields_two_weapons())
3336         name2 = alt->name(DESC_A, false, false, true, false);
3337 
3338     if (name1.empty() && !name2.empty())
3339         name1.swap(name2);
3340 
3341     if (name1 == name2 && weap && !name1.empty())
3342     {
3343         item_def dup = *weap;
3344         ++dup.quantity;
3345         name1 = dup.name(DESC_A, false, false, true, true);
3346         name2.clear();
3347     }
3348 
3349     if (mi.props.exists(SPECIAL_WEAPON_KEY))
3350     {
3351         name1 = article_a(ghost_brand_name(
3352             (brand_type) mi.props[SPECIAL_WEAPON_KEY].get_int(), mi.type));
3353     }
3354 
3355     if (name1.empty())
3356         return desc;
3357 
3358     if (mi.type == MONS_PANDEMONIUM_LORD)
3359         desc += " armed with ";
3360     else if (mons_class_is_animated_weapon(mi.type))
3361         desc += " ";
3362     else
3363         desc += " wielding ";
3364     desc += name1;
3365 
3366     if (!name2.empty())
3367     {
3368         desc += " and ";
3369         desc += name2;
3370     }
3371 
3372     return desc;
3373 }
3374 
3375 #ifdef DEBUG_DIAGNOSTICS
_stair_destination_description(const coord_def & pos)3376 static string _stair_destination_description(const coord_def &pos)
3377 {
3378     if (LevelInfo *linf = travel_cache.find_level_info(level_id::current()))
3379     {
3380         const stair_info *si = linf->get_stair(pos);
3381         if (si)
3382             return " " + si->describe();
3383         else if (feat_is_stair(env.grid(pos)))
3384             return " (unknown stair)";
3385     }
3386     return "";
3387 }
3388 #endif
3389 
_mon_enchantments_string(const monster_info & mi)3390 static string _mon_enchantments_string(const monster_info& mi)
3391 {
3392     const vector<string> enchant_descriptors = mi.attributes();
3393 
3394     if (!enchant_descriptors.empty())
3395     {
3396         return uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE))
3397             + " "
3398             + conjugate_verb("are", mi.pronoun_plurality())
3399             + " "
3400             + comma_separated_line(enchant_descriptors.begin(),
3401                                    enchant_descriptors.end())
3402             + ".";
3403     }
3404     else
3405         return "";
3406 }
3407 
_get_monster_behaviour_vector(const monster_info & mi)3408 static vector<string> _get_monster_behaviour_vector(const monster_info& mi)
3409 {
3410     vector<string> descs;
3411 
3412     if ((mi.is(MB_SLEEPING) || mi.is(MB_DORMANT)))
3413     {
3414         if (mi.is(MB_CONFUSED))
3415             descs.emplace_back("sleepwalking");
3416         else if (mons_class_flag(mi.type, M_CONFUSED))
3417             descs.emplace_back("drifting");
3418     }
3419     else if (mi.attitude == ATT_HOSTILE && (mi.is(MB_UNAWARE) || mi.is(MB_WANDERING)))
3420         descs.emplace_back("hasn't noticed you");
3421 
3422     return descs;
3423 }
3424 
3425 // FIXME: this duplicates _get_monster_desc(). Unite them.
_get_monster_desc_vector(const monster_info & mi)3426 static vector<string> _get_monster_desc_vector(const monster_info& mi)
3427 {
3428     vector<string> descs;
3429 
3430     _append_container(descs, _get_monster_behaviour_vector(mi));
3431 
3432     if (you.duration[DUR_CONFUSING_TOUCH])
3433     {
3434         const int pow = you.props["confusing touch power"].get_int();
3435         descs.emplace_back(make_stringf("chance to confuse on hit: %d%%",
3436                                         hex_success_chance(mi.willpower(),
3437                                                            pow, 100)));
3438     }
3439     else if (you.form == transformation::fungus
3440              && !mons_is_unbreathing(mi.type))
3441     {
3442         descs.emplace_back(make_stringf("chance to confuse on hit: %d%%",
3443                                         melee_confuse_chance(mi.hd)));
3444     }
3445 
3446     if (mi.attitude == ATT_FRIENDLY)
3447         descs.emplace_back("friendly");
3448     else if (mi.attitude == ATT_GOOD_NEUTRAL)
3449         descs.emplace_back("peaceful");
3450     else if (mi.attitude != ATT_HOSTILE && !mi.is(MB_INSANE))
3451     {
3452         // don't differentiate between permanent or not
3453         descs.emplace_back("indifferent");
3454     }
3455 
3456     if (mi.is(MB_HALOED))
3457         descs.emplace_back("haloed");
3458 
3459     if (mi.is(MB_UMBRAED))
3460         descs.emplace_back("umbra");
3461 
3462     if (mi.fire_blocker)
3463     {
3464         descs.push_back("fire blocked by " // FIXME, renamed features
3465                         + feature_description(mi.fire_blocker, NUM_TRAPS, "",
3466                                               DESC_A));
3467     }
3468 
3469     return descs;
3470 }
3471 
3472 // Returns the description string for a given monster, including attitude
3473 // and enchantments but not equipment or wounds.
_get_monster_desc(const monster_info & mi)3474 static string _get_monster_desc(const monster_info& mi)
3475 {
3476     string text    = "";
3477     string pronoun = uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE));
3478 
3479     if (mi.is(MB_MESMERIZING))
3480     {
3481         text += string("You are mesmerised by ")
3482                 + mi.pronoun(PRONOUN_POSSESSIVE) + " song.\n";
3483     }
3484 
3485     if (mi.is(MB_SLEEPING) || mi.is(MB_DORMANT))
3486     {
3487         text += pronoun + " "
3488                 + conjugate_verb("appear", mi.pronoun_plurality()) + " to be "
3489                 + (mi.is(MB_CONFUSED) ? "sleepwalking" : "resting") + ".\n";
3490     }
3491     // Applies to both friendlies and hostiles
3492     else if (mi.is(MB_FLEEING))
3493     {
3494         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3495                 + " fleeing.\n";
3496     }
3497     // hostile with target != you
3498     else if (mi.attitude == ATT_HOSTILE
3499              && (mi.is(MB_UNAWARE) || mi.is(MB_WANDERING)))
3500     {
3501         text += pronoun + " " + conjugate_verb("have", mi.pronoun_plurality())
3502                 + " not noticed you.\n";
3503     }
3504 
3505     if (mi.attitude == ATT_FRIENDLY)
3506     {
3507         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3508                 + " friendly.\n";
3509     }
3510     else if (mi.attitude == ATT_GOOD_NEUTRAL)
3511     {
3512         text += pronoun + " " + conjugate_verb("seem", mi.pronoun_plurality())
3513                 + " to be peaceful towards you.\n";
3514     }
3515     else if (mi.attitude != ATT_HOSTILE && !mi.is(MB_INSANE))
3516     {
3517         // don't differentiate between permanent or not
3518         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3519                 + " indifferent to you.\n";
3520     }
3521 
3522     if (mi.is(MB_SUMMONED) || mi.is(MB_PERM_SUMMON))
3523     {
3524         text += pronoun + " " + conjugate_verb("have", mi.pronoun_plurality())
3525                 + " been summoned";
3526         if (mi.is(MB_SUMMONED_CAPPED))
3527         {
3528             text += ", and " + conjugate_verb("are", mi.pronoun_plurality())
3529                     + " expiring";
3530         }
3531         else if (mi.is(MB_PERM_SUMMON))
3532             text += " but will not time out";
3533         text += ".\n";
3534     }
3535 
3536     if (mi.is(MB_HALOED))
3537     {
3538         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3539                 + " illuminated by a divine halo.\n";
3540     }
3541 
3542     if (mi.is(MB_UMBRAED))
3543     {
3544         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3545                 + " wreathed by an umbra.\n";
3546     }
3547 
3548     if (mi.intel() <= I_BRAINLESS)
3549     {
3550         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3551                 + " mindless.\n";
3552     }
3553 
3554     if (mi.is(MB_CHAOTIC))
3555     {
3556         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3557                 + " chaotic.\n";
3558     }
3559 
3560     if (mi.is(MB_POSSESSABLE))
3561     {
3562         text += string(mi.pronoun(PRONOUN_POSSESSIVE))
3563                 + " soul is ripe for the taking.\n";
3564     }
3565     else if (mi.is(MB_ENSLAVED))
3566     {
3567         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3568                 + " a disembodied soul.\n";
3569     }
3570 
3571     if (mi.is(MB_MIRROR_DAMAGE))
3572     {
3573         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3574                 + " reflecting injuries back at attackers.\n";
3575     }
3576 
3577     if (mi.is(MB_INNER_FLAME))
3578     {
3579         text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality())
3580                 + " filled with an inner flame.\n";
3581     }
3582 
3583     if (mi.fire_blocker)
3584     {
3585         text += string("Your line of fire to ") + mi.pronoun(PRONOUN_OBJECTIVE)
3586                 + " is blocked by " // FIXME: renamed features
3587                 + feature_description(mi.fire_blocker, NUM_TRAPS, "",
3588                                       DESC_A)
3589                 + ".\n";
3590     }
3591 
3592     text += _mon_enchantments_string(mi);
3593     if (!text.empty() && text.back() == '\n')
3594         text.pop_back();
3595     return text;
3596 }
3597 
_describe_monster(const monster_info & mi)3598 static void _describe_monster(const monster_info& mi)
3599 {
3600     // First print type and equipment.
3601     string text = uppercase_first(get_monster_equipment_desc(mi)) + ".";
3602     const string wounds_desc = mi.wounds_description_sentence();
3603     if (!wounds_desc.empty())
3604         text += " " + uppercase_first(wounds_desc);
3605     const string constriction_desc = mi.constriction_description();
3606     if (!constriction_desc.empty())
3607         text += " It is" + constriction_desc + ".";
3608     mprf(MSGCH_EXAMINE, "%s", text.c_str());
3609 
3610     // Print the rest of the description.
3611     text = _get_monster_desc(mi);
3612     if (!text.empty())
3613         mprf(MSGCH_EXAMINE, "%s", text.c_str());
3614 }
3615 
3616 // This method is called in two cases:
3617 // a) Monsters coming into view: "An ogre comes into view. It is wielding ..."
3618 // b) Monster description via 'x': "An ogre, wielding a club, and wearing ..."
get_monster_equipment_desc(const monster_info & mi,mons_equip_desc_level_type level,description_level_type mondtype,bool print_attitude)3619 string get_monster_equipment_desc(const monster_info& mi,
3620                                   mons_equip_desc_level_type level,
3621                                   description_level_type mondtype,
3622                                   bool print_attitude)
3623 {
3624     string desc = "";
3625     if (mondtype != DESC_NONE)
3626     {
3627         if (print_attitude && mons_is_pghost(mi.type))
3628             desc = get_ghost_description(mi);
3629         else
3630             desc = mi.full_name(mondtype);
3631 
3632         if (print_attitude)
3633         {
3634             vector<string> attributes;
3635             if (mi.attitude == ATT_FRIENDLY)
3636                 attributes.emplace_back("friendly");
3637             else if (mi.attitude == ATT_GOOD_NEUTRAL)
3638                 attributes.emplace_back("peaceful");
3639             else if (mi.attitude != ATT_HOSTILE && !mi.is(MB_INSANE))
3640                 attributes.emplace_back("neutral");
3641             _append_container(attributes, mi.attributes());
3642 
3643             string str = comma_separated_line(attributes.begin(),
3644                                               attributes.end());
3645 
3646             if (mons_class_is_animated_weapon(mi.type)
3647                 || mi.type == MONS_PANDEMONIUM_LORD
3648                 || mi.type == MONS_PLAYER_GHOST)
3649             {
3650                 if (!str.empty())
3651                     str += " ";
3652 
3653                 // animated armour is has "animated" in its name already,
3654                 // spectral weapons have "spectral".
3655                 if (mi.type == MONS_DANCING_WEAPON)
3656                     str += "dancing weapon";
3657                 else if (mi.type == MONS_PANDEMONIUM_LORD)
3658                     str += "pandemonium lord";
3659                 else if (mi.type == MONS_PLAYER_GHOST)
3660                     str += "ghost";
3661             }
3662             if (!str.empty())
3663                 desc += " (" + str + ")";
3664         }
3665     }
3666 
3667     string weap = _describe_monster_weapon(mi, level == DESC_IDENTIFIED);
3668 
3669     // Print the rest of the equipment only for full descriptions.
3670     if (level == DESC_WEAPON || level == DESC_WEAPON_WARNING)
3671         return desc + weap;
3672 
3673     item_def* mon_arm = mi.inv[MSLOT_ARMOUR].get();
3674     item_def* mon_shd = mi.inv[MSLOT_SHIELD].get();
3675     item_def* mon_qvr = mi.inv[MSLOT_MISSILE].get();
3676     item_def* mon_alt = mi.inv[MSLOT_ALT_WEAPON].get();
3677     item_def* mon_wnd = mi.inv[MSLOT_WAND].get();
3678     item_def* mon_rng = mi.inv[MSLOT_JEWELLERY].get();
3679 
3680 #define uninteresting(x) (x && !item_is_branded(*x) && !is_artefact(*x))
3681     // For "comes into view" msgs, only care about branded stuff and artefacts
3682     if (level == DESC_IDENTIFIED)
3683     {
3684         if (uninteresting(mon_arm))
3685             mon_arm = nullptr;
3686         if (uninteresting(mon_shd))
3687             mon_shd = nullptr;
3688         if (uninteresting(mon_qvr))
3689             mon_qvr = nullptr;
3690         if (uninteresting(mon_rng))
3691             mon_rng = nullptr;
3692         if (uninteresting(mon_alt) && mon_alt->base_type != OBJ_WANDS)
3693             mon_alt = nullptr;
3694     }
3695 #undef uninteresting
3696 
3697     // _describe_monster_weapon already took care of this
3698     if (mi.wields_two_weapons())
3699         mon_alt = 0;
3700 
3701     const bool mon_has_wand = mon_wnd;
3702     const bool mon_carry = mon_alt || mon_has_wand;
3703 
3704     vector<string> item_descriptions;
3705 
3706     // Dancing weapons have all their weapon information in their full_name, so
3707     // we don't need to add another weapon description here (see Mantis 11887).
3708     if (!weap.empty() && !mons_class_is_animated_weapon(mi.type))
3709         item_descriptions.push_back(weap.substr(1)); // strip leading space
3710 
3711     // as with dancing weapons, don't claim animated armours 'wear' their armour
3712     if (mon_arm && mi.type != MONS_ANIMATED_ARMOUR)
3713     {
3714         const string armour_desc = make_stringf("wearing %s",
3715                                                 mon_arm->name(DESC_A).c_str());
3716         item_descriptions.push_back(armour_desc);
3717     }
3718 
3719     if (mon_shd)
3720     {
3721         const string shield_desc = make_stringf("wearing %s",
3722                                                 mon_shd->name(DESC_A).c_str());
3723         item_descriptions.push_back(shield_desc);
3724     }
3725 
3726     if (mon_rng)
3727     {
3728         const string rng_desc = make_stringf("wearing %s",
3729                                              mon_rng->name(DESC_A).c_str());
3730         item_descriptions.push_back(rng_desc);
3731     }
3732 
3733     if (mon_qvr)
3734     {
3735         const bool net = mon_qvr->sub_type == MI_THROWING_NET;
3736         const string qvr_desc = net ? mon_qvr->name(DESC_A)
3737                                     : pluralise(mon_qvr->name(DESC_PLAIN));
3738         item_descriptions.push_back(make_stringf("quivering %s", qvr_desc.c_str()));
3739     }
3740 
3741     if (mon_carry)
3742     {
3743         string carried_desc = "carrying ";
3744 
3745         if (mon_alt)
3746         {
3747             carried_desc += mon_alt->name(DESC_A);
3748             if (mon_has_wand)
3749                 carried_desc += " and ";
3750         }
3751 
3752         if (mon_has_wand)
3753             carried_desc += mon_wnd->name(DESC_A);
3754 
3755         item_descriptions.push_back(carried_desc);
3756     }
3757 
3758     const string item_description = comma_separated_line(
3759                                                 item_descriptions.begin(),
3760                                                 item_descriptions.end());
3761 
3762     if (!item_description.empty() && !desc.empty())
3763         desc += ", ";
3764     return desc + item_description;
3765 }
3766 
_print_cloud_desc(const coord_def where)3767 static bool _print_cloud_desc(const coord_def where)
3768 {
3769     vector<string> areas;
3770     if (is_sanctuary(where))
3771         areas.emplace_back("lies inside a sanctuary");
3772     if (silenced(where))
3773         areas.emplace_back("is shrouded in silence");
3774     if (haloed(where) && !umbraed(where))
3775         areas.emplace_back("is lit by a halo");
3776     if (umbraed(where) && !haloed(where))
3777         areas.emplace_back("is wreathed by an umbra");
3778     if (liquefied(where))
3779         areas.emplace_back("is liquefied");
3780     if (orb_haloed(where) || quad_haloed(where))
3781         areas.emplace_back("is covered in magical glow");
3782     if (disjunction_haloed(where))
3783         areas.emplace_back("is bathed in translocational energy");
3784     if (!areas.empty())
3785     {
3786         mprf("This square %s.",
3787              comma_separated_line(areas.begin(), areas.end()).c_str());
3788     }
3789 
3790     if (cloud_struct* cloud = cloud_at(where))
3791     {
3792         mprf(MSGCH_EXAMINE, "There is a cloud of %s here.",
3793              cloud->cloud_name(true).c_str());
3794         return true;
3795     }
3796 
3797     return false;
3798 }
3799 
_print_item_desc(const coord_def where)3800 static bool _print_item_desc(const coord_def where)
3801 {
3802     int targ_item = you.visible_igrd(where);
3803 
3804     if (targ_item == NON_ITEM)
3805         return false;
3806 
3807     string name = menu_colour_item_name(env.item[targ_item], DESC_A);
3808     mprf(MSGCH_FLOOR_ITEMS, "You see %s here.", name.c_str());
3809 
3810     if (env.item[ targ_item ].link != NON_ITEM)
3811         mprf(MSGCH_FLOOR_ITEMS, "There is something else lying underneath.");
3812 
3813     return true;
3814 }
3815 
3816 #ifdef DEBUG_DIAGNOSTICS
_debug_describe_feature_at(const coord_def & where)3817 static void _debug_describe_feature_at(const coord_def &where)
3818 {
3819     const string feature_desc = feature_description_at(where, true);
3820     string marker;
3821     if (map_marker *mark = env.markers.find(where, MAT_ANY))
3822     {
3823         string desc = mark->debug_describe();
3824         if (desc.empty())
3825             desc = "?";
3826         marker = " (" + desc + ")";
3827     }
3828     const string traveldest = _stair_destination_description(where);
3829     string height_desc;
3830     if (env.heightmap)
3831         height_desc = make_stringf(" (height: %d)", (*env.heightmap)(where));
3832     const dungeon_feature_type feat = env.grid(where);
3833 
3834     string vault;
3835     const int map_index = env.level_map_ids(where);
3836     if (map_index != INVALID_MAP_INDEX)
3837     {
3838         const vault_placement &vp(*env.level_vaults[map_index]);
3839         const coord_def br = vp.pos + vp.size - 1;
3840         vault = make_stringf(" [Vault: %s (%d,%d)-(%d,%d) (%dx%d)]",
3841                              vp.map_name_at(where).c_str(),
3842                              vp.pos.x, vp.pos.y,
3843                              br.x, br.y,
3844                              vp.size.x, vp.size.y);
3845     }
3846 
3847     char32_t ch = get_cell_glyph(where).ch;
3848     dprf("(%d,%d): %s - %s. (%d/%s)%s%s%s%s map: %x",
3849          where.x, where.y,
3850          ch == '<' ? "<<" : stringize_glyph(ch).c_str(),
3851          feature_desc.c_str(),
3852          feat,
3853          dungeon_feature_name(feat),
3854          marker.c_str(),
3855          traveldest.c_str(),
3856          height_desc.c_str(),
3857          vault.c_str(),
3858          env.map_knowledge(where).flags);
3859 }
3860 #endif
3861 
3862 // Describe a cell, guaranteed to be in view.
_describe_cell(const coord_def & where,bool in_range)3863 static void _describe_cell(const coord_def& where, bool in_range)
3864 {
3865 #ifndef DEBUG_DIAGNOSTICS
3866     bool monster_described = false;
3867 #endif
3868 
3869     if (where == you.pos() && !crawl_state.arena_suspended)
3870         mprf(MSGCH_EXAMINE_FILTER, "You.");
3871 
3872     if (const monster* mon = monster_at(where))
3873     {
3874 #ifdef DEBUG_DIAGNOSTICS
3875         if (!mon->visible_to(&you))
3876         {
3877             mprf(MSGCH_DIAGNOSTICS, "There is a non-visible %smonster here.",
3878                  _mon_exposed_in_water(mon) ? "exposed by water " :
3879                  _mon_exposed_in_cloud(mon) ? "exposed by cloud " : "");
3880         }
3881 #else
3882         if (!mon->visible_to(&you))
3883         {
3884             if (_mon_exposed_in_water(mon))
3885                 mprf(MSGCH_EXAMINE_FILTER, "There is a strange disturbance in the water here.");
3886             else if (_mon_exposed_in_cloud(mon))
3887                 mprf(MSGCH_EXAMINE_FILTER, "There is a strange disturbance in the cloud here.");
3888 
3889             goto look_clouds;
3890         }
3891 #endif
3892 
3893         monster_info mi(mon);
3894         _describe_monster(mi);
3895 
3896         if (!in_range)
3897         {
3898             mprf(MSGCH_EXAMINE_FILTER, "%s %s out of range.",
3899                  mon->pronoun(PRONOUN_SUBJECTIVE).c_str(),
3900                  conjugate_verb("are", mi.pronoun_plurality()).c_str());
3901         }
3902 #ifndef DEBUG_DIAGNOSTICS
3903         monster_described = true;
3904 #endif
3905 
3906 #if defined(DEBUG_DIAGNOSTICS) && defined(WIZARD)
3907         debug_stethoscope(env.mgrid(where));
3908 #endif
3909         if (crawl_state.game_is_hints() && hints_monster_interesting(mon))
3910         {
3911             const char *msg;
3912 #ifdef USE_TILE_LOCAL
3913             msg = "(<w>Right-click</w> for more information.)";
3914 #else
3915             msg = "(Press <w>v</w> for more information.)";
3916 #endif
3917             mpr(msg);
3918         }
3919     }
3920 
3921 #ifdef DEBUG_DIAGNOSTICS
3922     _print_cloud_desc(where);
3923     _print_item_desc(where);
3924     _debug_describe_feature_at(where);
3925 #else
3926   // removing warning
3927   look_clouds:
3928 
3929     bool cloud_described = _print_cloud_desc(where);
3930     bool item_described = _print_item_desc(where);
3931 
3932     string feature_desc = feature_description_at(where, true);
3933     const bool bloody = is_bloodcovered(where);
3934     if (crawl_state.game_is_hints() && hints_pos_interesting(where.x, where.y))
3935     {
3936 #ifdef USE_TILE_LOCAL
3937         feature_desc += " (<w>Right-click</w> for more information.)";
3938 #else
3939         feature_desc += " (Press <w>v</w> for more information.)";
3940 #endif
3941         mpr(feature_desc);
3942     }
3943     else
3944     {
3945         dungeon_feature_type feat = env.grid(where);
3946 
3947         if (_interesting_feature(feat))
3948         {
3949 #ifdef USE_TILE_LOCAL
3950             feature_desc += " (Right-click for more information.)";
3951 #else
3952             feature_desc += " (Press 'v' for more information.)";
3953 #endif
3954         }
3955 
3956         // Suppress "Floor." if there's something on that square that we've
3957         // already described.
3958         if (feat == DNGN_FLOOR && !bloody
3959             && (monster_described || item_described || cloud_described))
3960         {
3961             return;
3962         }
3963 
3964         msg_channel_type channel = MSGCH_EXAMINE;
3965         if (feat == DNGN_FLOOR || feat_is_water(feat))
3966             channel = MSGCH_EXAMINE_FILTER;
3967 
3968         mprf(channel, "%s", feature_desc.c_str());
3969     }
3970 #endif
3971 }
3972 
3973 ///////////////////////////////////////////////////////////////////////////
3974 // targeting_behaviour
3975 
targeting_behaviour(bool look_around)3976 targeting_behaviour::targeting_behaviour(bool look_around)
3977     : just_looking(look_around), needs_path(MB_MAYBE)
3978 {
3979 }
3980 
~targeting_behaviour()3981 targeting_behaviour::~targeting_behaviour()
3982 {
3983 }
3984 
get_command(int key)3985 command_type targeting_behaviour::get_command(int key)
3986 {
3987     command_type cmd = key_to_command(key, KMC_TARGETING);
3988     if (cmd >= CMD_MIN_TARGET && cmd < CMD_TARGET_PREV_TARGET)
3989         return cmd;
3990 
3991     // XXX: hack
3992     if (cmd == CMD_TARGET_SELECT && key == ' ' && just_looking)
3993         cmd = CMD_TARGET_CANCEL;
3994 
3995     return cmd;
3996 }
3997 
get_monster_desc(const monster_info & mi)3998 vector<string> targeting_behaviour::get_monster_desc(const monster_info& mi)
3999 {
4000     vector<string> descs;
4001     if (get_desc_func)
4002         _append_container(descs, (get_desc_func)(mi));
4003     return descs;
4004 }
4005