1 /**
2  * @file
3  * @brief Functions used when Bad Things happen to the player.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "ouch.h"
9 
10 #include <cctype>
11 #include <cmath>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <cstring>
15 #include <iostream>
16 #include <string>
17 #ifdef UNIX
18 #include <fcntl.h>
19 #include <sys/types.h>
20 #include <unistd.h>
21 #endif
22 
23 #include "artefact.h"
24 #include "art-enum.h"
25 #include "beam.h"
26 #include "chardump.h"
27 #include "colour.h"
28 #include "delay.h"
29 #include "dgn-event.h"
30 #include "end.h"
31 #include "env.h"
32 #include "fight.h"
33 #include "files.h"
34 #include "fineff.h"
35 #include "god-abil.h"
36 #include "god-passive.h"
37 #include "hints.h"
38 #include "hiscores.h"
39 #include "invent.h"
40 #include "item-prop.h"
41 #include "libutil.h"
42 #include "message.h"
43 #include "mgen-data.h"
44 #include "mon-death.h"
45 #include "mon-place.h"
46 #include "mon-util.h"
47 #include "mutation.h"
48 #include "nearby-danger.h"
49 #include "notes.h"
50 #include "options.h"
51 #include "output.h"
52 #include "player.h"
53 #include "player-stats.h"
54 #include "potion.h"
55 #include "prompt.h"
56 #include "random.h"
57 #include "religion.h"
58 #include "shopping.h"
59 #include "shout.h"
60 #include "spl-clouds.h"
61 #include "spl-selfench.h"
62 #include "state.h"
63 #include "stringutil.h"
64 #include "teleport.h"
65 #include "transform.h"
66 #include "tutorial.h"
67 #include "view.h"
68 #include "xom.h"
69 
maybe_melt_player_enchantments(beam_type flavour,int damage)70 void maybe_melt_player_enchantments(beam_type flavour, int damage)
71 {
72     if (flavour == BEAM_FIRE || flavour == BEAM_LAVA
73         || flavour == BEAM_STICKY_FLAME || flavour == BEAM_STEAM)
74     {
75         if (you.has_mutation(MUT_CONDENSATION_SHIELD))
76         {
77             if (!you.duration[DUR_ICEMAIL_DEPLETED])
78             {
79                 if (you.has_mutation(MUT_ICEMAIL))
80                     mprf(MSGCH_DURATION, "Your icy defenses dissipate!");
81                 else
82                     mprf(MSGCH_DURATION, "Your condensation shield dissipates!");
83             }
84             you.duration[DUR_ICEMAIL_DEPLETED] = ICEMAIL_TIME;
85             you.redraw_armour_class = true;
86         }
87 
88         if (you.duration[DUR_ICY_ARMOUR] > 0)
89         {
90             you.duration[DUR_ICY_ARMOUR] -= damage * BASELINE_DELAY;
91             if (you.duration[DUR_ICY_ARMOUR] <= 0)
92                 remove_ice_armour();
93             else
94                 you.props[MELT_ARMOUR_KEY] = true;
95         }
96     }
97 }
98 
check_your_resists(int hurted,beam_type flavour,string source,bolt * beam,bool doEffects)99 int check_your_resists(int hurted, beam_type flavour, string source,
100                        bolt *beam, bool doEffects)
101 {
102     int original = hurted;
103 
104     dprf("checking resistance: flavour=%d", flavour);
105 
106     string kaux = "";
107     if (beam)
108     {
109         source = beam->get_source_name();
110         kaux = beam->name;
111     }
112 
113     if (doEffects)
114         maybe_melt_player_enchantments(flavour, hurted);
115 
116     switch (flavour)
117     {
118     case BEAM_WATER:
119         hurted = resist_adjust_damage(&you, flavour, hurted);
120         if (!hurted && doEffects)
121             mpr("You shrug off the wave.");
122         break;
123 
124     case BEAM_STEAM:
125         hurted = resist_adjust_damage(&you, flavour, hurted);
126         if (hurted < original && doEffects)
127             canned_msg(MSG_YOU_RESIST);
128         else if (hurted > original && doEffects)
129         {
130             mpr("The steam scalds you terribly!");
131             xom_is_stimulated(200);
132         }
133         break;
134 
135     case BEAM_FIRE:
136         hurted = resist_adjust_damage(&you, flavour, hurted);
137         if (hurted < original && doEffects)
138             canned_msg(MSG_YOU_RESIST);
139         else if (hurted > original && doEffects)
140         {
141             mpr("The fire burns you terribly!");
142             xom_is_stimulated(200);
143         }
144         break;
145 
146     case BEAM_DAMNATION:
147         break; // sucks to be you (:
148 
149     case BEAM_COLD:
150         hurted = resist_adjust_damage(&you, flavour, hurted);
151         if (hurted < original && doEffects)
152             canned_msg(MSG_YOU_RESIST);
153         else if (hurted > original && doEffects)
154         {
155             mpr("You feel a terrible chill!");
156             xom_is_stimulated(200);
157         }
158         break;
159 
160     case BEAM_STUN_BOLT:
161     case BEAM_ELECTRICITY:
162     case BEAM_THUNDER:
163         hurted = resist_adjust_damage(&you, flavour, hurted);
164 
165         if (hurted < original && doEffects)
166             canned_msg(MSG_YOU_RESIST);
167         break;
168 
169     case BEAM_POISON:
170         hurted = resist_adjust_damage(&you, flavour, hurted);
171 
172         if (doEffects)
173         {
174             // Ensure that we received a valid beam object before proceeding.
175             // See also melee-attack.cc:_print_resist_messages() which cannot be
176             // used with this beam type (as it does not provide a valid beam).
177             ASSERT(beam);
178 
179             if (beam->origin_spell == SPELL_SPIT_POISON &&
180                 beam->agent(true)->is_monster() &&
181                 beam->agent(true)->as_monster()->has_ench(ENCH_CONCENTRATE_VENOM))
182             {
183                 curare_actor(beam->agent(), &you, 2, "concentrated venom",
184                              beam->agent(true)->name(DESC_PLAIN));
185             }
186             else
187             {
188                 int pois = div_rand_round(beam->damage.num * beam->damage.size, 3);
189                 pois = 3 + random_range(pois * 2 / 3, pois * 4 / 3);
190                 poison_player(pois, source, kaux);
191 
192                 if (player_res_poison() > 0)
193                     canned_msg(MSG_YOU_RESIST);
194             }
195         }
196 
197         break;
198 
199     case BEAM_POISON_ARROW:
200         if (doEffects)
201         {
202             // Ensure that we received a valid beam object before proceeding.
203             // See also melee-attack.cc:_print_resist_messages() which cannot be
204             // used with this beam type (as it does not provide a valid beam).
205             ASSERT(beam);
206             int pois = div_rand_round(beam->damage.num * beam->damage.size, 3);
207             pois = 3 + random_range(pois * 2 / 3, pois * 4 / 3);
208 
209             const int resist = player_res_poison();
210             poison_player((resist ? pois / 2 : pois), source, kaux, true);
211         }
212 
213         hurted = resist_adjust_damage(&you, flavour, hurted);
214         if (hurted < original && doEffects)
215             canned_msg(MSG_YOU_PARTIALLY_RESIST);
216         break;
217 
218     case BEAM_NEG:
219         hurted = resist_adjust_damage(&you, flavour, hurted);
220 
221         if (doEffects)
222         {
223             // drain_player handles the messaging here
224             drain_player(original, true);
225         }
226         break;
227 
228     case BEAM_ICE:
229         hurted = resist_adjust_damage(&you, flavour, hurted);
230 
231         if (hurted < original && doEffects)
232             canned_msg(MSG_YOU_PARTIALLY_RESIST);
233         else if (hurted > original && doEffects)
234         {
235             mpr("You feel a painful chill!");
236             xom_is_stimulated(200);
237         }
238         break;
239 
240     case BEAM_LAVA:
241         hurted = resist_adjust_damage(&you, flavour, hurted);
242 
243         if (hurted < original && doEffects)
244             canned_msg(MSG_YOU_PARTIALLY_RESIST);
245         else if (hurted > original && doEffects)
246         {
247             mpr("The lava burns you terribly!");
248             xom_is_stimulated(200);
249         }
250         break;
251 
252     case BEAM_ACID:
253         hurted = resist_adjust_damage(&you, flavour, hurted);
254         if (hurted < original && doEffects)
255             canned_msg(MSG_YOU_RESIST);
256         break;
257 
258     case BEAM_MIASMA:
259         if (you.res_miasma())
260         {
261             if (doEffects)
262                 canned_msg(MSG_YOU_RESIST);
263             hurted = 0;
264         }
265         break;
266 
267     case BEAM_HOLY:
268     {
269         hurted = resist_adjust_damage(&you, flavour, hurted);
270         if (hurted < original && doEffects)
271             canned_msg(MSG_YOU_RESIST);
272         else if (hurted > original && doEffects)
273         {
274             mpr("You writhe in agony!");
275             xom_is_stimulated(200);
276         }
277         break;
278     }
279 
280     default:
281         break;
282     }                           // end switch
283 
284     return hurted;
285 }
286 
287 /**
288  * Handle side-effects for exposure to element other than damage.
289  * Historically this handled item destruction, and melting meltable enchantments. Now it takes care of 3 things:
290  *   - triggering qazlal's elemental adaptations
291  *   - slowing cold-blooded players (draconians, hydra form)
292  *   - putting out fires
293  * This function should be called exactly once any time a player is exposed to the
294  * following elements/beam types: cold, fire, elec, water, steam, lava, BEAM_FRAG. For the sake of Qazlal's
295  * elemental adaptation, it should also be called (exactly once) with BEAM_MISSILE when
296  * receiving physical damage. Hybrid damage (brands) should call it twice with appropriate
297  * flavours.
298  *
299  * @param flavour The beam type.
300  * @param strength The strength of the attack. Used in different ways for different side-effects.
301  *     For qazlal_elemental_adapt: (i) it is used for the probability of triggering, and (ii) the resulting length of the effect.
302  * @param slow_cold_blooded If True, the beam_type is BEAM_COLD, and the player
303  *                          is cold-blooded and not cold-resistant, slow the
304  *                          player 50% of the time.
305  */
expose_player_to_element(beam_type flavour,int strength,bool slow_cold_blooded)306 void expose_player_to_element(beam_type flavour, int strength, bool slow_cold_blooded)
307 {
308     dprf("expose_player_to_element, strength %i, flavor %i, slow_cold_blooded is %i", strength, flavour, slow_cold_blooded);
309     qazlal_element_adapt(flavour, strength);
310 
311     if (flavour == BEAM_COLD && slow_cold_blooded
312         && you.get_mutation_level(MUT_COLD_BLOODED)
313         && you.res_cold() <= 0 && coinflip())
314     {
315         you.slow_down(0, strength);
316     }
317 
318     if (flavour == BEAM_WATER && you.duration[DUR_LIQUID_FLAMES])
319     {
320         mprf(MSGCH_WARN, "The flames go out!");
321         you.duration[DUR_LIQUID_FLAMES] = 0;
322         you.props.erase("sticky_flame_source");
323         you.props.erase("sticky_flame_aux");
324     }
325 }
326 
lose_level()327 void lose_level()
328 {
329     // Because you.experience is unsigned long, if it's going to be
330     // negative, must die straightaway.
331     if (you.experience_level == 1)
332     {
333         ouch(INSTANT_DEATH, KILLED_BY_DRAINING);
334         // Return in case death was cancelled via wizard mode
335         return;
336     }
337 
338     you.experience_level--;
339 
340     mprf(MSGCH_WARN,
341          "You are now level %d!", you.experience_level);
342 
343     calc_hp();
344     calc_mp();
345 
346     char buf[200];
347     sprintf(buf, "HP: %d/%d MP: %d/%d",
348             you.hp, you.hp_max, you.magic_points, you.max_magic_points);
349     take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0, buf));
350 
351     you.redraw_title = true;
352     you.redraw_experience = true;
353 #ifdef USE_TILE_LOCAL
354     // In case of intrinsic ability changes.
355     tiles.layout_statcol();
356     redraw_screen();
357     update_screen();
358 #endif
359 
360     xom_is_stimulated(200);
361 
362     // Kill the player if maxhp <= 0. We can't just move the ouch() call past
363     // dec_max_hp() since it would decrease hp twice, so here's another one.
364     ouch(0, KILLED_BY_DRAINING);
365 }
366 
367 /**
368  * Drain the player.
369  *
370  * @param power             The amount by which to drain the player.
371  * @param announce_full     Whether to print messages even when fully resisting
372  *                          the drain.
373  * @param ignore_protection Whether to ignore the player's rN.
374  * @return                  Whether draining occurred.
375  */
drain_player(int power,bool announce_full,bool ignore_protection)376 bool drain_player(int power, bool announce_full, bool ignore_protection)
377 {
378     if (crawl_state.disables[DIS_AFFLICTIONS])
379         return false;
380 
381     const int protection = ignore_protection ? 0 : player_prot_life();
382 
383     if (protection == 3)
384     {
385         if (announce_full)
386             canned_msg(MSG_YOU_RESIST);
387 
388         return false;
389     }
390 
391     if (protection > 0)
392     {
393         canned_msg(MSG_YOU_PARTIALLY_RESIST);
394         power /= (protection * 2);
395     }
396 
397     if (power > 0)
398     {
399         const int mhp = 1 + div_rand_round(power * get_real_hp(false, false),
400                 750);
401         you.hp_max_adj_temp -= mhp;
402         you.hp_max_adj_temp = max(-(get_real_hp(false, false) - 1),
403                 you.hp_max_adj_temp);
404 
405         dprf("Drained by %d max hp (%d total)", mhp, you.hp_max_adj_temp);
406         calc_hp();
407 
408         mpr("You feel drained.");
409         xom_is_stimulated(15);
410         return true;
411     }
412 
413     return false;
414 }
415 
_xom_checks_damage(kill_method_type death_type,int dam,mid_t death_source)416 static void _xom_checks_damage(kill_method_type death_type,
417                                int dam, mid_t death_source)
418 {
419     if (you_worship(GOD_XOM))
420     {
421         if (death_type == KILLED_BY_TARGETING
422             || death_type == KILLED_BY_BOUNCE
423             || death_type == KILLED_BY_REFLECTION
424             || death_type == KILLED_BY_SELF_AIMED
425                && player_in_a_dangerous_place())
426         {
427             // Xom thinks the player accidentally hurting him/herself is funny.
428             // Deliberate damage is only amusing if it's dangerous.
429             int amusement = 200 * dam / (dam + you.hp);
430             if (death_type == KILLED_BY_SELF_AIMED)
431                 amusement /= 5;
432             xom_is_stimulated(amusement);
433             return;
434         }
435         else if (death_type == KILLED_BY_FALLING_DOWN_STAIRS
436                  || death_type == KILLED_BY_FALLING_THROUGH_GATE)
437         {
438             // Xom thinks falling down the stairs is hilarious.
439             xom_is_stimulated(200);
440             return;
441         }
442         else if (death_type == KILLED_BY_DISINT)
443         {
444             // flying chunks...
445             xom_is_stimulated(100);
446             return;
447         }
448         else if (death_type != KILLED_BY_MONSTER
449                     && death_type != KILLED_BY_BEAM
450                     && death_type != KILLED_BY_DISINT
451                  || !monster_by_mid(death_source))
452         {
453             return;
454         }
455 
456         int amusementvalue = 1;
457         const monster* mons = monster_by_mid(death_source);
458 
459         if (!mons->alive())
460             return;
461 
462         if (mons->wont_attack())
463         {
464             // Xom thinks collateral damage is funny.
465             xom_is_stimulated(200 * dam / (dam + you.hp));
466             return;
467         }
468 
469         int leveldif = mons->get_experience_level() - you.experience_level;
470         if (leveldif == 0)
471             leveldif = 1;
472 
473         // Note that Xom is amused when you are significantly hurt by a
474         // creature of higher level than yourself, as well as by a
475         // creature of lower level than yourself.
476         amusementvalue += leveldif * leveldif * dam;
477 
478         if (!mons->visible_to(&you))
479             amusementvalue += 8;
480 
481         if (mons->speed < 100/player_movement_speed())
482             amusementvalue += 7;
483 
484         if (player_in_a_dangerous_place())
485             amusementvalue += 2;
486 
487         amusementvalue /= (you.hp > 0) ? you.hp : 1;
488 
489         xom_is_stimulated(amusementvalue);
490     }
491 }
492 
_yred_mirrors_injury(int dam,mid_t death_source)493 static void _yred_mirrors_injury(int dam, mid_t death_source)
494 {
495     if (yred_injury_mirror())
496     {
497         // Cap damage to what was enough to kill you. Can matter if
498         // Yred saves your life or you have an extra kitty.
499         if (you.hp < 0)
500             dam += you.hp;
501 
502         monster* mons = monster_by_mid(death_source);
503         if (dam <= 0 || !mons)
504             return;
505 
506         mirror_damage_fineff::schedule(mons, &you, dam);
507     }
508 }
509 
_maybe_ru_retribution(int dam,mid_t death_source)510 static void _maybe_ru_retribution(int dam, mid_t death_source)
511 {
512     if (will_ru_retaliate())
513     {
514         // Cap damage to what was enough to kill you. Can matter if
515         // you have an extra kitty.
516         if (you.hp < 0)
517             dam += you.hp;
518 
519         monster* mons = monster_by_mid(death_source);
520         if (dam <= 0 || !mons || death_source == MID_YOU_FAULTLESS)
521             return;
522 
523         ru_retribution_fineff::schedule(mons, &you, dam);
524     }
525 }
526 
_maybe_spawn_rats(int dam,kill_method_type death_type)527 static void _maybe_spawn_rats(int dam, kill_method_type death_type)
528 {
529     if (dam <= 0
530         || death_type == KILLED_BY_POISON
531         || !player_equip_unrand(UNRAND_RATSKIN_CLOAK))
532     {
533         return;
534     }
535 
536     // chance rises linearly with damage taken, up to 50% at half hp.
537     const int capped_dam = min(dam, you.hp_max / 2);
538     if (!x_chance_in_y(capped_dam, you.hp_max))
539         return;
540 
541     monster_type mon = coinflip() ? MONS_HELL_RAT : MONS_RIVER_RAT;
542 
543     mgen_data mg(mon, BEH_FRIENDLY, you.pos(), MHITYOU);
544     mg.flags |= MG_FORCE_BEH; // don't mention how much it hates you before it appears
545     if (monster *m = create_monster(mg))
546     {
547         m->add_ench(mon_enchant(ENCH_FAKE_ABJURATION, 3));
548         mprf("%s scurries out from under your cloak.", m->name(DESC_A).c_str());
549         check_lovelessness(*m);
550     }
551 }
552 
_maybe_summon_demonic_guardian(int dam,kill_method_type death_type)553 static void _maybe_summon_demonic_guardian(int dam, kill_method_type death_type)
554 {
555     if (death_type == KILLED_BY_POISON)
556         return;
557     // low chance to summon on any hit that dealt damage
558     // always tries to summon if the hit did 50% max hp or if we're about to die
559     if (you.has_mutation(MUT_DEMONIC_GUARDIAN)
560         && (x_chance_in_y(dam, you.hp_max)
561             || dam > you.hp_max / 2
562             || you.hp * 5 < you.hp_max))
563     {
564         check_demonic_guardian();
565     }
566 }
567 
_maybe_spawn_monsters(int dam,kill_method_type death_type,mid_t death_source)568 static void _maybe_spawn_monsters(int dam, kill_method_type death_type,
569                                   mid_t death_source)
570 {
571     monster* damager = monster_by_mid(death_source);
572     // We need to exclude acid damage and similar things or this function
573     // will crash later.
574     if (!damager)
575         return;
576 
577     monster_type mon;
578     int how_many = 0;
579 
580     if (have_passive(passive_t::spawn_slimes_on_hit))
581     {
582         mon = royal_jelly_ejectable_monster();
583         if (dam >= you.hp_max * 3 / 4)
584             how_many = random2(4) + 2;
585         else if (dam >= you.hp_max / 2)
586             how_many = random2(2) + 2;
587         else if (dam >= you.hp_max / 4)
588             how_many = 1;
589     }
590     else if (you_worship(GOD_XOM)
591              && dam >= you.hp_max / 4
592              && x_chance_in_y(dam, 3 * you.hp_max))
593     {
594         mon = MONS_BUTTERFLY;
595         how_many = 2 + random2(5);
596     }
597 
598     if (how_many > 0)
599     {
600         int count_created = 0;
601         for (int i = 0; i < how_many; ++i)
602         {
603             mgen_data mg(mon, BEH_FRIENDLY, you.pos(), damager->mindex());
604             mg.set_summoned(&you, 2, 0, you.religion);
605 
606             if (create_monster(mg))
607                 count_created++;
608         }
609 
610         if (count_created > 0)
611         {
612             if (mon == MONS_BUTTERFLY)
613             {
614                 mprf(MSGCH_GOD, "A shower of butterflies erupts from you!");
615                 take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "butterfly on damage"), true);
616             }
617             else
618             {
619                 mprf("You shudder from the %s and a %s!",
620                      death_type == KILLED_BY_MONSTER ? "blow" : "blast",
621                      count_created > 1 ? "flood of jellies pours out from you"
622                                        : "jelly pops out");
623             }
624         }
625     }
626 }
627 
_powered_by_pain(int dam)628 static void _powered_by_pain(int dam)
629 {
630     const int level = you.get_mutation_level(MUT_POWERED_BY_PAIN);
631 
632     if (level > 0
633         && (random2(dam) > 4 + div_rand_round(you.experience_level, 4)
634             || dam >= you.hp_max / 2))
635     {
636         switch (random2(4))
637         {
638         case 0:
639         case 1:
640         {
641             if (you.magic_points < you.max_magic_points)
642             {
643                 mpr("You focus on the pain.");
644                 int mp = roll_dice(3, 2 + 3 * level);
645                 canned_msg(MSG_GAIN_MAGIC);
646                 inc_mp(mp);
647                 break;
648             }
649             break;
650         }
651         case 2:
652             mpr("You focus on the pain.");
653             potionlike_effect(POT_MIGHT, level * 20);
654             break;
655         case 3:
656             mpr("You focus on the pain.");
657             you.be_agile(level * 20);
658             break;
659         }
660     }
661 }
662 
_maybe_fog(int dam)663 static void _maybe_fog(int dam)
664 {
665     const int minpiety = have_passive(passive_t::hit_smoke)
666         ? piety_breakpoint(rank_for_passive(passive_t::hit_smoke) - 1)
667         : piety_breakpoint(2); // Xom
668 
669     const int upper_threshold = you.hp_max / 2;
670     const int lower_threshold = upper_threshold
671                                 - upper_threshold
672                                   * (you.piety - minpiety)
673                                   / (MAX_PIETY - minpiety);
674     if (have_passive(passive_t::hit_smoke)
675         && (dam > 0 && you.form == transformation::shadow
676             || dam >= lower_threshold
677                && x_chance_in_y(dam - lower_threshold,
678                                 upper_threshold - lower_threshold)))
679     {
680         mpr("You emit a cloud of dark smoke.");
681         big_cloud(CLOUD_BLACK_SMOKE, &you, you.pos(), 50, 4 + random2(5));
682     }
683     else if (player_equip_unrand(UNRAND_THIEF)
684              && dam > you.hp_max / 10 && coinflip())
685     {
686         mpr("With a swish of your cloak, you release a cloud of fog.");
687         big_cloud(random_smoke_type(), &you, you.pos(), 50, 8 + random2(8));
688     }
689     else if (you_worship(GOD_XOM) && x_chance_in_y(dam, 30 * upper_threshold))
690     {
691         mprf(MSGCH_GOD, "You emit a cloud of colourful smoke!");
692         big_cloud(CLOUD_XOM_TRAIL, &you, you.pos(), 50, 4 + random2(5), -1);
693         take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "smoke on damage"), true);
694     }
695 }
696 
_deteriorate(int dam)697 static void _deteriorate(int dam)
698 {
699     if (x_chance_in_y(you.get_mutation_level(MUT_DETERIORATION), 4)
700         && dam > you.hp_max / 10)
701     {
702         mprf(MSGCH_WARN, "Your body deteriorates!");
703         lose_stat(STAT_RANDOM, 1);
704     }
705 }
706 
707 /**
708  * Maybe corrode the player after taking damage if they're wearing *Corrode.
709  **/
_maybe_corrode()710 static void _maybe_corrode()
711 {
712     int corrosion_sources = you.scan_artefacts(ARTP_CORRODE);
713     int degree = binomial(corrosion_sources, 3);
714     if (degree > 0)
715         you.corrode_equipment("Your corrosive artefact", degree);
716 }
717 
718 /**
719  * Maybe slow the player after taking damage if they're wearing *Slow.
720  **/
_maybe_slow()721 static void _maybe_slow()
722 {
723     int slow_sources = you.scan_artefacts(ARTP_SLOW);
724     for (int degree = binomial(slow_sources, 1); degree > 0; degree--)
725         slow_player(10 + random2(5));
726 }
727 
_place_player_corpse(bool explode)728 static void _place_player_corpse(bool explode)
729 {
730     if (!in_bounds(you.pos()))
731         return;
732 
733     monster dummy;
734     dummy.type = player_mons(false);
735     define_monster(dummy); // assumes player_mons is not a zombie
736     dummy.position = you.pos();
737     dummy.props["always_corpse"] = true;
738     dummy.mname = you.your_name;
739     dummy.set_hit_dice(you.experience_level);
740     if (explode)
741         dummy.flags &= MF_EXPLODE_KILL;
742 
743     if (you.form != transformation::none)
744         mpr("Your shape twists and changes as you die.");
745 
746     place_monster_corpse(dummy, false);
747 }
748 
749 #if defined(WIZARD) || defined(DEBUG)
_wizard_restore_life()750 static void _wizard_restore_life()
751 {
752     if (you.hp_max <= 0)
753         undrain_hp(9999);
754     while (you.hp_max <= 0)
755         you.hp_max_adj_perm++, calc_hp();
756     if (you.hp <= 0)
757         set_hp(you.hp_max);
758 }
759 #endif
760 
_apply_extra_harm(int dam,mid_t source)761 static int _apply_extra_harm(int dam, mid_t source)
762 {
763     monster* damager = monster_by_mid(source);
764     // Don't check for monster amulet if there source isn't a monster
765     if (damager && damager->extra_harm())
766         return dam * 13 / 10; // +30% damage when the opponent has harm
767     else if (you.extra_harm())
768         return dam * 6 / 5; // +20% damage when you have harm
769 
770     return dam;
771 }
772 
reset_damage_counters()773 void reset_damage_counters()
774 {
775     you.turn_damage = 0;
776     you.damage_source = NON_MONSTER;
777     you.source_damage = 0;
778 }
779 
can_shave_damage()780 bool can_shave_damage()
781 {
782     return you.species == SP_DEEP_DWARF;
783 }
784 
do_shave_damage(int dam)785 int do_shave_damage(int dam)
786 {
787     if (!can_shave_damage())
788         return dam;
789 
790     // Deep Dwarves get to shave any hp loss.
791     int shave = 1 + random2(2 + random2(1 + you.experience_level / 3));
792     dprf("HP shaved: %d.", shave);
793     dam -= shave;
794 
795     return dam;
796 }
797 
798 // Determine what's threatening for purposes of sacrifice drink and reading.
799 // the statuses are guaranteed not to happen if the incoming damage is less
800 // than 5% max hp. Otherwise, they scale up with damage taken and with lower
801 // health, becoming certain at 20% max health damage.
_is_damage_threatening(int damage_fraction_of_hp)802 static bool _is_damage_threatening (int damage_fraction_of_hp)
803 {
804     const int hp_fraction = you.hp * 100 / you.hp_max;
805     return damage_fraction_of_hp > 5
806             && hp_fraction <= 85
807             && (damage_fraction_of_hp + random2(20) >= 20
808                 || random2(100) > hp_fraction);
809 }
810 
811 // Palentongas curl up after the first time they've been hit in a round.
_consider_curling(kill_method_type death_type)812 static void _consider_curling(kill_method_type death_type)
813 {
814     if (!you.has_mutation(MUT_CURL)
815         || you.props[PALENTONGA_CURL_KEY].get_bool())
816     {
817         return;
818     }
819 
820     switch (death_type)
821     {
822         case KILLED_BY_MONSTER:
823         case KILLED_BY_BEAM:
824         case KILLED_BY_DEATH_EXPLOSION:
825         case KILLED_BY_TRAP:
826         case KILLED_BY_BOUNCE:
827         case KILLED_BY_REFLECTION:
828         case KILLED_BY_DISINT:
829         case KILLED_BY_HEADBUTT:
830         case KILLED_BY_ROLLING:
831         case KILLED_BY_BEING_THROWN:
832         case KILLED_BY_COLLISION:
833             break;
834         default:
835             // stuff like poison, smiting, etc
836             return;
837     }
838 
839     you.props[PALENTONGA_CURL_KEY] = true;
840     you.redraw_armour_class = true;
841 }
842 
843 /** Hurt the player. Isn't it fun?
844  *
845  *  @param dam How much damage -- may be INSTANT_DEATH.
846  *  @param death_type how did you get hurt?
847  *  @param source who could do such a thing?
848  *  @param aux what did they do it with?
849  *  @param see_source whether the attacker was visible to you
850  *  @param death_source_name the attacker's name if it is already dead.
851  */
ouch(int dam,kill_method_type death_type,mid_t source,const char * aux,bool see_source,const char * death_source_name)852 void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux,
853           bool see_source, const char *death_source_name)
854 {
855     ASSERT(!crawl_state.game_is_arena());
856     if (you.duration[DUR_TIME_STEP])
857         return;
858 
859     if (you.pending_revival)
860         return;
861 
862     int drain_amount = 0;
863 
864     // Multiply damage if scarf of harm is in play
865     if (dam != INSTANT_DEATH)
866         dam = _apply_extra_harm(dam, source);
867 
868     if (can_shave_damage() && dam != INSTANT_DEATH
869         && death_type != KILLED_BY_POISON)
870     {
871         dam = max(0, do_shave_damage(dam));
872     }
873 
874     if (dam != INSTANT_DEATH)
875     {
876         if (you.form == transformation::shadow)
877         {
878             drain_amount = (dam - (dam / 2));
879             dam /= 2;
880         }
881         if (you.petrified())
882             dam /= 2;
883         else if (you.petrifying())
884             dam = dam * 10 / 15;
885     }
886     ait_hp_loss hpl(dam, death_type);
887     interrupt_activity(activity_interrupt::hp_loss, &hpl);
888 
889     // Don't wake the player with fatal or poison damage.
890     if (dam > 0 && dam < you.hp && death_type != KILLED_BY_POISON)
891         you.check_awaken(500);
892 
893     _consider_curling(death_type);
894 
895     const bool non_death = death_type == KILLED_BY_QUITTING
896                         || death_type == KILLED_BY_WINNING
897                         || death_type == KILLED_BY_LEAVING;
898 
899     // certain effects (e.g. drowned souls) use KILLED_BY_WATER for flavour
900     // reasons (morgue messages?), with regrettable consequences if we don't
901     // double-check.
902     const bool env_death = source == MID_NOBODY
903                            && (death_type == KILLED_BY_LAVA
904                                || death_type == KILLED_BY_WATER);
905 
906     // death's door protects against everything but falling into water/lava,
907     // Zot, excessive rot, leaving the dungeon, or quitting.
908     if (you.duration[DUR_DEATHS_DOOR] && !env_death && !non_death
909         && death_type != KILLED_BY_ZOT && you.hp_max > 0)
910     {
911         return;
912     }
913 
914     if (dam > 0 && death_type != KILLED_BY_POISON)
915     {
916         int damage_fraction_of_hp = dam * 100 / you.hp_max;
917 
918         // Check _is_damage_threatening separately for read and drink so they
919         // don't always trigger in unison when you have both.
920         if (you.get_mutation_level(MUT_READ_SAFETY))
921         {
922             if (_is_damage_threatening(damage_fraction_of_hp))
923             {
924                 if (!you.duration[DUR_NO_SCROLLS])
925                     mpr("You feel threatened and lose the ability to read scrolls!");
926 
927                 you.increase_duration(DUR_NO_SCROLLS, 1 + random2(dam), 30);
928             }
929         }
930 
931         if (you.get_mutation_level(MUT_DRINK_SAFETY))
932         {
933             if (_is_damage_threatening(damage_fraction_of_hp))
934             {
935                 if (!you.duration[DUR_NO_POTIONS])
936                     mpr("You feel threatened and lose the ability to drink potions!");
937 
938                 you.increase_duration(DUR_NO_POTIONS, 1 + random2(dam), 30);
939             }
940         }
941     }
942 
943     if (dam != INSTANT_DEATH)
944     {
945         if (you.spirit_shield() && death_type != KILLED_BY_POISON
946             && !(aux && strstr(aux, "flay_damage")))
947         {
948             // round off fairly (important for taking 1 damage at a time)
949             int mp = div_rand_round(dam * you.magic_points,
950                                     max(you.hp + you.magic_points, 1));
951             // but don't kill the player with round-off errors
952             mp = max(mp, dam + 1 - you.hp);
953             mp = min(mp, you.magic_points);
954 
955             dam -= mp;
956             drain_mp(mp);
957 
958             // Wake players who took fatal damage exactly equal to current HP,
959             // but had it reduced below fatal threshhold by spirit shield.
960             if (dam < you.hp)
961                 you.check_awaken(500);
962 
963             if (dam <= 0 && you.hp > 0)
964                 return;
965         }
966 
967         if (dam >= you.hp && you.hp_max > 0 && god_protects_from_harm())
968         {
969             simple_god_message(" protects you from harm!");
970             // Ensure divine intervention wakes sleeping players. Necessary
971             // because we otherwise don't wake players who take fatal damage.
972             you.check_awaken(500);
973             return;
974         }
975 
976         you.turn_damage += dam;
977         if (you.damage_source != source)
978         {
979             you.damage_source = source;
980             you.source_damage = 0;
981         }
982         you.source_damage += dam;
983 
984         dec_hp(dam, true);
985 
986         // Even if we have low HP messages off, we'll still give a
987         // big hit warning (in this case, a hit for half our HPs) -- bwr
988         if (dam > 0 && you.hp_max <= dam * 2)
989             mprf(MSGCH_DANGER, "Ouch! That really hurt!");
990 
991         if (you.hp > 0 && dam > 0)
992         {
993             if (death_type != KILLED_BY_POISON || poison_is_lethal())
994                 flush_hp();
995 
996             hints_healing_check();
997 
998             _xom_checks_damage(death_type, dam, source);
999 
1000             // for note taking
1001             string damage_desc;
1002             if (!see_source)
1003                 damage_desc = make_stringf("something (%d)", dam);
1004             else
1005             {
1006                 damage_desc = scorefile_entry(dam, source,
1007                                               death_type, aux, true)
1008                     .death_description(scorefile_entry::DDV_TERSE);
1009             }
1010 
1011             take_note(Note(NOTE_HP_CHANGE, you.hp, you.hp_max,
1012                            damage_desc.c_str()));
1013 
1014             _deteriorate(dam);
1015             _yred_mirrors_injury(dam, source);
1016             _maybe_ru_retribution(dam, source);
1017             _maybe_spawn_monsters(dam, death_type, source);
1018             _maybe_spawn_rats(dam, death_type);
1019             _maybe_summon_demonic_guardian(dam, death_type);
1020             _maybe_fog(dam);
1021             _powered_by_pain(dam);
1022             if (sanguine_armour_valid())
1023                 activate_sanguine_armour();
1024             if (death_type != KILLED_BY_POISON)
1025             {
1026                 _maybe_corrode();
1027                 _maybe_slow();
1028             }
1029             if (drain_amount > 0)
1030                 drain_player(drain_amount, true, true);
1031         }
1032         if (you.hp > 0)
1033           return;
1034     }
1035 
1036     // Is the player being killed by a direct act of Xom?
1037     if (crawl_state.is_god_acting()
1038         && crawl_state.which_god_acting() == GOD_XOM
1039         && crawl_state.other_gods_acting().empty())
1040     {
1041         you.escaped_death_cause = death_type;
1042         you.escaped_death_aux   = aux == nullptr ? "" : aux;
1043 
1044         // Xom should only kill his worshippers if they're under penance
1045         // or Xom is bored.
1046         if (you_worship(GOD_XOM) && !you.penance[GOD_XOM]
1047             && you.gift_timeout > 0)
1048         {
1049             return;
1050         }
1051 
1052         // Also don't kill wizards testing Xom acts.
1053         if ((crawl_state.repeat_cmd == CMD_WIZARD
1054                 || crawl_state.prev_cmd == CMD_WIZARD)
1055             && !you_worship(GOD_XOM))
1056         {
1057             return;
1058         }
1059 
1060         // Okay, you *didn't* escape death.
1061         you.reset_escaped_death();
1062 
1063         // Ensure some minimal information about Xom's involvement.
1064         if (aux == nullptr || !*aux)
1065         {
1066             if (death_type != KILLED_BY_XOM)
1067                 aux = "Xom";
1068         }
1069         else if (strstr(aux, "Xom") == nullptr)
1070             death_type = KILLED_BY_XOM;
1071     }
1072     // Xom may still try to save your life.
1073     else if (xom_saves_your_life(death_type))
1074         return;
1075 
1076 #if defined(WIZARD) || defined(DEBUG)
1077     if (!non_death && crawl_state.disables[DIS_DEATH])
1078     {
1079         _wizard_restore_life();
1080         return;
1081     }
1082 #endif
1083 
1084     crawl_state.cancel_cmd_all();
1085 
1086     // Construct scorefile entry.
1087     scorefile_entry se(dam, source, death_type, aux, false,
1088                        death_source_name);
1089 
1090 #ifdef WIZARD
1091     if (!non_death)
1092     {
1093         if (crawl_state.test || you.wizard || you.suppress_wizard || (you.explore && !you.lives))
1094         {
1095             const string death_desc
1096                 = se.death_description(scorefile_entry::DDV_VERBOSE);
1097 
1098             dprf("Damage: %d; Hit points: %d", dam, you.hp);
1099 
1100             if (crawl_state.test || !yesno("Die?", false, 'n'))
1101             {
1102                 mpr("Thought so.");
1103                 take_note(Note(NOTE_DEATH, you.hp, you.hp_max,
1104                                 death_desc.c_str()), true);
1105                 _wizard_restore_life();
1106                 return;
1107             }
1108         }
1109     }
1110 #endif  // WIZARD
1111 
1112     if (crawl_state.game_is_tutorial())
1113     {
1114         crawl_state.need_save = false;
1115         if (!non_death)
1116             tutorial_death_message();
1117 
1118         screen_end_game("");
1119     }
1120 
1121     // Okay, so you're dead.
1122     take_note(Note(NOTE_DEATH, you.hp, you.hp_max,
1123                     se.death_description(scorefile_entry::DDV_NORMAL).c_str()),
1124               true);
1125     if (you.lives && !non_death)
1126     {
1127         mark_milestone("death", lowercase_first(se.long_kill_message()).c_str());
1128 
1129         you.deaths++;
1130         you.lives--;
1131         you.pending_revival = true;
1132 
1133         stop_delay(true);
1134 
1135         // You wouldn't want to lose this accomplishment to a crash, would you?
1136         // Especially if you manage to trigger one via lua somehow...
1137         if (!crawl_state.disables[DIS_SAVE_CHECKPOINTS])
1138             save_game(false);
1139 
1140         canned_msg(MSG_YOU_DIE);
1141         xom_death_message((kill_method_type) se.get_death_type());
1142         more();
1143 
1144         _place_player_corpse(death_type == KILLED_BY_DISINT);
1145         return;
1146     }
1147 
1148     // Prevent bogus notes.
1149     activate_notes(false);
1150 
1151     end_game(se);
1152 }
1153 
morgue_name(string char_name,time_t when_crawl_got_even)1154 string morgue_name(string char_name, time_t when_crawl_got_even)
1155 {
1156     string name = "morgue-" + char_name;
1157 
1158     string time = make_file_time(when_crawl_got_even);
1159     if (!time.empty())
1160         name += "-" + time;
1161 
1162     return name;
1163 }
1164 
actor_to_death_source(const actor * agent)1165 int actor_to_death_source(const actor* agent)
1166 {
1167     return agent ? agent->mindex() : NON_MONSTER;
1168 }
1169 
timescale_damage(const actor * act,int damage)1170 int timescale_damage(const actor *act, int damage)
1171 {
1172     if (damage < 0)
1173         damage = 0;
1174     // Can we have a uniform player/monster speed system yet?
1175     if (act->is_player())
1176         return div_rand_round(damage * you.time_taken, BASELINE_DELAY);
1177     else
1178     {
1179         const monster *mons = act->as_monster();
1180         const int speed = mons->speed > 0? mons->speed : BASELINE_DELAY;
1181         return div_rand_round(damage * BASELINE_DELAY, speed);
1182     }
1183 }
1184