1 /**
2  * @file
3  * @brief Misc function used to render the dungeon.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "view.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstring>
13 #include <memory>
14 #include <sstream>
15 
16 #include "act-iter.h"
17 #include "artefact.h"
18 #include "cio.h"
19 #include "cloud.h"
20 #include "clua.h"
21 #include "colour.h"
22 #include "coord.h"
23 #include "coordit.h"
24 #include "database.h"
25 #include "delay.h"
26 #include "dgn-overview.h"
27 #include "directn.h"
28 #include "english.h"
29 #include "env.h"
30 #include "tile-env.h"
31 #include "exclude.h"
32 #include "feature.h"
33 #include "files.h"
34 #include "fprop.h"
35 #include "god-conduct.h"
36 #include "god-passive.h"
37 #include "god-wrath.h"
38 #include "hints.h"
39 #include "items.h"
40 #include "item-name.h" // item_type_known
41 #include "item-prop.h" // get_weapon_brand
42 #include "libutil.h"
43 #include "macro.h"
44 #include "map-knowledge.h"
45 #include "message.h"
46 #include "misc.h"
47 #include "mon-abil.h" // boris_covet_orb
48 #include "mon-behv.h"
49 #include "mon-death.h"
50 #include "mon-poly.h"
51 #include "mon-tentacle.h"
52 #include "mon-util.h"
53 #include "nearby-danger.h"
54 #include "notes.h"
55 #include "options.h"
56 #include "output.h"
57 #include "player.h"
58 #include "random.h"
59 #include "religion.h"
60 #include "shout.h"
61 #include "show.h"
62 #include "showsymb.h"
63 #include "state.h"
64 #include "stringutil.h"
65 #include "tag-version.h"
66 #include "target.h"
67 #include "terrain.h"
68 #include "tilemcache.h"
69 #ifdef USE_TILE
70  #include "tile-flags.h"
71  #include "tilepick.h"
72  #include "tilepick-p.h"
73  #include "tileview.h"
74 #endif
75 #include "tiles-build-specific.h"
76 #include "traps.h"
77 #include "travel.h"
78 #include "unicode.h"
79 #include "unwind.h"
80 #include "viewchar.h"
81 #include "viewmap.h"
82 #include "xom.h"
83 
84 static layers_type _layers = LAYERS_ALL;
85 static layers_type _layers_saved = LAYERS_NONE;
86 
87 crawl_view_geometry crawl_view;
88 
handle_seen_interrupt(monster * mons,vector<string> * msgs_buf)89 bool handle_seen_interrupt(monster* mons, vector<string>* msgs_buf)
90 {
91     activity_interrupt_data aid(mons);
92     if (mons->seen_context)
93         aid.context = mons->seen_context;
94     // XXX: Hack to make the 'seen' monster spec flag work.
95     else if (testbits(mons->flags, MF_WAS_IN_VIEW)
96              || testbits(mons->flags, MF_SEEN))
97     {
98         aid.context = SC_ALREADY_SEEN;
99     }
100     else
101         aid.context = SC_NEWLY_SEEN;
102 
103     if (!mons_is_safe(mons))
104     {
105         return interrupt_activity(activity_interrupt::see_monster,
106                                   aid, msgs_buf);
107     }
108 
109     return false;
110 }
111 
flush_comes_into_view()112 void flush_comes_into_view()
113 {
114     if (!you.turn_is_over
115         || (!you_are_delayed() && !crawl_state.is_repeating_cmd()))
116     {
117         return;
118     }
119 
120     monster* mon = crawl_state.which_mon_acting();
121 
122     if (!mon || !mon->alive() || (mon->flags & MF_WAS_IN_VIEW)
123         || !you.can_see(*mon))
124     {
125         return;
126     }
127 
128     handle_seen_interrupt(mon);
129 }
130 
seen_monsters_react(int stealth)131 void seen_monsters_react(int stealth)
132 {
133     if (you.duration[DUR_TIME_STEP] || crawl_state.game_is_arena())
134         return;
135 
136     for (monster_near_iterator mi(you.pos()); mi; ++mi)
137     {
138         if ((mi->asleep() || mons_is_wandering(**mi))
139             && check_awaken(*mi, stealth))
140         {
141             behaviour_event(*mi, ME_ALERT, &you, you.pos(), false);
142 
143             // That might have caused a pacified monster to leave the level.
144             if (!(*mi)->alive())
145                 continue;
146 
147             monster_consider_shouting(**mi);
148         }
149 
150         if (!mi->visible_to(&you))
151             continue;
152 
153         if (!mi->has_ench(ENCH_INSANE) && mi->can_see(you))
154         {
155             // Trigger Duvessa & Dowan upgrades
156             if (mi->props.exists(ELVEN_ENERGIZE_KEY))
157             {
158                 mi->props.erase(ELVEN_ENERGIZE_KEY);
159                 elven_twin_energize(*mi);
160             }
161             else if (mi->type == MONS_BORIS && player_has_orb()
162                      && !mi->props.exists(BORIS_ORB_KEY))
163             {
164                 mi->props[BORIS_ORB_KEY] = true;
165                 boris_covet_orb(*mi);
166             }
167 #if TAG_MAJOR_VERSION == 34
168             else if (mi->props.exists(OLD_DUVESSA_ENERGIZE_KEY))
169             {
170                 mi->props.erase(OLD_DUVESSA_ENERGIZE_KEY);
171                 elven_twin_energize(*mi);
172             }
173             else if (mi->props.exists(OLD_DOWAN_ENERGIZE_KEY))
174             {
175                 mi->props.erase(OLD_DOWAN_ENERGIZE_KEY);
176                 elven_twin_energize(*mi);
177             }
178 #endif
179         }
180     }
181 }
182 
_desc_mons_type_map(map<monster_type,int> types)183 static string _desc_mons_type_map(map<monster_type, int> types)
184 {
185     string message;
186     unsigned int count = 1;
187     for (const auto &entry : types)
188     {
189         string name;
190         description_level_type desc;
191         if (entry.second == 1)
192             desc = DESC_A;
193         else
194             desc = DESC_PLAIN;
195 
196         name = mons_type_name(entry.first, desc);
197         if (entry.second > 1)
198         {
199             name = make_stringf("%d %s", entry.second,
200                                 pluralise_monster(name).c_str());
201         }
202 
203         message += name;
204         if (count == types.size() - 1)
205             message += " and ";
206         else if (count < types.size())
207             message += ", ";
208         ++count;
209     }
210     return message;
211 }
212 
_mons_genus_keep_uniques(monster_type mc)213 static monster_type _mons_genus_keep_uniques(monster_type mc)
214 {
215     return mons_is_unique(mc) ? mc : mons_genus(mc);
216 }
217 
218 /**
219  * Monster list simplification
220  *
221  * When too many monsters come into view at once, we group the ones with the
222  * same genus, starting with the most represented genus.
223  *
224  * @param types monster types and the number of monster for each type.
225  * @param genera monster genera and the number of monster for each genus.
226  */
_genus_factoring(map<monster_type,int> & types,map<monster_type,int> & genera)227 static void _genus_factoring(map<monster_type, int> &types,
228                              map<monster_type, int> &genera)
229 {
230     monster_type genus = MONS_NO_MONSTER;
231     int num = 0;
232     // Find the most represented genus.
233     for (const auto &entry : genera)
234         if (entry.second > num)
235         {
236             genus = entry.first;
237             num = entry.second;
238         }
239 
240     // The most represented genus has a single member.
241     // No more factoring is possible, we're done.
242     if (num == 1)
243     {
244         genera.clear();
245         return;
246     }
247 
248     genera.erase(genus);
249     auto it = types.begin();
250     do
251     {
252         if (_mons_genus_keep_uniques(it->first) != genus)
253         {
254             ++it;
255             continue;
256         }
257 
258         // This genus has a single monster type. Can't factor.
259         if (it->second == num)
260             return;
261 
262         types.erase(it++);
263 
264     } while (it != types.end());
265 
266     types[genus] = num;
267 }
268 
_is_weapon_worth_listing(const unique_ptr<item_def> & wpn)269 static bool _is_weapon_worth_listing(const unique_ptr<item_def> &wpn)
270 {
271     return wpn && (wpn->base_type == OBJ_STAVES
272                    || is_unrandom_artefact(*wpn.get())
273                    || get_weapon_brand(*wpn.get()) != SPWPN_NORMAL);
274 }
275 
_is_item_worth_listing(const unique_ptr<item_def> & item)276 static bool _is_item_worth_listing(const unique_ptr<item_def> &item)
277 {
278     return item && (item_is_branded(*item.get())
279                     || is_artefact(*item.get()));
280 }
281 
_is_mon_equipment_worth_listing(const monster_info & mi)282 static bool _is_mon_equipment_worth_listing(const monster_info &mi)
283 {
284 
285     if (_is_weapon_worth_listing(mi.inv[MSLOT_WEAPON]))
286         return true;
287     const unique_ptr<item_def> &alt_weap = mi.inv[MSLOT_ALT_WEAPON];
288     if (mi.wields_two_weapons() && _is_weapon_worth_listing(alt_weap))
289         return true;
290     // can a wand be in the alt weapon slot? get_monster_equipment_desc seems to
291     // think so, so we'll check
292     if (alt_weap && alt_weap->base_type == OBJ_WANDS)
293         return true;
294     if (mi.inv[MSLOT_WAND])
295         return true;
296 
297     return _is_item_worth_listing(mi.inv[MSLOT_SHIELD])
298         || _is_item_worth_listing(mi.inv[MSLOT_ARMOUR])
299         || _is_item_worth_listing(mi.inv[MSLOT_JEWELLERY])
300         || _is_item_worth_listing(mi.inv[MSLOT_MISSILE]);
301 }
302 
303 /// Return a warning for the player about newly-seen monsters, as appropriate.
_monster_headsup(const vector<monster * > & monsters,const map<monster_type,int> & types,bool divine)304 static string _monster_headsup(const vector<monster*> &monsters,
305                                const map<monster_type, int> &types,
306                                bool divine)
307 {
308     string warning_msg = "";
309     for (const monster* mon : monsters)
310     {
311         monster_info mi(mon);
312         const bool zin_ided = mon->props.exists("zin_id");
313         const bool has_interesting_equipment
314             = _is_mon_equipment_worth_listing(mi);
315         if ((divine && !zin_ided)
316             || (!divine && !has_interesting_equipment))
317         {
318             continue;
319         }
320 
321         if (!divine && monsters.size() == 1)
322             continue; // don't give redundant warnings for enemies
323 
324         if (warning_msg.size())
325             warning_msg += " ";
326 
327         string monname;
328         if (monsters.size() == 1)
329             monname = mon->pronoun(PRONOUN_SUBJECTIVE);
330         else if (mon->type == MONS_DANCING_WEAPON)
331             monname = "There";
332         else if (types.at(mon->type) == 1)
333             monname = mon->full_name(DESC_THE);
334         else
335             monname = mon->full_name(DESC_A);
336         warning_msg += uppercase_first(monname);
337 
338         warning_msg += " ";
339         if (monsters.size() == 1)
340             warning_msg += conjugate_verb("are", mon->pronoun_plurality());
341         else
342             warning_msg += "is";
343 
344         mons_equip_desc_level_type level = mon->type != MONS_DANCING_WEAPON
345             ? DESC_IDENTIFIED : DESC_WEAPON_WARNING;
346 
347         if (!divine)
348         {
349             if (mon->type != MONS_DANCING_WEAPON)
350                 warning_msg += " ";
351             warning_msg += get_monster_equipment_desc(mi, level, DESC_NONE);
352             warning_msg += ".";
353             continue;
354         }
355 
356         if (you_worship(GOD_ZIN))
357         {
358             warning_msg += " a foul ";
359             if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER))
360                 warning_msg += "glowing ";
361             warning_msg += "shapeshifter";
362         }
363         else
364         {
365             // TODO: deduplicate
366             if (mon->type != MONS_DANCING_WEAPON)
367                 warning_msg += " ";
368             warning_msg += get_monster_equipment_desc(mi, level, DESC_NONE);
369         }
370         warning_msg += ".";
371     }
372 
373     return warning_msg;
374 }
375 
376 /// Let Ash/Zin warn the player about newly-seen monsters, as appropriate.
_divine_headsup(const vector<monster * > & monsters,const map<monster_type,int> & types)377 static void _divine_headsup(const vector<monster*> &monsters,
378                             const map<monster_type, int> &types)
379 {
380     const string warnings = _monster_headsup(monsters, types, true);
381     if (!warnings.size())
382         return;
383 
384     const string warning_msg = " warns you: " + warnings;
385     simple_god_message(warning_msg.c_str());
386 #ifndef USE_TILE_LOCAL
387     // XXX: should this really be here...?
388     if (you_worship(GOD_ZIN))
389         update_monster_pane();
390 #endif
391 }
392 
_secular_headsup(const vector<monster * > & monsters,const map<monster_type,int> & types)393 static void _secular_headsup(const vector<monster*> &monsters,
394                              const map<monster_type, int> &types)
395 {
396     const string warnings = _monster_headsup(monsters, types, false);
397     if (!warnings.size())
398         return;
399     mprf(MSGCH_MONSTER_WARNING, "%s", warnings.c_str());
400 }
401 
_count_monster_types(const vector<monster * > & monsters,const unsigned int max_types=UINT_MAX)402 static map<monster_type, int> _count_monster_types(const vector<monster*>& monsters,
403                                                    const unsigned int max_types = UINT_MAX)
404 {
405     map<monster_type, int> types;
406     map<monster_type, int> genera; // This is the plural for genus!
407     for (const monster *mon : monsters)
408     {
409         const monster_type type = mon->type;
410         types[type]++;
411         genera[_mons_genus_keep_uniques(type)]++;
412     }
413 
414     while (types.size() > max_types && !genera.empty())
415         _genus_factoring(types, genera);
416 
417     return types;
418 }
419 
420 /**
421  * Return a string listing monsters in a human readable form.
422  * E.g. "a hydra and 2 liches".
423  *
424  * @param monsters      A list of monsters that just became visible.
425  */
describe_monsters_condensed(const vector<monster * > & monsters)426 string describe_monsters_condensed(const vector<monster*>& monsters)
427 {
428     return _desc_mons_type_map(_count_monster_types(monsters, 4));
429 }
430 
431 /**
432  * Handle printing "foo comes into view" messages for newly appeared monsters.
433  * Also let Ash/Zin warn the player about newly-seen monsters, as appropriate.
434  *
435  * @param msgs          A list of individual 'comes into view' messages; e.g.
436  *                      "the goblin comes into view.", "Mara opens the gate."
437  * @param monsters      A list of monsters that just became visible.
438  */
_handle_comes_into_view(const vector<string> & msgs,const vector<monster * > monsters)439 static void _handle_comes_into_view(const vector<string> &msgs,
440                                     const vector<monster*> monsters)
441 {
442     if (monsters.size() == 1)
443         mprf(MSGCH_MONSTER_WARNING, "%s", msgs[0].c_str());
444     else
445         mprf(MSGCH_MONSTER_WARNING, "%s come into view.",
446              describe_monsters_condensed(monsters).c_str());
447 
448     const auto& types = _count_monster_types(monsters);
449     _divine_headsup(monsters, types);
450     _secular_headsup(monsters, types);
451 }
452 
453 /// If the player has the shout mutation, maybe shout at newly-seen monsters.
_maybe_trigger_shoutitis(const vector<monster * > monsters)454 static void _maybe_trigger_shoutitis(const vector<monster*> monsters)
455 {
456     if (!you.get_mutation_level(MUT_SCREAM))
457         return;
458 
459     for (const monster* mon : monsters)
460     {
461         if (!mons_is_tentacle_or_tentacle_segment(mon->type)
462             && !mons_is_conjured(mon->type)
463             && x_chance_in_y(you.get_mutation_level(MUT_SCREAM) * 6, 100))
464         {
465             yell(mon);
466             return;
467         }
468     }
469 }
470 
471 /// Let Gozag's wrath buff newly-seen hostile monsters, maybe.
_maybe_gozag_incite(vector<monster * > monsters)472 static void _maybe_gozag_incite(vector<monster*> monsters)
473 {
474     if (!player_under_penance(GOD_GOZAG))
475         return;
476 
477     counted_monster_list mon_count;
478     vector<monster *> incited;
479     for (monster* mon : monsters)
480     {
481         // XXX: some of this is probably redundant with interrupt_activity
482         if (!mon->see_cell(you.pos()) // xray_vision
483             || mon->wont_attack()
484             || mon->is_stationary()
485             || mons_is_object(mon->type)
486             || mons_is_tentacle_or_tentacle_segment(mon->type))
487         {
488             continue;
489         }
490 
491         if (coinflip()
492             && mon->get_experience_level() >= random2(you.experience_level))
493         {
494             mon_count.add(mon);
495             incited.push_back(mon);
496         }
497     }
498 
499     if (incited.empty())
500         return;
501 
502     string msg = make_stringf("%s incites %s against you.",
503                               god_name(GOD_GOZAG).c_str(),
504                               mon_count.describe().c_str());
505     if (strwidth(msg) >= get_number_of_cols() - 2)
506     {
507         msg = make_stringf("%s incites your enemies against you.",
508                            god_name(GOD_GOZAG).c_str());
509     }
510     mprf(MSGCH_GOD, GOD_GOZAG, "%s", msg.c_str());
511 
512     for (monster *mon : incited)
513         gozag_incite(mon);
514 }
515 
update_monsters_in_view()516 void update_monsters_in_view()
517 {
518     int num_hostile = 0;
519     vector<string> msgs;
520     vector<monster*> monsters;
521 
522     for (monster_iterator mi; mi; ++mi)
523     {
524         if (you.see_cell(mi->pos()))
525         {
526             if (mi->attitude == ATT_HOSTILE)
527                 num_hostile++;
528 
529             if (mi->visible_to(&you))
530             {
531                 if (handle_seen_interrupt(*mi, &msgs))
532                     monsters.push_back(*mi);
533                 seen_monster(*mi);
534             }
535             else
536                 mi->flags &= ~MF_WAS_IN_VIEW;
537         }
538         else if (!you.turn_is_over)
539         {
540             if (mi->flags & MF_WAS_IN_VIEW)
541             {
542                 // Reset client id so the player doesn't know (for sure) he
543                 // has seen this monster before when it reappears.
544                 mi->reset_client_id();
545             }
546 
547             mi->flags &= ~MF_WAS_IN_VIEW;
548 
549             // If the monster hasn't been seen by the time that the player
550             // gets control back then seen_context is out of date.
551             mi->seen_context = SC_NONE;
552         }
553     }
554 
555     // Summoners may have lost their summons upon seeing us and converting
556     // leaving invalid monsters in this vector.
557     monsters.erase(
558         std::remove_if(monsters.begin(), monsters.end(),
559             [](const monster * m) { return !m->alive(); }),
560         monsters.end());
561 
562     if (!msgs.empty())
563     {
564         _handle_comes_into_view(msgs, monsters);
565         // XXX: does interrupt_activity() add 'comes into view' messages to
566         // 'msgs' in ALL cases we want shoutitis/gozag wrath to trigger?
567         _maybe_trigger_shoutitis(monsters);
568         _maybe_gozag_incite(monsters);
569     }
570 
571     // Xom thinks it's hilarious the way the player picks up an ever
572     // growing entourage of monsters while running through the Abyss.
573     // To approximate this, if the number of hostile monsters in view
574     // is greater than it ever was for this particular trip to the
575     // Abyss, Xom is stimulated in proportion to the number of
576     // hostile monsters. Thus if the entourage doesn't grow, then
577     // Xom becomes bored.
578     if (player_in_branch(BRANCH_ABYSS)
579         && you.attribute[ATTR_ABYSS_ENTOURAGE] < num_hostile)
580     {
581         you.attribute[ATTR_ABYSS_ENTOURAGE] = num_hostile;
582         xom_is_stimulated(12 * num_hostile);
583     }
584 }
585 
586 
587 // We logically associate a difficulty parameter with each tile on each level,
588 // to make deterministic passive mapping work. It is deterministic so that the
589 // reveal order doesn't, for example, change on reload.
590 //
591 // This function returns the difficulty parameters for each tile on the current
592 // level, whose difficulty is less than a certain amount.
593 //
594 // Random difficulties are used in the few cases where we want repeated maps
595 // to give different results; scrolls and cards, since they are a finite
596 // resource.
_tile_difficulties(bool random)597 static const FixedArray<uint8_t, GXM, GYM>& _tile_difficulties(bool random)
598 {
599     // We will often be called with the same level parameter and cutoff, so
600     // cache this (DS with passive mapping autoexploring could be 5000 calls
601     // in a second or so).
602     static FixedArray<uint8_t, GXM, GYM> cache;
603     static int cache_seed = -1;
604 
605     if (random)
606     {
607         cache_seed = -1;
608         for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y)
609             for (int x = X_BOUND_1; x <= X_BOUND_2; ++x)
610                 cache[x][y] = random2(100);
611         return cache;
612     }
613 
614     // must not produce the magic value (-1)
615     int seed = ((static_cast<int>(you.where_are_you) << 8) + you.depth)
616              ^ (you.game_seed & 0x7fffffff);
617 
618     if (seed == cache_seed)
619         return cache;
620 
621     cache_seed = seed;
622 
623     for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y)
624         for (int x = X_BOUND_1; x <= X_BOUND_2; ++x)
625             cache[x][y] = hash_with_seed(100, seed, y * GXM + x);
626 
627     return cache;
628 }
629 
_feat_default_map_colour(dungeon_feature_type feat)630 static colour_t _feat_default_map_colour(dungeon_feature_type feat)
631 {
632     if (player_in_branch(BRANCH_SEWER) && feat_is_water(feat))
633         return feat == DNGN_DEEP_WATER ? GREEN : LIGHTGREEN;
634     return BLACK;
635 }
636 
637 // Returns true if it succeeded.
magic_mapping(int map_radius,int proportion,bool suppress_msg,bool force,bool deterministic,coord_def origin)638 bool magic_mapping(int map_radius, int proportion, bool suppress_msg,
639                    bool force, bool deterministic,
640                    coord_def origin)
641 {
642     if (!force && !is_map_persistent())
643     {
644         if (!suppress_msg)
645             canned_msg(MSG_DISORIENTED);
646 
647         return false;
648     }
649 
650     const bool wizard_map = (you.wizard && map_radius == 1000);
651 
652     if (map_radius < 5)
653         map_radius = 5;
654 
655     // now gradually weaker with distance:
656     const int pfar     = map_radius * 7 / 10;
657     const int very_far = map_radius * 9 / 10;
658 
659     bool did_map = false;
660     int  num_altars        = 0;
661     int  num_shops_portals = 0;
662 
663     const FixedArray<uint8_t, GXM, GYM>& difficulty =
664         _tile_difficulties(!deterministic);
665 
666     for (radius_iterator ri(in_bounds(origin) ? origin : you.pos(),
667                             map_radius, C_SQUARE);
668          ri; ++ri)
669     {
670         coord_def pos = *ri;
671         if (!wizard_map)
672         {
673             int threshold = proportion;
674 
675             const int dist = grid_distance(you.pos(), pos);
676 
677             if (dist > very_far)
678                 threshold = threshold / 3;
679             else if (dist > pfar)
680                 threshold = threshold * 2 / 3;
681 
682             if (difficulty(pos) > threshold)
683                 continue;
684         }
685 
686         map_cell& knowledge = env.map_knowledge(pos);
687 
688         if (knowledge.changed())
689         {
690             // If the player has already seen the square, update map
691             // knowledge with the new terrain. Otherwise clear what we had
692             // before.
693             if (knowledge.seen())
694             {
695                 dungeon_feature_type newfeat = env.grid(pos);
696                 trap_type tr = feat_is_trap(newfeat) ? get_trap_type(pos) : TRAP_UNASSIGNED;
697                 knowledge.set_feature(newfeat, env.grid_colours(pos), tr);
698             }
699             else
700                 knowledge.clear();
701         }
702 
703         // Don't assume that DNGN_UNSEEN cells ever count as mapped.
704         // Because of a bug at one point in map forgetting, cells could
705         // spuriously get marked as mapped even when they were completely
706         // unseen.
707         const bool already_mapped = knowledge.mapped()
708                             && knowledge.feat() != DNGN_UNSEEN;
709 
710         if (!wizard_map && (knowledge.seen() || already_mapped))
711             continue;
712 
713         const dungeon_feature_type feat = env.grid(pos);
714 
715         bool open = true;
716 
717         if (feat_is_solid(feat) && !feat_is_closed_door(feat))
718         {
719             open = false;
720             for (adjacent_iterator ai(pos); ai; ++ai)
721             {
722                 if (map_bounds(*ai) && (!feat_is_opaque(env.grid(*ai))
723                                         || feat_is_closed_door(env.grid(*ai))))
724                 {
725                     open = true;
726                     break;
727                 }
728             }
729         }
730 
731         if (open)
732         {
733             if (wizard_map)
734             {
735                 knowledge.set_feature(feat, _feat_default_map_colour(feat),
736                     feat_is_trap(env.grid(pos)) ? get_trap_type(pos)
737                                            : TRAP_UNASSIGNED);
738             }
739             else if (!knowledge.feat())
740             {
741                 auto base_feat = magic_map_base_feat(feat);
742                 auto colour = _feat_default_map_colour(base_feat);
743                 auto trap = feat_is_trap(env.grid(pos)) ? get_trap_type(pos)
744                                                    : TRAP_UNASSIGNED;
745                 knowledge.set_feature(base_feat, colour, trap);
746             }
747             if (emphasise(pos))
748                 knowledge.flags |= MAP_EMPHASIZE;
749 
750             if (wizard_map)
751             {
752                 if (is_notable_terrain(feat))
753                     seen_notable_thing(feat, pos);
754 
755                 set_terrain_seen(pos);
756 #ifdef USE_TILE
757                 tile_wizmap_terrain(pos);
758 #endif
759             }
760             else
761             {
762                 set_terrain_mapped(pos);
763 
764                 if (get_cell_map_feature(knowledge) == MF_STAIR_BRANCH)
765                     seen_notable_thing(feat, pos);
766 
767                 if (get_feature_dchar(feat) == DCHAR_ALTAR)
768                     num_altars++;
769                 else if (get_feature_dchar(feat) == DCHAR_ARCH)
770                     num_shops_portals++;
771             }
772 
773             did_map = true;
774         }
775     }
776 
777     if (!suppress_msg)
778     {
779         if (did_map)
780             mpr("You feel aware of your surroundings.");
781         else
782             canned_msg(MSG_DISORIENTED);
783 
784         vector<string> sensed;
785 
786         if (num_altars > 0)
787         {
788             sensed.push_back(make_stringf("%d altar%s", num_altars,
789                                           num_altars > 1 ? "s" : ""));
790         }
791 
792         if (num_shops_portals > 0)
793         {
794             const char* plur = num_shops_portals > 1 ? "s" : "";
795             sensed.push_back(make_stringf("%d shop%s/portal%s",
796                                           num_shops_portals, plur, plur));
797         }
798 
799         if (!sensed.empty())
800             mpr_comma_separated_list("You sensed ", sensed);
801     }
802 
803     return did_map;
804 }
805 
fully_map_level()806 void fully_map_level()
807 {
808     for (rectangle_iterator ri(1); ri; ++ri)
809     {
810         bool ok = false;
811         for (adjacent_iterator ai(*ri, false); ai; ++ai)
812             if (!feat_is_opaque(env.grid(*ai)))
813                 ok = true;
814         if (!ok)
815             continue;
816         env.map_knowledge(*ri).set_feature(env.grid(*ri), 0,
817             feat_is_trap(env.grid(*ri)) ? get_trap_type(*ri) : TRAP_UNASSIGNED);
818         set_terrain_seen(*ri);
819 #ifdef USE_TILE
820         tile_wizmap_terrain(*ri);
821 #endif
822         if (env.igrid(*ri) != NON_ITEM)
823             env.map_knowledge(*ri).set_detected_item();
824         env.pgrid(*ri) |= FPROP_SEEN_OR_NOEXP;
825     }
826 }
827 
mon_enemies_around(const monster * mons)828 bool mon_enemies_around(const monster* mons)
829 {
830     // If the monster has a foe, return true.
831     if (mons->foe != MHITNOT && mons->foe != MHITYOU)
832         return true;
833 
834     if (crawl_state.game_is_arena())
835     {
836         // If the arena-mode code in _handle_behaviour() hasn't set a foe then
837         // we don't have one.
838         return false;
839     }
840     else if (mons->wont_attack())
841     {
842         // Additionally, if an ally is nearby and *you* have a foe,
843         // consider it as the ally's enemy too.
844         return you.can_see(*mons) && there_are_monsters_nearby(true);
845     }
846     else
847     {
848         // For hostile monster* you* are the main enemy.
849         return mons->can_see(you);
850     }
851 }
852 
853 // Returns a string containing a representation of the map. Leading and
854 // trailing spaces are trimmed from each line. Leading and trailing empty
855 // lines are also snipped.
screenshot()856 string screenshot()
857 {
858     vector<string> lines(crawl_view.viewsz.y);
859     unsigned int lsp = GXM;
860     for (int y = 0; y < crawl_view.viewsz.y; y++)
861     {
862         string line;
863         for (int x = 0; x < crawl_view.viewsz.x; x++)
864         {
865             // in grid coords
866             const coord_def gc = view2grid(crawl_view.viewp +
867                                      coord_def(x, y));
868             char32_t ch =
869                   (!map_bounds(gc))             ? ' ' :
870                   (gc == you.pos())             ? mons_char(you.symbol)
871                                                 : get_cell_glyph(gc).ch;
872             line += stringize_glyph(ch);
873         }
874         // right-trim the line
875         for (int x = line.length() - 1; x >= 0; x--)
876             if (line[x] == ' ')
877                 line.erase(x);
878             else
879                 break;
880         // see how much it can be left-trimmed
881         for (unsigned int x = 0; x < line.length(); x++)
882             if (line[x] != ' ')
883             {
884                 if (lsp > x)
885                     lsp = x;
886                 break;
887             }
888         lines[y] = line;
889     }
890 
891     for (string &line : lines)
892         line.erase(0, lsp);     // actually trim from the left
893     while (!lines.empty() && lines.back().empty())
894         lines.pop_back();       // then from the bottom
895 
896     ostringstream ss;
897     unsigned int y = 0;
898     for (y = 0; y < lines.size() && lines[y].empty(); y++)
899         ;                       // ... and from the top
900     for (; y < lines.size(); y++)
901         ss << lines[y] << "\n";
902     return ss.str();
903 }
904 
viewmap_flash_colour()905 int viewmap_flash_colour()
906 {
907     return _layers & LAYERS_ALL && you.berserk() ? RED : BLACK;
908 }
909 
910 // Updates one square of the view area. Should only be called for square
911 // in LOS.
view_update_at(const coord_def & pos)912 void view_update_at(const coord_def &pos)
913 {
914     if (pos == you.pos())
915         return;
916 
917     show_update_at(pos);
918 #ifdef USE_TILE
919     tile_draw_map_cell(pos, true);
920 #endif
921 #ifdef USE_TILE_WEB
922     tiles.mark_for_redraw(pos);
923 #endif
924 
925 #ifndef USE_TILE_LOCAL
926     if (!env.map_knowledge(pos).visible())
927         return;
928     cglyph_t g = get_cell_glyph(pos);
929 
930     int flash_colour = you.flash_colour == BLACK
931         ? viewmap_flash_colour()
932         : you.flash_colour;
933     monster_type mons = env.map_knowledge(pos).monster();
934     int cell_colour =
935         flash_colour &&
936         (mons == MONS_NO_MONSTER || mons_class_is_firewood(mons))
937             ? real_colour(flash_colour)
938             : g.col;
939 
940     const coord_def vp = grid2view(pos);
941     // Don't draw off-screen.
942     if (crawl_view.in_viewport_v(vp))
943     {
944         cgotoxy(vp.x, vp.y, GOTO_DNGN);
945         put_colour_ch(cell_colour, g.ch);
946     }
947 
948     // Force colour back to normal, else clrscr() will flood screen
949     // with this colour on DOS.
950     textcolour(LIGHTGREY);
951 
952 #endif
953 #ifdef USE_TILE_WEB
954     tiles.mark_for_redraw(pos);
955 #endif
956 }
957 
view_update()958 bool view_update()
959 {
960     if (you.num_turns > you.last_view_update)
961     {
962         viewwindow();
963         update_screen();
964         return true;
965     }
966     return false;
967 }
968 
flash_view(use_animation_type a,colour_t colour,targeter * where)969 void flash_view(use_animation_type a, colour_t colour, targeter *where)
970 {
971     if (crawl_state.need_save && Options.use_animations & a)
972     {
973 #ifndef USE_TILE_LOCAL
974         save_cursor_pos save;
975 #endif
976 
977         you.flash_colour = colour;
978         you.flash_where = where;
979         viewwindow(false);
980         update_screen();
981     }
982 }
983 
flash_view_delay(use_animation_type a,colour_t colour,int flash_delay,targeter * where)984 void flash_view_delay(use_animation_type a, colour_t colour, int flash_delay,
985                       targeter *where)
986 {
987     if (crawl_state.need_save && Options.use_animations & a)
988     {
989         flash_view(a, colour, where);
990         scaled_delay(flash_delay);
991         flash_view(a, 0);
992     }
993 }
994 
995 enum class update_flag
996 {
997     affect_excludes = (1 << 0),
998     added_exclude   = (1 << 1),
999 };
1000 DEF_BITFIELD(update_flags, update_flag);
1001 
1002 // Do various updates when the player sees a cell. Returns whether
1003 // exclusion LOS might have been affected.
player_view_update_at(const coord_def & gc)1004 static update_flags player_view_update_at(const coord_def &gc)
1005 {
1006     maybe_remove_autoexclusion(gc);
1007     update_flags ret;
1008 
1009     // Set excludes in a radius of 1 around harmful clouds genereated
1010     // by neither monsters nor the player.
1011     const cloud_struct* cloud = cloud_at(gc);
1012     if (cloud && !crawl_state.game_is_arena())
1013     {
1014         const cloud_struct &cl = *cloud;
1015 
1016         bool did_exclude = false;
1017         if (!cl.temporary() && is_damaging_cloud(cl.type, false))
1018         {
1019             int size = cl.exclusion_radius();
1020 
1021             // Steam clouds are less dangerous than the other ones,
1022             // so don't exclude the neighbour cells.
1023             if (cl.type == CLOUD_STEAM && size == 1)
1024                 size = 0;
1025 
1026             bool was_exclusion = is_exclude_root(gc);
1027             set_exclude(gc, size, true, false, true);
1028             if (!did_exclude && !was_exclusion)
1029                 ret |= update_flag::added_exclude;
1030         }
1031     }
1032 
1033     // Print hints mode messages for features in LOS.
1034     if (crawl_state.game_is_hints())
1035         hints_observe_cell(gc);
1036 
1037     if (env.map_knowledge(gc).changed() || !env.map_knowledge(gc).seen())
1038         ret |= update_flag::affect_excludes;
1039 
1040     set_terrain_visible(gc);
1041 
1042     if (!(env.pgrid(gc) & FPROP_SEEN_OR_NOEXP))
1043     {
1044         if (!crawl_state.game_is_arena()
1045             && cell_triggers_conduct(gc)
1046             && !player_in_branch(BRANCH_TEMPLE))
1047         {
1048             did_god_conduct(DID_EXPLORATION, 2500);
1049             const int density = env.density ? env.density : 2000;
1050             you.exploration += div_rand_round(1<<16, density);
1051             roll_trap_effects();
1052         }
1053         env.pgrid(gc) |= FPROP_SEEN_OR_NOEXP;
1054     }
1055 
1056 #ifdef USE_TILE
1057     const coord_def ep = grid2show(gc);
1058 
1059     // We remove any references to mcache when
1060     // writing to the background.
1061     tile_env.bk_fg(gc) = tile_env.fg(ep);
1062     tile_env.bk_bg(gc) = tile_env.bg(ep);
1063     tile_env.bk_cloud(gc) = tile_env.cloud(ep);
1064 #endif
1065 
1066     return ret;
1067 }
1068 
player_view_update()1069 static void player_view_update()
1070 {
1071     if (crawl_state.game_is_arena())
1072     {
1073         for (rectangle_iterator ri(crawl_view.vgrdc, LOS_MAX_RANGE); ri; ++ri)
1074             player_view_update_at(*ri);
1075         // no need to do excludes on the arena
1076         return;
1077     }
1078 
1079     vector<coord_def> update_excludes;
1080     bool need_update = false;
1081 
1082     for (vision_iterator ri(you); ri; ++ri)
1083     {
1084         update_flags flags = player_view_update_at(*ri);
1085         if (flags & update_flag::affect_excludes)
1086             update_excludes.push_back(*ri);
1087         if (flags & update_flag::added_exclude)
1088             need_update = true;
1089     }
1090     // Update exclusion LOS for possibly affected excludes.
1091     update_exclusion_los(update_excludes);
1092     // Catch up on deferred updates for cloud excludes.
1093     if (need_update)
1094         deferred_exclude_update();
1095 }
1096 
_draw_out_of_bounds(screen_cell_t * cell)1097 static void _draw_out_of_bounds(screen_cell_t *cell)
1098 {
1099 #ifndef USE_TILE_LOCAL
1100     cell->glyph  = ' ';
1101     cell->colour = DARKGREY;
1102 #endif
1103 #ifdef USE_TILE
1104     cell->tile.fg = 0;
1105     cell->tile.bg = tileidx_out_of_bounds(you.where_are_you);
1106 #endif
1107 }
1108 
_draw_outside_los(screen_cell_t * cell,const coord_def & gc,const coord_def & ep)1109 static void _draw_outside_los(screen_cell_t *cell, const coord_def &gc,
1110                                     const coord_def &ep)
1111 {
1112 #ifndef USE_TILE_LOCAL
1113     // Outside the env.show area.
1114     cglyph_t g = get_cell_glyph(gc);
1115     cell->glyph  = g.ch;
1116     cell->colour = g.col;
1117 #endif
1118 
1119 #ifdef USE_TILE
1120     // this is just for out-of-los rays, but I don't see a more efficient way..
1121     if (in_bounds(gc))
1122         cell->tile.bg = tile_env.bg(ep);
1123 
1124     tileidx_out_of_los(&cell->tile.fg, &cell->tile.bg, &cell->tile.cloud, gc);
1125 #else
1126     UNUSED(ep);
1127 #endif
1128 }
1129 
_draw_player(screen_cell_t * cell,const coord_def & gc,const coord_def & ep,bool anim_updates)1130 static void _draw_player(screen_cell_t *cell,
1131                          const coord_def &gc, const coord_def &ep,
1132                          bool anim_updates)
1133 {
1134 #ifndef USE_TILE_LOCAL
1135     // Player overrides everything in cell.
1136     cell->glyph  = mons_char(you.symbol);
1137     cell->colour = mons_class_colour(you.symbol);
1138     if (you.swimming())
1139     {
1140         if (env.grid(gc) == DNGN_DEEP_WATER)
1141             cell->colour = BLUE;
1142         else
1143             cell->colour = CYAN;
1144     }
1145     if (Options.use_fake_player_cursor)
1146         cell->colour |= COLFLAG_REVERSE;
1147 
1148     cell->colour = real_colour(cell->colour);
1149 #endif
1150 
1151 #ifdef USE_TILE
1152     cell->tile.fg = tile_env.fg(ep) = tileidx_player();
1153     cell->tile.bg = tile_env.bg(ep);
1154     cell->tile.cloud = tile_env.cloud(ep);
1155     if (anim_updates)
1156         tile_apply_animations(cell->tile.bg, &tile_env.flv(gc));
1157 #else
1158     UNUSED(ep, anim_updates);
1159 #endif
1160 }
1161 
_draw_los(screen_cell_t * cell,const coord_def & gc,const coord_def & ep,bool anim_updates)1162 static void _draw_los(screen_cell_t *cell,
1163                       const coord_def &gc, const coord_def &ep,
1164                       bool anim_updates)
1165 {
1166 #ifndef USE_TILE_LOCAL
1167     cglyph_t g = get_cell_glyph(gc);
1168     cell->glyph  = g.ch;
1169     cell->colour = g.col;
1170 #endif
1171 
1172 #ifdef USE_TILE
1173     cell->tile.fg = tile_env.fg(ep);
1174     cell->tile.bg = tile_env.bg(ep);
1175     cell->tile.cloud = tile_env.cloud(ep);
1176     if (anim_updates)
1177         tile_apply_animations(cell->tile.bg, &tile_env.flv(gc));
1178 #else
1179     UNUSED(ep, anim_updates);
1180 #endif
1181 }
1182 
1183 class shake_viewport_animation: public animation
1184 {
1185 public:
shake_viewport_animation()1186     shake_viewport_animation() { frames = 5; frame_delay = 40; }
1187 
init_frame(int)1188     void init_frame(int /*frame*/) override
1189     {
1190         offset = coord_def();
1191         offset.x = random2(3) - 1;
1192         offset.y = random2(3) - 1;
1193     }
1194 
cell_cb(const coord_def & pos,int &)1195     coord_def cell_cb(const coord_def &pos, int &/*colour*/) override
1196     {
1197         return pos + offset;
1198     }
1199 
1200 private:
1201     coord_def offset;
1202 };
1203 
1204 class banish_animation: public animation
1205 {
1206 public:
banish_animation()1207     banish_animation(): remaining(false) { }
1208 
init_frame(int frame)1209     void init_frame(int frame) override
1210     {
1211         current_frame = frame;
1212 
1213         if (!frame)
1214         {
1215             frames = 10;
1216             hidden.clear();
1217             remaining = true;
1218         }
1219 
1220         if (remaining)
1221             frames = frame + 2;
1222         else
1223             frames = frame;
1224 
1225         remaining = false;
1226     }
1227 
cell_cb(const coord_def & pos,int &)1228     coord_def cell_cb(const coord_def &pos, int &/*colour*/) override
1229     {
1230         if (pos == you.pos())
1231             return pos;
1232 
1233         if (bool *found = map_find(hidden, pos))
1234         {
1235             if (*found)
1236                 return coord_def(-1, -1);
1237         }
1238 
1239         if (!random2(10 - current_frame))
1240         {
1241             hidden.insert(make_pair(pos, true));
1242             return coord_def(-1, -1);
1243         }
1244 
1245         remaining = true;
1246         return pos;
1247     }
1248 
1249     bool remaining;
1250     map<coord_def, bool> hidden;
1251     int current_frame;
1252 };
1253 
1254 class orb_animation: public animation
1255 {
1256 public:
init_frame(int frame)1257     void init_frame(int frame) override
1258     {
1259         current_frame = frame;
1260         range = current_frame > 5
1261             ? (10 - current_frame)
1262             : current_frame;
1263         frame_delay = 3 * (6 - range) * (6 - range);
1264     }
1265 
cell_cb(const coord_def & pos,int & colour)1266     coord_def cell_cb(const coord_def &pos, int &colour) override
1267     {
1268         const coord_def diff = pos - you.pos();
1269         const int dist = diff.x * diff.x * 4 / 9 + diff.y * diff.y;
1270         const int min = range * range;
1271         const int max = (range + 2) * (range  + 2);
1272         if (dist >= min && dist < max)
1273             if (is_tiles())
1274                 colour = MAGENTA;
1275             else
1276                 colour = DARKGREY;
1277         else
1278             colour = 0;
1279 
1280         return pos;
1281     }
1282 
1283     int range;
1284     int current_frame;
1285 };
1286 
1287 static shake_viewport_animation shake_viewport;
1288 static banish_animation banish;
1289 static orb_animation orb;
1290 
1291 static animation *animations[NUM_ANIMATIONS] = {
1292     &shake_viewport,
1293     &banish,
1294     &orb
1295 };
1296 
run_animation(animation_type anim,use_animation_type type,bool cleanup)1297 void run_animation(animation_type anim, use_animation_type type, bool cleanup)
1298 {
1299 #ifdef USE_TILE_WEB
1300     // XXX this doesn't work in webtiles yet
1301     if (is_tiles())
1302         return;
1303 #endif
1304     if (Options.use_animations & type)
1305     {
1306         animation *a = animations[anim];
1307 
1308         viewwindow();
1309         update_screen();
1310 
1311         for (int i = 0; i < a->frames; ++i)
1312         {
1313             a->init_frame(i);
1314             viewwindow(false, false, a);
1315             update_screen();
1316             delay(a->frame_delay);
1317         }
1318 
1319         if (cleanup)
1320         {
1321             viewwindow();
1322             update_screen();
1323         }
1324     }
1325 }
1326 
1327 static bool _view_is_updating = false;
1328 
1329 crawl_view_buffer view_dungeon(animation *a, bool anim_updates, view_renderer *renderer);
1330 
_viewwindow_should_render()1331 static bool _viewwindow_should_render()
1332 {
1333     if (you.asleep())
1334         return false;
1335     if (mouse_control::current_mode() != MOUSE_MODE_NORMAL)
1336         return true;
1337     if (you.running && you.running.is_rest())
1338         return Options.rest_delay != -1;
1339     const bool run_dont_draw = you.running && Options.travel_delay < 0
1340                 && (!you.running.is_explore() || Options.explore_delay < 0);
1341     return !run_dont_draw;
1342 }
1343 
1344 /**
1345  * Draws the main window using the character set returned
1346  * by get_show_glyph().
1347  *
1348  * @param show_updates if true, env.show and dependent structures
1349  *                     are updated. Should be set if anything in
1350  *                     view has changed.
1351  * @param tiles_only if true, only the tile view will be updated. This
1352  *                   is only relevant for Webtiles.
1353  * @param a[in] the animation to be showing, if any.
1354  * @param renderer[in] A view renderer used to inject extra visual elements.
1355  */
viewwindow(bool show_updates,bool tiles_only,animation * a,view_renderer * renderer)1356 void viewwindow(bool show_updates, bool tiles_only, animation *a, view_renderer *renderer)
1357 {
1358     if (_view_is_updating)
1359     {
1360         // recursive calls to this function can lead to memory corruption or
1361         // crashes, depending on the circumstance, because some functions
1362         // called from here (e.g. show_init) will wipe out a whole bunch of
1363         // map data that will still be lurking around higher on the call stack
1364         // as references. Because the call paths are so complicated, it's hard
1365         // to find a principled / non-brute-force way of preventing recursion
1366         // here -- though it's still better to prevent it by other means if
1367         // possible.
1368         dprf("Recursive viewwindow call attempted!");
1369         return;
1370     }
1371 
1372     {
1373         unwind_bool updating(_view_is_updating, true);
1374 
1375 #ifndef USE_TILE_LOCAL
1376         save_cursor_pos save;
1377 
1378         if (crawl_state.smallterm)
1379         {
1380             smallterm_warning();
1381             update_screen();
1382             return;
1383         }
1384 #endif
1385 
1386         // The player could be at (0,0) if we are called during level-gen; this can
1387         // happen via mpr -> interrupt_activity -> stop_delay -> runrest::stop
1388         if (you.duration[DUR_TIME_STEP] || you.pos().origin())
1389             return;
1390 
1391         // Update the animation of cells only once per turn.
1392         const bool anim_updates = (you.last_view_update != you.num_turns);
1393 
1394         if (anim_updates)
1395             you.frame_no++;
1396 
1397 #ifdef USE_TILE
1398         tiles.clear_text_tags(TAG_NAMED_MONSTER);
1399 
1400         if (show_updates)
1401             mcache.clear_nonref();
1402 #endif
1403 
1404         if (show_updates || _layers != LAYERS_ALL)
1405         {
1406             if (!is_map_persistent())
1407                 ash_detect_portals(false);
1408 
1409             // TODO: why on earth is this called from here? It seems like it
1410             // should be called directly on changing location, or something
1411             // like that...
1412             if (you.on_current_level)
1413                 show_init(_layers);
1414 
1415 #ifdef USE_TILE
1416             tile_draw_floor();
1417             tile_draw_map_cells();
1418 #endif
1419             view_clear_overlays();
1420         }
1421 
1422         if (show_updates)
1423             player_view_update();
1424 
1425         if (_viewwindow_should_render())
1426         {
1427             const auto vbuf = view_dungeon(a, anim_updates, renderer);
1428 
1429             you.last_view_update = you.num_turns;
1430 #ifndef USE_TILE_LOCAL
1431             if (!tiles_only)
1432             {
1433                 puttext(crawl_view.viewp.x, crawl_view.viewp.y, vbuf);
1434                 update_monster_pane();
1435             }
1436 #else
1437             UNUSED(tiles_only);
1438 #endif
1439 #ifdef USE_TILE
1440             tiles.set_need_redraw(you.running ? Options.tile_runrest_rate : 0);
1441             tiles.load_dungeon(vbuf, crawl_view.vgrdc);
1442             tiles.update_tabs();
1443 #endif
1444 
1445             // Leaving it this way because short flashes can occur in long ones,
1446             // and this simply works without requiring a stack.
1447             you.flash_colour = BLACK;
1448             you.flash_where = 0;
1449         }
1450 
1451         // Reset env.show if we munged it.
1452         if (_layers != LAYERS_ALL)
1453             show_init();
1454     }
1455 }
1456 
1457 #ifdef USE_TILE
1458 struct tile_overlay
1459 {
1460     coord_def gc;
1461     tileidx_t tile;
1462 };
1463 static vector<tile_overlay> tile_overlays;
1464 static unsigned int tile_overlay_i;
1465 
view_add_tile_overlay(const coord_def & gc,tileidx_t tile)1466 void view_add_tile_overlay(const coord_def &gc, tileidx_t tile)
1467 {
1468     tile_overlays.push_back({gc, tile});
1469 }
1470 #endif
1471 
1472 #ifndef USE_TILE_LOCAL
1473 struct glyph_overlay
1474 {
1475     coord_def gc;
1476     cglyph_t glyph;
1477 };
1478 static vector<glyph_overlay> glyph_overlays;
1479 static unsigned int glyph_overlay_i;
1480 
view_add_glyph_overlay(const coord_def & gc,cglyph_t glyph)1481 void view_add_glyph_overlay(const coord_def &gc, cglyph_t glyph)
1482 {
1483     glyph_overlays.push_back({gc, glyph});
1484 }
1485 #endif
1486 
view_clear_overlays()1487 void view_clear_overlays()
1488 {
1489 #ifdef USE_TILE
1490     tile_overlays.clear();
1491 #endif
1492 #ifndef USE_TILE_LOCAL
1493     glyph_overlays.clear();
1494 #endif
1495 }
1496 
1497 /**
1498  * Comparison function for coord_defs that orders coords based on the ordering
1499  * used by rectangle_iterator.
1500  */
_coord_def_cmp(const coord_def & l,const coord_def & r)1501 static bool _coord_def_cmp(const coord_def& l, const coord_def& r)
1502 {
1503     return l.y < r.y || (l.y == r.y && l.x < r.x);
1504 }
1505 
_sort_overlays()1506 static void _sort_overlays()
1507 {
1508     /* Stable sort is needed so that we don't swap draw order within cells. */
1509 #ifdef USE_TILE
1510     stable_sort(begin(tile_overlays), end(tile_overlays),
1511                 [](const tile_overlay &left, const tile_overlay &right) {
1512                     return _coord_def_cmp(left.gc, right.gc);
1513                 });
1514     tile_overlay_i = 0;
1515 #endif
1516 #ifndef USE_TILE_LOCAL
1517     stable_sort(begin(glyph_overlays), end(glyph_overlays),
1518                 [](const glyph_overlay &left, const glyph_overlay &right) {
1519                     return _coord_def_cmp(left.gc, right.gc);
1520                 });
1521     glyph_overlay_i = 0;
1522 #endif
1523 }
1524 
add_overlays(const coord_def & gc,screen_cell_t * cell)1525 static void add_overlays(const coord_def& gc, screen_cell_t* cell)
1526 {
1527 #ifdef USE_TILE
1528     while (tile_overlay_i < tile_overlays.size()
1529            && _coord_def_cmp(tile_overlays[tile_overlay_i].gc, gc))
1530     {
1531         tile_overlay_i++;
1532     }
1533     while (tile_overlay_i < tile_overlays.size()
1534            && tile_overlays[tile_overlay_i].gc == gc)
1535     {
1536         const auto &overlay = tile_overlays[tile_overlay_i];
1537         if (cell->tile.num_dngn_overlay == 0
1538             || cell->tile.dngn_overlay[cell->tile.num_dngn_overlay - 1]
1539                                             != static_cast<int>(overlay.tile))
1540         {
1541             cell->tile.dngn_overlay[cell->tile.num_dngn_overlay++] = overlay.tile;
1542         }
1543         tile_overlay_i++;
1544     }
1545 #endif
1546 #ifndef USE_TILE_LOCAL
1547     while (glyph_overlay_i < glyph_overlays.size()
1548            && _coord_def_cmp(glyph_overlays[glyph_overlay_i].gc, gc))
1549     {
1550         glyph_overlay_i++;
1551     }
1552     while (glyph_overlay_i < glyph_overlays.size()
1553            && glyph_overlays[glyph_overlay_i].gc == gc)
1554     {
1555         const auto &overlay = glyph_overlays[glyph_overlay_i];
1556         cell->glyph = overlay.glyph.ch;
1557         cell->colour = overlay.glyph.col;
1558         glyph_overlay_i++;
1559     }
1560 #endif
1561 }
1562 
1563 /**
1564  * Constructs the main dungeon view, rendering it into a new crawl_view_buffer.
1565  *
1566  * @param a[in] the animation to be showing, if any.
1567  * @return A new view buffer with the rendered content.
1568  */
view_dungeon(animation * a,bool anim_updates,view_renderer * renderer)1569 crawl_view_buffer view_dungeon(animation *a, bool anim_updates, view_renderer *renderer)
1570 {
1571     crawl_view_buffer vbuf(crawl_view.viewsz);
1572 
1573     screen_cell_t *cell(vbuf);
1574 
1575     cursor_control cs(false);
1576 
1577     _sort_overlays();
1578 
1579     int flash_colour = you.flash_colour;
1580     if (flash_colour == BLACK)
1581         flash_colour = viewmap_flash_colour();
1582 
1583     const coord_def tl = coord_def(1, 1);
1584     const coord_def br = vbuf.size();
1585     for (rectangle_iterator ri(tl, br); ri; ++ri)
1586     {
1587         // in grid coords
1588         const coord_def gc = a
1589             ? a->cell_cb(view2grid(*ri), flash_colour)
1590             : view2grid(*ri);
1591 
1592         if (you.flash_where && you.flash_where->is_affected(gc) <= 0)
1593             draw_cell(cell, gc, anim_updates, 0);
1594         else
1595             draw_cell(cell, gc, anim_updates, flash_colour);
1596 
1597         cell++;
1598     }
1599 
1600     if (renderer)
1601         renderer->render(vbuf);
1602 
1603     return vbuf;
1604 }
1605 
draw_cell(screen_cell_t * cell,const coord_def & gc,bool anim_updates,int flash_colour)1606 void draw_cell(screen_cell_t *cell, const coord_def &gc,
1607                bool anim_updates, int flash_colour)
1608 {
1609 #ifdef USE_TILE
1610     cell->tile.clear();
1611 #endif
1612     const coord_def ep = grid2show(gc);
1613 
1614     if (!map_bounds(gc))
1615         _draw_out_of_bounds(cell);
1616     else if (!crawl_view.in_los_bounds_g(gc))
1617         _draw_outside_los(cell, gc, coord_def());
1618     else if (gc == you.pos() && you.on_current_level
1619              && _layers & LAYER_PLAYER
1620              && !crawl_state.game_is_arena()
1621              && !crawl_state.arena_suspended)
1622     {
1623         _draw_player(cell, gc, ep, anim_updates);
1624     }
1625     else if (you.see_cell(gc) && you.on_current_level)
1626         _draw_los(cell, gc, ep, anim_updates);
1627     else
1628         _draw_outside_los(cell, gc, ep); // in los bounds but not visible
1629 
1630 #ifdef USE_TILE
1631     cell->tile.map_knowledge = map_bounds(gc) ? env.map_knowledge(gc) : map_cell();
1632     cell->flash_colour = BLACK;
1633 #endif
1634 
1635 #ifndef USE_TILE_LOCAL
1636     // Don't hide important information by recolouring monsters.
1637     bool allow_mon_recolour = query_map_knowledge(true, gc, [](const map_cell& m) {
1638         return m.monster() == MONS_NO_MONSTER || mons_class_is_firewood(m.monster());
1639     });
1640 #endif
1641 
1642     // Is this cell excluded from movement by mesmerise-related statuses?
1643     // MAP_WITHHELD is set in `show.cc:_update_feat_at`.
1644     bool mesmerise_excluded = (gc != you.pos() // for fungus form
1645                                && map_bounds(gc)
1646                                && you.on_current_level
1647                                && (env.map_knowledge(gc).flags & MAP_WITHHELD)
1648                                && !feat_is_solid(env.grid(gc)));
1649 
1650     // Alter colour if flashing the characters vision.
1651     if (flash_colour)
1652     {
1653 #ifndef USE_TILE_LOCAL
1654         if (!you.see_cell(gc))
1655             cell->colour = DARKGREY;
1656         else if (gc != you.pos() && allow_mon_recolour)
1657             cell->colour = real_colour(flash_colour);
1658 #endif
1659 #ifdef USE_TILE
1660         if (you.see_cell(gc))
1661             cell->flash_colour = real_colour(flash_colour);
1662 #endif
1663     }
1664     else if (crawl_state.darken_range)
1665     {
1666         if ((crawl_state.darken_range->obeys_mesmerise && mesmerise_excluded)
1667             || (!crawl_state.darken_range->valid_aim(gc)))
1668         {
1669 #ifndef USE_TILE_LOCAL
1670             if (allow_mon_recolour)
1671                 cell->colour = DARKGREY;
1672 #endif
1673 #ifdef USE_TILE
1674             if (you.see_cell(gc))
1675                 cell->tile.bg |= TILE_FLAG_OOR;
1676 #endif
1677         }
1678     }
1679     else if (crawl_state.flash_monsters)
1680     {
1681 #ifndef USE_TILE_LOCAL
1682         bool found = gc == you.pos();
1683 
1684         if (!found)
1685             for (auto mon : *crawl_state.flash_monsters)
1686             {
1687                 if (gc == mon->pos())
1688                 {
1689                     found = true;
1690                     break;
1691                 }
1692             }
1693 
1694         if (!found)
1695             cell->colour = DARKGREY;
1696 #endif
1697     }
1698     else if (mesmerise_excluded) // but no range limits in place
1699     {
1700 #ifndef USE_TILE_LOCAL
1701         if (allow_mon_recolour)
1702             cell->colour = DARKGREY;
1703 #endif
1704 
1705 #ifdef USE_TILE
1706         // Only grey out tiles within LOS; out-of-LOS tiles are already
1707         // darkened.
1708         if (you.see_cell(gc))
1709             cell->tile.bg |= TILE_FLAG_OOR;
1710 #endif
1711     }
1712 
1713 #ifdef USE_TILE
1714     tile_apply_properties(gc, cell->tile);
1715 #endif
1716 
1717 #ifndef USE_TILE_LOCAL
1718     if ((_layers != LAYERS_ALL || Options.always_show_exclusions)
1719         && you.on_current_level
1720         && map_bounds(gc)
1721         && (_layers == LAYERS_NONE
1722             || gc != you.pos()
1723                && (env.map_knowledge(gc).monster() == MONS_NO_MONSTER
1724                    || !you.see_cell(gc)))
1725         && travel_colour_override(gc))
1726     {
1727         if (is_exclude_root(gc))
1728             cell->colour = Options.tc_excluded;
1729         else if (is_excluded(gc))
1730             cell->colour = Options.tc_exclude_circle;
1731     }
1732 #endif
1733 
1734     add_overlays(gc, cell);
1735 }
1736 
1737 // Hide view layers. The player can toggle certain layers back on
1738 // and the resulting configuration will be remembered for the
1739 // remainder of the game session.
_config_layers_menu()1740 static void _config_layers_menu()
1741 {
1742     bool exit = false;
1743 
1744     _layers = _layers_saved;
1745     crawl_state.viewport_weapons    = !!(_layers & LAYER_MONSTER_WEAPONS);
1746     crawl_state.viewport_monster_hp = !!(_layers & LAYER_MONSTER_HEALTH);
1747 
1748     msgwin_set_temporary(true);
1749     while (!exit)
1750     {
1751         viewwindow();
1752         update_screen();
1753         mprf(MSGCH_PROMPT, "Select layers to display:\n"
1754                            "<%s>(m)onsters</%s>|"
1755                            "<%s>(p)layer</%s>|"
1756                            "<%s>(i)tems</%s>|"
1757                            "<%s>(c)louds</%s>"
1758 #ifndef USE_TILE_LOCAL
1759                            "|"
1760                            "<%s>monster (w)eapons</%s>|"
1761                            "<%s>monster (h)ealth</%s>"
1762 #endif
1763                            ,
1764            _layers & LAYER_MONSTERS        ? "lightgrey" : "darkgrey",
1765            _layers & LAYER_MONSTERS        ? "lightgrey" : "darkgrey",
1766            _layers & LAYER_PLAYER          ? "lightgrey" : "darkgrey",
1767            _layers & LAYER_PLAYER          ? "lightgrey" : "darkgrey",
1768            _layers & LAYER_ITEMS           ? "lightgrey" : "darkgrey",
1769            _layers & LAYER_ITEMS           ? "lightgrey" : "darkgrey",
1770            _layers & LAYER_CLOUDS          ? "lightgrey" : "darkgrey",
1771            _layers & LAYER_CLOUDS          ? "lightgrey" : "darkgrey"
1772 #ifndef USE_TILE_LOCAL
1773            ,
1774            _layers & LAYER_MONSTER_WEAPONS ? "lightgrey" : "darkgrey",
1775            _layers & LAYER_MONSTER_WEAPONS ? "lightgrey" : "darkgrey",
1776            _layers & LAYER_MONSTER_HEALTH  ? "lightgrey" : "darkgrey",
1777            _layers & LAYER_MONSTER_HEALTH  ? "lightgrey" : "darkgrey"
1778 #endif
1779         );
1780         mprf(MSGCH_PROMPT, "Press 'a' to toggle all layers. "
1781                            "Press any other key to exit.");
1782 
1783         switch (get_ch())
1784         {
1785         case 'm': _layers_saved = _layers ^= LAYER_MONSTERS;        break;
1786         case 'p': _layers_saved = _layers ^= LAYER_PLAYER;          break;
1787         case 'i': _layers_saved = _layers ^= LAYER_ITEMS;           break;
1788         case 'c': _layers_saved = _layers ^= LAYER_CLOUDS;          break;
1789 #ifndef USE_TILE_LOCAL
1790         case 'w': _layers_saved = _layers ^= LAYER_MONSTER_WEAPONS;
1791                   if (_layers & LAYER_MONSTER_WEAPONS)
1792                       _layers_saved = _layers |= LAYER_MONSTERS;
1793                   break;
1794         case 'h': _layers_saved = _layers ^= LAYER_MONSTER_HEALTH;
1795                   if (_layers & LAYER_MONSTER_HEALTH)
1796                       _layers_saved = _layers |= LAYER_MONSTERS;
1797                   break;
1798 #endif
1799         case 'a': if (_layers)
1800                       _layers_saved = _layers = LAYERS_NONE;
1801                   else
1802                   {
1803 #ifndef USE_TILE_LOCAL
1804                       _layers_saved = _layers = LAYERS_ALL
1805                                       | LAYER_MONSTER_WEAPONS
1806                                       | LAYER_MONSTER_HEALTH;
1807 #else
1808                       _layers_saved = _layers = LAYERS_ALL;
1809 #endif
1810                   }
1811                   break;
1812         default:
1813             _layers = LAYERS_ALL;
1814             crawl_state.viewport_weapons    = !!(_layers & LAYER_MONSTER_WEAPONS);
1815             crawl_state.viewport_monster_hp = !!(_layers & LAYER_MONSTER_HEALTH);
1816             exit = true;
1817             break;
1818         }
1819 
1820         crawl_state.viewport_weapons    = !!(_layers & LAYER_MONSTER_WEAPONS);
1821         crawl_state.viewport_monster_hp = !!(_layers & LAYER_MONSTER_HEALTH);
1822 
1823         msgwin_clear_temporary();
1824     }
1825     msgwin_set_temporary(false);
1826 
1827     canned_msg(MSG_OK);
1828 }
1829 
toggle_show_terrain()1830 void toggle_show_terrain()
1831 {
1832     if (_layers == LAYERS_ALL)
1833         _config_layers_menu();
1834     else
1835         reset_show_terrain();
1836 }
1837 
reset_show_terrain()1838 void reset_show_terrain()
1839 {
1840     if (_layers != LAYERS_ALL)
1841         mprf(MSGCH_PROMPT, "Restoring view layers.");
1842 
1843     _layers = LAYERS_ALL;
1844     crawl_state.viewport_weapons    = !!(_layers & LAYER_MONSTER_WEAPONS);
1845     crawl_state.viewport_monster_hp = !!(_layers & LAYER_MONSTER_HEALTH);
1846 }
1847 
1848 ////////////////////////////////////////////////////////////////////////////
1849 // Term resize handling (generic).
1850 
handle_terminal_resize()1851 void handle_terminal_resize()
1852 {
1853     crawl_state.terminal_resized = false;
1854 
1855     if (crawl_state.terminal_resize_handler)
1856         (*crawl_state.terminal_resize_handler)();
1857     else
1858         crawl_view.init_geometry();
1859 
1860     redraw_screen();
1861     update_screen();
1862 }
1863