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