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