1 #include "AppHdr.h"
2 
3 #include "status.h"
4 
5 #include "areas.h"
6 #include "branch.h"
7 #include "cloud.h"
8 #include "duration-type.h"
9 #include "env.h"
10 #include "evoke.h"
11 #include "god-abil.h"
12 #include "god-passive.h"
13 #include "item-prop.h"
14 #include "level-state-type.h"
15 #include "mon-transit.h" // untag_followers() in duration-data
16 #include "mutation.h"
17 #include "options.h"
18 #include "orb.h" // orb_limits_translocation in fill_status_info
19 #include "player-stats.h"
20 #include "random.h" // for midpoint_msg.offset() in duration-data
21 #include "religion.h"
22 #include "spl-summoning.h" // NEXT_DOOM_HOUND_KEY in duration-data
23 #include "spl-transloc.h" // for you_teleport_now() in duration-data
24 #include "spl-wpnench.h" // for _end_weapon_brand() in duration-data
25 #include "stringutil.h"
26 #include "throw.h"
27 #include "timed-effects.h" // bezotting_level
28 #include "transform.h"
29 #include "traps.h"
30 
31 #include "duration-data.h"
32 
33 static int duration_index[NUM_DURATIONS];
34 
init_duration_index()35 void init_duration_index()
36 {
37     COMPILE_CHECK(ARRAYSZ(duration_data) == NUM_DURATIONS);
38     for (int i = 0; i < NUM_DURATIONS; ++i)
39         duration_index[i] = -1;
40 
41     for (unsigned i = 0; i < ARRAYSZ(duration_data); ++i)
42     {
43         duration_type dur = duration_data[i].dur;
44         ASSERT_RANGE(dur, 0, NUM_DURATIONS);
45         // Catch redefinitions.
46         ASSERT(duration_index[dur] == -1);
47         duration_index[dur] = i;
48     }
49 }
50 
_lookup_duration(duration_type dur)51 static const duration_def* _lookup_duration(duration_type dur)
52 {
53     ASSERT_RANGE(dur, 0, NUM_DURATIONS);
54     if (duration_index[dur] == -1)
55         return nullptr;
56     else
57         return &duration_data[duration_index[dur]];
58 }
59 
duration_name(duration_type dur)60 const char *duration_name(duration_type dur)
61 {
62     return _lookup_duration(dur)->name();
63 }
64 
duration_dispellable(duration_type dur)65 bool duration_dispellable(duration_type dur)
66 {
67     return _lookup_duration(dur)->duration_has_flag(D_DISPELLABLE);
68 }
69 
_bad_ench_colour(int lvl,int orange,int red)70 static int _bad_ench_colour(int lvl, int orange, int red)
71 {
72     if (lvl >= red)
73         return RED;
74     else if (lvl >= orange)
75         return LIGHTRED;
76 
77     return YELLOW;
78 }
79 
_dur_colour(int exp_colour,bool expiring)80 static int _dur_colour(int exp_colour, bool expiring)
81 {
82     if (expiring)
83         return exp_colour;
84     else
85     {
86         switch (exp_colour)
87         {
88         case GREEN:
89             return LIGHTGREEN;
90         case BLUE:
91             return LIGHTBLUE;
92         case MAGENTA:
93             return LIGHTMAGENTA;
94         case LIGHTGREY:
95             return WHITE;
96         default:
97             return exp_colour;
98         }
99     }
100 }
101 
_mark_expiring(status_info & inf,bool expiring)102 static void _mark_expiring(status_info& inf, bool expiring)
103 {
104     if (expiring)
105     {
106         if (!inf.short_text.empty())
107             inf.short_text += " (expiring)";
108         if (!inf.long_text.empty())
109             inf.long_text = "Expiring: " + inf.long_text;
110     }
111 }
112 
_ray_text()113 static string _ray_text()
114 {
115     // i feel like we could do this with math instead...
116     switch (you.attribute[ATTR_SEARING_RAY])
117     {
118         case 2:
119             return "Ray+";
120         case 3:
121             return "Ray++";
122         default:
123             return "Ray";
124     }
125 }
126 
127 static vector<string> _charge_strings = { "Charge-", "Charge/",
128                                           "Charge|", "Charge\\"};
129 
_charge_text()130 static string _charge_text()
131 {
132     static int charge_index = 0;
133     charge_index = (charge_index + 1) % 4;
134     return _charge_strings[charge_index];
135 }
136 
137 /**
138  * Populate a status_info struct from the duration_data struct corresponding
139  * to the given duration_type.
140  *
141  * @param[in] dur    The duration in question.
142  * @param[out] inf   The status_info struct to be filled.
143  * @return           Whether a duration_data struct was found.
144  */
_fill_inf_from_ddef(duration_type dur,status_info & inf)145 static bool _fill_inf_from_ddef(duration_type dur, status_info& inf)
146 {
147     const duration_def* ddef = _lookup_duration(dur);
148     if (!ddef)
149         return false;
150 
151     inf.light_colour = ddef->light_colour;
152     inf.light_text   = ddef->light_text;
153     inf.short_text   = ddef->short_text;
154     inf.long_text    = ddef->long_text;
155     if (ddef->duration_has_flag(D_EXPIRES))
156     {
157         inf.light_colour = _dur_colour(inf.light_colour, dur_expiring(dur));
158         _mark_expiring(inf, dur_expiring(dur));
159     }
160 
161     return true;
162 }
163 
164 static void _describe_airborne(status_info& inf);
165 static void _describe_glow(status_info& inf);
166 static void _describe_regen(status_info& inf);
167 static void _describe_sickness(status_info& inf);
168 static void _describe_speed(status_info& inf);
169 static void _describe_poison(status_info& inf);
170 static void _describe_transform(status_info& inf);
171 static void _describe_stat_zero(status_info& inf, stat_type st);
172 static void _describe_terrain(status_info& inf);
173 static void _describe_missiles(status_info& inf);
174 static void _describe_invisible(status_info& inf);
175 static void _describe_zot(status_info& inf);
176 
fill_status_info(int status,status_info & inf)177 bool fill_status_info(int status, status_info& inf)
178 {
179     inf = status_info();
180 
181     bool found = false;
182 
183     // Sort out inactive durations, and fill in data from duration_data
184     // for the simple durations.
185     if (status >= 0 && status < NUM_DURATIONS)
186     {
187         duration_type dur = static_cast<duration_type>(status);
188 
189         if (!you.duration[dur])
190             return false;
191 
192         found = _fill_inf_from_ddef(dur, inf);
193     }
194 
195     // Now treat special status types and durations, possibly
196     // completing or overriding the defaults set above.
197     switch (status)
198     {
199     case DUR_CORROSION:
200         inf.light_text = make_stringf("Corr (%d)",
201                           (-4 * you.props["corrosion_amount"].get_int()));
202         break;
203 
204     case DUR_FLAYED:
205         inf.light_text = make_stringf("Flay (%d)",
206                           (-1 * you.props["flay_damage"].get_int()));
207         break;
208 
209     case DUR_NO_POTIONS:
210         if (!you.can_drink(false))
211             inf.light_colour = DARKGREY;
212         break;
213 
214     case DUR_SWIFTNESS:
215         if (you.attribute[ATTR_SWIFTNESS] < 0)
216         {
217             inf.light_text   = "-Swift";
218             inf.light_colour = RED;
219             inf.short_text   = "sluggish";
220             inf.long_text    = "You are moving sluggishly.";
221         }
222         if (you.in_liquid())
223             inf.light_colour = DARKGREY;
224         break;
225 
226     case STATUS_ZOT:
227         _describe_zot(inf);
228         break;
229 
230     case STATUS_CURL:
231         if (you.props[PALENTONGA_CURL_KEY].get_bool())
232         {
233             inf.light_text = "Curl";
234             inf.light_colour = BLUE;
235             inf.short_text = "curled up";
236             inf.long_text = "You are defensively curled.";
237         }
238         break;
239 
240     case STATUS_AIRBORNE:
241         _describe_airborne(inf);
242         break;
243 
244     case STATUS_BEHELD:
245         if (you.beheld())
246         {
247             inf.light_colour = RED;
248             inf.light_text   = "Mesm";
249             inf.short_text   = "mesmerised";
250             inf.long_text    = "You are mesmerised.";
251         }
252         break;
253 
254     case STATUS_CONTAMINATION:
255         _describe_glow(inf);
256         break;
257 
258     case STATUS_BACKLIT:
259         if (you.backlit())
260         {
261             inf.short_text = "glowing";
262             inf.long_text  = "You are glowing.";
263         }
264         break;
265 
266     case STATUS_UMBRA:
267         if (you.umbra())
268         {
269             inf.short_text   = "wreathed by umbra";
270             inf.long_text    = "You are wreathed by an umbra.";
271         }
272         break;
273 
274     case STATUS_NET:
275         if (you.attribute[ATTR_HELD])
276         {
277             inf.light_colour = RED;
278             inf.light_text   = "Held";
279             inf.short_text   = "held";
280             inf.long_text    = make_stringf("You are %s.", held_status());
281         }
282         break;
283 
284     case STATUS_ALIVE_STATE:
285         if (you.has_mutation(MUT_VAMPIRISM))
286         {
287             if (!you.vampire_alive)
288             {
289                 inf.light_colour = LIGHTRED;
290                 inf.light_text = "Bloodless";
291                 inf.short_text = "bloodless";
292             }
293             else
294             {
295                 inf.light_colour = GREEN;
296                 inf.light_text = "Alive";
297             }
298         }
299         break;
300 
301     case STATUS_REGENERATION:
302         // DUR_TROGS_HAND + some vampire and non-healing stuff
303         _describe_regen(inf);
304         break;
305 
306     case STATUS_SICK:
307         _describe_sickness(inf);
308         break;
309 
310     case STATUS_SPEED:
311         _describe_speed(inf);
312         break;
313 
314     case STATUS_LIQUEFIED:
315     {
316         if (you.liquefied_ground() || you.duration[DUR_LIQUEFYING])
317         {
318             inf.light_colour = BROWN;
319             inf.light_text   = "SlowM";
320             inf.short_text   = "slowed movement";
321             inf.long_text    = "Your movement is slowed on this liquid ground.";
322         }
323         break;
324     }
325 
326     case STATUS_AUGMENTED:
327     {
328         int level = augmentation_amount();
329 
330         if (level > 0)
331         {
332             inf.light_colour = (level == 3) ? WHITE :
333                                (level == 2) ? LIGHTBLUE
334                                             : BLUE;
335 
336             inf.light_text = "Aug";
337         }
338         break;
339     }
340 
341     case DUR_CONFUSING_TOUCH:
342     {
343         inf.long_text = you.hands_act("are", "glowing red.");
344         break;
345     }
346 
347     case DUR_SLIMIFY:
348     {
349         inf.long_text = you.hands_act("are", "covered in slime.");
350         break;
351     }
352 
353     case DUR_POISONING:
354         _describe_poison(inf);
355         break;
356 
357     case DUR_POWERED_BY_DEATH:
358     {
359         const int pbd_str = you.props[POWERED_BY_DEATH_KEY].get_int();
360         if (pbd_str > 0)
361         {
362             inf.light_colour = LIGHTMAGENTA;
363             inf.light_text   = make_stringf("Regen (%d)", pbd_str);
364         }
365         break;
366     }
367 
368     case STATUS_MISSILES:
369         _describe_missiles(inf);
370         break;
371 
372     case STATUS_INVISIBLE:
373         _describe_invisible(inf);
374         break;
375 
376     case STATUS_MANUAL:
377     {
378         string skills = manual_skill_names();
379         if (!skills.empty())
380         {
381             inf.short_text = "studying " + manual_skill_names(true);
382             inf.long_text = "You are studying " + skills + ".";
383         }
384         break;
385     }
386 
387     case DUR_TRANSFORMATION:
388         _describe_transform(inf);
389         break;
390 
391     case STATUS_STR_ZERO:
392         _describe_stat_zero(inf, STAT_STR);
393         break;
394     case STATUS_INT_ZERO:
395         _describe_stat_zero(inf, STAT_INT);
396         break;
397     case STATUS_DEX_ZERO:
398         _describe_stat_zero(inf, STAT_DEX);
399         break;
400 
401     case STATUS_CONSTRICTED:
402         if (you.is_constricted())
403         {
404             // Our constrictor isn't, valid so don't report this status.
405             if (you.has_invalid_constrictor())
406                 return false;
407 
408             const monster * const cstr = monster_by_mid(you.constricted_by);
409             ASSERT(cstr);
410 
411             const bool damage =
412                 cstr->constriction_does_damage(you.is_directly_constricted());
413 
414             inf.light_colour = YELLOW;
415             inf.light_text   = damage ? "Constr"      : "Held";
416             inf.short_text   = damage ? "constricted" : "held";
417         }
418         break;
419 
420     case STATUS_TERRAIN:
421         _describe_terrain(inf);
422         break;
423 
424     // Also handled by DUR_SILENCE, see duration-data.h
425     case STATUS_SILENCE:
426         if (silenced(you.pos()) && !you.duration[DUR_SILENCE])
427         {
428             // Only display the status light if not using the noise bar.
429             if (Options.equip_bar)
430             {
431                 inf.light_colour = LIGHTRED;
432                 inf.light_text   = "Sil";
433             }
434             inf.short_text   = "silenced";
435             inf.long_text    = "You are silenced.";
436         }
437         if (Options.equip_bar && you.duration[DUR_SILENCE])
438         {
439             inf.light_colour = LIGHTMAGENTA;
440             inf.light_text = "Sil";
441         }
442         break;
443 
444     case STATUS_SERPENTS_LASH:
445         if (you.attribute[ATTR_SERPENTS_LASH] > 0)
446         {
447             inf.light_colour = WHITE;
448             inf.light_text
449                = make_stringf("Lash (%u)",
450                               you.attribute[ATTR_SERPENTS_LASH]);
451             inf.short_text = "serpent's lash";
452             inf.long_text = "You are moving at supernatural speed.";
453         }
454         break;
455 
456     case STATUS_HEAVENLY_STORM:
457         if (you.props.exists(WU_JIAN_HEAVENLY_STORM_KEY))
458         {
459             inf.light_colour = WHITE;
460             inf.light_text
461                 = make_stringf("Storm (%d)",
462                                you.props[WU_JIAN_HEAVENLY_STORM_KEY].get_int());
463         }
464         break;
465 
466     case DUR_WEREBLOOD:
467         inf.light_text
468             = make_stringf("Slay (%u)",
469                            you.props[WEREBLOOD_KEY].get_int());
470         break;
471 
472     case STATUS_BEOGH:
473         if (env.level_state & LSTATE_BEOGH && can_convert_to_beogh())
474         {
475             inf.light_colour = WHITE;
476             inf.light_text = "Beogh";
477         }
478         break;
479 
480     case STATUS_RECALL:
481         if (you.attribute[ATTR_NEXT_RECALL_INDEX] > 0)
482         {
483             inf.light_colour = WHITE;
484             inf.light_text   = "Recall";
485             inf.short_text   = "recalling";
486             inf.long_text    = "You are recalling your allies.";
487         }
488         break;
489 
490     case DUR_WATER_HOLD:
491         inf.light_text   = "Engulf";
492         if (you.res_water_drowning())
493         {
494             inf.short_text   = "engulfed";
495             inf.long_text    = "You are engulfed.";
496             inf.light_colour = DARKGREY;
497         }
498         else
499         {
500             inf.short_text   = "engulfed (cannot breathe)";
501             inf.long_text    = "You are engulfed and unable to breathe.";
502             inf.light_colour = RED;
503         }
504         break;
505 
506     case STATUS_DRAINED:
507     {
508         const int drain_perc = 100 * -you.hp_max_adj_temp / get_real_hp(false, false);
509 
510         if (drain_perc >= 50)
511         {
512             inf.light_colour = MAGENTA;
513             inf.light_text   = "Drain";
514             inf.short_text   = "extremely drained";
515             inf.long_text    = "Your life force is extremely drained.";
516         }
517         else if (drain_perc >= 25)
518         {
519             inf.light_colour = RED;
520             inf.light_text   = "Drain";
521             inf.short_text   = "very heavily drained";
522             inf.long_text    = "Your life force is very heavily drained.";
523         }
524         else if (drain_perc >= 10)
525         {
526             inf.light_colour = LIGHTRED;
527             inf.light_text   = "Drain";
528             inf.short_text   = "heavily drained";
529             inf.long_text    = "Your life force is heavily drained.";
530         }
531         else if (drain_perc >= 5)
532         {
533             inf.light_colour = YELLOW;
534             inf.light_text   = "Drain";
535             inf.short_text   = "drained";
536             inf.long_text    = "Your life force is drained.";
537         }
538         else if (you.hp_max_adj_temp)
539         {
540             inf.light_colour = LIGHTGREY;
541             inf.light_text   = "Drain";
542             inf.short_text   = "lightly drained";
543             inf.long_text    = "Your life force is lightly drained.";
544         }
545         break;
546 
547     }
548     case STATUS_RAY:
549         if (you.attribute[ATTR_SEARING_RAY])
550         {
551             inf.light_colour = WHITE;
552             inf.light_text   = _ray_text().c_str();
553         }
554         break;
555 
556     case STATUS_DIG:
557         if (you.digging)
558         {
559             inf.light_colour = WHITE;
560             inf.light_text   = "Dig";
561         }
562         break;
563 
564     case STATUS_MAGIC_SAPPED:
565         if (you.props[SAP_MAGIC_KEY].get_int() >= 3)
566         {
567             inf.light_colour = RED;
568             inf.light_text   = "-Wiz";
569             inf.short_text   = "extremely magic sapped";
570             inf.long_text    = "Your control over your magic has "
571                                 "been greatly sapped.";
572         }
573         else if (you.props[SAP_MAGIC_KEY].get_int() == 2)
574         {
575             inf.light_colour = LIGHTRED;
576             inf.light_text   = "-Wiz";
577             inf.short_text   = "very magic sapped";
578             inf.long_text    = "Your control over your magic has "
579                                 "been significantly sapped.";
580         }
581         else if (you.props[SAP_MAGIC_KEY].get_int() == 1)
582         {
583             inf.light_colour = YELLOW;
584             inf.light_text   = "-Wiz";
585             inf.short_text   = "magic sapped";
586             inf.long_text    = "Your control over your magic has "
587                                 "been sapped.";
588         }
589         break;
590 
591     case STATUS_BRIBE:
592     {
593         int bribe = 0;
594         vector<const char *> places;
595         for (int i = 0; i < NUM_BRANCHES; i++)
596         {
597             branch_type br = gozag_fixup_branch(static_cast<branch_type>(i));
598 
599             if (branch_bribe[br] > 0)
600             {
601                 if (player_in_branch(static_cast<branch_type>(i)))
602                     bribe = branch_bribe[br];
603 
604                 places.push_back(branches[static_cast<branch_type>(i)]
605                                  .longname);
606             }
607         }
608 
609         if (bribe > 0)
610         {
611             inf.light_colour = (bribe >= 2000) ? WHITE :
612                                 (bribe >= 1000) ? LIGHTBLUE
613                                                 : BLUE;
614 
615             inf.light_text = "Bribe";
616             inf.short_text = make_stringf("bribing [%s]",
617                                            comma_separated_line(places.begin(),
618                                                                 places.end(),
619                                                                 ", ", ", ")
620                                                                 .c_str());
621             inf.long_text = "You are bribing "
622                              + comma_separated_line(places.begin(),
623                                                     places.end())
624                              + ".";
625         }
626         break;
627     }
628 
629     case DUR_HORROR:
630     {
631         const int horror = you.props[HORROR_PENALTY_KEY].get_int();
632         inf.light_text = make_stringf("Horr(%d)", -1 * horror);
633         if (horror >= HORROR_LVL_OVERWHELMING)
634         {
635             inf.light_colour = RED;
636             inf.short_text   = "overwhelmed with horror";
637             inf.long_text    = "Horror overwhelms you!";
638         }
639         else if (horror >= HORROR_LVL_EXTREME)
640         {
641             inf.light_colour = LIGHTRED;
642             inf.short_text   = "extremely horrified";
643             inf.long_text    = "You are extremely horrified!";
644         }
645         else if (horror)
646         {
647             inf.light_colour = YELLOW;
648             inf.short_text   = "horrified";
649             inf.long_text    = "You are horrified!";
650         }
651         break;
652     }
653 
654     case STATUS_CLOUD:
655     {
656         cloud_type cloud = cloud_type_at(you.pos());
657         if (Options.cloud_status && cloud != CLOUD_NONE)
658         {
659             inf.light_text = "Cloud";
660             // TODO: make the colour based on the cloud's color; requires elemental
661             // status lights, though.
662             inf.light_colour =
663                 is_damaging_cloud(cloud, true, cloud_is_yours_at(you.pos())) ? LIGHTRED : DARKGREY;
664         }
665         break;
666     }
667 
668     case DUR_CLEAVE:
669     {
670         const item_def* weapon = you.weapon();
671 
672         if (weapon && item_attack_skill(*weapon) == SK_AXES)
673             inf.light_colour = DARKGREY;
674 
675         break;
676     }
677 
678     case DUR_PORTAL_PROJECTILE:
679     {
680         if (!is_pproj_active())
681             inf.light_colour = DARKGREY;
682         break;
683     }
684 
685     case STATUS_ORB:
686     {
687         if (player_has_orb())
688         {
689             inf.light_colour = LIGHTMAGENTA;
690             inf.light_text = "Orb";
691         }
692         else if (orb_limits_translocation())
693         {
694             inf.light_colour = MAGENTA;
695             inf.light_text = "Orb";
696         }
697 
698         break;
699     }
700 
701     case STATUS_STILL_WINDS:
702         if (env.level_state & LSTATE_STILL_WINDS)
703         {
704             inf.light_colour = BROWN;
705             inf.light_text = "-Clouds";
706         }
707         break;
708 
709     case STATUS_MAXWELLS:
710         if (you.props.exists("maxwells_charge_time"))
711         {
712             inf.light_colour = LIGHTCYAN;
713             inf.light_text   = _charge_text().c_str();
714         }
715         break;
716 
717     default:
718         if (!found)
719         {
720             inf.light_colour = RED;
721             inf.light_text   = "Missing";
722             inf.short_text   = "missing status";
723             inf.long_text    = "Missing status description.";
724             return false;
725         }
726         else
727             break;
728     }
729     return true;
730 }
731 
_describe_zot(status_info & inf)732 static void _describe_zot(status_info& inf)
733 {
734     const int lvl = bezotting_level();
735     if (lvl > 0)
736     {
737         inf.short_text = "bezotted";
738         inf.long_text = "Zot is approaching!";
739     }
740     else if (!Options.always_show_zot || !zot_clock_active())
741         return;
742 
743     inf.light_text = make_stringf("Zot (%d)", turns_until_zot());
744     switch (lvl)
745     {
746         case 0:
747             inf.light_colour = WHITE;
748             break;
749         case 1:
750             inf.light_colour = YELLOW;
751             break;
752         case 2:
753             inf.light_colour = RED;
754             break;
755         case 3:
756         default:
757             inf.light_colour = MAGENTA;
758             break;
759     }
760 }
761 
_describe_glow(status_info & inf)762 static void _describe_glow(status_info& inf)
763 {
764     const int signed_cont = get_contamination_level();
765     if (signed_cont <= 0)
766         return;
767 
768     const unsigned int cont = signed_cont; // so we don't get compiler warnings
769     if (player_severe_contamination())
770     {
771         inf.light_colour = _bad_ench_colour(cont, SEVERE_CONTAM_LEVEL + 1,
772                                                   SEVERE_CONTAM_LEVEL + 2);
773     }
774     else if (cont > 1)
775         inf.light_colour = LIGHTGREY;
776     else
777         inf.light_colour = DARKGREY;
778     inf.light_text = "Contam";
779 
780     /// Mappings from contamination levels to descriptions.
781     static const string contam_adjectives[] =
782     {
783         "",
784         "very slightly ",
785         "slightly ",
786         "",
787         "heavily ",
788         "very heavily ",
789         "very very heavily ", // this is silly but no one will ever see it
790         "impossibly ",        // (likewise)
791     };
792     ASSERT(signed_cont >= 0);
793 
794     const int adj_i = min((size_t) cont, ARRAYSZ(contam_adjectives) - 1);
795     inf.short_text = contam_adjectives[adj_i] + "contaminated";
796     inf.long_text = describe_contamination(cont);
797 }
798 
_describe_regen(status_info & inf)799 static void _describe_regen(status_info& inf)
800 {
801     if (you.duration[DUR_TROGS_HAND])
802     {
803         inf.light_colour = _dur_colour(BLUE, dur_expiring(DUR_TROGS_HAND));
804         inf.light_text = "Regen Will++";
805         inf.short_text = "regenerating";
806         inf.long_text  = "You are regenerating.";
807         _mark_expiring(inf, dur_expiring(DUR_TROGS_HAND));
808     }
809     else if (you.has_mutation(MUT_VAMPIRISM)
810              && you.vampire_alive
811              && !you.duration[DUR_SICKNESS])
812     {
813         inf.short_text = "healing quickly";
814     }
815     else if (regeneration_is_inhibited())
816     {
817         inf.light_colour = RED;
818         inf.light_text = "-Regen";
819         inf.short_text = "inhibited regen";
820         inf.long_text = "Your regeneration is inhibited by nearby monsters.";
821     }
822 }
823 
_describe_poison(status_info & inf)824 static void _describe_poison(status_info& inf)
825 {
826     int pois_perc = (you.hp <= 0) ? 100
827                                   : ((you.hp - max(0, poison_survival())) * 100 / you.hp);
828     inf.light_colour = (player_res_poison(false) >= 3
829                          ? DARKGREY : _bad_ench_colour(pois_perc, 35, 100));
830     inf.light_text   = "Pois";
831     const string adj =
832          (pois_perc >= 100) ? "lethally" :
833          (pois_perc > 65)   ? "seriously" :
834          (pois_perc > 35)   ? "quite"
835                             : "mildly";
836     inf.short_text   = adj + " poisoned"
837         + make_stringf(" (%d -> %d)", you.hp, poison_survival());
838     inf.long_text    = "You are " + inf.short_text + ".";
839 }
840 
_describe_speed(status_info & inf)841 static void _describe_speed(status_info& inf)
842 {
843     bool slow = you.duration[DUR_SLOW] || have_stat_zero();
844     bool fast = you.duration[DUR_HASTE];
845 
846     if (slow && fast)
847     {
848         inf.light_colour = MAGENTA;
849         inf.light_text   = "Fast+Slow";
850         inf.short_text   = "hasted and slowed";
851         inf.long_text = "You are under both slowing and hasting effects.";
852     }
853     else if (slow)
854     {
855         inf.light_colour = RED;
856         inf.light_text   = "Slow";
857         inf.short_text   = "slowed";
858         inf.long_text    = "You are slowed.";
859     }
860     else if (fast)
861     {
862         inf.light_colour = _dur_colour(BLUE, dur_expiring(DUR_HASTE));
863         inf.light_text   = "Fast";
864         inf.short_text = "hasted";
865         inf.long_text = "Your actions are hasted.";
866         _mark_expiring(inf, dur_expiring(DUR_HASTE));
867     }
868 }
869 
_describe_airborne(status_info & inf)870 static void _describe_airborne(status_info& inf)
871 {
872     if (!you.airborne())
873         return;
874 
875     const bool perm      = you.permanent_flight();
876     const bool expiring  = (!perm && dur_expiring(DUR_FLIGHT));
877     const bool emergency = you.props[EMERGENCY_FLIGHT_KEY].get_bool();
878     const string desc   = you.tengu_flight() ? " quickly and evasively" : "";
879 
880     inf.light_colour = perm ? WHITE : emergency ? LIGHTRED : BLUE;
881     inf.light_text   = "Fly";
882     inf.short_text   = "flying" + desc;
883     inf.long_text    = "You are flying" + desc + ".";
884     inf.light_colour = _dur_colour(inf.light_colour, expiring);
885     _mark_expiring(inf, expiring);
886 }
887 
_describe_sickness(status_info & inf)888 static void _describe_sickness(status_info& inf)
889 {
890     if (you.duration[DUR_SICKNESS])
891     {
892         const int high = 120 * BASELINE_DELAY;
893         const int low  =  40 * BASELINE_DELAY;
894 
895         inf.light_colour   = _bad_ench_colour(you.duration[DUR_SICKNESS],
896                                               low, high);
897         inf.light_text     = "Sick";
898 
899         string mod = (you.duration[DUR_SICKNESS] > high) ? "badly "  :
900                      (you.duration[DUR_SICKNESS] >  low) ? ""
901                                                          : "mildly ";
902 
903         inf.short_text = mod + "diseased";
904         inf.long_text  = "You are " + mod + "diseased.";
905     }
906 }
907 
908 /**
909  * Populate a status info struct with a description of the player's current
910  * form.
911  *
912  * @param[out] inf  The status info struct to be populated.
913  */
_describe_transform(status_info & inf)914 static void _describe_transform(status_info& inf)
915 {
916     if (you.form == transformation::none)
917         return;
918 
919     const Form * const form = get_form();
920     inf.light_text = form->short_name;
921     inf.short_text = form->get_long_name();
922     inf.long_text = form->get_description();
923 
924     const bool vampbat = (you.get_mutation_level(MUT_VAMPIRISM) >= 2
925                           && you.form == transformation::bat);
926     const bool expire  = dur_expiring(DUR_TRANSFORMATION) && !vampbat;
927 
928     inf.light_colour = _dur_colour(GREEN, expire);
929     _mark_expiring(inf, expire);
930 }
931 
932 static const char* s0_names[NUM_STATS] = { "Collapse", "Brainless", "Clumsy", };
933 
_describe_stat_zero(status_info & inf,stat_type st)934 static void _describe_stat_zero(status_info& inf, stat_type st)
935 {
936     if (you.duration[stat_zero_duration(st)])
937     {
938         inf.light_colour = you.stat(st) ? LIGHTRED : RED;
939         inf.light_text   = s0_names[st];
940         inf.short_text   = make_stringf("lost %s", stat_desc(st, SD_NAME));
941         inf.long_text    = make_stringf(you.stat(st) ?
942                 "You are recovering from loss of %s." : "You have no %s!",
943                 stat_desc(st, SD_NAME));
944     }
945 }
946 
_describe_terrain(status_info & inf)947 static void _describe_terrain(status_info& inf)
948 {
949     switch (env.grid(you.pos()))
950     {
951     case DNGN_SHALLOW_WATER:
952         inf.light_colour = LIGHTBLUE;
953         inf.light_text = "Water";
954         break;
955     case DNGN_DEEP_WATER:
956         inf.light_colour = BLUE;
957         inf.light_text = "Water";
958         break;
959     case DNGN_LAVA:
960         inf.light_colour = RED;
961         inf.light_text = "Lava";
962         break;
963     default:
964         ;
965     }
966 }
967 
_describe_missiles(status_info & inf)968 static void _describe_missiles(status_info& inf)
969 {
970     if (you.missile_repulsion())
971     {
972         inf.light_colour = WHITE;
973         inf.light_text   = "RMsl";
974         inf.short_text   = "repel missiles";
975         inf.long_text    = "You repel missiles.";
976     }
977 }
978 
_describe_invisible(status_info & inf)979 static void _describe_invisible(status_info& inf)
980 {
981     if (!you.duration[DUR_INVIS] && you.form != transformation::shadow)
982         return;
983 
984     if (you.form == transformation::shadow)
985     {
986         inf.light_colour = _dur_colour(WHITE,
987                                         dur_expiring(DUR_TRANSFORMATION));
988     }
989     else
990         inf.light_colour = _dur_colour(BLUE, dur_expiring(DUR_INVIS));
991     inf.light_text   = "Invis";
992     inf.short_text   = "invisible";
993     if (you.backlit())
994     {
995         inf.light_colour = DARKGREY;
996         inf.short_text += " (but backlit and visible)";
997     }
998     inf.long_text = "You are " + inf.short_text + ".";
999     _mark_expiring(inf, dur_expiring(you.form == transformation::shadow
1000                                      ? DUR_TRANSFORMATION
1001                                      : DUR_INVIS));
1002 }
1003 
1004 /**
1005  * Does a given duration tick down simply over time?
1006  *
1007  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1008  * @return      Whether the duration's end_msg is non-null.
1009  */
duration_decrements_normally(duration_type dur)1010 bool duration_decrements_normally(duration_type dur)
1011 {
1012     return _lookup_duration(dur)->decr.end.msg != nullptr;
1013 }
1014 
1015 /**
1016  * What message should a given duration print when it expires, if any?
1017  *
1018  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1019  * @return      A message to print for the duration when it ends.
1020  */
duration_end_message(duration_type dur)1021 const char *duration_end_message(duration_type dur)
1022 {
1023     return _lookup_duration(dur)->decr.end.msg;
1024 }
1025 
1026 /**
1027  * What message should a given duration print when it reaches 50%, if any?
1028  *
1029  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1030  * @return      A message to print for the duration when it hits 50%.
1031  */
duration_mid_message(duration_type dur)1032 const char *duration_mid_message(duration_type dur)
1033 {
1034     return _lookup_duration(dur)->decr.mid_msg.msg;
1035 }
1036 
1037 /**
1038  * How much should the duration be decreased by when it hits the midpoint (to
1039  * fuzz the remaining time), if at all?
1040  *
1041  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1042  * @return      A random value to reduce the remaining duration by; may be 0.
1043  */
duration_mid_offset(duration_type dur)1044 int duration_mid_offset(duration_type dur)
1045 {
1046     return _lookup_duration(dur)->decr.mid_msg.offset();
1047 }
1048 
1049 /**
1050  * At what number of turns remaining is the given duration considered to be
1051  * 'expiring', for purposes of messaging & status light colouring?
1052  *
1053  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1054  * @return      The maximum number of remaining turns at which the duration
1055  *              is considered 'expiring'; may be 0.
1056  */
duration_expire_point(duration_type dur)1057 int duration_expire_point(duration_type dur)
1058 {
1059     return _lookup_duration(dur)->expire_threshold * BASELINE_DELAY;
1060 }
1061 
1062 /**
1063  * What channel should the duration messages be printed in?
1064  *
1065  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1066  * @return      The appropriate message channel, e.g. MSGCH_RECOVERY.
1067  */
duration_mid_chan(duration_type dur)1068 msg_channel_type duration_mid_chan(duration_type dur)
1069 {
1070     return _lookup_duration(dur)->decr.recovery ? MSGCH_RECOVERY
1071                                                 : MSGCH_DURATION;
1072 }
1073 
1074 /**
1075  * If a duration has some special effect when ending, trigger it.
1076  *
1077  * @param dur   The duration in question (e.g. DUR_PETRIFICATION).
1078  */
duration_end_effect(duration_type dur)1079 void duration_end_effect(duration_type dur)
1080 {
1081     if (_lookup_duration(dur)->decr.end.on_end)
1082         _lookup_duration(dur)->decr.end.on_end();
1083 }
1084