Merge branch 'panlord-colour'
[crawl.git] / crawl-ref / source / describe.cc
1 /**
2  * @file
3  * @brief Functions used to print information about various game objects.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "describe.h"
9
10 #include <cstdio>
11 #include <iomanip>
12 #include <numeric>
13 #include <set>
14 #include <sstream>
15 #include <string>
16
17 #include "artefact.h"
18 #include "branch.h"
19 #include "butcher.h"
20 #include "clua.h"
21 #include "command.h"
22 #include "database.h"
23 #include "decks.h"
24 #include "delay.h"
25 #include "describe-spells.h"
26 #include "directn.h"
27 #include "english.h"
28 #include "env.h"
29 #include "evoke.h"
30 #include "fight.h"
31 #include "food.h"
32 #include "ghost.h"
33 #include "godabil.h"
34 #include "goditem.h"
35 #include "hints.h"
36 #include "invent.h"
37 #include "itemprop.h"
38 #include "items.h"
39 #include "item_use.h"
40 #include "jobs.h"
41 #include "libutil.h"
42 #include "macro.h"
43 #include "message.h"
44 #include "mon-book.h"
45 #include "mon-chimera.h"
46 #include "mon-tentacle.h"
47 #include "options.h"
48 #include "output.h"
49 #include "process_desc.h"
50 #include "prompt.h"
51 #include "religion.h"
52 #include "skills.h"
53 #include "spl-book.h"
54 #include "spl-summoning.h"
55 #include "spl-util.h"
56 #include "spl-wpnench.h"
57 #include "stash.h"
58 #include "state.h"
59 #include "terrain.h"
60 #ifdef USE_TILE_LOCAL
61  #include "tilereg-crt.h"
62 #endif
63 #include "unicode.h"
64
65 extern const spell_type serpent_of_hell_breaths[4][3];
66
67 // ========================================================================
68 //      Internal Functions
69 // ========================================================================
70
71 //---------------------------------------------------------------
72 //
73 // append_value
74 //
75 // Appends a value to the string. If plussed == 1, will add a + to
76 // positive values.
77 //
78 //---------------------------------------------------------------
79 static void _append_value(string & description, int valu, bool plussed)
80 {
81     char value_str[80];
82     sprintf(value_str, plussed ? "%+d" : "%d", valu);
83     description += value_str;
84 }
85
86 static void _append_value(string & description, float fvalu, bool plussed)
87 {
88     char value_str[80];
89     sprintf(value_str, plussed ? "%+.1f" : "%.1f", fvalu);
90     description += value_str;
91 }
92
93 int count_desc_lines(const string &_desc, const int width)
94 {
95     string desc = get_linebreak_string(_desc, width);
96
97     int count = 0;
98     for (int i = 0, size = desc.size(); i < size; ++i)
99     {
100         const char ch = desc[i];
101
102         if (ch == '\n')
103             count++;
104     }
105
106     return count;
107 }
108
109 static void _adjust_item(item_def &item);
110
111 //---------------------------------------------------------------
112 //
113 // print_description
114 //
115 // Takes a descrip string filled up with stuff from other functions,
116 // and displays it with minor formatting to avoid cut-offs in mid
117 // word and such.
118 //
119 //---------------------------------------------------------------
120
121 void print_description(const string &body)
122 {
123     describe_info inf;
124     inf.body << body;
125     print_description(inf);
126 }
127
128 class default_desc_proc
129 {
130 public:
131     int width() { return get_number_of_cols() - 1; }
132     int height() { return get_number_of_lines(); }
133     void print(const string &str) { cprintf("%s", str.c_str()); }
134
135     void nextline()
136     {
137         if (wherey() < height())
138             cgotoxy(1, wherey() + 1);
139         else
140             cgotoxy(1, height());
141         // Otherwise cgotoxy asserts; let's just clobber the last line
142         // instead, which should be noticeable enough.
143     }
144 };
145
146 void print_description(const describe_info &inf)
147 {
148     clrscr();
149     textcolour(LIGHTGREY);
150
151     default_desc_proc proc;
152     process_description<default_desc_proc>(proc, inf);
153 }
154
155 static void _print_quote(const describe_info &inf)
156 {
157     clrscr();
158     textcolour(LIGHTGREY);
159
160     default_desc_proc proc;
161     process_quote<default_desc_proc>(proc, inf);
162 }
163
164 static const char* _jewellery_base_ability_string(int subtype)
165 {
166     switch (subtype)
167     {
168     case RING_SUSTAIN_ABILITIES: return "SustAb";
169     case RING_WIZARDRY:          return "Wiz";
170     case RING_FIRE:              return "Fire";
171     case RING_ICE:               return "Ice";
172     case RING_TELEPORTATION:     return "+/*Tele";
173     case RING_TELEPORT_CONTROL:  return "+cTele";
174     case AMU_CLARITY:            return "Clar";
175     case AMU_WARDING:            return "Ward";
176     case AMU_RESIST_CORROSION:   return "rCorr";
177     case AMU_THE_GOURMAND:       return "Gourm";
178 #if TAG_MAJOR_VERSION == 34
179     case AMU_CONSERVATION:       return "Cons";
180     case AMU_CONTROLLED_FLIGHT:  return "cFly";
181 #endif
182     case AMU_RESIST_MUTATION:    return "rMut";
183     case AMU_GUARDIAN_SPIRIT:    return "Spirit";
184     case AMU_FAITH:              return "Faith";
185     case AMU_STASIS:             return "Stasis";
186     case AMU_INACCURACY:         return "Inacc";
187     }
188     return "";
189 }
190
191 #define known_proprt(prop) (proprt[(prop)] && known[(prop)])
192
193 struct property_annotators
194 {
195     const char* name;
196     artefact_prop_type prop;
197     int spell_out;              // 0: "+3", 1: "+++", 2: value doesn't matter
198 };
199
200 static vector<string> _randart_propnames(const item_def& item,
201                                          bool no_comma = false)
202 {
203     artefact_properties_t  proprt;
204     artefact_known_props_t known;
205     artefact_desc_properties(item, proprt, known, true);
206
207     vector<string> propnames;
208
209     // list the following in rough order of importance
210     const property_annotators propanns[] =
211     {
212         // (Generally) negative attributes
213         // These come first, so they don't get chopped off!
214         { "-Cast",  ARTP_PREVENT_SPELLCASTING,  2 },
215         { "-Tele",  ARTP_PREVENT_TELEPORTATION, 2 },
216         { "Contam", ARTP_MUTAGENIC,             2 },
217         { "*Rage",  ARTP_ANGRY,                 2 },
218         { "*Tele",  ARTP_CAUSE_TELEPORTATION,   2 },
219         { "Noisy",  ARTP_NOISES,                2 },
220
221         // Evokable abilities come second
222         { "+Blink", ARTP_BLINK,                 2 },
223         { "+Rage",  ARTP_BERSERK,               2 },
224         { "+Inv",   ARTP_INVISIBLE,             2 },
225         { "+Fly",   ARTP_FLY,                   2 },
226         { "+Fog",   ARTP_FOG,                   2 },
227
228         // Resists, also really important
229         { "rElec",  ARTP_ELECTRICITY,           2 },
230         { "rPois",  ARTP_POISON,                2 },
231         { "rF",     ARTP_FIRE,                  1 },
232         { "rC",     ARTP_COLD,                  1 },
233         { "rN",     ARTP_NEGATIVE_ENERGY,       1 },
234         { "MR",     ARTP_MAGIC,                 1 },
235         { "rMut",   ARTP_RMUT,                  2 },
236         { "rCorr",  ARTP_RCORR,                 2 },
237
238         // Quantitative attributes
239         { "HP",     ARTP_HP,                    0 },
240         { "MP",     ARTP_MAGICAL_POWER,         0 },
241         { "AC",     ARTP_AC,                    0 },
242         { "EV",     ARTP_EVASION,               0 },
243         { "Str",    ARTP_STRENGTH,              0 },
244         { "Int",    ARTP_INTELLIGENCE,          0 },
245         { "Dex",    ARTP_DEXTERITY,             0 },
246         { "Slay",   ARTP_SLAYING,               0 },
247
248         // Qualitative attributes (and Stealth)
249         { "SInv",   ARTP_EYESIGHT,              2 },
250         { "Stlth",  ARTP_STEALTH,               1 },
251         { "Curse",  ARTP_CURSED,                2 },
252         { "Clar",   ARTP_CLARITY,               2 },
253         { "RMsl",   ARTP_RMSL,                  2 },
254         { "Regen",  ARTP_REGENERATION,          2 },
255         { "SustAb", ARTP_SUSTAB,                2 },
256     };
257
258     // For randart jewellery, note the base jewellery type if it's not
259     // covered by artefact_desc_properties()
260     if (item.base_type == OBJ_JEWELLERY
261         && (item_ident(item, ISFLAG_KNOW_TYPE)))
262     {
263         const char* type = _jewellery_base_ability_string(item.sub_type);
264         if (*type)
265             propnames.push_back(type);
266     }
267     else if (item_ident(item, ISFLAG_KNOW_TYPE)
268              || is_artefact(item)
269                 && artefact_known_wpn_property(item, ARTP_BRAND))
270     {
271         string ego;
272         if (item.base_type == OBJ_WEAPONS)
273             ego = weapon_brand_name(item, true);
274         else if (item.base_type == OBJ_ARMOUR)
275             ego = armour_ego_name(item, true);
276         if (!ego.empty())
277         {
278             // XXX: Ugly hack for adding a comma if needed.
279             for (const property_annotators &ann : propanns)
280                 if (known_proprt(ann.prop) && ann.prop != ARTP_BRAND
281                     && !no_comma)
282                 {
283                     ego += ",";
284                     break;
285                 }
286
287             propnames.push_back(ego);
288         }
289     }
290
291     if (is_unrandom_artefact(item))
292     {
293         const unrandart_entry *entry = get_unrand_entry(item.special);
294         if (entry && entry->inscrip != nullptr)
295             propnames.push_back(entry->inscrip);
296      }
297
298     for (const property_annotators &ann : propanns)
299     {
300         if (known_proprt(ann.prop))
301         {
302             const int val = proprt[ann.prop];
303
304             // Don't show rF+/rC- for =Fire, or vice versa for =Ice.
305             if (item.base_type == OBJ_JEWELLERY)
306             {
307                 if (item.sub_type == RING_FIRE
308                     && (ann.prop == ARTP_FIRE && val == 1
309                         || ann.prop == ARTP_COLD && val == -1))
310                 {
311                     continue;
312                 }
313                 if (item.sub_type == RING_ICE
314                     && (ann.prop == ARTP_COLD && val == 1
315                         || ann.prop == ARTP_FIRE && val == -1))
316                 {
317                     continue;
318                 }
319             }
320
321             ostringstream work;
322             switch (ann.spell_out)
323             {
324             case 0: // e.g. AC+4
325                 work << showpos << ann.name << val;
326                 break;
327             case 1: // e.g. F++
328             {
329                 // XXX: actually handle absurd values instead of displaying
330                 // the wrong number of +s or -s
331                 const int sval = min(abs(val), 6);
332                 work << ann.name
333                      << string(sval, (val > 0 ? '+' : '-'));
334                 break;
335             }
336             case 2: // e.g. rPois or SInv
337                 if (ann.prop == ARTP_CURSED && val < 1)
338                     continue;
339
340                 work << ann.name;
341                 break;
342             }
343             propnames.push_back(work.str());
344         }
345     }
346
347     return propnames;
348 }
349
350 string artefact_inscription(const item_def& item)
351 {
352     if (item.base_type == OBJ_BOOKS)
353         return "";
354
355     const vector<string> propnames = _randart_propnames(item);
356
357     string insc = comma_separated_line(propnames.begin(), propnames.end(),
358                                        " ", " ");
359     if (!insc.empty() && insc[insc.length() - 1] == ',')
360         insc.erase(insc.length() - 1);
361     return insc;
362 }
363
364 void add_inscription(item_def &item, string inscrip)
365 {
366     if (!item.inscription.empty())
367     {
368         if (ends_with(item.inscription, ","))
369             item.inscription += " ";
370         else
371             item.inscription += ", ";
372     }
373
374     item.inscription += inscrip;
375 }
376
377 static const char* _jewellery_base_ability_description(int subtype)
378 {
379     switch (subtype)
380     {
381     case RING_SUSTAIN_ABILITIES:
382         return "It sustains your strength, intelligence and dexterity.";
383     case RING_WIZARDRY:
384         return "It improves your spell success rate.";
385     case RING_FIRE:
386         return "It enhances your fire magic, and weakens your ice magic.";
387     case RING_ICE:
388         return "It enhances your ice magic, and weakens your fire magic.";
389     case RING_TELEPORTATION:
390         return "It causes random teleportation, and can be evoked to teleport "
391                "at will.";
392     case RING_TELEPORT_CONTROL:
393         return "It can be evoked for teleport control.";
394     case AMU_CLARITY:
395         return "It provides mental clarity.";
396     case AMU_WARDING:
397         return "It may prevent the melee attacks of summoned creatures.";
398     case AMU_RESIST_CORROSION:
399         return "It protects you from acid and corrosion.";
400     case AMU_THE_GOURMAND:
401         return "It allows you to eat raw meat even when not hungry.";
402 #if TAG_MAJOR_VERSION == 34
403     case AMU_CONSERVATION:
404         return "It protects your inventory from destruction.";
405 #endif
406     case AMU_RESIST_MUTATION:
407         return "It protects you from mutation.";
408     case AMU_GUARDIAN_SPIRIT:
409         return "It causes incoming damage to be split between your health and "
410                "magic.";
411     case AMU_FAITH:
412         return "It allows you to gain divine favour quickly.";
413     case AMU_STASIS:
414         return "It prevents you from being teleported, slowed, hasted or "
415                "paralysed.";
416     case AMU_INACCURACY:
417         return "It reduces the accuracy of all your attacks.";
418     }
419     return "";
420 }
421
422 struct property_descriptor
423 {
424     artefact_prop_type property;
425     const char* desc;           // If it contains %d, will be replaced by value.
426     bool is_graded_resist;
427 };
428
429 static string _randart_descrip(const item_def &item)
430 {
431     string description;
432
433     artefact_properties_t  proprt;
434     artefact_known_props_t known;
435     artefact_desc_properties(item, proprt, known, true);
436
437     const property_descriptor propdescs[] =
438     {
439         { ARTP_AC, "It affects your AC (%d).", false },
440         { ARTP_EVASION, "It affects your evasion (%d).", false},
441         { ARTP_STRENGTH, "It affects your strength (%d).", false},
442         { ARTP_INTELLIGENCE, "It affects your intelligence (%d).", false},
443         { ARTP_DEXTERITY, "It affects your dexterity (%d).", false},
444         { ARTP_SLAYING, "It affects your accuracy and damage with ranged "
445                         "weapons and melee attacks (%d).", false},
446         { ARTP_FIRE, "fire", true},
447         { ARTP_COLD, "cold", true},
448         { ARTP_ELECTRICITY, "It insulates you from electricity.", false},
449         { ARTP_POISON, "It protects you from poison.", false},
450         { ARTP_NEGATIVE_ENERGY, "negative energy", true},
451         { ARTP_SUSTAB, "It sustains your strength, intelligence and dexterity.", false},
452         { ARTP_MAGIC, "It affects your resistance to hostile enchantments.", false},
453         { ARTP_HP, "It affects your health (%d).", false},
454         { ARTP_MAGICAL_POWER, "It affects your magic capacity (%d).", false},
455         { ARTP_EYESIGHT, "It enhances your eyesight.", false},
456         { ARTP_INVISIBLE, "It lets you turn invisible.", false},
457         { ARTP_FLY, "It lets you fly.", false},
458         { ARTP_BLINK, "It lets you blink.", false},
459         { ARTP_BERSERK, "It lets you go berserk.", false},
460         { ARTP_NOISES, "It may make noises in combat.", false},
461         { ARTP_PREVENT_SPELLCASTING, "It prevents spellcasting.", false},
462         { ARTP_CAUSE_TELEPORTATION, "It causes random teleportation.", false},
463         { ARTP_PREVENT_TELEPORTATION, "It prevents most forms of teleportation.",
464           false},
465         { ARTP_ANGRY,  "It may make you go berserk in combat.", false},
466         { ARTP_CURSED, "It may recurse itself when equipped.", false},
467         { ARTP_CLARITY, "It protects you against confusion.", false},
468         { ARTP_MUTAGENIC, "It causes magical contamination when unequipped.", false},
469         { ARTP_RMSL, "It protects you from missiles.", false},
470         { ARTP_FOG, "It can be evoked to emit clouds of fog.", false},
471         { ARTP_REGENERATION, "It increases your rate of regeneration.", false},
472         { ARTP_RCORR, "It protects you from acid and corrosion.", false},
473         { ARTP_RMUT, "It protects you from mutation.", false},
474     };
475
476     // Give a short description of the base type, for base types with no
477     // corresponding ARTP.
478     if (item.base_type == OBJ_JEWELLERY
479         && (item_ident(item, ISFLAG_KNOW_TYPE)))
480     {
481         const char* type = _jewellery_base_ability_description(item.sub_type);
482         if (*type)
483         {
484             description += "\n";
485             description += type;
486         }
487     }
488
489     for (const property_descriptor &desc : propdescs)
490     {
491         if (known_proprt(desc.property))
492         {
493             // Only randarts with ARTP_CURSED > 0 may recurse themselves.
494             if (desc.property == ARTP_CURSED && proprt[desc.property] < 1)
495                 continue;
496
497             string sdesc = desc.desc;
498
499             // FIXME Not the nicest hack.
500             char buf[80];
501             snprintf(buf, sizeof buf, "%+d", proprt[desc.property]);
502             sdesc = replace_all(sdesc, "%d", buf);
503
504             if (desc.is_graded_resist)
505             {
506                 int idx = proprt[desc.property] + 3;
507                 idx = min(idx, 6);
508                 idx = max(idx, 0);
509
510                 const char* prefixes[] =
511                 {
512                     "It makes you extremely vulnerable to ",
513                     "It makes you very vulnerable to ",
514                     "It makes you vulnerable to ",
515                     "Buggy descriptor!",
516                     "It protects you from ",
517                     "It greatly protects you from ",
518                     "It renders you almost immune to "
519                 };
520                 sdesc = prefixes[idx] + sdesc + '.';
521             }
522
523             description += '\n';
524             description += sdesc;
525         }
526     }
527
528     if (known_proprt(ARTP_STEALTH))
529     {
530         const int stval = proprt[ARTP_STEALTH];
531         char buf[80];
532         snprintf(buf, sizeof buf, "\nIt makes you %s%s stealthy.",
533                  (stval < -1 || stval > 1) ? "much " : "",
534                  (stval < 0) ? "less" : "more");
535         description += buf;
536     }
537
538     return description;
539 }
540 #undef known_proprt
541
542 static const char *trap_names[] =
543 {
544 #if TAG_MAJOR_VERSION == 34
545     "dart",
546 #endif
547     "arrow", "spear",
548 #if TAG_MAJOR_VERSION > 34
549     "teleport",
550 #endif
551     "permanent teleport",
552     "alarm", "blade",
553     "bolt", "net", "Zot", "needle",
554     "shaft", "passage", "pressure plate", "web",
555 #if TAG_MAJOR_VERSION == 34
556     "gas", "teleport",
557 #endif
558 };
559
560 string trap_name(trap_type trap)
561 {
562     COMPILE_CHECK(ARRAYSZ(trap_names) == NUM_TRAPS);
563
564     if (trap >= 0 && trap < NUM_TRAPS)
565         return trap_names[trap];
566     return "";
567 }
568
569 string full_trap_name(trap_type trap)
570 {
571     string basename = trap_name(trap);
572     switch (trap)
573     {
574     case TRAP_GOLUBRIA:
575         return basename + " of Golubria";
576     case TRAP_PLATE:
577     case TRAP_WEB:
578     case TRAP_SHAFT:
579         return basename;
580     default:
581         return basename + " trap";
582     }
583 }
584
585 int str_to_trap(const string &s)
586 {
587     // "Zot trap" is capitalised in trap_names[], but the other trap
588     // names aren't.
589     const string tspec = lowercase_string(s);
590
591     // allow a couple of synonyms
592     if (tspec == "random" || tspec == "any")
593         return TRAP_RANDOM;
594
595     for (int i = 0; i < NUM_TRAPS; ++i)
596         if (tspec == lowercase_string(trap_names[i]))
597             return i;
598
599     return -1;
600 }
601
602 //---------------------------------------------------------------
603 //
604 // describe_demon
605 //
606 // Describes the random demons you find in Pandemonium.
607 //
608 //---------------------------------------------------------------
609 static string _describe_demon(const string& name, flight_type fly)
610 {
611     const uint32_t seed = hash32(&name[0], name.size());
612     #define HRANDOM_ELEMENT(arr, id) arr[hash_rand(ARRAYSZ(arr), seed, id)]
613
614     const char* body_descs[] =
615     {
616         "armoured ",
617         "vast, spindly ",
618         "fat ",
619         "obese ",
620         "muscular ",
621         "spiked ",
622         "splotchy ",
623         "slender ",
624         "tentacled ",
625         "emaciated ",
626         "bug-like ",
627         "skeletal ",
628         "mantis ",
629     };
630
631     const char* wing_names[] =
632     {
633         " with small, bat-like wings",
634         " with bony wings",
635         " with sharp, metallic wings",
636         " with the wings of a moth",
637         " with thin, membranous wings",
638         " with dragonfly wings",
639         " with large, powerful wings",
640         " with fluttering wings",
641         " with great, sinister wings",
642         " with hideous, tattered wings",
643         " with sparrow-like wings",
644         " with hooked wings",
645         " with strange knobs attached",
646     };
647
648     const char* lev_names[] =
649     {
650         " which hovers in mid-air",
651         " with sacs of gas hanging from its back",
652     };
653
654     const char* head_names[] =
655     {
656         " and a cubic structure in place of a head",
657         " and a brain for a head",
658         " and a hideous tangle of tentacles for a mouth",
659         " and the head of an elephant",
660         " and an eyeball for a head",
661         " and wears a helmet over its head",
662         " and a horn in place of a head",
663         " and a thick, horned head",
664         " and the head of a horse",
665         " and a vicious glare",
666         " and snakes for hair",
667         " and the face of a baboon",
668         " and the head of a mouse",
669         " and a ram's head",
670         " and the head of a rhino",
671         " and eerily human features",
672         " and a gigantic mouth",
673         " and a mass of tentacles growing from its neck",
674         " and a thin, worm-like head",
675         " and huge, compound eyes",
676         " and the head of a frog",
677         " and an insectoid head",
678         " and a great mass of hair",
679         " and a skull for a head",
680         " and a cow's skull for a head",
681         " and the head of a bird",
682         " and a large fungus growing from its neck",
683     };
684
685     const char* misc_descs[] =
686     {
687         " It seethes with hatred of the living.",
688         " Tiny orange flames dance around it.",
689         " Tiny purple flames dance around it.",
690         " It is surrounded by a weird haze.",
691         " It glows with a malevolent light.",
692         " It looks incredibly angry.",
693         " It oozes with slime.",
694         " It dribbles constantly.",
695         " Mould grows all over it.",
696         " It looks diseased.",
697         " It looks as frightened of you as you are of it.",
698         " It moves in a series of hideous convulsions.",
699         " It moves with an unearthly grace.",
700         " It hungers for your soul!",
701         " It leaves a glistening oily trail.",
702         " It shimmers before your eyes.",
703         " It is surrounded by a brilliant glow.",
704         " It radiates an aura of extreme power.",
705         " It seems utterly heartbroken.",
706         " It seems filled with irrepressible glee.",
707         " It constantly shivers and twitches.",
708         " Blue sparks crawl across its body.",
709         " It seems uncertain.",
710         " A cloud of flies swarms around it.",
711         " The air ripples with heat as it passes.",
712         " It appears supremely confident.",
713         " Its skin is covered in a network of cracks.",
714         " Its skin has a disgusting oily sheen.",
715         " It seems completely insane!",
716         " It seems somehow familiar."
717     };
718
719     ostringstream description;
720     description << "One of the many lords of Pandemonium, " << name << " has ";
721
722     const string a_body = HRANDOM_ELEMENT(body_descs, 2);
723     description << article_a(a_body) << "body";
724
725     string head_desc = HRANDOM_ELEMENT(head_names, 1);
726
727     switch (fly)
728     {
729     case FL_WINGED:
730         description << HRANDOM_ELEMENT(wing_names, 3);
731         if (head_desc.find(" with") == 0)
732             description << " and";
733         break;
734
735     case FL_LEVITATE:
736         description << HRANDOM_ELEMENT(lev_names, 3);
737         if (head_desc.find(" with") == 0)
738             description << " and";
739         break;
740
741     default:
742         break;
743     }
744
745     description << head_desc << ".";
746
747     if (hash_rand(40, seed, 4) < 3)
748     {
749         if (you.can_smell())
750         {
751             switch (hash_rand(4, seed, 5))
752             {
753             case 0:
754                 description << " It stinks of brimstone.";
755                 break;
756             case 1:
757                 description << " It is surrounded by a sickening stench.";
758                 break;
759             case 2:
760                 description << " It smells delicious!";
761                 break;
762             case 3:
763                 description << " It smells like rotting flesh"
764                             << (you.species == SP_GHOUL ? " - yum!"
765                                                        : ".");
766                 break;
767             }
768         }
769     }
770     else if (hash_rand(2, seed, 6))
771         description << HRANDOM_ELEMENT(misc_descs, 5);
772
773     return description.str();
774 }
775
776 void append_weapon_stats(string &description, const item_def &item)
777 {
778     description += "\nBase accuracy: ";
779     _append_value(description, property(item, PWPN_HIT), true);
780     description += "  ";
781
782     const int base_dam = property(item, PWPN_DAMAGE);
783     const int ammo_type = fires_ammo_type(item);
784     const int ammo_dam = ammo_type == MI_NONE ? 0 :
785                                                 ammo_type_damage(ammo_type);
786     description += "Base damage: ";
787     _append_value(description, base_dam + ammo_dam, false);
788     description += "  ";
789
790     description += "Base attack delay: ";
791     _append_value(description, property(item, PWPN_SPEED) / 10.0f, false);
792     description += "  ";
793
794     description += "Minimum delay: ";
795     _append_value(description, weapon_min_delay(item) / 10.0f, false);
796
797     if (item_attack_skill(item) == SK_SLINGS)
798     {
799         description += make_stringf("\nFiring bullets:    Base damage: %d",
800                                     base_dam +
801                                     ammo_type_damage(MI_SLING_BULLET));
802     }
803 }
804
805 static string _handedness_string(const item_def &item)
806 {
807     string description;
808
809     switch (you.hands_reqd(item))
810     {
811     case HANDS_ONE:
812         if (you.species == SP_FORMICID)
813             description += "It is a weapon for one hand-pair.";
814         else
815             description += "It is a one handed weapon.";
816         break;
817     case HANDS_TWO:
818         description += "It is a two handed weapon.";
819         break;
820     }
821
822     return description;
823 }
824
825 //---------------------------------------------------------------
826 //
827 // describe_weapon
828 //
829 //---------------------------------------------------------------
830 static string _describe_weapon(const item_def &item, bool verbose)
831 {
832     string description;
833
834     description.reserve(200);
835
836     description = "";
837
838     if (verbose)
839     {
840         description += "\n";
841         append_weapon_stats(description, item);
842     }
843
844     const int spec_ench = (is_artefact(item) || verbose)
845                           ? get_weapon_brand(item) : SPWPN_NORMAL;
846     const int damtype = get_vorpal_type(item);
847
848     if (verbose)
849     {
850         switch (item_attack_skill(item))
851         {
852         case SK_POLEARMS:
853             description += "\n\nIt can be evoked to extend its reach.";
854             break;
855         case SK_AXES:
856             description += "\n\nIt can hit multiple enemies in an arc"
857                            " around the wielder.";
858             break;
859         case SK_SHORT_BLADES:
860             // TODO: should we mention stabbing for "ok" stabbing weapons?
861             // (long blades, piercing polearms, and clubs)
862             {
863                 string adj = (item.sub_type == WPN_DAGGER) ? "extremely"
864                                                            : "particularly";
865                 description += "\n\nIt is " + adj + " good for stabbing"
866                                " unaware enemies.";
867             }
868             break;
869         default:
870             break;
871         }
872     }
873
874     // ident known & no brand but still glowing
875     // TODO: deduplicate this with the code in itemname.cc
876     const bool enchanted = get_equip_desc(item) && spec_ench == SPWPN_NORMAL
877                            && !item_ident(item, ISFLAG_KNOW_PLUSES);
878
879     // special weapon descrip
880     if (item_type_known(item) && (spec_ench != SPWPN_NORMAL || enchanted))
881     {
882         description += "\n\n";
883
884         switch (spec_ench)
885         {
886         case SPWPN_FLAMING:
887             if (is_range_weapon(item))
888             {
889                 description += "It turns projectiles fired from it into "
890                     "bolts of flame.";
891             }
892             else
893             {
894                 description += "It emits flame when wielded, causing extra "
895                     "injury to most foes and up to double damage against "
896                     "particularly susceptible opponents.";
897                 if (damtype == DVORP_SLICING || damtype == DVORP_CHOPPING)
898                 {
899                     description += " Big, fiery blades are also staple "
900                         "armaments of hydra-hunters.";
901                 }
902             }
903             break;
904         case SPWPN_FREEZING:
905             if (is_range_weapon(item))
906             {
907                 description += "It turns projectiles fired from it into "
908                     "bolts of frost.";
909             }
910             else
911             {
912                 description += "It has been specially enchanted to freeze "
913                     "those struck by it, causing extra injury to most foes "
914                     "and up to double damage against particularly "
915                     "susceptible opponents. It can also slow down "
916                     "cold-blooded creatures.";
917             }
918             break;
919         case SPWPN_HOLY_WRATH:
920             description += "It has been blessed by the Shining One to "
921                 "cause great damage to the undead and demons.";
922             break;
923         case SPWPN_ELECTROCUTION:
924             if (is_range_weapon(item))
925             {
926                 description += "It charges the ammunition it shoots with "
927                     "electricity; occasionally upon a hit, such missiles "
928                     "may discharge and cause terrible harm.";
929             }
930             else
931             {
932                 description += "Occasionally, upon striking a foe, it will "
933                     "discharge some electrical energy and cause terrible "
934                     "harm.";
935             }
936             break;
937         case SPWPN_VENOM:
938             if (is_range_weapon(item))
939                 description += "It poisons the ammo it fires.";
940             else
941                 description += "It poisons the flesh of those it strikes.";
942             break;
943         case SPWPN_PROTECTION:
944             description += "It protects the one who wields it against "
945                 "injury (+5 to AC).";
946             break;
947         case SPWPN_EVASION:
948             description += "It affects your evasion (+5 to EV).";
949             break;
950         case SPWPN_DRAINING:
951             description += "A truly terrible weapon, it drains the "
952                 "life of those it strikes.";
953             break;
954         case SPWPN_SPEED:
955             description += "Attacks with this weapon are significantly faster.";
956             break;
957         case SPWPN_VORPAL:
958             if (is_range_weapon(item))
959             {
960                 description += "Any ";
961                 description += ammo_name(item);
962                 description += " fired from it inflicts extra damage.";
963             }
964             else
965             {
966                 description += "It inflicts extra damage upon your "
967                     "enemies.";
968             }
969             break;
970         case SPWPN_CHAOS:
971             if (is_range_weapon(item))
972             {
973                 description += "Each time it fires, it turns the "
974                     "launched projectile into a different, random type "
975                     "of bolt.";
976             }
977             else
978             {
979                 description += "Each time it hits an enemy it has a "
980                     "different, random effect.";
981             }
982             break;
983         case SPWPN_VAMPIRISM:
984             description += "It inflicts no extra harm, but heals its "
985                 "wielder somewhat when it strikes a living foe.";
986             break;
987         case SPWPN_PAIN:
988             description += "In the hands of one skilled in necromantic "
989                 "magic, it inflicts extra damage on living creatures.";
990             break;
991         case SPWPN_DISTORTION:
992             description += "It warps and distorts space around it. "
993                 "Unwielding it can cause banishment or high damage.";
994             break;
995         case SPWPN_PENETRATION:
996             description += "Ammo fired by it will pass through the "
997                 "targets it hits, potentially hitting all targets in "
998                 "its path until it reaches maximum range.";
999             break;
1000         case SPWPN_REAPING:
1001             description += "If a monster killed with it leaves a "
1002                 "corpse in good enough shape, the corpse will be "
1003                 "animated as a zombie friendly to the killer.";
1004             break;
1005         case SPWPN_ANTIMAGIC:
1006             description += "It disrupts the flow of magical energy around "
1007                     "spellcasters and certain magical creatures (including "
1008                     "the wielder).";
1009             break;
1010         case SPWPN_NORMAL:
1011             ASSERT(enchanted);
1012             description += "It has no special brand (it is not flaming, "
1013                     "freezing, etc), but is still enchanted in some way - "
1014                     "positive or negative.";
1015             break;
1016         }
1017     }
1018
1019     if (you.duration[DUR_WEAPON_BRAND] && &item == you.weapon())
1020     {
1021         description += "\nIt is temporarily rebranded; it is actually a";
1022         if ((int) you.props[ORIGINAL_BRAND_KEY] == SPWPN_NORMAL)
1023             description += "n unbranded weapon.";
1024         else
1025         {
1026             description += " weapon of "
1027                         + ego_type_string(item, false, you.props[ORIGINAL_BRAND_KEY])
1028                         + ".";
1029         }
1030     }
1031
1032     if (is_artefact(item))
1033     {
1034         string rand_desc = _randart_descrip(item);
1035         if (!rand_desc.empty())
1036         {
1037             description += "\n";
1038             description += rand_desc;
1039         }
1040
1041         // XXX: Can't happen, right?
1042         if (!item_ident(item, ISFLAG_KNOW_PROPERTIES)
1043             && item_type_known(item))
1044         {
1045             description += "\nThis weapon may have some hidden properties.";
1046         }
1047     }
1048
1049     if (verbose)
1050     {
1051         description += "\n\nThis weapon falls into the";
1052
1053         const skill_type skill = item_attack_skill(item);
1054
1055         description +=
1056             make_stringf(" '%s' category. ",
1057                          skill == SK_FIGHTING ? "buggy" : skill_name(skill));
1058
1059         description += _handedness_string(item);
1060
1061         if (!you.could_wield(item, true))
1062             description += "\nIt is too large for you to wield.";
1063     }
1064
1065     if (!is_artefact(item))
1066     {
1067         if (item_ident(item, ISFLAG_KNOW_PLUSES) && item.plus >= MAX_WPN_ENCHANT)
1068             description += "\nIt cannot be enchanted further.";
1069         else
1070         {
1071             description += "\nIt can be maximally enchanted to +";
1072             _append_value(description, MAX_WPN_ENCHANT, false);
1073             description += ".";
1074         }
1075     }
1076
1077     return description;
1078 }
1079
1080 //---------------------------------------------------------------
1081 //
1082 // describe_ammo
1083 //
1084 //---------------------------------------------------------------
1085 static string _describe_ammo(const item_def &item)
1086 {
1087     string description;
1088
1089     description.reserve(64);
1090
1091     const bool can_launch = has_launcher(item);
1092     const bool can_throw  = is_throwable(&you, item, true);
1093
1094     if (item.special && item_type_known(item))
1095     {
1096         description += "\n\n";
1097
1098         string threw_or_fired;
1099         if (can_throw)
1100         {
1101             threw_or_fired += "threw";
1102             if (can_launch)
1103                 threw_or_fired += " or ";
1104         }
1105         if (can_launch)
1106             threw_or_fired += "fired";
1107
1108         switch (item.special)
1109         {
1110         case SPMSL_FLAME:
1111             description += "It turns into a bolt of flame.";
1112             break;
1113         case SPMSL_FROST:
1114             description += "It turns into a bolt of frost.";
1115             break;
1116         case SPMSL_CHAOS:
1117             description += "When ";
1118
1119             if (can_throw)
1120             {
1121                 description += "thrown, ";
1122                 if (can_launch)
1123                     description += "or ";
1124             }
1125
1126             if (can_launch)
1127                 description += "fired from an appropriate launcher, ";
1128
1129             description += "it turns into a bolt of a random type.";
1130             break;
1131         case SPMSL_POISONED:
1132             description += "It is coated with poison.";
1133             break;
1134         case SPMSL_CURARE:
1135             description += "It is tipped with asphyxiating poison.";
1136             break;
1137         case SPMSL_PARALYSIS:
1138             description += "It is tipped with a paralysing substance.";
1139             break;
1140         case SPMSL_SLOW:
1141             description += "It is coated with a substance that causes slowness of the body.";
1142             break;
1143         case SPMSL_SLEEP:
1144             description += "It is coated with a fast-acting tranquilizer.";
1145             break;
1146         case SPMSL_CONFUSION:
1147             description += "It is tipped with a substance that causes confusion.";
1148             break;
1149 #if TAG_MAJOR_VERSION == 34
1150         case SPMSL_SICKNESS:
1151             description += "It has been contaminated by something likely to cause disease.";
1152             break;
1153 #endif
1154         case SPMSL_FRENZY:
1155             description += "It is tipped with a substance that causes a mindless "
1156                 "rage, making people attack friend and foe alike.";
1157             break;
1158        case SPMSL_RETURNING:
1159             description += "A skilled user can throw it in such a way "
1160                 "that it will return to its owner.";
1161             break;
1162         case SPMSL_PENETRATION:
1163             description += "It will pass through any targets it hits, "
1164                 "potentially hitting all targets in its path until it "
1165                 "reaches maximum range.";
1166             break;
1167         case SPMSL_DISPERSAL:
1168             description += "Any target it hits will blink, with a "
1169                 "tendency towards blinking further away from the one "
1170                 "who " + threw_or_fired + " it.";
1171             break;
1172         case SPMSL_EXPLODING:
1173             description += "It will explode into fragments upon "
1174                 "hitting a target, hitting an obstruction, or reaching "
1175                 "the end of its range.";
1176             break;
1177         case SPMSL_STEEL:
1178             description += "Compared to normal ammo, it does 30% more "
1179                 "damage, is destroyed upon impact only 1/10th of the "
1180                 "time.";
1181             break;
1182         case SPMSL_SILVER:
1183             description += "Silver sears all those touched by chaos. "
1184                 "Compared to normal ammo, it does 75% more damage to "
1185                 "chaotic and magically transformed beings. It also does "
1186                 "extra damage against mutated beings according to how "
1187                 "mutated they are. With due care, silver ammo can still "
1188                 "be handled by those it affects.";
1189             break;
1190         }
1191     }
1192
1193     append_missile_info(description, item);
1194
1195     return description;
1196 }
1197
1198 void append_armour_stats(string &description, const item_def &item)
1199 {
1200     description += "\nBase armour rating: ";
1201     _append_value(description, property(item, PARM_AC), false);
1202     description += "       ";
1203
1204     description += "Encumbrance rating: ";
1205     _append_value(description, -property(item, PARM_EVASION), false);
1206 }
1207
1208 void append_missile_info(string &description, const item_def &item)
1209 {
1210     const int dam = property(item, PWPN_DAMAGE);
1211     if (dam)
1212         description += make_stringf("\nBase damage: %d\n", dam);
1213
1214     if (ammo_always_destroyed(item))
1215         description += "\nIt will always be destroyed on impact.";
1216     else if (!ammo_never_destroyed(item))
1217         description += "\nIt may be destroyed on impact.";
1218 }
1219
1220 //---------------------------------------------------------------
1221 //
1222 // describe_armour
1223 //
1224 //---------------------------------------------------------------
1225 static string _describe_armour(const item_def &item, bool verbose)
1226 {
1227     string description;
1228
1229     description.reserve(200);
1230
1231     if (verbose
1232         && item.sub_type != ARM_SHIELD
1233         && item.sub_type != ARM_BUCKLER
1234         && item.sub_type != ARM_LARGE_SHIELD)
1235     {
1236         description += "\n";
1237         append_armour_stats(description, item);
1238     }
1239
1240     const int ego = get_armour_ego_type(item);
1241     if (ego != SPARM_NORMAL && item_type_known(item) && verbose)
1242     {
1243         description += "\n\n";
1244
1245         switch (ego)
1246         {
1247         case SPARM_RUNNING:
1248             if (item.sub_type == ARM_NAGA_BARDING)
1249                 description += "It allows its wearer to slither at a great speed.";
1250             else
1251                 description += "It allows its wearer to run at a great speed.";
1252             break;
1253         case SPARM_FIRE_RESISTANCE:
1254             description += "It protects its wearer from heat.";
1255             break;
1256         case SPARM_COLD_RESISTANCE:
1257             description += "It protects its wearer from cold.";
1258             break;
1259         case SPARM_POISON_RESISTANCE:
1260             description += "It protects its wearer from poison.";
1261             break;
1262         case SPARM_SEE_INVISIBLE:
1263             description += "It allows its wearer to see invisible things.";
1264             break;
1265         case SPARM_INVISIBILITY:
1266             description += "When activated it hides its wearer from "
1267                 "the sight of others, but also increases "
1268                 "their metabolic rate by a large amount.";
1269             break;
1270         case SPARM_STRENGTH:
1271             description += "It increases the physical power of its wearer (+3 to strength).";
1272             break;
1273         case SPARM_DEXTERITY:
1274             description += "It increases the dexterity of its wearer (+3 to dexterity).";
1275             break;
1276         case SPARM_INTELLIGENCE:
1277             description += "It makes you more clever (+3 to intelligence).";
1278             break;
1279         case SPARM_PONDEROUSNESS:
1280             description += "It is very cumbersome, thus slowing your movement.";
1281             break;
1282         case SPARM_FLYING:
1283             description += "It can be activated to allow its wearer to "
1284                 "fly indefinitely.";
1285             break;
1286         case SPARM_MAGIC_RESISTANCE:
1287             description += "It increases its wearer's resistance "
1288                 "to enchantments.";
1289             break;
1290         case SPARM_PROTECTION:
1291             description += "It protects its wearer from harm (+3 to AC).";
1292             break;
1293         case SPARM_STEALTH:
1294             description += "It enhances the stealth of its wearer.";
1295             break;
1296         case SPARM_RESISTANCE:
1297             description += "It protects its wearer from the effects "
1298                 "of both cold and heat.";
1299             break;
1300
1301         // These two are only for robes.
1302         case SPARM_POSITIVE_ENERGY:
1303             description += "It protects its wearer from "
1304                 "the effects of negative energy.";
1305             break;
1306         case SPARM_ARCHMAGI:
1307             description += "It increases the power of its wearer's "
1308                 "magical spells.";
1309             break;
1310 #if TAG_MAJOR_VERSION == 34
1311         case SPARM_PRESERVATION:
1312             description += "It does nothing special.";
1313             break;
1314 #endif
1315         case SPARM_REFLECTION:
1316             description += "It reflects blocked things back in the "
1317                 "direction they came from.";
1318             break;
1319
1320         case SPARM_SPIRIT_SHIELD:
1321             description += "It shields its wearer from harm at the cost "
1322                 "of magical power.";
1323             break;
1324
1325         // This is only for gloves.
1326         case SPARM_ARCHERY:
1327             description += "It improves your effectiveness with ranged weaponry.";
1328             break;
1329         }
1330     }
1331
1332     if (is_artefact(item))
1333     {
1334         string rand_desc = _randart_descrip(item);
1335         if (!rand_desc.empty())
1336         {
1337             description += "\n";
1338             description += rand_desc;
1339         }
1340
1341         // Can't happen, right? (XXX)
1342         if (!item_ident(item, ISFLAG_KNOW_PROPERTIES) && item_type_known(item))
1343             description += "\nThis armour may have some hidden properties.";
1344     }
1345
1346     if (!is_artefact(item))
1347     {
1348         const int max_ench = armour_max_enchant(item);
1349         if (armour_is_hide(item))
1350         {
1351             description += "\nEnchanting it will turn it into a suit of "
1352                            "magical armour.";
1353         }
1354         else if (item.plus < max_ench || !item_ident(item, ISFLAG_KNOW_PLUSES))
1355         {
1356             description += "\nIt can be maximally enchanted to +";
1357             _append_value(description, max_ench, false);
1358             description += ".";
1359         }
1360         else
1361             description += "\nIt cannot be enchanted further.";
1362     }
1363
1364     return description;
1365 }
1366
1367 //---------------------------------------------------------------
1368 //
1369 // describe_jewellery
1370 //
1371 //---------------------------------------------------------------
1372 static string _describe_jewellery(const item_def &item, bool verbose)
1373 {
1374     string description;
1375
1376     description.reserve(200);
1377
1378     if (verbose && !is_artefact(item)
1379         && item_ident(item, ISFLAG_KNOW_PLUSES))
1380     {
1381         // Explicit description of ring power.
1382         if (item.plus != 0)
1383         {
1384             switch (item.sub_type)
1385             {
1386             case RING_PROTECTION:
1387                 description += "\nIt affects your AC (";
1388                 _append_value(description, item.plus, true);
1389                 description += ").";
1390                 break;
1391
1392             case RING_EVASION:
1393                 description += "\nIt affects your evasion (";
1394                 _append_value(description, item.plus, true);
1395                 description += ").";
1396                 break;
1397
1398             case RING_STRENGTH:
1399                 description += "\nIt affects your strength (";
1400                 _append_value(description, item.plus, true);
1401                 description += ").";
1402                 break;
1403
1404             case RING_INTELLIGENCE:
1405                 description += "\nIt affects your intelligence (";
1406                 _append_value(description, item.plus, true);
1407                 description += ").";
1408                 break;
1409
1410             case RING_DEXTERITY:
1411                 description += "\nIt affects your dexterity (";
1412                 _append_value(description, item.plus, true);
1413                 description += ").";
1414                 break;
1415
1416             case RING_SLAYING:
1417                 description += "\nIt affects your accuracy and damage "
1418                                "with ranged weapons and melee attacks (";
1419                 _append_value(description, item.plus, true);
1420                 description += ").";
1421                 break;
1422
1423             default:
1424                 break;
1425             }
1426         }
1427     }
1428
1429     // Artefact properties.
1430     if (is_artefact(item))
1431     {
1432         string rand_desc = _randart_descrip(item);
1433         if (!rand_desc.empty())
1434         {
1435             description += "\n";
1436             description += rand_desc;
1437         }
1438         if (!item_ident(item, ISFLAG_KNOW_PROPERTIES) ||
1439             !item_ident(item, ISFLAG_KNOW_TYPE))
1440         {
1441             description += "\nThis ";
1442             description += (jewellery_is_amulet(item) ? "amulet" : "ring");
1443             description += " may have hidden properties.";
1444         }
1445     }
1446
1447     return description;
1448 }
1449
1450 static bool _compare_card_names(card_type a, card_type b)
1451 {
1452     return string(card_name(a)) < string(card_name(b));
1453 }
1454
1455 //---------------------------------------------------------------
1456 //
1457 // describe_misc_item
1458 //
1459 //---------------------------------------------------------------
1460 static bool _check_buggy_deck(const item_def &deck, string &desc)
1461 {
1462     if (!is_deck(deck))
1463     {
1464         desc += "This isn't a deck at all!\n";
1465         return true;
1466     }
1467
1468     const CrawlHashTable &props = deck.props;
1469
1470     if (!props.exists("cards")
1471         || props["cards"].get_type() != SV_VEC
1472         || props["cards"].get_vector().get_type() != SV_BYTE
1473         || cards_in_deck(deck) == 0)
1474     {
1475         return true;
1476     }
1477
1478     return false;
1479 }
1480
1481 static string _describe_deck(const item_def &item)
1482 {
1483     string description;
1484
1485     description.reserve(100);
1486
1487     description += "\n";
1488
1489     if (_check_buggy_deck(item, description))
1490         return "";
1491
1492     if (item_type_known(item))
1493         description += deck_contents(item.sub_type) + "\n";
1494
1495     const vector<card_type> drawn_cards = get_drawn_cards(item);
1496     if (!drawn_cards.empty())
1497     {
1498         description += "Drawn card(s): ";
1499         for (unsigned int i = 0; i < drawn_cards.size(); ++i)
1500         {
1501             if (i != 0)
1502                 description += ", ";
1503             description += card_name(drawn_cards[i]);
1504         }
1505         description += "\n";
1506     }
1507
1508     const int num_cards = cards_in_deck(item);
1509     int last_known_card = -1;
1510     if (top_card_is_known(item))
1511     {
1512         description += "Next card(s): ";
1513         for (int i = 0; i < num_cards; ++i)
1514         {
1515             uint8_t flags;
1516             const card_type card = get_card_and_flags(item, -i-1, flags);
1517             if (flags & CFLAG_MARKED)
1518             {
1519                 if (i != 0)
1520                     description += ", ";
1521                 description += card_name(card);
1522                 last_known_card = i;
1523             }
1524             else
1525                 break;
1526         }
1527         description += "\n";
1528     }
1529
1530     // Marked cards which we don't know straight off.
1531     vector<card_type> marked_cards;
1532     for (int i = last_known_card + 1; i < num_cards; ++i)
1533     {
1534         uint8_t flags;
1535         const card_type card = get_card_and_flags(item, -i-1, flags);
1536         if (flags & CFLAG_MARKED)
1537             marked_cards.push_back(card);
1538     }
1539     if (!marked_cards.empty())
1540     {
1541         sort(marked_cards.begin(), marked_cards.end(),
1542                   _compare_card_names);
1543
1544         description += "Marked card(s): ";
1545         for (unsigned int i = 0; i < marked_cards.size(); ++i)
1546         {
1547             if (i != 0)
1548                 description += ", ";
1549             description += card_name(marked_cards[i]);
1550         }
1551         description += "\n";
1552     }
1553
1554     // Seen cards in the deck.
1555     vector<card_type> seen_cards;
1556     for (int i = 0; i < num_cards; ++i)
1557     {
1558         uint8_t flags;
1559         const card_type card = get_card_and_flags(item, -i-1, flags);
1560
1561         // This *might* leak a bit of information...oh well.
1562         if ((flags & CFLAG_SEEN) && !(flags & CFLAG_MARKED))
1563             seen_cards.push_back(card);
1564     }
1565     if (!seen_cards.empty())
1566     {
1567         sort(seen_cards.begin(), seen_cards.end(),
1568                   _compare_card_names);
1569
1570         description += "Seen card(s): ";
1571         for (unsigned int i = 0; i < seen_cards.size(); ++i)
1572         {
1573             if (i != 0)
1574                 description += ", ";
1575             description += card_name(seen_cards[i]);
1576         }
1577         description += "\n";
1578     }
1579
1580     return description;
1581 }
1582
1583 // ========================================================================
1584 //      Public Functions
1585 // ========================================================================
1586
1587 bool is_dumpable_artefact(const item_def &item)
1588 {
1589     return is_known_artefact(item) && item_ident(item, ISFLAG_KNOW_PROPERTIES);
1590 }
1591
1592 //---------------------------------------------------------------
1593 //
1594 // get_item_description
1595 //
1596 //---------------------------------------------------------------
1597 string get_item_description(const item_def &item, bool verbose,
1598                             bool dump, bool noquote)
1599 {
1600     if (dump)
1601         noquote = true;
1602
1603     ostringstream description;
1604
1605     if (!dump)
1606     {
1607         string name = item.name(DESC_INVENTORY_EQUIP);
1608         if (!in_inventory(item))
1609             name = uppercase_first(name);
1610         description << name << ".";
1611     }
1612
1613 #ifdef DEBUG_DIAGNOSTICS
1614     if (!dump)
1615     {
1616         description << setfill('0');
1617         description << "\n\n"
1618                     << "base: " << static_cast<int>(item.base_type)
1619                     << " sub: " << static_cast<int>(item.sub_type)
1620                     << " plus: " << item.plus << " plus2: " << item.plus2
1621                     << " special: " << item.special
1622                     << "\n"
1623                     << "quant: " << item.quantity
1624                     << " rnd?: " << static_cast<int>(item.rnd)
1625                     << " flags: " << hex << setw(8) << item.flags
1626                     << dec << "\n"
1627                     << "x: " << item.pos.x << " y: " << item.pos.y
1628                     << " link: " << item.link
1629                     << " slot: " << item.slot
1630                     << " ident_type: "
1631                     << static_cast<int>(get_ident_type(item))
1632                     << " book_number: " << item.book_number()
1633                     << "\nannotate: "
1634                     << stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item);
1635     }
1636 #endif
1637
1638     if (verbose || (item.base_type != OBJ_WEAPONS
1639                     && item.base_type != OBJ_ARMOUR
1640                     && item.base_type != OBJ_BOOKS))
1641     {
1642         description << "\n\n";
1643
1644         bool need_base_desc = true;
1645
1646         if (dump)
1647         {
1648             description << "["
1649                         << item.name(DESC_DBNAME, true, false, false)
1650                         << "]";
1651             need_base_desc = false;
1652         }
1653         else if (is_unrandom_artefact(item) && item_type_known(item))
1654         {
1655             const string desc = getLongDescription(get_artefact_name(item));
1656             if (!desc.empty())
1657             {
1658                 description << desc;
1659                 need_base_desc = false;
1660                 description.seekp((streamoff)-1, ios_base::cur);
1661                 description << " ";
1662             }
1663         }
1664         // Randart jewellery properties will be listed later,
1665         // just describe artefact status here.
1666         else if (is_artefact(item) && item_type_known(item)
1667                  && item.base_type == OBJ_JEWELLERY)
1668         {
1669             description << "It is an ancient artefact.";
1670             need_base_desc = false;
1671         }
1672
1673         if (need_base_desc)
1674         {
1675             string db_name = item.name(DESC_DBNAME, true, false, false);
1676             string db_desc = getLongDescription(db_name);
1677
1678             if (db_desc.empty())
1679             {
1680                 if (item_type_known(item))
1681                 {
1682                     description << "[ERROR: no desc for item name '" << db_name
1683                                 << "']\n";
1684                 }
1685                 else
1686                 {
1687                     description << uppercase_first(item.name(DESC_A, true,
1688                                                              false, false));
1689                     description << ".\n";
1690                 }
1691             }
1692             else
1693                 description << db_desc;
1694
1695             // Get rid of newline at end of description; in most cases we
1696             // will be adding "\n\n" immediately, and we want only one,
1697             // not two, blank lines.  This allow allows the "unpleasant"
1698             // message for chunks to appear on the same line.
1699             description.seekp((streamoff)-1, ios_base::cur);
1700             description << " ";
1701         }
1702     }
1703
1704     bool need_extra_line = true;
1705     string desc;
1706     switch (item.base_type)
1707     {
1708     // Weapons, armour, jewellery, books might be artefacts.
1709     case OBJ_WEAPONS:
1710         desc = _describe_weapon(item, verbose);
1711         if (desc.empty())
1712             need_extra_line = false;
1713         else
1714             description << desc;
1715         break;
1716
1717     case OBJ_ARMOUR:
1718         desc = _describe_armour(item, verbose);
1719         if (desc.empty())
1720             need_extra_line = false;
1721         else
1722             description << desc;
1723         break;
1724
1725     case OBJ_JEWELLERY:
1726         desc = _describe_jewellery(item, verbose);
1727         if (desc.empty())
1728             need_extra_line = false;
1729         else
1730             description << desc;
1731         break;
1732
1733     case OBJ_BOOKS:
1734         if (!player_can_memorise_from_spellbook(item))
1735         {
1736             description << "\nThis book is beyond your current level of "
1737                            "understanding.";
1738
1739             if (!item_type_known(item))
1740                 break;
1741         }
1742
1743         if (!verbose
1744             && (Options.dump_book_spells || is_random_artefact(item)))
1745         {
1746             desc += describe_item_spells(item);
1747             if (desc.empty())
1748                 need_extra_line = false;
1749             else
1750                 description << desc;
1751         }
1752         break;
1753
1754     case OBJ_MISSILES:
1755         description << _describe_ammo(item);
1756         break;
1757
1758     case OBJ_WANDS:
1759     {
1760         const bool known_empty = is_known_empty_wand(item);
1761
1762         if (!item_ident(item, ISFLAG_KNOW_PLUSES) && !known_empty)
1763         {
1764             description << "\nIf evoked without being fully identified,"
1765                            " several charges will be wasted out of"
1766                            " unfamiliarity with the device.";
1767         }
1768
1769
1770         if (item_type_known(item))
1771         {
1772             const int max_charges = wand_max_charges(item.sub_type);
1773             if (item.charges < max_charges
1774                 || !item_ident(item, ISFLAG_KNOW_PLUSES))
1775             {
1776                 description << "\nIt can have at most " << max_charges
1777                             << " charges.";
1778             }
1779             else
1780                 description << "\nIt is fully charged.";
1781         }
1782
1783         if (known_empty)
1784             description << "\nUnfortunately, it has no charges left.";
1785         break;
1786     }
1787
1788     case OBJ_CORPSES:
1789         if (item.sub_type == CORPSE_SKELETON)
1790             break;
1791
1792         if (mons_class_leaves_hide(item.mon_type))
1793         {
1794             description << "\n\n";
1795             if (item.props.exists(MANGLED_CORPSE_KEY))
1796             {
1797                 description << "This corpse is badly mangled; its hide is "
1798                                "beyond any hope of recovery.";
1799             }
1800             else
1801             {
1802                 description << "Butchering may allow you to recover this "
1803                                "creature's hide, which can be enchanted into "
1804                                "armour.";
1805             }
1806         }
1807         // intentional fall-through
1808     case OBJ_FOOD:
1809         if (item.base_type == OBJ_FOOD)
1810         {
1811             description << "\n\n";
1812
1813             const int turns = food_turns(item);
1814             ASSERT(turns > 0);
1815             if (turns > 1)
1816             {
1817                 description << "It is large enough that eating it takes "
1818                             << ((turns > 2) ? "several" : "a couple of")
1819                             << " turns, during which time the eater is vulnerable"
1820                                " to attack.";
1821             }
1822             else
1823                 description << "It is small enough that eating it takes "
1824                                "only one turn.";
1825         }
1826         if (item.base_type == OBJ_CORPSES || item.sub_type == FOOD_CHUNK)
1827         {
1828             switch (determine_chunk_effect(item, true))
1829             {
1830             case CE_POISONOUS:
1831                 description << "\n\nThis meat is poisonous.";
1832                 break;
1833             case CE_MUTAGEN:
1834                 description << "\n\nEating this meat will cause random "
1835                                "mutations.";
1836                 break;
1837             case CE_ROT:
1838                 description << "\n\nEating this meat will cause rotting.";
1839                 break;
1840             default:
1841                 break;
1842             }
1843         }
1844         break;
1845
1846     case OBJ_RODS:
1847         if (verbose)
1848         {
1849             description <<
1850                 "\nIt uses its own magic reservoir for casting spells, and "
1851                 "recharges automatically according to the recharging "
1852                 "rate.";
1853
1854             const int max_charges = MAX_ROD_CHARGE;
1855             const int max_recharge_rate = MAX_WPN_ENCHANT;
1856             if (item_ident(item, ISFLAG_KNOW_PLUSES))
1857             {
1858                 const int num_charges = item.charge_cap / ROD_CHARGE_MULT;
1859                 if (max_charges > num_charges)
1860                 {
1861                     description << "\nIt can currently hold " << num_charges
1862                                 << " charges. It can be magically "
1863                                 << "recharged to contain up to "
1864                                 << max_charges << " charges.";
1865                 }
1866                 else
1867                     description << "\nIts capacity can be increased no further.";
1868
1869                 const int recharge_rate = item.special;
1870                 if (recharge_rate < max_recharge_rate)
1871                 {
1872                     description << "\nIts current recharge rate is "
1873                                 << (recharge_rate >= 0 ? "+" : "")
1874                                 << recharge_rate << ". It can be magically "
1875                                 << "recharged up to +" << max_recharge_rate
1876                                 << ".";
1877                 }
1878                 else
1879                     description << "\nIts recharge rate is at maximum.";
1880             }
1881             else
1882             {
1883                 description << "\nIt can have at most " << max_charges
1884                             << " charges and +" << max_recharge_rate
1885                             << " recharge rate.";
1886             }
1887         }
1888         else if (Options.dump_book_spells)
1889         {
1890             desc += describe_item_spells(item);
1891             if (desc.empty())
1892                 need_extra_line = false;
1893             else
1894                 description << desc;
1895         }
1896
1897         {
1898             string stats = "\n";
1899             append_weapon_stats(stats, item);
1900             description << stats;
1901         }
1902         description << "\n\nIt falls into the 'Maces & Flails' category.";
1903         break;
1904
1905     case OBJ_STAVES:
1906         {
1907             string stats = "\n";
1908             append_weapon_stats(stats, item);
1909             description << stats;
1910         }
1911         description << "\n\nIt falls into the 'Staves' category. ";
1912         description << _handedness_string(item);
1913         break;
1914
1915     case OBJ_MISCELLANY:
1916         if (is_deck(item))
1917             description << _describe_deck(item);
1918         if (is_xp_evoker(item))
1919         {
1920             description << "\nOnce released, the spirits within this device "
1921                            "will dissipate, leaving it inert, though new ones "
1922                            "may be attracted as its bearer battles through the "
1923                            "dungeon and grows in power and wisdom.";
1924
1925             if (!evoker_is_charged(item))
1926             {
1927                 description << "\n\nThe device is presently inert.";
1928                 if (evoker_is_charging(item))
1929                     description << " Gaining experience will recharge it.";
1930                 else if (in_inventory(item))
1931                 {
1932                     description << " Another item of the same type is"
1933                                    " currently charging.";
1934                 }
1935             }
1936         }
1937         break;
1938
1939     case OBJ_POTIONS:
1940 #ifdef DEBUG_BLOOD_POTIONS
1941         // List content of timer vector for blood potions.
1942         if (!dump && is_blood_potion(item))
1943         {
1944             item_def stack = static_cast<item_def>(item);
1945             CrawlHashTable &props = stack.props;
1946             if (!props.exists("timer"))
1947                 description << "\nTimers not yet initialized.";
1948             else
1949             {
1950                 CrawlVector &timer = props["timer"].get_vector();
1951                 ASSERT(!timer.empty());
1952
1953                 description << "\nQuantity: " << stack.quantity
1954                             << "        Timer size: " << (int) timer.size();
1955                 description << "\nTimers:\n";
1956                 for (int i = 0; i < timer.size(); ++i)
1957                     description << (timer[i].get_int()) << "  ";
1958             }
1959         }
1960 #endif
1961
1962     case OBJ_SCROLLS:
1963     case OBJ_ORBS:
1964     case OBJ_GOLD:
1965         // No extra processing needed for these item types.
1966         break;
1967
1968     default:
1969         die("Bad item class");
1970     }
1971
1972     if (!verbose && item_known_cursed(item))
1973         description << "\nIt has a curse placed upon it.";
1974     else
1975     {
1976         if (verbose)
1977         {
1978             if (need_extra_line)
1979                 description << "\n";
1980             if (item_known_cursed(item))
1981                 description << "\nIt has a curse placed upon it.";
1982
1983             if (is_artefact(item))
1984             {
1985                 if (item.base_type == OBJ_ARMOUR
1986                     || item.base_type == OBJ_WEAPONS)
1987                 {
1988                     description << "\nThis ancient artefact cannot be changed "
1989                         "by magic or mundane means.";
1990                 }
1991                 // Randart jewellery has already displayed this line.
1992                 else if (item.base_type != OBJ_JEWELLERY
1993                          || (item_type_known(item) && is_unrandom_artefact(item)))
1994                 {
1995                     description << "\nIt is an ancient artefact.";
1996                 }
1997             }
1998         }
1999     }
2000
2001     if (conduct_type ct = good_god_hates_item_handling(item))
2002     {
2003         description << "\n\n" << uppercase_first(god_name(you.religion))
2004                     << " opposes the use of such an ";
2005
2006         if (ct == DID_NECROMANCY)
2007             description << "evil";
2008         else
2009             description << "unholy";
2010
2011         description << " item.";
2012     }
2013     else if (god_hates_item_handling(item))
2014     {
2015         description << "\n\n" << uppercase_first(god_name(you.religion))
2016                     << " disapproves of the use of such an item.";
2017     }
2018
2019     if (verbose && origin_describable(item))
2020         description << "\n" << origin_desc(item) << ".";
2021
2022     // This information is obscure and differs per-item, so looking it up in
2023     // a docs file you don't know to exist is tedious.  On the other hand,
2024     // it breaks the screen for people on very small terminals.
2025     if (verbose && get_number_of_lines() >= 28)
2026     {
2027         description << "\n\n" << "Stash search prefixes: "
2028                     << userdef_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item);
2029         string menu_prefix = item_prefix(item, false);
2030         if (!menu_prefix.empty())
2031             description << "\nMenu/colouring prefixes: " << menu_prefix;
2032     }
2033
2034     if (verbose && !noquote && (!item_type_known(item) || !is_random_artefact(item)))
2035     {
2036         const unsigned int lineWidth = get_number_of_cols();
2037         const          int height    = get_number_of_lines();
2038         string quote;
2039         if (is_unrandom_artefact(item) && item_type_known(item))
2040             quote = getQuoteString(get_artefact_name(item));
2041         else
2042             quote = getQuoteString(item.name(DESC_DBNAME, true, false, false));
2043
2044         if (count_desc_lines(description.str(), lineWidth)
2045             + count_desc_lines(quote, lineWidth) < height)
2046         {
2047             if (!quote.empty())
2048                 description << "\n\n" << quote;
2049         }
2050     }
2051
2052     return description.str();
2053 }
2054
2055 void get_feature_desc(const coord_def &pos, describe_info &inf)
2056 {
2057     dungeon_feature_type feat = env.map_knowledge(pos).feat();
2058
2059     string desc      = feature_description_at(pos, false, DESC_A, false);
2060     string db_name   = feat == DNGN_ENTER_SHOP ? "a shop" : desc;
2061     string long_desc = getLongDescription(db_name);
2062
2063     inf.title = uppercase_first(desc);
2064     if (!ends_with(desc, ".") && !ends_with(desc, "!")
2065         && !ends_with(desc, "?"))
2066     {
2067         inf.title += ".";
2068     }
2069
2070     // If we couldn't find a description in the database then see if
2071     // the feature's base name is different.
2072     if (long_desc.empty())
2073     {
2074         db_name   = feature_description_at(pos, false, DESC_A, false, true);
2075         long_desc = getLongDescription(db_name);
2076     }
2077
2078     const string marker_desc =
2079         env.markers.property_at(pos, MAT_ANY, "feature_description_long");
2080
2081     // suppress this if the feature changed out of view
2082     if (!marker_desc.empty() && grd(pos) == feat)
2083         long_desc += marker_desc;
2084
2085     // Display branch descriptions on the entries to those branches.
2086     if (feat_is_stair(feat))
2087     {
2088         for (branch_iterator it; it; ++it)
2089         {
2090             if (it->entry_stairs == feat)
2091             {
2092                 long_desc += "\n";
2093                 long_desc += getLongDescription(it->shortname);
2094                 break;
2095             }
2096         }
2097     }
2098
2099     // mention the ability to pray at altars
2100     if (feat_is_altar(feat))
2101         long_desc += "(Pray here to learn more.)";
2102
2103     inf.body << long_desc;
2104
2105     inf.quote = getQuoteString(db_name);
2106 }
2107
2108 // Returns the pressed key in key
2109 static int _print_toggle_message(const describe_info &inf, int& key)
2110 {
2111     if (inf.quote.empty())
2112     {
2113         mouse_control mc(MOUSE_MODE_MORE);
2114         key = getchm();
2115         return false;
2116     }
2117     else
2118     {
2119         const int bottom_line = min(30, get_number_of_lines());
2120         cgotoxy(1, bottom_line);
2121         formatted_string::parse_string(
2122             "Press '<w>!</w>'"
2123 #ifdef USE_TILE_LOCAL
2124             " or <w>Right-click</w>"
2125 #endif
2126             " to toggle between the description and quote.").display();
2127
2128         mouse_control mc(MOUSE_MODE_MORE);
2129         key = getchm();
2130
2131         if (key == '!' || key == CK_MOUSE_CMD)
2132             return true;
2133
2134         return false;
2135     }
2136 }
2137
2138 void describe_feature_wide(const coord_def& pos, bool show_quote)
2139 {
2140     describe_info inf;
2141     get_feature_desc(pos, inf);
2142
2143 #ifdef USE_TILE_WEB
2144     tiles_crt_control show_as_menu(CRT_MENU, "describe_feature");
2145 #endif
2146
2147     if (show_quote)
2148         _print_quote(inf);
2149     else
2150         print_description(inf);
2151
2152     if (crawl_state.game_is_hints())
2153         hints_describe_pos(pos.x, pos.y);
2154
2155     int key;
2156     if (_print_toggle_message(inf, key))
2157         describe_feature_wide(pos, !show_quote);
2158 }
2159
2160 void get_item_desc(const item_def &item, describe_info &inf)
2161 {
2162     // Don't use verbose descriptions if the item contains spells,
2163     // so we can actually output these spells if space is scarce.
2164     const bool verbose = !item.has_spells();
2165     inf.body << get_item_description(item, verbose, false, true);
2166 }
2167
2168 // Returns true if spells can be shown to player.
2169 static bool _can_show_spells(const item_def &item)
2170 {
2171     return item.has_spells()
2172            && (item.base_type != OBJ_BOOKS || item_type_known(item)
2173                || player_can_memorise_from_spellbook(item));
2174 }
2175
2176 static void _show_item_description(const item_def &item)
2177 {
2178     const unsigned int lineWidth = get_number_of_cols() - 1;
2179     const          int height    = get_number_of_lines();
2180
2181     string desc = get_item_description(item, true, false,
2182                                        crawl_state.game_is_hints_tutorial());
2183
2184     const int num_lines = count_desc_lines(desc, lineWidth) + 1;
2185
2186     // XXX: hack: Leave room for "Inscribe item?" and the blank line above
2187     // it by removing item quote.  This should really be taken care of
2188     // by putting the quotes into a separate DB and treating them as
2189     // a suffix that can be ignored by print_description().
2190     if (height - num_lines <= 2)
2191         desc = get_item_description(item, true, false, true);
2192
2193     print_description(desc);
2194     if (crawl_state.game_is_hints())
2195         hints_describe_item(item);
2196
2197     if (_can_show_spells(item))
2198     {
2199         formatted_string fdesc;
2200         fdesc.cprintf("%s", desc.c_str());
2201         list_spellset(item_spellset(item), &item, fdesc);
2202     }
2203 }
2204
2205 static bool _can_memorise(item_def &item)
2206 {
2207     return item.base_type == OBJ_BOOKS && in_inventory(item)
2208            && player_can_memorise_from_spellbook(item);
2209 }
2210
2211 static void _update_inscription(item_def &item)
2212 {
2213     if (item.base_type == OBJ_BOOKS && in_inventory(item)
2214         && !_can_memorise(item))
2215     {
2216         inscribe_book_highlevel(item);
2217     }
2218 }
2219
2220 // it takes a key and a list of commands and it returns
2221 // the command from the list which corresponds to the key
2222 static command_type _get_action(int key, vector<command_type> actions)
2223 {
2224     static bool act_key_init = true; // Does act_key needs to be initialise?
2225     static map<command_type, int> act_key;
2226     if (act_key_init)
2227     {
2228         act_key[CMD_WIELD_WEAPON]       = 'w';
2229         act_key[CMD_UNWIELD_WEAPON]     = 'u';
2230         act_key[CMD_QUIVER_ITEM]        = 'q';
2231         act_key[CMD_WEAR_ARMOUR]        = 'w';
2232         act_key[CMD_REMOVE_ARMOUR]      = 't';
2233         act_key[CMD_EVOKE]              = 'v';
2234         act_key[CMD_EAT]                = 'e';
2235         act_key[CMD_READ]               = 'r';
2236         act_key[CMD_WEAR_JEWELLERY]     = 'p';
2237         act_key[CMD_REMOVE_JEWELLERY]   = 'r';
2238         act_key[CMD_QUAFF]              = 'q';
2239         act_key[CMD_DROP]               = 'd';
2240         act_key[CMD_INSCRIBE_ITEM]      = 'i';
2241         act_key[CMD_ADJUST_INVENTORY]   = '=';
2242         act_key_init = false;
2243     }
2244
2245     for (auto cmd : actions)
2246         if (key == act_key[cmd])
2247             return cmd;
2248
2249     return CMD_NO_CMD;
2250 }
2251
2252 //---------------------------------------------------------------
2253 //
2254 // _actions_prompt
2255 //
2256 // print a list of actions to be performed on the item
2257 static bool _actions_prompt(item_def &item, bool allow_inscribe, bool do_prompt)
2258 {
2259 #ifdef USE_TILE_LOCAL
2260     PrecisionMenu menu;
2261     TextItem* tmp = nullptr;
2262     MenuFreeform* freeform = new MenuFreeform();
2263     menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT);
2264     freeform->init(coord_def(1, 1),
2265                    coord_def(get_number_of_cols(), get_number_of_lines()),
2266                    "freeform");
2267     menu.attach_object(freeform);
2268     menu.set_active_object(freeform);
2269
2270     BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu);
2271     highlighter->init(coord_def(0, 0), coord_def(0, 0), "highlighter");
2272     menu.attach_object(highlighter);
2273 #endif
2274     string prompt = "You can ";
2275     int keyin;
2276     vector<command_type> actions;
2277     actions.push_back(CMD_ADJUST_INVENTORY);
2278     if (item_equip_slot(item) == EQ_WEAPON)
2279         actions.push_back(CMD_UNWIELD_WEAPON);
2280     switch (item.base_type)
2281     {
2282     case OBJ_WEAPONS:
2283     case OBJ_STAVES:
2284     case OBJ_RODS:
2285     case OBJ_MISCELLANY:
2286         if (!item_is_equipped(item))
2287         {
2288             if (item_is_wieldable(item))
2289                 actions.push_back(CMD_WIELD_WEAPON);
2290             if (is_throwable(&you, item))
2291                 actions.push_back(CMD_QUIVER_ITEM);
2292         }
2293         break;
2294     case OBJ_MISSILES:
2295         if (you.species != SP_FELID)
2296             actions.push_back(CMD_QUIVER_ITEM);
2297         break;
2298     case OBJ_ARMOUR:
2299         if (item_is_equipped(item))
2300             actions.push_back(CMD_REMOVE_ARMOUR);
2301         else
2302             actions.push_back(CMD_WEAR_ARMOUR);
2303         break;
2304     case OBJ_FOOD:
2305         if (can_eat(item, true, false))
2306             actions.push_back(CMD_EAT);
2307         break;
2308     case OBJ_SCROLLS:
2309     case OBJ_BOOKS: // only unknown ones
2310         if (item.sub_type != BOOK_MANUAL)
2311             actions.push_back(CMD_READ);
2312         break;
2313     case OBJ_JEWELLERY:
2314         if (item_is_equipped(item))
2315             actions.push_back(CMD_REMOVE_JEWELLERY);
2316         else
2317             actions.push_back(CMD_WEAR_JEWELLERY);
2318         break;
2319     case OBJ_POTIONS:
2320         if (you.undead_state() != US_UNDEAD) // mummies and lich form forbidden
2321             actions.push_back(CMD_QUAFF);
2322         break;
2323     default:
2324         ;
2325     }
2326 #if defined(CLUA_BINDINGS)
2327     if (clua.callbooleanfn(false, "ch_item_wieldable", "i", &item))
2328         actions.push_back(CMD_WIELD_WEAPON);
2329 #endif
2330
2331     if (item_is_evokable(item))
2332         actions.push_back(CMD_EVOKE);
2333
2334     actions.push_back(CMD_DROP);
2335
2336     if (allow_inscribe)
2337         actions.push_back(CMD_INSCRIBE_ITEM);
2338
2339     static bool act_str_init = true; // Does act_str needs to be initialised?
2340     static map<command_type, string> act_str;
2341     if (act_str_init)
2342     {
2343         act_str[CMD_WIELD_WEAPON]       = "(w)ield";
2344         act_str[CMD_UNWIELD_WEAPON]     = "(u)nwield";
2345         act_str[CMD_QUIVER_ITEM]        = "(q)uiver";
2346         act_str[CMD_WEAR_ARMOUR]        = "(w)ear";
2347         act_str[CMD_REMOVE_ARMOUR]      = "(t)ake off";
2348         act_str[CMD_EVOKE]              = "e(v)oke";
2349         act_str[CMD_EAT]                = "(e)at";
2350         act_str[CMD_READ]               = "(r)ead";
2351         act_str[CMD_WEAR_JEWELLERY]     = "(p)ut on";
2352         act_str[CMD_REMOVE_JEWELLERY]   = "(r)emove";
2353         act_str[CMD_QUAFF]              = "(q)uaff";
2354         act_str[CMD_DROP]               = "(d)rop";
2355         act_str[CMD_INSCRIBE_ITEM]      = "(i)nscribe";
2356         act_str[CMD_ADJUST_INVENTORY]   = "(=)adjust";
2357         act_str_init = false;
2358     }
2359
2360     for (auto at = actions.begin(); at < actions.end(); ++at)
2361     {
2362 #ifdef USE_TILE_LOCAL
2363         tmp = new TextItem();
2364         tmp->set_id(*at);
2365         tmp->set_text(act_str[*at]);
2366         tmp->set_fg_colour(CYAN);
2367         tmp->set_bg_colour(BLACK);
2368         tmp->set_highlight_colour(LIGHTGRAY);
2369         tmp->set_description_text(act_str[*at]);
2370         tmp->set_bounds(coord_def(prompt.size() + 1, wherey()),
2371                         coord_def(prompt.size() + act_str[*at].size() + 1,
2372                                   wherey() + 1));
2373         freeform->attach_item(tmp);
2374         tmp->set_visible(true);
2375 #endif
2376         prompt += act_str[*at];
2377         if (at < actions.end() - 2)
2378             prompt += ", ";
2379         else if (at == actions.end() - 2)
2380             prompt += " or ";
2381     }
2382     prompt += " the " + item.name(DESC_BASENAME) + ".";
2383
2384     prompt = "<cyan>" + prompt + "</cyan>";
2385     if (do_prompt)
2386         formatted_string::parse_string(prompt).display();
2387
2388 #ifdef TOUCH_UI
2389
2390     //draw menu over the top of the prompt text
2391     tiles.get_crt()->attach_menu(&menu);
2392     freeform->set_visible(true);
2393     highlighter->set_visible(true);
2394     menu.draw_menu();
2395 #endif
2396
2397     keyin = toalower(getch_ck());
2398 #ifdef USE_TILE_LOCAL
2399     while (keyin == CK_REDRAW)
2400     {
2401         menu.draw_menu();
2402         keyin = toalower(getch_ck());
2403     }
2404 #endif
2405     command_type action = _get_action(keyin, actions);
2406
2407 #ifdef TOUCH_UI
2408     if (menu.process_key(keyin))
2409     {
2410         vector<MenuItem*> selection = menu.get_selected_items();
2411         if (selection.size() == 1)
2412             action = (command_type) selection.at(0)->get_id();
2413     }
2414 #endif
2415
2416     const int slot = item.link;
2417     ASSERT_RANGE(slot, 0, ENDOFPACK);
2418
2419     switch (action)
2420     {
2421     case CMD_WIELD_WEAPON:
2422         redraw_screen();
2423         wield_weapon(true, slot);
2424         return false;
2425     case CMD_UNWIELD_WEAPON:
2426         redraw_screen();
2427         wield_weapon(true, SLOT_BARE_HANDS);
2428         return false;
2429     case CMD_QUIVER_ITEM:
2430         redraw_screen();
2431         quiver_item(slot);
2432         return false;
2433     case CMD_WEAR_ARMOUR:
2434         redraw_screen();
2435         wear_armour(slot);
2436         return false;
2437     case CMD_REMOVE_ARMOUR:
2438         redraw_screen();
2439         takeoff_armour(slot);
2440         return false;
2441     case CMD_EVOKE:
2442         redraw_screen();
2443         evoke_item(slot);
2444         return false;
2445     case CMD_EAT:
2446         redraw_screen();
2447         eat_food(slot);
2448         return false;
2449     case CMD_READ:
2450         if (item.base_type != OBJ_BOOKS || item.sub_type == BOOK_DESTRUCTION)
2451             redraw_screen();
2452         read_scroll(slot);
2453         // In case of a book, stay in the inventory to see the content.
2454         return item.base_type == OBJ_BOOKS && item.sub_type != BOOK_DESTRUCTION;
2455     case CMD_WEAR_JEWELLERY:
2456         redraw_screen();
2457         puton_ring(slot);
2458         return false;
2459     case CMD_REMOVE_JEWELLERY:
2460         redraw_screen();
2461         remove_ring(slot, true);
2462         return false;
2463     case CMD_QUAFF:
2464         redraw_screen();
2465         drink(slot);
2466         return false;
2467     case CMD_DROP:
2468         redraw_screen();
2469         drop_item(slot, you.inv[slot].quantity);
2470         return false;
2471     case CMD_INSCRIBE_ITEM:
2472         inscribe_item(item, false);
2473         break;
2474     case CMD_ADJUST_INVENTORY:
2475         _adjust_item(item);
2476         return false;
2477     case CMD_NO_CMD:
2478     default:
2479         return true;
2480     }
2481     return true;
2482 }
2483
2484 //---------------------------------------------------------------
2485 //
2486 // describe_item
2487 //
2488 // Describes all items in the game.
2489 // Returns false if we should break out of the inventory loop.
2490 //---------------------------------------------------------------
2491 bool describe_item(item_def &item, bool allow_inscribe, bool shopping)
2492 {
2493     if (!item.defined())
2494         return true;
2495
2496 #ifdef USE_TILE_WEB
2497     tiles_crt_control show_as_menu(CRT_MENU, "describe_item");
2498 #endif
2499
2500     // we might destroy the item, so save this first.
2501     const bool item_had_spells = _can_show_spells(item);
2502     _show_item_description(item);
2503
2504     // spellbooks & rods have their own UIs, so we don't currently support the
2505     // inscribe/drop/etc prompt UI for them.
2506     // ...it would be nice if we did, though.
2507     if (item_had_spells)
2508     {
2509         // only continue the inventory loop if we didn't start memorizing a
2510         // spell & didn't destroy the item for amnesia.
2511         return !already_learning_spell() && item.is_valid();
2512     }
2513
2514     _update_inscription(item);
2515
2516     if (allow_inscribe && crawl_state.game_is_tutorial())
2517         allow_inscribe = false;
2518
2519     // Don't ask if we're dead.
2520     if (in_inventory(item) && crawl_state.prev_cmd != CMD_RESISTS_SCREEN
2521         && !(you.dead || crawl_state.updating_scores))
2522     {
2523         // Don't draw the prompt if there aren't enough rows left.
2524         const bool do_prompt = wherey() <= get_number_of_lines() - 2;
2525         if (do_prompt)
2526             cgotoxy(1, wherey() + 2);
2527         return _actions_prompt(item, allow_inscribe, do_prompt);
2528     }
2529     else
2530         getchm();
2531
2532     return true;
2533 }
2534
2535 static void _safe_newline()
2536 {
2537     if (wherey() == get_number_of_lines())
2538     {
2539         cgotoxy(1, wherey());
2540         formatted_string::parse_string(string(80, ' ')).display();
2541         cgotoxy(1, wherey());
2542     }
2543     else
2544         formatted_string::parse_string("\n").display();
2545 }
2546
2547 // There are currently two ways to inscribe an item:
2548 // * using the inscribe command ('{') -> msgwin = true
2549 // * from the inventory when viewing an item -> msgwin = false
2550 //
2551 // msgwin also controls whether a hints mode explanation can be
2552 // shown, or whether the pre- and post-inscription item names need to be
2553 // printed.
2554 void inscribe_item(item_def &item, bool msgwin)
2555 {
2556     if (msgwin)
2557         mprf_nocap(MSGCH_EQUIPMENT, "%s", item.name(DESC_INVENTORY).c_str());
2558
2559     const bool is_inscribed = !item.inscription.empty();
2560     string prompt = is_inscribed ? "Replace inscription with what? "
2561                                  : "Inscribe with what? ";
2562
2563     char buf[79];
2564     int ret;
2565     if (msgwin)
2566     {
2567         ret = msgwin_get_line(prompt, buf, sizeof buf, nullptr,
2568                               item.inscription);
2569     }
2570     else
2571     {
2572         _safe_newline();
2573         prompt = "<cyan>" + prompt + "</cyan>";
2574         formatted_string::parse_string(prompt).display();
2575         ret = cancellable_get_line(buf, sizeof buf, nullptr, nullptr,
2576                                   item.inscription);
2577     }
2578
2579     if (ret)
2580     {
2581         if (msgwin)
2582             canned_msg(MSG_OK);
2583         return;
2584     }
2585
2586     // Strip spaces from the end.
2587     for (int i = strlen(buf) - 1; i >= 0; --i)
2588     {
2589         if (isspace(buf[i]))
2590             buf[i] = 0;
2591         else
2592             break;
2593     }
2594
2595     if (item.inscription == buf)
2596     {
2597         if (msgwin)
2598             canned_msg(MSG_OK);
2599         return;
2600     }
2601
2602     item.inscription = buf;
2603
2604     if (msgwin)
2605     {
2606         mprf_nocap(MSGCH_EQUIPMENT, "%s", item.name(DESC_INVENTORY).c_str());
2607         you.wield_change  = true;
2608         you.redraw_quiver = true;
2609     }
2610 }
2611
2612 static void _adjust_item(item_def &item)
2613 {
2614     _safe_newline();
2615     string prompt = "<cyan>Adjust to which letter? </cyan>";
2616     formatted_string::parse_string(prompt).display();
2617     const int keyin = getch_ck();
2618     // TODO: CK_RESIZE?
2619
2620     if (isaalpha(keyin))
2621     {
2622         const int a = letter_to_index(item.slot);
2623         const int b = letter_to_index(keyin);
2624         swap_inv_slots(a,b,true);
2625     }
2626 }
2627
2628 static void _append_spell_stats(const spell_type spell,
2629                                 string &description,
2630                                 bool rod)
2631 {
2632     if (rod)
2633     {
2634         snprintf(info, INFO_SIZE,
2635                  "\nLevel: %d",
2636                  spell_difficulty(spell));
2637     }
2638     else
2639     {
2640         const string schools = spell_schools_string(spell);
2641         char* failure = failure_rate_to_string(spell_fail(spell));
2642         snprintf(info, INFO_SIZE,
2643                  "\nLevel: %d        School%s: %s        Fail: %s",
2644                  spell_difficulty(spell),
2645                  schools.find("/") != string::npos ? "s" : "",
2646                  schools.c_str(),
2647                  failure);
2648         free(failure);
2649     }
2650     description += info;
2651     description += "\n\nPower : ";
2652     description += spell_power_string(spell, rod);
2653     description += "\nRange : ";
2654     description += spell_range_string(spell, rod);
2655     description += "\nHunger: ";
2656     description += spell_hunger_string(spell, rod);
2657     description += "\nNoise : ";
2658     description += spell_noise_string(spell);
2659     description += "\n";
2660 }
2661
2662 string get_skill_description(skill_type skill, bool need_title)
2663 {
2664     string lookup = skill_name(skill);
2665     string result = "";
2666
2667     if (need_title)
2668     {
2669         result = lookup;
2670         result += "\n\n";
2671     }
2672
2673     result += getLongDescription(lookup);
2674
2675     switch (skill)
2676     {
2677         case SK_INVOCATIONS:
2678             if (you.species == SP_DEMIGOD)
2679             {
2680                 result += "\n";
2681                 result += "How on earth did you manage to pick this up?";
2682             }
2683             else if (you_worship(GOD_TROG))
2684             {
2685                 result += "\n";
2686                 result += "Note that Trog doesn't use Invocations, due to its "
2687                           "close connection to magic.";
2688             }
2689             else if (you_worship(GOD_NEMELEX_XOBEH))
2690             {
2691                 result += "\n";
2692                 result += "Note that Nemelex uses Evocations rather than "
2693                           "Invocations.";
2694             }
2695             break;
2696
2697         case SK_EVOCATIONS:
2698             if (you_worship(GOD_NEMELEX_XOBEH))
2699             {
2700                 result += "\n";
2701                 result += "This is the skill all of Nemelex's abilities rely "
2702                           "on.";
2703             }
2704             break;
2705
2706         case SK_SPELLCASTING:
2707             if (you_worship(GOD_TROG))
2708             {
2709                 result += "\n";
2710                 result += "Keep in mind, though, that Trog will greatly "
2711                           "disapprove of this.";
2712             }
2713             break;
2714         default:
2715             // No further information.
2716             break;
2717     }
2718
2719     return result;
2720 }
2721
2722
2723 // Returns BOOK_MEM if you can memorise the spell BOOK_FORGET if you can
2724 // forget it and BOOK_NEITHER if you can do neither
2725 static int _get_spell_description(const spell_type spell,
2726                                    string &description,
2727                                    const item_def* item = nullptr)
2728 {
2729     description.reserve(500);
2730
2731     description  = spell_title(spell);
2732     description += "\n\n";
2733     const string long_descrip = getLongDescription(string(spell_title(spell))
2734                                                    + " spell");
2735
2736     if (!long_descrip.empty())
2737         description += long_descrip;
2738     else
2739     {
2740         description += "This spell has no description. "
2741                        "Casting it may therefore be unwise. "
2742 #ifdef DEBUG
2743                        "Instead, go fix it. ";
2744 #else
2745                        "Please file a bug report.";
2746 #endif
2747     }
2748
2749     // Report summon cap
2750     if (const int limit = summons_limit(spell))
2751     {
2752         description += "You can sustain at most " + number_in_words(limit)
2753                        + " creature" + (limit > 1 ? "s" : "") + " summoned by this spell.\n";
2754     }
2755
2756     const bool rod = item && item->base_type == OBJ_RODS;
2757
2758     if (god_hates_spell(spell, you.religion, rod))
2759     {
2760         description += uppercase_first(god_name(you.religion))
2761                        + " frowns upon the use of this spell.\n";
2762         if (god_loathes_spell(spell, you.religion))
2763             description += "You'd be excommunicated if you dared to cast it!\n";
2764     }
2765     else if (god_likes_spell(spell, you.religion))
2766     {
2767         description += uppercase_first(god_name(you.religion))
2768                        + " supports the use of this spell.\n";
2769     }
2770     if (item && !player_can_memorise_from_spellbook(*item))
2771     {
2772         description += "The spell is scrawled in ancient runes that are beyond "
2773                        "your current level of understanding.\n";
2774     }
2775     if (spell_is_useless(spell, true, false, rod) && you_can_memorise(spell))
2776     {
2777         description += "\nThis spell will have no effect right now: "
2778         + spell_uselessness_reason(spell, true, false, rod)
2779         + "\n";
2780     }
2781
2782     _append_spell_stats(spell, description, rod);
2783
2784     if (crawl_state.player_is_dead())
2785         return BOOK_NEITHER;
2786
2787     const string quote = getQuoteString(string(spell_title(spell)) + " spell");
2788     if (!quote.empty())
2789         description += "\n" + quote;
2790
2791     if (!you_can_memorise(spell))
2792         description += "\n" + desc_cannot_memorise_reason(spell) + "\n";
2793
2794     if (item && item->base_type == OBJ_BOOKS && in_inventory(*item))
2795     {
2796         if (you.has_spell(spell))
2797         {
2798             description += "\n(F)orget this spell by destroying the book.\n";
2799             if (you_worship(GOD_SIF_MUNA))
2800                 description +="Sif Muna frowns upon the destroying of books.\n";
2801             return BOOK_FORGET;
2802         }
2803         else if (player_can_memorise_from_spellbook(*item)
2804                  && you_can_memorise(spell))
2805         {
2806             description += "\n(M)emorise this spell.\n";
2807             return BOOK_MEM;
2808         }
2809     }
2810
2811     return BOOK_NEITHER;
2812 }
2813
2814 void get_spell_desc(const spell_type spell, describe_info &inf)
2815 {
2816     string desc;
2817     _get_spell_description(spell, desc);
2818     inf.body << desc;
2819 }
2820
2821 //---------------------------------------------------------------
2822 //
2823 // describe_spell
2824 //
2825 // Describes (most) every spell in the game.
2826 //
2827 //---------------------------------------------------------------
2828 void describe_spell(spell_type spelled, const item_def* item)
2829 {
2830 #ifdef USE_TILE_WEB
2831     tiles_crt_control show_as_menu(CRT_MENU, "describe_spell");
2832 #endif
2833
2834     string desc;
2835     const int mem_or_forget = _get_spell_description(spelled, desc, item);
2836     print_description(desc);
2837
2838     mouse_control mc(MOUSE_MODE_MORE);
2839     char ch;
2840     if ((ch = getchm()) == 0)
2841         ch = getchm();
2842
2843     if (mem_or_forget == BOOK_MEM && toupper(ch) == 'M')
2844     {
2845         redraw_screen();
2846         if (!learn_spell(spelled) || !you.turn_is_over)
2847             more();
2848         redraw_screen();
2849     }
2850     else if (mem_or_forget == BOOK_FORGET && toupper(ch) == 'F')
2851     {
2852         redraw_screen();
2853         if (!forget_spell_from_book(spelled, item) || !you.turn_is_over)
2854             more();
2855         redraw_screen();
2856     }
2857 }
2858
2859 static string _describe_draconian(const monster_info& mi)
2860 {
2861     string description;
2862     const int subsp = mi.draco_or_demonspawn_subspecies();
2863
2864     if (subsp != mi.type)
2865     {
2866         description += "It has ";
2867
2868         switch (subsp)
2869         {
2870         case MONS_BLACK_DRACONIAN:      description += "black ";   break;
2871         case MONS_MOTTLED_DRACONIAN:    description += "mottled "; break;
2872         case MONS_YELLOW_DRACONIAN:     description += "yellow ";  break;
2873         case MONS_GREEN_DRACONIAN:      description += "green ";   break;
2874         case MONS_PURPLE_DRACONIAN:     description += "purple ";  break;
2875         case MONS_RED_DRACONIAN:        description += "red ";     break;
2876         case MONS_WHITE_DRACONIAN:      description += "white ";   break;
2877         case MONS_GREY_DRACONIAN:       description += "grey ";    break;
2878         case MONS_PALE_DRACONIAN:       description += "pale ";    break;
2879         default:
2880             break;
2881         }
2882
2883         description += "scales. ";
2884     }
2885
2886     switch (subsp)
2887     {
2888     case MONS_BLACK_DRACONIAN:
2889         description += "Sparks flare out of its mouth and nostrils.";
2890         break;
2891     case MONS_MOTTLED_DRACONIAN:
2892         description += "Liquid flames drip from its mouth.";
2893         break;
2894     case MONS_YELLOW_DRACONIAN:
2895         description += "Acidic fumes swirl around it.";
2896         break;
2897     case MONS_GREEN_DRACONIAN:
2898         description += "Venom drips from its jaws.";
2899         break;
2900     case MONS_PURPLE_DRACONIAN:
2901         description += "Its outline shimmers with magical energy.";
2902         break;
2903     case MONS_RED_DRACONIAN:
2904         description += "Smoke pours from its nostrils.";
2905         break;
2906     case MONS_WHITE_DRACONIAN:
2907         description += "Frost pours from its nostrils.";
2908         break;
2909     case MONS_GREY_DRACONIAN:
2910         description += "Its scales and tail are adapted to the water.";
2911         break;
2912     case MONS_PALE_DRACONIAN:
2913         description += "It is cloaked in a pall of superheated steam.";
2914         break;
2915     default:
2916         break;
2917     }
2918
2919     return description;
2920 }
2921
2922 static string _describe_chimera(const monster_info& mi)
2923 {
2924     string description = "It has the head of ";
2925
2926     description += apply_description(DESC_A, get_monster_data(mi.base_type)->name);
2927
2928     monster_type part2 = get_chimera_part(&mi,2);
2929     description += ", the head of ";
2930     if (part2 == mi.base_type)
2931     {
2932         description += "another ";
2933         description += apply_description(DESC_PLAIN,
2934                                          get_monster_data(part2)->name);
2935     }
2936     else
2937         description += apply_description(DESC_A, get_monster_data(part2)->name);
2938
2939     monster_type part3 = get_chimera_part(&mi,3);
2940     description += ", and the head of ";
2941     if (part3 == mi.base_type || part3 == part2)
2942     {
2943         if (part2 == mi.base_type)
2944             description += "yet ";
2945         description += "another ";
2946         description += apply_description(DESC_PLAIN,
2947                                          get_monster_data(part3)->name);
2948     }
2949     else
2950         description += apply_description(DESC_A, get_monster_data(part3)->name);
2951
2952     description += ". It has the body of ";
2953     description += apply_description(DESC_A,
2954                                      get_monster_data(mi.base_type)->name);
2955
2956     const bool has_wings = mi.props.exists("chimera_batty")
2957                            || mi.props.exists("chimera_wings");
2958     if (mi.props.exists("chimera_legs"))
2959     {
2960         const monster_type leggy_part =
2961             get_chimera_part(&mi, mi.props["chimera_legs"].get_int());
2962         if (has_wings)
2963             description += ", ";
2964         else
2965             description += ", and ";
2966         description += "the legs of ";
2967         description += apply_description(DESC_A,
2968                                          get_monster_data(leggy_part)->name);
2969     }
2970
2971     if (has_wings)
2972     {
2973         monster_type wing_part = mi.props.exists("chimera_batty") ?
2974             get_chimera_part(&mi, mi.props["chimera_batty"].get_int())
2975             : get_chimera_part(&mi, mi.props["chimera_wings"].get_int());
2976
2977         switch (mons_class_flies(wing_part))
2978         {
2979         case FL_WINGED:
2980             description += " and the wings of ";
2981             break;
2982         case FL_LEVITATE:
2983             description += " and it hovers like ";
2984             break;
2985         case FL_NONE:
2986             description += " and it moves like "; // Unseen horrors
2987             break;
2988         }
2989         description += apply_description(DESC_A,
2990                                          get_monster_data(wing_part)->name);
2991     }
2992     description += ".";
2993     return description;
2994 }
2995
2996 static string _describe_demonspawn_role(monster_type type)
2997 {
2998     switch (type)
2999     {
3000     case MONS_BLOOD_SAINT:
3001         return "It weaves powerful and unpredictable spells of devastation.";
3002     case MONS_CHAOS_CHAMPION:
3003         return "It possesses chaotic, reality-warping powers.";
3004     case MONS_WARMONGER:
3005         return "It is devoted to combat, disrupting the magic of its foes as "
3006                "it battles endlessly.";
3007     case MONS_CORRUPTER:
3008         return "It corrupts space around itself, and can twist even the very "
3009                "flesh of its opponents.";
3010     case MONS_BLACK_SUN:
3011         return "It shines with an unholy radiance, and wields powers of "
3012                "darkness from its devotion to the deities of death.";
3013     default:
3014         return "";
3015     }
3016 }
3017
3018 static string _describe_demonspawn_base(int species)
3019 {
3020     switch (species)
3021     {
3022     case MONS_MONSTROUS_DEMONSPAWN:
3023         return "It is more beast now than whatever species it is descended from.";
3024     case MONS_GELID_DEMONSPAWN:
3025         return "It is covered in icy armour.";
3026     case MONS_INFERNAL_DEMONSPAWN:
3027         return "It gives off an intense heat.";
3028     case MONS_PUTRID_DEMONSPAWN:
3029         return "It is surrounded by sickly fumes and gases.";
3030     case MONS_TORTUROUS_DEMONSPAWN:
3031         return "It menaces with bony spines.";
3032     }
3033     return "";
3034 }
3035
3036 static string _describe_demonspawn(const monster_info& mi)
3037 {
3038     string description;
3039     const int subsp = mi.draco_or_demonspawn_subspecies();
3040
3041     description += _describe_demonspawn_base(subsp);
3042
3043     if (subsp != mi.type)
3044     {
3045         const string demonspawn_role = _describe_demonspawn_role(mi.type);
3046         if (!demonspawn_role.empty())
3047             description += " " + demonspawn_role;
3048     }
3049
3050     return description;
3051 }
3052
3053 static const char* _get_resist_name(mon_resist_flags res_type)
3054 {
3055     switch (res_type)
3056     {
3057     case MR_RES_ELEC:
3058         return "electricity";
3059     case MR_RES_POISON:
3060         return "poison";
3061     case MR_RES_FIRE:
3062         return "fire";
3063     case MR_RES_STEAM:
3064         return "steam";
3065     case MR_RES_COLD:
3066         return "cold";
3067     case MR_RES_ACID:
3068         return "acid";
3069     case MR_RES_ROTTING:
3070         return "rotting";
3071     case MR_RES_NEG:
3072         return "negative energy";
3073     default:
3074         return "buggy resistance";
3075     }
3076 }
3077
3078 static const char* _get_threat_desc(mon_threat_level_type threat)
3079 {
3080     switch (threat)
3081     {
3082     case MTHRT_TRIVIAL: return "harmless";
3083     case MTHRT_EASY:    return "easy";
3084     case MTHRT_TOUGH:   return "dangerous";
3085     case MTHRT_NASTY:   return "extremely dangerous";
3086     case MTHRT_UNDEF:
3087     default:            return "buggily threatening";
3088     }
3089 }
3090
3091 static const char* _describe_attack_flavour(attack_flavour flavour)
3092 {
3093     switch (flavour)
3094     {
3095     case AF_ACID:            return "deal extra acid damage";
3096     case AF_BLINK:           return "blink self";
3097     case AF_COLD:            return "deal extra cold damage";
3098     case AF_CONFUSE:         return "cause confusion";
3099     case AF_DRAIN_STR:       return "drain strength";
3100     case AF_DRAIN_INT:       return "drain intelligence";
3101     case AF_DRAIN_DEX:       return "drain dexterity";
3102     case AF_DRAIN_STAT:      return "drain strength, intelligence or dexterity";
3103     case AF_DRAIN_XP:        return "drain skills";
3104     case AF_ELEC:            return "cause electrocution";
3105     case AF_FIRE:            return "deal extra fire damage";
3106     case AF_HUNGER:          return "cause hungering";
3107     case AF_MUTATE:          return "cause mutations";
3108     case AF_PARALYSE:        return "cause paralysis";
3109     case AF_POISON:          return "cause poisoning";
3110     case AF_POISON_STRONG:   return "cause strong poisoning through resistance";
3111     case AF_ROT:             return "cause rotting";
3112     case AF_VAMPIRIC:        return "drain health";
3113     case AF_KLOWN:           return "cause random powerful effects";
3114     case AF_DISTORT:         return "cause wild translocation effects";
3115     case AF_RAGE:            return "cause berserking";
3116     case AF_STICKY_FLAME:    return "apply sticky flame";
3117     case AF_CHAOS:           return "cause unpredictable effects";
3118     case AF_STEAL:           return "steal items";
3119     case AF_CRUSH:           return "constrict";
3120     case AF_REACH:           return "deal damage from a distance";
3121     case AF_HOLY:            return "deal extra damage to undead and demons";
3122     case AF_ANTIMAGIC:       return "drain magic";
3123     case AF_PAIN:            return "cause pain to the living";
3124     case AF_ENSNARE:         return "ensnare with webbing";
3125     case AF_ENGULF:          return "engulf with water";
3126     case AF_PURE_FIRE:       return "deal pure fire damage";
3127     case AF_DRAIN_SPEED:     return "drain speed";
3128     case AF_VULN:            return "reduce resistance to hostile enchantments";
3129     case AF_WEAKNESS_POISON: return "cause poison and weakness";
3130     case AF_SHADOWSTAB:      return "deal extra damage from the shadows";
3131     case AF_DROWN:           return "deal drowning damage";
3132     case AF_FIREBRAND:       return "deal extra fire damage and surround the defender with flames";
3133     case AF_CORRODE:         return "corrode armour";
3134     case AF_SCARAB:          return "reduce negative energy resistance and drain health, speed, or skills";
3135     default:                 return "";
3136     }
3137 }
3138
3139 static string _monster_attacks_description(const monster_info& mi)
3140 {
3141     ostringstream result;
3142     set<attack_flavour> attack_flavours;
3143     vector<string> attack_descs;
3144     // Weird attack types that act like attack flavours.
3145     bool trample = false;
3146     bool reach_sting = false;
3147
3148     for (const auto &attack : mi.attack)
3149     {
3150         attack_flavour af = attack.flavour;
3151         if (!attack_flavours.count(af))
3152         {
3153             attack_flavours.insert(af);
3154             const char * const desc = _describe_attack_flavour(af);
3155             if (desc[0]) // non-empty
3156                 attack_descs.push_back(desc);
3157         }
3158
3159         if (attack.type == AT_TRAMPLE)
3160             trample = true;
3161
3162         if (attack.type == AT_REACH_STING)
3163             reach_sting = true;
3164     }
3165
3166     if (trample)
3167         attack_descs.emplace_back("knock back the defender");
3168
3169     // Assumes nothing has both AT_REACH_STING and AF_REACH.
3170     if (reach_sting)
3171         attack_descs.emplace_back(_describe_attack_flavour(AF_REACH));
3172
3173     if (!attack_descs.empty())
3174     {
3175         result << uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE));
3176         result << " may attack to " << comma_separated_line(attack_descs.begin(), attack_descs.end());
3177         result << ".\n";
3178     }
3179
3180     return result.str();
3181 }
3182
3183 static string _monster_spell_type_description(const monster_info& mi,
3184                                               mon_spell_slot_flags flags,
3185                                               string set_name,
3186                                               string desc_singular,
3187                                               string desc_plural)
3188 {
3189     unique_books books = get_unique_spells(mi, flags);
3190     const size_t num_books = books.size();
3191
3192     if (num_books == 0)
3193         return "";
3194
3195     ostringstream result;
3196
3197     result << uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE));
3198
3199     if (num_books > 1)
3200         result << desc_plural;
3201     else
3202         result << desc_singular;
3203
3204     // Loop through books and display spells/abilities for each of them
3205     for (size_t i = 0; i < num_books; ++i)
3206     {
3207         vector<spell_type> &book_spells = books[i];
3208
3209         // Display spells for this book
3210         if (num_books > 1)
3211             result << set_name << " " << i+1 << ": ";
3212
3213         for (size_t j = 0; j < book_spells.size(); ++j)
3214         {
3215             const spell_type spell = book_spells[j];
3216             if (spell == SPELL_SERPENT_OF_HELL_BREATH)
3217             {
3218                 const int idx =
3219                         mi.type == MONS_SERPENT_OF_HELL          ? 0
3220                       : mi.type == MONS_SERPENT_OF_HELL_COCYTUS  ? 1
3221                       : mi.type == MONS_SERPENT_OF_HELL_DIS      ? 2
3222                       : mi.type == MONS_SERPENT_OF_HELL_TARTARUS ? 3
3223                       :                                           -1;
3224                 ASSERT(idx >= 0 && idx <= 3);
3225                 for (size_t k = 0;
3226                      k < ARRAYSZ(serpent_of_hell_breaths[idx]);
3227                      ++k)
3228                 {
3229                     if (j > 0 || k > 0)
3230                         result << ", ";
3231                     result << spell_title(serpent_of_hell_breaths[idx][k]);
3232                 }
3233             }
3234             else
3235             {
3236                 if (j > 0)
3237                     result << ", ";
3238                 result << spell_title(spell);
3239             }
3240         }
3241         result << "\n";
3242     }
3243
3244     return result.str();
3245 }
3246
3247 static string _monster_spells_description(const monster_info& mi)
3248 {
3249     // Show a generic message for pan lords, since they're secret.
3250     if (mi.type == MONS_PANDEMONIUM_LORD)
3251         return "It may possess any of a vast number of diabolical powers.\n";
3252
3253     // Ditto for (a)liches.
3254     if (mi.type == MONS_LICH || mi.type == MONS_ANCIENT_LICH)
3255         return "It has mastered any of a vast number of powerful spells.\n";
3256
3257     // Show monster spells and spell-like abilities.
3258     if (!mi.has_spells())
3259         return "";
3260
3261     ostringstream result;
3262
3263     result << _monster_spell_type_description(
3264         mi, MON_SPELL_NATURAL, "Set",
3265         " possesses the following special abilities: ",
3266         " possesses one of the following sets of special abilities:\n");
3267     result << _monster_spell_type_description(
3268         mi, MON_SPELL_MAGICAL, "Set",
3269         " possesses the following magical abilities: ",
3270         " possesses one of the following sets of magical abilities:\n");
3271     if (mi.holi == MH_HOLY)
3272     {
3273         result << _monster_spell_type_description(
3274             mi, MON_SPELL_DEMONIC, "Set",
3275             " possesses the following angelic abilities: ",
3276             " possesses one of the following sets of angelic abilities:\n");
3277     }
3278     else
3279     {
3280         result << _monster_spell_type_description(
3281             mi, MON_SPELL_DEMONIC, "Set",
3282             " possesses the following demonic abilities: ",
3283             " possesses one of the following sets of demonic abilities:\n");
3284     }
3285     result << _monster_spell_type_description(
3286         mi, MON_SPELL_PRIEST, "Set",
3287         " possesses the following divine abilities: ",
3288         " possesses one of the following sets of divine abilities:\n");
3289     result << _monster_spell_type_description(
3290         mi, MON_SPELL_WIZARD, "Book",
3291         " has mastered the following spells: ",
3292         " has mastered one of the following spellbooks:\n");
3293
3294     return result.str();
3295 }
3296
3297 static const char *_speed_description(int speed)
3298 {
3299     // These thresholds correspond to the player mutations for fast and slow.
3300     ASSERT(speed != 10);
3301     if (speed < 7)
3302         return "extremely slowly";
3303     else if (speed < 8)
3304         return "very slowly";
3305     else if (speed < 10)
3306         return "slowly";
3307     else if (speed > 15)
3308         return "extremely quickly";
3309     else if (speed > 13)
3310         return "very quickly";
3311     else if (speed > 10)
3312         return "quickly";
3313
3314     return "buggily";
3315 }
3316
3317 static void _add_energy_to_string(int speed, int energy, string what,
3318                                   vector<string> &fast, vector<string> &slow)
3319 {
3320     if (energy == 10)
3321         return;
3322
3323     const int act_speed = (speed * 10) / energy;
3324     if (act_speed > 10)
3325         fast.push_back(what + " " + _speed_description(act_speed));
3326     if (act_speed < 10)
3327         slow.push_back(what + " " + _speed_description(act_speed));
3328 }
3329
3330
3331 /**
3332  * Print a bar of +s and .s representing a given stat to a provided stream.
3333  *
3334  * @param value[in]         The current value represented by the bar.
3335  * @param max[in]           The max value that can be represented by the bar.
3336  * @param scale[in]         The value that each + and . represents.
3337  * @param name              The name of the bar.
3338  * @param result[in,out]    The stringstream to append to.
3339  * @param base_value[in]    The 'base' value represented by the bar. If
3340  *                          INT_MAX, is ignored.
3341  */
3342 static void _print_bar(int value, int max, int scale,
3343                        string name, ostringstream &result,
3344                        int base_value = INT_MAX)
3345 {
3346     if (base_value == INT_MAX)
3347         base_value = value;
3348
3349     result << name << " ";
3350
3351     const int cur_bars = value / scale;
3352     const int base_bars = base_value / scale;
3353     const int bars = cur_bars ? cur_bars : base_bars;
3354     const int max_bars = max / scale;
3355
3356     const bool currently_disabled = !cur_bars && base_bars;
3357
3358     if (currently_disabled)
3359         result << "(";
3360
3361     for (int i = 0; i < min(bars, max_bars); i++)
3362         result << "+";
3363
3364     if (currently_disabled)
3365         result << ")";
3366
3367     for (int i = max_bars - 1; i >= bars; --i)
3368         result << ".";
3369
3370 #ifdef DEBUG_DIAGNOSTICS
3371     result << " (" << value << ")";
3372 #endif
3373
3374     if (currently_disabled)
3375     {
3376         result << " (Normal " << name << ")";
3377
3378 #ifdef DEBUG_DIAGNOSTICS
3379         result << " (" << base_value << ")";
3380 #endif
3381     }
3382
3383     result << "\n";
3384 }
3385
3386 /**
3387  * Append information about a given monster's AC to the provided stream.
3388  *
3389  * @param mi[in]            Player-visible info about the monster in question.
3390  * @param result[in,out]    The stringstream to append to.
3391  */
3392 static void _describe_monster_ac(const monster_info& mi, ostringstream &result)
3393 {
3394     // max ac 40 (dispater)
3395     _print_bar(mi.ac, 40, 5, "AC", result);
3396 }
3397
3398 /**
3399  * Append information about a given monster's EV to the provided stream.
3400  *
3401  * @param mi[in]            Player-visible info about the monster in question.
3402  * @param result[in,out]    The stringstream to append to.
3403  */
3404 static void _describe_monster_ev(const monster_info& mi, ostringstream &result)
3405 {
3406     // max ev 30 (eresh) (also to make space for parens)
3407     _print_bar(mi.ev, 30, 5, "EV", result, mi.base_ev);
3408 }
3409