1 /**
2  * @file
3  * @brief Rendering of map_cell to glyph and colour.
4  *
5  * This only needs the information within one object of type map_cell.
6 **/
7 
8 #include "AppHdr.h"
9 
10 #include "showsymb.h"
11 
12 #include "cloud.h"
13 #include "colour.h"
14 #include "env.h"
15 #include "item-name.h"
16 #include "libutil.h" // map_find
17 #include "options.h"
18 #include "religion.h"
19 #include "stash.h"
20 #include "state.h"
21 #include "stringutil.h"
22 #include "tag-version.h"
23 #include "terrain.h"
24 #include "travel.h"
25 #include "viewchar.h"
26 #include "mon-tentacle.h"
27 #include "tilepick.h"
28 #include "rltiles/tiledef-player.h"
29 
_cell_feat_show_colour(const map_cell & cell,const coord_def & loc,bool coloured)30 static unsigned short _cell_feat_show_colour(const map_cell& cell,
31                                              const coord_def& loc,
32                                              bool coloured)
33 {
34     dungeon_feature_type feat = cell.feat();
35     unsigned short colour = BLACK;
36     const feature_def &fdef = get_feature_def(feat);
37 
38     // These do not obey vault recolouring.
39     const bool no_vault_recolour = feat_has_dry_floor(feat)
40                                    && feat != DNGN_FLOOR
41                                    && !feat_is_open_door(feat);
42 
43     // These aren't shown mossy/bloody/slimy in console.
44     const bool norecolour = feat_is_door(feat) || no_vault_recolour;
45 
46     if (is_stair_exclusion(loc))
47         colour = Options.tc_excluded;
48     else if (!coloured)
49     {
50         if (cell.flags & MAP_EMPHASIZE)
51             colour = fdef.seen_em_colour();
52         else if (cell.flags & MAP_MAGIC_MAPPED_FLAG)
53             colour = fdef.unseen_colour();
54         else
55             colour = fdef.seen_colour();
56 
57         if (colour)
58             return colour;
59     }
60     else if (!feat_is_solid(feat)
61              && (cell.flags & (MAP_SANCTUARY_1 | MAP_SANCTUARY_2)))
62     {
63         if (cell.flags & MAP_SANCTUARY_1)
64             colour = YELLOW;
65         else if (cell.flags & MAP_SANCTUARY_2)
66         {
67             if (!one_chance_in(4))
68                 colour = WHITE;     // 3/4
69             else if (!one_chance_in(3))
70                 colour = LIGHTCYAN; // 1/6
71             else
72                 colour = LIGHTGREY; // 1/12
73         }
74     }
75     else if (cell.flags & MAP_BLOODY && !norecolour)
76         colour = RED;
77     else if (cell.flags & MAP_CORRODING && feat == DNGN_FLOOR)
78         colour = LIGHTGREEN;
79     else if (cell.flags & MAP_ICY
80              && (feat_is_wall(feat) || feat == DNGN_FLOOR))
81     {
82         if (feat_is_wall(feat))
83             colour = ETC_ICE;
84         else
85             colour = LIGHTCYAN;
86     }
87     else if (cell.feat_colour() && !no_vault_recolour)
88         colour = cell.feat_colour();
89     else
90     {
91         colour = fdef.colour();
92 
93         if (fdef.em_colour() && fdef.em_colour() != fdef.colour()
94             && cell.flags & MAP_EMPHASIZE)
95         {
96             colour = fdef.em_colour();
97         }
98     }
99 
100     if (feat == DNGN_SHALLOW_WATER && player_in_branch(BRANCH_SHOALS))
101         colour = ETC_WAVES;
102 
103     if (feat_is_tree(feat) && env.forest_awoken_until)
104         colour = ETC_AWOKEN_FOREST;
105 
106     if (feat == DNGN_FLOOR)
107     {
108         if (cell.flags & MAP_LIQUEFIED)
109             colour = ETC_LIQUEFIED;
110         else if (cell.flags & MAP_DISJUNCT)
111             colour = ETC_DISJUNCTION;
112         else if (cell.flags & MAP_HALOED)
113         {
114             if (cell.flags & MAP_SILENCED && cell.flags & MAP_UMBRAED)
115                 colour = CYAN; // Default for silence.
116             else if (cell.flags & MAP_SILENCED)
117                 colour = LIGHTCYAN;
118             else if (cell.flags & MAP_UMBRAED)
119                 colour = fdef.colour(); // Cancels out!
120             else
121                 colour = YELLOW;
122         }
123         else if (cell.flags & MAP_UMBRAED)
124         {
125             if (cell.flags & MAP_SILENCED)
126                 colour = BLUE; // Silence gets darker
127             else
128                 colour = MAGENTA; // If no holy or silence
129         }
130         else if (cell.flags & MAP_SILENCED)
131             colour = CYAN; // Silence but no holy/unholy
132         else if (cell.flags & MAP_ORB_HALOED)
133             colour = ETC_ORB_GLOW;
134         else if (cell.flags & MAP_QUAD_HALOED)
135             colour = BLUE;
136 #if TAG_MAJOR_VERSION == 34
137         else if (cell.flags & MAP_HOT)
138             colour = ETC_FIRE;
139 #endif
140     }
141 
142     return colour;
143 }
144 
_show_mons_type(const monster_info & mi)145 static monster_type _show_mons_type(const monster_info& mi)
146 {
147     if (mi.type == MONS_SLIME_CREATURE && mi.slime_size > 1)
148         return MONS_MERGED_SLIME_CREATURE;
149     else if (mi.type == MONS_ZOMBIE)
150     {
151         return mons_zombie_size(mi.base_type) == Z_BIG ?
152             MONS_ZOMBIE_LARGE : MONS_ZOMBIE_SMALL;
153     }
154     else if (mi.type == MONS_SKELETON)
155     {
156         return mons_zombie_size(mi.base_type) == Z_BIG ?
157             MONS_SKELETON_LARGE : MONS_SKELETON_SMALL;
158     }
159     else if (mi.type == MONS_SIMULACRUM)
160     {
161         return mons_zombie_size(mi.base_type) == Z_BIG ?
162             MONS_SIMULACRUM_LARGE : MONS_SIMULACRUM_SMALL;
163     }
164     else if (mi.type == MONS_SENSED)
165         return mi.base_type;
166 
167     return mi.type;
168 }
169 
_get_mons_colour(const monster_info & mi)170 static int _get_mons_colour(const monster_info& mi)
171 {
172     // Show hp directly on the monster, except for irrelevant ones.
173     // Fedhas worshippers might be interested in their plants however.
174     if (crawl_state.viewport_monster_hp
175         && (you_worship(GOD_FEDHAS) || !mons_class_is_firewood(mi.type)))
176     {
177         return dam_colour(mi) | COLFLAG_ITEM_HEAP;
178     }
179 
180     int col = mi.colour();
181 
182     // We really shouldn't store unmodified colour. This hack compares
183     // effective type, but really, all redefinitions should work instantly,
184     // rather than for newly spawned monsters only.
185     monster_type stype = _show_mons_type(mi);
186     if (stype != mi.type && mi.type != MONS_SENSED)
187         col = mons_class_colour(stype);
188 
189     if (mi.is(MB_ROLLING))
190         col = ETC_BONE;
191 
192     if (mi.is(MB_BERSERK))
193         col = RED;
194 
195     if (mi.is(MB_MIRROR_DAMAGE))
196         col = ETC_DEATH;
197 
198     if (mi.is(MB_INNER_FLAME))
199         col = ETC_FIRE;
200 
201     if (mi.attitude == ATT_FRIENDLY)
202         col |= COLFLAG_FRIENDLY_MONSTER;
203     else if (mi.attitude != ATT_HOSTILE)
204         col |= COLFLAG_NEUTRAL_MONSTER;
205     else if (Options.stab_brand != CHATTR_NORMAL
206              && mi.is(MB_STABBABLE))
207     {
208         col |= COLFLAG_WILLSTAB;
209     }
210     else if (Options.may_stab_brand != CHATTR_NORMAL
211              && mi.is(MB_DISTRACTED))
212     {
213         col |= COLFLAG_MAYSTAB;
214     }
215     else if (mons_class_is_stationary(mi.type))
216     {
217         if (Options.feature_item_brand != CHATTR_NORMAL
218             && feat_stair_direction(env.grid(mi.pos)) != CMD_NO_CMD)
219         {
220             col |= COLFLAG_FEATURE_ITEM;
221         }
222         else if (Options.heap_brand != CHATTR_NORMAL
223                  && you.visible_igrd(mi.pos) != NON_ITEM
224                  && !crawl_state.game_is_arena())
225         {
226             col |= COLFLAG_ITEM_HEAP;
227         }
228     }
229 
230     // Backlit monsters are fuzzy and override colours, but not brands.
231     if (!crawl_state.game_is_arena()
232         && !you.can_see_invisible()
233         && mi.is(MB_INVISIBLE)
234         && mi.attitude != ATT_FRIENDLY)
235     {
236         col = (col & COLFLAG_MASK) | DARKGREY;
237     }
238 
239     return col;
240 }
241 
_get_item_override(const item_def & item)242 static cglyph_t _get_item_override(const item_def &item)
243 {
244     cglyph_t g;
245     g.ch = 0;
246     g.col = 0;
247 
248     // Skip costly name calculations if not needed.
249     if (Options.item_glyph_overrides.empty())
250         return g;
251 
252     // use qualname for gold so that pile quantity doesn't affect caching.
253     // Could extend this to missiles, but in that case I can actually imagine
254     // someone writing annotation code that relies on a count.
255     // TODO: the caching here avoids the regex penalty, but annotation and
256     // item.name themselves are quite heavy when called a lot, so some better
257     // caching might be in order. (Or more efficient calls to this function.)
258     string name = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item)
259                 + " {" + item_prefix(item, false) + "} "
260                 + item.name(item.base_type == OBJ_GOLD ? DESC_QUALNAME : DESC_PLAIN);
261 
262     {
263         // Check the cache...
264         if (cglyph_t *gly = map_find(Options.item_glyph_cache, name))
265             return *gly;
266     }
267 
268     for (auto ir : Options.item_glyph_overrides)
269     {
270         text_pattern tpat(ir.first);
271         if (tpat.matches(name))
272         {
273             // You may have a rule that sets the glyph but not colour for
274             // axes, then another that sets colour only for artefacts
275             // (useless items, etc). Thus, apply only parts that apply.
276             if (ir.second.ch)
277                 g.ch = ir.second.ch;
278             if (ir.second.col)
279                 g.col = ir.second.col;
280         }
281     }
282 
283     // Matching against a list of regexps can be costly, save up to 1000
284     // last matches.
285     if (Options.item_glyph_cache.size() >= 1000)
286         Options.item_glyph_cache.clear();
287     Options.item_glyph_cache[name] = g;
288 
289     return g;
290 }
291 
get_cell_show_class(const map_cell & cell,bool only_stationary_monsters)292 show_class get_cell_show_class(const map_cell& cell,
293                                bool only_stationary_monsters)
294 {
295     if (cell.invisible_monster())
296         return SH_INVIS_EXPOSED;
297 
298     if (cell.monster() != MONS_NO_MONSTER
299         && (!only_stationary_monsters
300             || mons_class_is_stationary(cell.monster())))
301     {
302         return SH_MONSTER;
303     }
304 
305     if (cell.cloud() != CLOUD_NONE)
306         return SH_CLOUD;
307 
308     const dungeon_feature_type feat = cell.feat();
309     if (feat && feat_is_solid(feat)
310         || feat_has_dry_floor(feat)
311            && feat != DNGN_FLOOR
312            && !feat_is_open_door(feat)
313            && feat != DNGN_ABANDONED_SHOP
314            && feat != DNGN_STONE_ARCH
315            && feat != DNGN_EXPIRED_PORTAL
316            && !feat_is_fountain(feat))
317     {
318         return SH_FEATURE;
319     }
320 
321     if (cell.item())
322         return SH_ITEM;
323 
324     if (cell.feat())
325         return SH_FEATURE;
326 
327     return SH_NOTHING;
328 }
329 
330 static const unsigned short ripple_table[] =
331 {
332      BLUE,          // BLACK        => BLUE (default)
333      BLUE,          // BLUE         => BLUE
334      GREEN,         // GREEN        => GREEN
335      CYAN,          // CYAN         => CYAN
336      RED,           // RED          => RED
337      MAGENTA,       // MAGENTA      => MAGENTA
338      BROWN,         // BROWN        => BROWN
339      DARKGREY,      // LIGHTGREY    => DARKGREY
340      DARKGREY,      // DARKGREY     => DARKGREY
341      BLUE,          // LIGHTBLUE    => BLUE
342      GREEN,         // LIGHTGREEN   => GREEN
343      BLUE,          // LIGHTCYAN    => BLUE
344      RED,           // LIGHTRED     => RED
345      MAGENTA,       // LIGHTMAGENTA => MAGENTA
346      BROWN,         // YELLOW       => BROWN
347      LIGHTGREY,     // WHITE        => LIGHTGREY
348 };
349 
_get_cell_glyph_with_class(const map_cell & cell,const coord_def & loc,const show_class cls,int colour_mode)350 static cglyph_t _get_cell_glyph_with_class(const map_cell& cell,
351                                            const coord_def& loc,
352                                            const show_class cls,
353                                            int colour_mode)
354 {
355     const bool coloured = colour_mode == 0 ? cell.visible() : (colour_mode > 0);
356     cglyph_t g;
357     show_type show;
358 
359     g.ch = 0;
360     const cloud_type cell_cloud = cell.cloud();
361 
362     switch (cls)
363     {
364     case SH_INVIS_EXPOSED:
365         ASSERT(cell.invisible_monster());
366 
367         show.cls = SH_INVIS_EXPOSED;
368         if (cell_cloud != CLOUD_NONE)
369             g.col = cell.cloud_colour();
370         else
371             g.col = ripple_table[cell.feat_colour() & 0xf];
372         break;
373 
374     case SH_MONSTER:
375     {
376         show = cell.monster();
377         const monster_info* mi = cell.monsterinfo();
378         ASSERT(mi);
379 
380         if (cell.detected_monster())
381         {
382             ASSERT(mi->type == MONS_SENSED);
383             if (mons_is_sensed(mi->base_type))
384                 g.col = mons_class_colour(mi->base_type);
385             else
386                 g.col = Options.detected_monster_colour;
387         }
388         else if (!coloured)
389             g.col = Options.remembered_monster_colour;
390         else
391             g.col = _get_mons_colour(*mi);
392 
393         monster_type stype = _show_mons_type(*mi);
394         if (show.mons == MONS_SENSED)
395             g.ch = mons_char(mi->base_type);
396         else
397             g.ch = mons_char(stype);
398 
399         if (mons_is_tentacle_segment(stype))
400         {
401             switch (tileidx_tentacle(*mi))
402             {
403             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_N_SW:
404             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_N_SE:
405             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_S_NW:
406             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_S_NE:
407             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_N_S:
408                 g.ch = dchar_glyph(DCHAR_DRAW_VERT);
409                 break;
410             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_W_NE:
411             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_W_SE:
412             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_E_NW:
413             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_E_SW:
414             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_E_W:
415                 g.ch = dchar_glyph(DCHAR_DRAW_HORIZ);
416                 break;
417             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_NE_SW:
418                 g.ch = dchar_glyph(DCHAR_DRAW_SLASH);
419                 break;
420             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_NW_SE:
421                 g.ch = dchar_glyph(DCHAR_DRAW_BACKSLASH);
422                 break;
423             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_E_N:
424                 g.ch = dchar_glyph(DCHAR_DRAW_BL);
425                 break;
426             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_E_S:
427                 g.ch = dchar_glyph(DCHAR_DRAW_TL);
428                 break;
429             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_S_W:
430                 g.ch = dchar_glyph(DCHAR_DRAW_TR);
431                 break;
432             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_N_W:
433                 g.ch = dchar_glyph(DCHAR_DRAW_BR);
434                 break;
435             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_NE_NW:
436                 g.ch = dchar_glyph(DCHAR_DRAW_DOWN);
437                 break;
438             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_SE_SW:
439                 g.ch = dchar_glyph(DCHAR_DRAW_UP);
440                 break;
441             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_NW_SW:
442                 g.ch = dchar_glyph(DCHAR_DRAW_RIGHT);
443                 break;
444             case TILEP_MONS_KRAKEN_TENTACLE_SEGMENT_NE_SE:
445                 g.ch = dchar_glyph(DCHAR_DRAW_LEFT);
446                 break;
447             default: break;
448             }
449         }
450 
451         // If we want to show weapons, overwrite all of that.
452         item_def* weapon = mi->inv[MSLOT_WEAPON].get();
453         if (crawl_state.viewport_weapons && weapon)
454         {
455             show = *weapon;
456             g = _get_item_override(*weapon);
457             if (!g.col)
458                 g.col = weapon->get_colour();
459         }
460 
461         break;
462     }
463 
464     case SH_CLOUD:
465         ASSERT(cell_cloud);
466         show.cls = SH_CLOUD;
467         if (coloured)
468             g.col = cell.cloud_colour();
469         else
470             g.col = DARKGRAY;
471 
472         if (cloud_type_tile_info(cell.cloudinfo()->type).variation
473             == CTVARY_DUR)
474         {
475             // duration is already clamped to 0-3
476             int dur = cell.cloudinfo()->duration;
477             switch (dur)
478             {
479             case 0:
480                 g.ch = dchar_glyph(DCHAR_CLOUD_TERMINAL);
481                 break;
482             case 1:
483                 g.ch = dchar_glyph(DCHAR_CLOUD_FADING);
484                 break;
485             case 2:
486                 g.ch = dchar_glyph(DCHAR_CLOUD_WEAK);
487                 break;
488             case 3:
489                 g.ch = dchar_glyph(DCHAR_CLOUD);
490                 break;
491             }
492         }
493         else
494             g.ch = dchar_glyph(DCHAR_CLOUD);
495         break;
496 
497     case SH_FEATURE:
498         show = cell.feat();
499         ASSERT(show);
500 
501         g.col = _cell_feat_show_colour(cell, loc, coloured);
502 
503         if (cell.item())
504         {
505             if (Options.feature_item_brand
506                 && (feat_is_critical(cell.feat())
507                     || feat_is_solid(cell.feat())))
508             {
509                 g.col |= COLFLAG_FEATURE_ITEM;
510             }
511             else if (Options.trap_item_brand && feat_is_trap(cell.feat()))
512                 g.col |= COLFLAG_TRAP_ITEM;
513         }
514         break;
515 
516     case SH_ITEM:
517     {
518         const item_def* eitem = cell.item();
519         ASSERT(eitem);
520         show = *eitem;
521 
522         g = _get_item_override(*eitem);
523 
524         if (feat_is_water(cell.feat()))
525             g.col = _cell_feat_show_colour(cell, loc, coloured);
526         else if (!g.col)
527             g.col = eitem->get_colour();
528 
529         // monster(mimic)-owned items have link = NON_ITEM+1+midx
530         if (cell.flags & MAP_MORE_ITEMS)
531             g.col |= COLFLAG_ITEM_HEAP;
532         break;
533     }
534 
535     case SH_NOTHING:
536     case NUM_SHOW_CLASSES:
537         // blackness
538         g.ch = ' ';
539         return g;
540     }
541 
542     if (Options.show_travel_trail && travel_trail_index(loc) >= 0)
543     {
544         const feature_def& fd = get_feature_def(DNGN_TRAVEL_TRAIL);
545 
546         if (fd.symbol())
547             g.ch = fd.symbol();
548         if (fd.colour() != COLOUR_UNDEF)
549             g.col = fd.colour();
550 
551         g.col |= COLFLAG_REVERSE;
552     }
553 
554     if (!g.ch)
555     {
556         const feature_def &fdef = get_feature_def(show);
557         g.ch = !cell.seen() || cell.flags & MAP_EMPHASIZE ? fdef.magic_symbol()
558                                                           : fdef.symbol();
559     }
560 
561     if (g.col)
562         g.col = real_colour(g.col, loc);
563 
564     return g;
565 }
566 
get_cell_glyph(const coord_def & loc,bool only_stationary_monsters,int colour_mode)567 cglyph_t get_cell_glyph(const coord_def& loc, bool only_stationary_monsters,
568                         int colour_mode)
569 {
570     // note: this does NOT determine output of the player glyph;
571     // that's handled by itself in _draw_player() in view.cc
572     const map_cell& cell = env.map_knowledge(loc);
573     const show_class cell_show_class =
574         get_cell_show_class(cell, only_stationary_monsters);
575     return _get_cell_glyph_with_class(cell, loc, cell_show_class, colour_mode);
576 }
577 
get_feat_symbol(dungeon_feature_type feat)578 char32_t get_feat_symbol(dungeon_feature_type feat)
579 {
580     return get_feature_def(feat).symbol();
581 }
582 
get_item_symbol(show_item_type it)583 char32_t get_item_symbol(show_item_type it)
584 {
585     return get_feature_def(show_type(it)).symbol();
586 }
587 
get_item_glyph(const item_def & item)588 cglyph_t get_item_glyph(const item_def& item)
589 {
590     cglyph_t g = _get_item_override(item);
591     if (!g.ch)
592         g.ch = get_feature_def(show_type(item)).symbol();
593     if (!g.col)
594         g.col = item.get_colour();
595     return g;
596 }
597 
get_mons_glyph(const monster_info & mi)598 cglyph_t get_mons_glyph(const monster_info& mi)
599 {
600     monster_type stype = _show_mons_type(mi);
601     cglyph_t g;
602 
603     g.ch = mons_char(stype);
604     g.col = _get_mons_colour(mi);
605     g.col = real_colour(g.col);
606     return g;
607 }
608 
glyph_to_tagstr(const cglyph_t & g)609 string glyph_to_tagstr(const cglyph_t& g)
610 {
611     string col = colour_to_str(g.col);
612     string ch = stringize_glyph(g.ch);
613     if (g.ch == '<')
614         ch += "<";
615     return make_stringf("<%s>%s</%s>", col.c_str(), ch.c_str(), col.c_str());
616 }
617