Revamp Item Destruction
[crawl:crawl.git] / crawl-ref / source / status.cc
1 #include "AppHdr.h"
2
3 #include "status.h"
4
5 #include "areas.h"
6 #include "env.h"
7 #include "evoke.h"
8 #include "godabil.h"
9 #include "libutil.h"
10 #include "misc.h"
11 #include "mutation.h"
12 #include "player.h"
13 #include "player-stats.h"
14 #include "skills2.h"
15 #include "terrain.h"
16 #include "transform.h"
17 #include "spl-transloc.h"
18 #include "stuff.h"
19
20 // Status defaults for durations that are handled straight-forwardly.
21 struct duration_def
22 {
23     duration_type dur;
24     bool expire;         // whether to do automat expiring transforms
25     int    light_colour; // status light base colour
26     string light_text;   // for the status lights
27     string short_text;   // for @: line
28     string long_text ;   // for @ message
29 };
30
31 static duration_def duration_data[] =
32 {
33     { DUR_AGILITY, false,
34       0, "", "agile", "You are agile." },
35     { DUR_ANTIMAGIC, true,
36       RED, "-Mag", "antimagic", "You have trouble accessing your magic." },
37     { DUR_BARGAIN, true,
38       BLUE, "Brgn", "charismatic", "You get a bargain in shops." },
39     { DUR_BERSERK, true,
40       BLUE, "Berserk", "berserking", "You are possessed by a berserker rage." },
41     { DUR_BREATH_WEAPON, false,
42       YELLOW, "Breath", "short of breath", "You are short of breath." },
43     { DUR_BRILLIANCE, false,
44       0, "", "brilliant", "You are brilliant." },
45     { DUR_CONF, false,
46       RED, "Conf", "confused", "You are confused." },
47     { DUR_CONFUSING_TOUCH, true,
48       BLUE, "Touch", "confusing touch", "" },
49     { DUR_CONTROL_TELEPORT, true,
50       MAGENTA, "cTele", "controlling teleports", "You can control teleportations." },
51     { DUR_CORONA, false,
52       YELLOW, "Corona", "", "" },
53     { DUR_DEATH_CHANNEL, true,
54       MAGENTA, "DChan", "death channel", "You are channeling the dead." },
55     { DUR_DIVINE_STAMINA, true,
56       WHITE, "Vit", "vitalised", "You are divinely vitalised." },
57     { DUR_DIVINE_VIGOUR, false,
58       0, "", "divinely vigorous", "You are imbued with divine vigour." },
59     { DUR_EXHAUSTED, false,
60       YELLOW, "Exh", "exhausted", "You are exhausted." },
61     { DUR_FIRE_SHIELD, true,
62       BLUE, "RoF", "immune to fire clouds", "" },
63     { DUR_ICY_ARMOUR, true,
64       0, "", "icy armour", "You are protected by a layer of icy armour." },
65     { DUR_LIQUID_FLAMES, false,
66       RED, "Fire", "liquid flames", "You are covered in liquid flames." },
67     { DUR_LOWERED_MR, false,
68       RED, "-MR", "vulnerable", "" },
69     { DUR_MAGIC_SHIELD, false,
70       0, "", "shielded", "" },
71     { DUR_MIGHT, false,
72       0, "", "mighty", "You are mighty." },
73     { DUR_MISLED, true,
74       LIGHTMAGENTA, "Misled", "misled", "" },
75     { DUR_PARALYSIS, false,
76       RED, "Para", "paralysed", "You are paralysed." },
77     { DUR_PETRIFIED, false,
78       RED, "Stone", "petrified", "You are petrified." },
79     { DUR_PETRIFYING, true,
80       MAGENTA, "Petr", "petrifying", "You are turning to stone." },
81     { DUR_JELLY_PRAYER, false,
82       WHITE, "Pray", "praying", "You are praying." },
83     { DUR_RESISTANCE, true,
84       LIGHTBLUE, "Resist", "resistant", "You resist elements." },
85     { DUR_SLAYING, false,
86       0, "", "deadly", "" },
87     { DUR_SLIMIFY, true,
88       GREEN, "Slime", "slimy", "" },
89     { DUR_SLEEP, false,
90       0, "", "sleeping", "You are sleeping." },
91     { DUR_STONESKIN, false,
92       0, "", "stone skin", "Your skin is tough as stone." },
93     { DUR_SWIFTNESS, true,
94       BLUE, "Swift", "swift", "You can move swiftly." },
95     { DUR_TELEPATHY, false,
96       LIGHTBLUE, "Emp", "empathic", "" },
97     { DUR_TELEPORT, false,
98       LIGHTBLUE, "Tele", "about to teleport", "You are about to teleport." },
99     { DUR_DEATHS_DOOR, true,
100       LIGHTGREY, "DDoor", "death's door", "" },
101     { DUR_PHASE_SHIFT, true,
102       0, "", "phasing", "You are out of phase with the material plane." },
103     { DUR_QUAD_DAMAGE, true,
104       BLUE, "Quad", "quad damage", "" },
105     { DUR_SILENCE, true,
106       BLUE, "Sil", "silence", "You radiate silence." },
107     { DUR_STEALTH, false,
108       BLUE, "Stlth", "especially stealthy", "" },
109     { DUR_AFRAID, true,
110       RED, "Fear", "afraid", "You are terrified." },
111     { DUR_MIRROR_DAMAGE, false,
112       WHITE, "Mirror", "injury mirror", "You mirror injuries." },
113     { DUR_SCRYING, false,
114       LIGHTBLUE, "Scry", "scrying",
115       "Your astral vision lets you see through walls." },
116     { DUR_TORNADO, true,
117       LIGHTGREY, "Tornado", "tornado",
118       "You are in the eye of a mighty hurricane." },
119     { DUR_LIQUEFYING, false,
120       LIGHTBLUE, "Liquid", "liquefying",
121       "The ground has become liquefied beneath your feet." },
122     { DUR_HEROISM, false,
123       LIGHTBLUE, "Hero", "heroism", "You possess the skills of a mighty hero." },
124     { DUR_FINESSE, false,
125       LIGHTBLUE, "Finesse", "finesse", "Your blows are lightning fast." },
126     { DUR_LIFESAVING, true,
127       LIGHTGREY, "Prot", "protection", "You ask for being saved." },
128     { DUR_DARKNESS, true,
129       BLUE, "Dark", "darkness", "You emit darkness." },
130     { DUR_SHROUD_OF_GOLUBRIA, true,
131       BLUE, "Shroud", "shrouded", "You are protected by a distorting shroud." },
132     { DUR_TORNADO_COOLDOWN, false,
133       YELLOW, "Tornado", "", "" ,},
134     { DUR_DISJUNCTION, true,
135       BLUE, "Disjoin", "disjoining", "You are disjoining your surroundings." },
136     { DUR_SENTINEL_MARK, true,
137       MAGENTA, "Mark", "marked", "You are marked for hunting." },
138     { DUR_INFUSION, true,
139       BLUE, "Infus", "infused", "Your attacks are magically infused."},
140     { DUR_SONG_OF_SLAYING, true,
141       BLUE, "Slay", "singing", "Your melee attacks are strengthened by your song."},
142     { DUR_SONG_OF_SHIELDING, true,
143       BLUE, "SShield", "shielded", "Your magic is protecting you."},
144     { DUR_FLAYED, true,
145       RED, "Flay", "flayed", "You are covered in terrible wounds." },
146     { DUR_RETCHING, true,
147       RED, "Retch", "retching", "You are retching with violent nausea." },
148     { DUR_WEAK, false,
149       RED, "Weak", "weakened", "Your attacks are enfeebled." },
150     { DUR_DIMENSION_ANCHOR, false,
151       RED, "-Tele", "cannot translocate", "You are firmly anchored to this plane." },
152     { DUR_SPIRIT_HOWL, false,
153       MAGENTA, "Howl", "spirit howling", "The howling of a spirit pack pursues you." },
154     { DUR_SMOLDERING, false,
155       RED, "-Scroll", "smoldering", "You are smoldering." },
156     { DUR_FREEZING, false,
157       RED, "-Potion", "freezing", "You are freezing." },
158
159 };
160
161 static int duration_index[NUM_DURATIONS];
162
163 void init_duration_index()
164 {
165     for (int i = 0; i < NUM_DURATIONS; ++i)
166         duration_index[i] = -1;
167
168     for (unsigned i = 0; i < ARRAYSZ(duration_data); ++i)
169     {
170         duration_type dur = duration_data[i].dur;
171         ASSERT_RANGE(dur, 0, NUM_DURATIONS);
172         ASSERT(duration_index[dur] == -1);
173         duration_index[dur] = i;
174     }
175 }
176
177 static const duration_def* _lookup_duration(duration_type dur)
178 {
179     ASSERT_RANGE(dur, 0, NUM_DURATIONS);
180     if (duration_index[dur] == -1)
181         return NULL;
182     else
183         return (&duration_data[duration_index[dur]]);
184 }
185
186 static void _reset_status_info(status_info* inf)
187 {
188     inf->light_colour = 0;
189     inf->light_text = "";
190     inf->short_text = "";
191     inf->long_text = "";
192 };
193
194 static int _bad_ench_colour(int lvl, int orange, int red)
195 {
196     if (lvl > red)
197         return RED;
198     else if (lvl > orange)
199         return LIGHTRED;
200
201     return YELLOW;
202 }
203
204 static int _dur_colour(int exp_colour, bool expiring)
205 {
206     if (expiring)
207         return exp_colour;
208     else
209     {
210         switch (exp_colour)
211         {
212         case GREEN:
213             return LIGHTGREEN;
214         case BLUE:
215             return LIGHTBLUE;
216         case MAGENTA:
217             return LIGHTMAGENTA;
218         case LIGHTGREY:
219             return WHITE;
220         default:
221             return exp_colour;
222         }
223     }
224 }
225
226 static void _mark_expiring(status_info* inf, bool expiring)
227 {
228     if (expiring)
229     {
230         if (!inf->short_text.empty())
231             inf->short_text += " (expiring)";
232         if (!inf->long_text.empty())
233             inf->long_text = "Expiring: " + inf->long_text;
234     }
235 }
236
237 static void _describe_airborne(status_info* inf);
238 static void _describe_burden(status_info* inf);
239 static void _describe_glow(status_info* inf);
240 static void _describe_hunger(status_info* inf);
241 static void _describe_regen(status_info* inf);
242 static void _describe_rotting(status_info* inf);
243 static void _describe_sickness(status_info* inf);
244 static void _describe_speed(status_info* inf);
245 static void _describe_sage(status_info* inf);
246 static void _describe_poison(status_info* inf);
247 static void _describe_transform(status_info* inf);
248 static void _describe_stat_zero(status_info* inf, stat_type st);
249 static void _describe_terrain(status_info* inf);
250 static void _describe_missiles(status_info* inf);
251
252 bool fill_status_info(int status, status_info* inf)
253 {
254     _reset_status_info(inf);
255
256     bool found = false;
257
258     // Sort out inactive durations, and fill in data from duration_data
259     // for the simple durations.
260     if (status >= 0 && status < NUM_DURATIONS)
261     {
262         duration_type dur = static_cast<duration_type>(status);
263
264         if (!you.duration[dur])
265             return false;
266
267         const duration_def* ddef = _lookup_duration(dur);
268         if (ddef)
269         {
270             found = true;
271             inf->light_colour = ddef->light_colour;
272             inf->light_text   = ddef->light_text;
273             inf->short_text   = ddef->short_text;
274             inf->long_text    = ddef->long_text;
275             if (ddef->expire)
276             {
277                 inf->light_colour = _dur_colour(inf->light_colour,
278                                                  dur_expiring(dur));
279                 _mark_expiring(inf, dur_expiring(dur));
280             }
281         }
282     }
283
284     // Now treat special status types and durations, possibly
285     // completing or overriding the defaults set above.
286     switch (status)
287     {
288     case DUR_CONTROL_TELEPORT:
289         if (!allow_control_teleport(true))
290             inf->light_colour = DARKGREY;
291         break;
292
293     case DUR_SWIFTNESS:
294         if (you.in_water() || you.liquefied_ground())
295             inf->light_colour = DARKGREY;
296         break;
297
298     case STATUS_AIRBORNE:
299         _describe_airborne(inf);
300         break;
301
302     case STATUS_BEHELD:
303         if (you.beheld())
304         {
305             inf->light_colour = RED;
306             inf->light_text   = "Mesm";
307             inf->short_text   = "mesmerised";
308             inf->long_text    = "You are mesmerised.";
309         }
310         break;
311
312     case STATUS_BURDEN:
313         _describe_burden(inf);
314         break;
315
316     case STATUS_CONTAMINATION:
317         _describe_glow(inf);
318         break;
319
320     case STATUS_BACKLIT:
321         if (you.backlit())
322         {
323             inf->short_text = "glowing";
324             inf->long_text  = "You are glowing.";
325         }
326         break;
327
328     case STATUS_UMBRA:
329         if (you.umbra())
330         {
331             inf->light_colour = MAGENTA;
332             inf->light_text   = "Umbra";
333             inf->short_text   = "wreathed by umbra";
334             inf->long_text    = "You are wreathed by an unholy umbra.";
335         }
336         break;
337
338     case STATUS_SUPPRESSED:
339         if (you.suppressed())
340         {
341             inf->light_colour = LIGHTGREEN;
342             inf->light_text   = "Suppress";
343             inf->short_text   = "magically suppressed";
344             inf->long_text    = "You are enveloped in a field of magical suppression.";
345         }
346         break;
347
348     case STATUS_NET:
349         if (you.attribute[ATTR_HELD])
350         {
351             inf->light_colour = RED;
352             inf->light_text   = "Held";
353             inf->short_text   = "held";
354             inf->long_text    = make_stringf("You are %s.", held_status());
355         }
356         break;
357
358     case STATUS_HUNGER:
359         _describe_hunger(inf);
360         break;
361
362     case STATUS_REGENERATION:
363         // DUR_REGENERATION + some vampire and non-healing stuff
364         _describe_regen(inf);
365         break;
366
367     case STATUS_ROT:
368         _describe_rotting(inf);
369         break;
370
371     case STATUS_SICK:
372         _describe_sickness(inf);
373         break;
374
375     case STATUS_SPEED:
376         _describe_speed(inf);
377         break;
378
379     case STATUS_LIQUEFIED:
380     {
381         if (you.liquefied_ground())
382         {
383             inf->light_colour = BROWN;
384             inf->light_text   = "SlowM";
385             inf->short_text   = "slowed movement";
386             inf->long_text    = "Your movement is slowed on this liquid ground.";
387         }
388         break;
389     }
390
391     case STATUS_SAGE:
392         _describe_sage(inf);
393         break;
394
395     case STATUS_AUGMENTED:
396     {
397         int level = augmentation_amount();
398
399         if (level > 0)
400         {
401             inf->light_colour = (level == 3) ? WHITE :
402                                 (level == 2) ? LIGHTBLUE
403                                              : BLUE;
404
405             inf->light_text = "Aug";
406         }
407         break;
408     }
409
410     case DUR_CONFUSING_TOUCH:
411     {
412         const int dur = you.duration[DUR_CONFUSING_TOUCH];
413         const int high = 40 * BASELINE_DELAY;
414         const int low  = 20 * BASELINE_DELAY;
415         inf->long_text = string("Your ") + you.hand_name(true) + " are glowing ";
416         if (dur > high)
417             inf->long_text += "an extremely bright ";
418         else if (dur > low)
419             inf->long_text += "bright ";
420         else
421            inf->long_text += "a soft ";
422         inf->long_text += "red.";
423         break;
424     }
425
426     case DUR_FIRE_SHIELD:
427     {
428         // Might be better to handle this with an extra virtual status.
429         const bool exp = dur_expiring(DUR_FIRE_SHIELD);
430         if (exp)
431             inf->long_text += "Expiring: ";
432         inf->long_text += "You are surrounded by a ring of flames.\n";
433         if (exp)
434             inf->long_text += "Expiring: ";
435         inf->long_text += "You are immune to clouds of flame.";
436         break;
437     }
438
439     case DUR_INVIS:
440         if (you.attribute[ATTR_INVIS_UNCANCELLABLE])
441             inf->light_colour = _dur_colour(BLUE, dur_expiring(DUR_INVIS));
442         else
443             inf->light_colour = _dur_colour(MAGENTA, dur_expiring(DUR_INVIS));
444         inf->light_text   = "Invis";
445         inf->short_text   = "invisible";
446         if (you.backlit())
447         {
448             inf->light_colour = DARKGREY;
449             inf->short_text += " (but backlit and visible)";
450         }
451         inf->long_text = "You are " + inf->short_text + ".";
452         _mark_expiring(inf, dur_expiring(DUR_INVIS));
453         break;
454
455     case DUR_POISONING:
456         _describe_poison(inf);
457         break;
458
459     case DUR_POWERED_BY_DEATH:
460         if (handle_pbd_corpses(false) > 0)
461         {
462             inf->light_colour = LIGHTMAGENTA;
463             inf->light_text   = "Regen+";
464         }
465         break;
466
467     case STATUS_MISSILES:
468         _describe_missiles(inf);
469         break;
470
471     case STATUS_MANUAL:
472     {
473         string skills = manual_skill_names();
474         if (!skills.empty())
475         {
476             inf->short_text = "studying " + manual_skill_names(true);
477             inf->long_text = "You are studying " + skills + ".";
478         }
479         break;
480     }
481
482     case DUR_SURE_BLADE:
483     {
484         inf->light_colour = BLUE;
485         inf->light_text   = "Blade";
486         inf->short_text   = "bonded with blade";
487         string desc;
488         if (you.duration[DUR_SURE_BLADE] > 15 * BASELINE_DELAY)
489             desc = "strong ";
490         else if (you.duration[DUR_SURE_BLADE] >  5 * BASELINE_DELAY)
491             desc = "";
492         else
493             desc = "weak";
494         inf->long_text = "You have a " + desc + "bond with your blade.";
495         break;
496     }
497
498     case DUR_TRANSFORMATION:
499         _describe_transform(inf);
500         break;
501
502     case STATUS_CLINGING:
503         if (you.is_wall_clinging())
504         {
505             inf->light_text   = "Cling";
506             inf->short_text   = "clinging";
507             inf->long_text    = "You cling to the nearby walls.";
508             const dungeon_feature_type feat = grd(you.pos());
509             if (is_feat_dangerous(feat))
510                 inf->light_colour = LIGHTGREEN;
511             else if (feat == DNGN_LAVA || feat_is_water(feat))
512                 inf->light_colour = GREEN;
513             else
514                 inf->light_colour = DARKGREY;
515             _mark_expiring(inf, dur_expiring(DUR_TRANSFORMATION));
516         }
517         break;
518
519     case STATUS_HOVER:
520         if (is_hovering())
521         {
522             inf->light_colour = RED;
523             inf->light_text   = "Hover";
524             inf->short_text   = "hovering above liquid";
525             inf->long_text    = "You are exerting yourself to hover high above the liquid.";
526         }
527         break;
528
529     case STATUS_STR_ZERO:
530         _describe_stat_zero(inf, STAT_STR);
531         break;
532     case STATUS_INT_ZERO:
533         _describe_stat_zero(inf, STAT_INT);
534         break;
535     case STATUS_DEX_ZERO:
536         _describe_stat_zero(inf, STAT_DEX);
537         break;
538
539     case STATUS_FIREBALL:
540         if (you.attribute[ATTR_DELAYED_FIREBALL])
541         {
542             inf->light_colour = LIGHTMAGENTA;
543             inf->light_text   = "Fball";
544             inf->short_text   = "delayed fireball";
545             inf->long_text    = "You have a stored fireball ready to release.";
546         }
547         break;
548
549     case STATUS_CONSTRICTED:
550         if (you.is_constricted())
551         {
552             inf->light_colour = YELLOW;
553             inf->light_text   = you.held == HELD_MONSTER ? "Held" : "Constr";
554             inf->short_text   = you.held == HELD_MONSTER ? "held" : "constricted";
555         }
556         break;
557
558     case STATUS_TERRAIN:
559         _describe_terrain(inf);
560         break;
561
562     // Silenced by an external source.
563     case STATUS_SILENCE:
564         if (silenced(you.pos()) && !you.duration[DUR_SILENCE])
565         {
566             inf->light_colour = LIGHTMAGENTA;
567             inf->light_text   = "Sil";
568             inf->short_text   = "silenced";
569             inf->long_text    = "You are silenced.";
570         }
571         break;
572
573     case DUR_SONG_OF_SLAYING:
574         inf->light_text = make_stringf("Slay (%u)",
575                                        you.props["song_of_slaying_bonus"].get_int());
576         break;
577
578     case STATUS_NO_CTELE:
579         if (!allow_control_teleport(true))
580         {
581             inf->light_colour = RED;
582             inf->light_text = "-cTele";
583         }
584         break;
585
586     case STATUS_BEOGH:
587         if (env.level_state & LSTATE_BEOGH && can_convert_to_beogh())
588         {
589             inf->light_colour = WHITE;
590             inf->light_text = "Beogh";
591         }
592         break;
593
594     case STATUS_RECALL:
595         if (you.attribute[ATTR_NEXT_RECALL_INDEX] > 0)
596         {
597             inf->light_colour = WHITE;
598             inf->light_text   = "Recall";
599             inf->short_text   = "recalling";
600             inf->long_text    = "You are recalling your allies.";
601         }
602         break;
603
604     case DUR_WATER_HOLD:
605         inf->light_text   = "Engulf";
606         if (you.res_water_drowning())
607         {
608             inf->short_text   = "engulfed";
609             inf->long_text    = "You are engulfed in water.";
610             if (you.can_swim())
611                 inf->light_colour = DARKGREY;
612             else
613                 inf->light_colour = YELLOW;
614         }
615         else
616         {
617             inf->short_text   = "engulfed (cannot breathe)";
618             inf->long_text    = "You are engulfed in water and unable to breathe.";
619             inf->light_colour = RED;
620         }
621         break;
622
623     default:
624         if (!found)
625         {
626             inf->light_colour = RED;
627             inf->light_text   = "Missing";
628             inf->short_text   = "missing status";
629             inf->long_text    = "Missing status description.";
630             return false;
631         }
632         else
633             break;
634     }
635     return true;
636 }
637
638 static void _describe_hunger(status_info* inf)
639 {
640     const bool vamp = (you.species == SP_VAMPIRE);
641
642     switch (you.hunger_state)
643     {
644     case HS_ENGORGED:
645         inf->light_colour = LIGHTGREEN;
646         inf->light_text   = (vamp ? "Alive" : "Engorged");
647         break;
648     case HS_VERY_FULL:
649         inf->light_colour = GREEN;
650         inf->light_text   = "Very Full";
651         break;
652     case HS_FULL:
653         inf->light_colour = GREEN;
654         inf->light_text   = "Full";
655         break;
656     case HS_HUNGRY:
657         inf->light_colour = YELLOW;
658         inf->light_text   = (vamp ? "Thirsty" : "Hungry");
659         break;
660     case HS_VERY_HUNGRY:
661         inf->light_colour = YELLOW;
662         inf->light_text   = (vamp ? "Very Thirsty" : "Very Hungry");
663         break;
664     case HS_NEAR_STARVING:
665         inf->light_colour = YELLOW;
666         inf->light_text   = (vamp ? "Near Bloodless" : "Near Starving");
667         break;
668     case HS_STARVING:
669         inf->light_colour = RED;
670         inf->light_text   = (vamp ? "Bloodless" : "Starving");
671         inf->short_text   = (vamp ? "bloodless" : "starving");
672         break;
673     case HS_SATIATED: // no status light
674     default:
675         break;
676     }
677 }
678
679 static void _describe_glow(status_info* inf)
680 {
681     const int cont = get_contamination_level();
682     if (cont > 0)
683     {
684         inf->light_colour = DARKGREY;
685         if (cont > 1)
686             inf->light_colour = _bad_ench_colour(cont, 2, 3);
687         if (cont > 1 || you.species != SP_DJINNI)
688             inf->light_text = "Contam";
689     }
690
691     if (cont > 0)
692     {
693         inf->short_text =
694                  (cont == 1) ? "very slightly " :
695                  (cont == 2) ? "slightly " :
696                  (cont == 3) ? "" :
697                  (cont == 4) ? "moderately " :
698                  (cont == 5) ? "heavily "
699                              : "really heavily ";
700         inf->short_text += "contaminated";
701         inf->long_text = describe_contamination(cont);
702     }
703 }
704
705 static void _describe_regen(status_info* inf)
706 {
707     const bool regen = (you.duration[DUR_REGENERATION] > 0);
708     const bool no_heal =
709             (you.species == SP_VAMPIRE && you.hunger_state == HS_STARVING)
710             || (player_mutation_level(MUT_SLOW_HEALING) == 3);
711     // Does vampire hunger level affect regeneration rate significantly?
712     const bool vampmod = !no_heal && !regen && you.species == SP_VAMPIRE
713                          && you.hunger_state != HS_SATIATED;
714
715     if (regen)
716     {
717         inf->light_colour = _dur_colour(BLUE, dur_expiring(DUR_REGENERATION));
718         inf->light_text   = "Regen";
719         if (you.attribute[ATTR_DIVINE_REGENERATION])
720             inf->light_text += " MR";
721         else if (no_heal)
722             inf->light_colour = DARKGREY;
723     }
724
725     if ((you.disease && !regen) || no_heal)
726        inf->short_text = "non-regenerating";
727     else if (regen)
728     {
729         if (you.disease)
730         {
731             inf->short_text = "recuperating";
732             inf->long_text  = "You are recuperating from your illness.";
733         }
734         else
735         {
736             inf->short_text = "regenerating";
737             inf->long_text  = "You are regenerating.";
738         }
739         _mark_expiring(inf, dur_expiring(DUR_REGENERATION));
740     }
741     else if (vampmod)
742     {
743         if (you.disease)
744             inf->short_text = "recuperating";
745         else
746             inf->short_text = "regenerating";
747
748         if (you.hunger_state < HS_SATIATED)
749             inf->short_text += " slowly";
750         else if (you.hunger_state < HS_ENGORGED)
751             inf->short_text += " quickly";
752         else
753             inf->short_text += " very quickly";
754     }
755 }
756
757 static void _describe_poison(status_info* inf)
758 {
759     int pois = you.duration[DUR_POISONING];
760     inf->light_colour = (player_res_poison(false) >= 3
761                          ? DARKGREY : _bad_ench_colour(pois, 5, 10));
762     inf->light_text   = "Pois";
763     const string adj =
764          (pois > 10) ? "extremely" :
765          (pois > 5)  ? "very" :
766          (pois > 3)  ? "quite"
767                      : "mildly";
768     inf->short_text   = adj + " poisoned";
769     inf->long_text    = "You are " + inf->short_text + ".";
770 }
771
772 static void _describe_speed(status_info* inf)
773 {
774     if (you.duration[DUR_SLOW] && you.duration[DUR_HASTE])
775     {
776         inf->light_colour = MAGENTA;
777         inf->light_text   = "Fast+Slow";
778         inf->short_text   = "hasted and slowed";
779         inf->long_text = "You are under both slowing and hasting effects.";
780     }
781     else if (you.duration[DUR_SLOW])
782     {
783         inf->light_colour = RED;
784         inf->light_text   = "Slow";
785         inf->short_text   = "slowed";
786         inf->long_text    = "You are slowed.";
787     }
788     else if (you.duration[DUR_HASTE])
789     {
790         inf->light_colour = _dur_colour(BLUE, dur_expiring(DUR_HASTE));
791         inf->light_text   = "Fast";
792         inf->short_text = "hasted";
793         inf->long_text = "Your actions are hasted.";
794         _mark_expiring(inf, dur_expiring(DUR_HASTE));
795     }
796 }
797
798 static void _describe_sage(status_info* inf)
799 {
800     if (you.sage_skills.empty())
801         return;
802
803     vector<const char*> sages;
804     for (unsigned long i = 0; i < you.sage_skills.size(); ++i)
805         sages.push_back(skill_name(you.sage_skills[i]));
806
807     inf->light_colour = LIGHTBLUE;
808     inf->light_text   = "Sage";
809     inf->short_text   = "sage [" + comma_separated_line(sages.begin(),
810                         sages.end(), ", ") + "]";
811     inf->long_text    = "You feel studious about " + comma_separated_line(
812                         sages.begin(), sages.end()) + ".";
813 }
814
815 static void _describe_airborne(status_info* inf)
816 {
817     if (!you.airborne())
818         return;
819
820     const bool perm     = you.permanent_flight();
821     const bool expiring = (!perm && dur_expiring(DUR_FLIGHT));
822
823     inf->light_colour = you.tengu_flight() ? BLUE : perm ? WHITE : MAGENTA;
824     inf->light_text   = "Fly";
825     inf->short_text   = "flying";
826     inf->long_text    = "You are flying.";
827     inf->light_colour = _dur_colour(inf->light_colour, expiring);
828     _mark_expiring(inf, expiring);
829 }
830
831 static void _describe_rotting(status_info* inf)
832 {
833     if (you.rotting)
834     {
835         inf->light_colour = _bad_ench_colour(you.rotting, 4, 8);
836         inf->light_text   = "Rot";
837     }
838
839     if (you.rotting || you.species == SP_GHOUL)
840     {
841         inf->short_text = "rotting";
842         inf->long_text = "Your flesh is rotting";
843         int rot = you.rotting;
844         if (you.species == SP_GHOUL)
845             rot += 1 + (1 << max(0, HS_SATIATED - you.hunger_state));
846         if (rot > 15)
847             inf->long_text += " before your eyes";
848         else if (rot > 8)
849             inf->long_text += " away quickly";
850         else if (rot > 4)
851             inf->long_text += " badly";
852         else if (you.species == SP_GHOUL)
853             if (rot > 2)
854                 inf->long_text += " faster than usual";
855             else
856                 inf->long_text += " at the usual pace";
857         inf->long_text += ".";
858     }
859 }
860
861 static void _describe_sickness(status_info* inf)
862 {
863     if (you.disease)
864     {
865         const int high = 120 * BASELINE_DELAY;
866         const int low  =  40 * BASELINE_DELAY;
867
868         inf->light_colour   = _bad_ench_colour(you.disease, low, high);
869         inf->light_text     = "Sick";
870
871         string mod = (you.disease > high) ? "badly "  :
872                      (you.disease >  low) ? ""
873                                           : "mildly ";
874
875         inf->short_text = mod + "diseased";
876         inf->long_text  = "You are " + mod + "diseased.";
877     }
878 }
879
880 static void _describe_burden(status_info* inf)
881 {
882     switch (you.burden_state)
883     {
884     case BS_OVERLOADED:
885         inf->light_colour = RED;
886         inf->light_text   = "Overloaded";
887         inf->short_text   = "overloaded";
888         inf->long_text    = "You are overloaded with stuff.";
889         break;
890     case BS_ENCUMBERED:
891         inf->light_colour = LIGHTRED;
892         inf->light_text   = "Burdened";
893         inf->short_text   = "burdened";
894         inf->long_text    = "You are burdened.";
895         break;
896     case BS_UNENCUMBERED:
897         break;
898     }
899 }
900
901 static void _describe_transform(status_info* inf)
902 {
903     const bool vampbat = (you.species == SP_VAMPIRE && you.form == TRAN_BAT);
904     const bool expire  = dur_expiring(DUR_TRANSFORMATION) && !vampbat;
905
906     switch (you.form)
907     {
908     case TRAN_BAT:
909         inf->light_text     = "Bat";
910         inf->short_text     = "bat-form";
911         inf->long_text      = "You are in ";
912         if (vampbat)
913             inf->long_text += "vampire ";
914         inf->long_text     += "bat-form.";
915         break;
916     case TRAN_BLADE_HANDS:
917         inf->light_text = "Blades";
918         inf->short_text = "blade " + blade_parts(true);
919         inf->long_text  = "You have blades for " + blade_parts() + ".";
920         break;
921     case TRAN_DRAGON:
922         inf->light_text = "Dragon";
923         inf->short_text = "dragon-form";
924         inf->long_text  = "You are in dragon-form.";
925         break;
926     case TRAN_ICE_BEAST:
927         inf->light_text = "Ice";
928         inf->short_text = "ice-form";
929         inf->long_text  = "You are an ice creature.";
930         break;
931     case TRAN_LICH:
932         inf->light_text = "Lich";
933         inf->short_text = "lich-form";
934         inf->long_text  = "You are in lich-form.";
935         break;
936     case TRAN_PIG:
937         inf->light_text = "Pig";
938         inf->short_text = "pig-form";
939         inf->long_text  = "You are a filthy swine.";
940         break;
941     case TRAN_SPIDER:
942         inf->light_text = "Spider";
943         inf->short_text = "spider-form";
944         inf->long_text  = "You are in spider-form.";
945         break;
946     case TRAN_STATUE:
947         inf->light_text = "Statue";
948         inf->short_text = "statue-form";
949         inf->long_text  = "You are a statue.";
950         break;
951     case TRAN_APPENDAGE:
952         inf->light_text = "App";
953         inf->short_text = "appendage";
954         inf->long_text  = "You have a beastly appendage.";
955         break;
956     case TRAN_FUNGUS:
957         inf->light_text = "Fungus";
958         inf->short_text = "fungus-form";
959         inf->long_text  = "You are a sentient fungus.";
960         break;
961     case TRAN_TREE:
962         inf->light_text = "Tree";
963         inf->short_text = "tree-form";
964         inf->long_text  = "You are an animated tree.";
965         break;
966     case TRAN_JELLY:
967         inf->light_text = "Jelly";
968         inf->short_text = "jelly-form";
969         inf->long_text  = "You are a lump of jelly.";
970         break;
971     case TRAN_PORCUPINE:
972         inf->light_text = "Porc";
973         inf->short_text = "porcupine-form";
974         inf->long_text  = "You are a porcupine.";
975         break;
976     case TRAN_WISP:
977         inf->light_text = "Wisp";
978         inf->short_text = "wisp-form";
979         inf->long_text  = "You are an insubstantial wisp.";
980         break;
981     case TRAN_NONE:
982         break;
983     }
984
985     inf->light_colour = _dur_colour(GREEN, expire);
986     _mark_expiring(inf, expire);
987 }
988
989 static const char* s0_names[NUM_STATS] = { "Collapse", "Brainless", "Clumsy", };
990
991 static void _describe_stat_zero(status_info* inf, stat_type st)
992 {
993     if (you.stat_zero[st])
994     {
995         inf->light_colour = you.stat(st) ? LIGHTRED : RED;
996         inf->light_text   = s0_names[st];
997         inf->short_text   = make_stringf("lost %s", stat_desc(st, SD_NAME));
998         inf->long_text    = make_stringf(you.stat(st) ?
999                 "You are recovering from loss of %s." : "You have no %s!",
1000                 stat_desc(st, SD_NAME));
1001     }
1002 }
1003
1004 static void _describe_terrain(status_info* inf)
1005 {
1006     switch (grd(you.pos()))
1007     {
1008     case DNGN_SHALLOW_WATER:
1009         inf->light_colour = LIGHTBLUE;
1010         inf->light_text = "Water";
1011         break;
1012     case DNGN_DEEP_WATER:
1013         inf->light_colour = BLUE;
1014         inf->light_text = "Water";
1015         break;
1016     case DNGN_LAVA:
1017         inf->light_colour = RED;
1018         inf->light_text = "Lava";
1019         break;
1020     default:
1021         ;
1022     }
1023 }
1024
1025 static void _describe_missiles(status_info* inf)
1026 {
1027     const int level = you.missile_deflection();
1028     if (!level)
1029         return;
1030
1031     bool expiring;
1032     if (level > 1)
1033     {
1034         inf->light_colour = MAGENTA;
1035         inf->light_text   = "DMsl";
1036         inf->short_text   = "deflect missiles";
1037         inf->long_text    = "You deflect missiles.";
1038         expiring = dur_expiring(DUR_DEFLECT_MISSILES);
1039     }
1040     else
1041     {
1042         bool perm = player_mutation_level(MUT_DISTORTION_FIELD) == 3
1043                     || !you.suppressed() && you.scan_artefacts(ARTP_RMSL);
1044         inf->light_colour = perm ? WHITE : BLUE;
1045         inf->light_text   = "RMsl";
1046         inf->short_text   = "repel missiles";
1047         inf->long_text    = "You repel missiles.";
1048         expiring = (!perm && dur_expiring(DUR_REPEL_MISSILES));
1049     }
1050
1051     inf->light_colour = _dur_colour(inf->light_colour, expiring);
1052     _mark_expiring(inf, expiring);
1053 }