Revert "Track who destroys an item; incur Nemelex penance for deck destruction."
[crawl.git] / crawl-ref / source / ouch.cc
1 /**
2  * @file
3  * @brief Functions used when Bad Things happen to the player.
4 **/
5
6 #include "AppHdr.h"
7
8 #include <cstring>
9 #include <string>
10 #include <iostream>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cctype>
14 #include <cmath>
15
16 #ifdef UNIX
17 #include <sys/types.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #endif
21
22 #include "ouch.h"
23
24 #include "externs.h"
25 #include "options.h"
26
27 #include "art-enum.h"
28 #include "artefact.h"
29 #include "beam.h"
30 #include "chardump.h"
31 #include "delay.h"
32 #include "describe.h"
33 #include "dgnevent.h"
34 #include "effects.h"
35 #include "env.h"
36 #include "files.h"
37 #include "fight.h"
38 #include "fineff.h"
39 #include "godabil.h"
40 #include "hints.h"
41 #include "hiscores.h"
42 #include "invent.h"
43 #include "itemname.h"
44 #include "itemprop.h"
45 #include "items.h"
46 #include "libutil.h"
47 #include "macro.h"
48 #include "message.h"
49 #include "mgen_data.h"
50 #include "misc.h"
51 #include "mon-util.h"
52 #include "mon-place.h"
53 #include "mon-stuff.h"
54 #include "mutation.h"
55 #include "notes.h"
56 #include "player.h"
57 #include "player-stats.h"
58 #include "potion.h"
59 #include "random.h"
60 #include "religion.h"
61 #include "shopping.h"
62 #include "skills2.h"
63 #include "spl-selfench.h"
64 #include "spl-other.h"
65 #include "state.h"
66 #include "stuff.h"
67 #include "transform.h"
68 #include "tutorial.h"
69 #include "view.h"
70 #include "shout.h"
71 #include "xom.h"
72
73 static void _end_game(scorefile_entry &se);
74
75 static void _maybe_melt_player_enchantments(beam_type flavour, int damage)
76 {
77     if (flavour == BEAM_FIRE || flavour == BEAM_LAVA
78         || flavour == BEAM_HELLFIRE || flavour == BEAM_NAPALM
79         || flavour == BEAM_STEAM)
80     {
81         if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
82         {
83             you.duration[DUR_CONDENSATION_SHIELD] -= damage * BASELINE_DELAY;
84             if (you.duration[DUR_CONDENSATION_SHIELD] <= 0)
85                 remove_condensation_shield();
86             else
87                 you.props["melt_shield"] = true;
88         }
89
90         if (you.mutation[MUT_ICEMAIL])
91         {
92             mpr("Your icy envelope dissipates!", MSGCH_DURATION);
93             you.duration[DUR_ICEMAIL_DEPLETED] = ICEMAIL_TIME;
94             you.redraw_armour_class = true;
95         }
96
97         if (you.duration[DUR_ICY_ARMOUR] > 0)
98         {
99             you.duration[DUR_ICY_ARMOUR] -= damage * BASELINE_DELAY;
100             if (you.duration[DUR_ICY_ARMOUR] <= 0)
101                 remove_ice_armour();
102             else
103                 you.props["melt_armour"] = true;
104         }
105     }
106 }
107
108 // NOTE: DOES NOT check for hellfire!!!
109 int check_your_resists(int hurted, beam_type flavour, string source,
110                        bolt *beam, bool doEffects)
111 {
112     int resist;
113     int original = hurted;
114
115     dprf("checking resistance: flavour=%d", flavour);
116
117     string kaux = "";
118     if (beam)
119     {
120         source = beam->get_source_name();
121         kaux = beam->name;
122     }
123
124     if (doEffects)
125         _maybe_melt_player_enchantments(flavour, hurted);
126
127     switch (flavour)
128     {
129     case BEAM_WATER:
130         hurted = resist_adjust_damage(&you, flavour,
131                                       you.res_water_drowning(), hurted, true);
132         if (!hurted && doEffects)
133             mpr("You shrug off the wave.");
134         else if (hurted > original && doEffects)
135         {
136             mpr("The water douses you terribly!");
137             xom_is_stimulated(200);
138         }
139         break;
140
141     case BEAM_STEAM:
142         hurted = resist_adjust_damage(&you, flavour,
143                                       player_res_steam(), hurted, true);
144         if (hurted < original && doEffects)
145             canned_msg(MSG_YOU_RESIST);
146         else if (hurted > original && doEffects)
147         {
148             mpr("The steam scalds you terribly!");
149             xom_is_stimulated(200);
150         }
151         break;
152
153     case BEAM_FIRE:
154         hurted = resist_adjust_damage(&you, flavour,
155                                       player_res_fire(), hurted, true);
156         if (hurted < original && doEffects)
157             canned_msg(MSG_YOU_RESIST);
158         else if (hurted > original && doEffects)
159         {
160             mpr("The fire burns you terribly!");
161             xom_is_stimulated(200);
162         }
163         break;
164
165     case BEAM_HELLFIRE:
166         if (you.species == SP_DJINNI)
167         {
168             hurted = 0;
169             if (doEffects)
170                 mpr("You resist completely.");
171         }
172         // Inconsistency: no penalty for rF-, unlike monsters.  That's
173         // probably good, and monsters should be changed.
174         break;
175
176     case BEAM_COLD:
177         hurted = resist_adjust_damage(&you, flavour,
178                                       player_res_cold(), hurted, true);
179         if (hurted < original && doEffects)
180             canned_msg(MSG_YOU_RESIST);
181         else if (hurted > original && doEffects)
182         {
183             mpr("You feel a terrible chill!");
184             xom_is_stimulated(200);
185         }
186         break;
187
188     case BEAM_ELECTRICITY:
189         hurted = resist_adjust_damage(&you, flavour,
190                                       player_res_electricity(),
191                                       hurted, true);
192
193         if (hurted < original && doEffects)
194             canned_msg(MSG_YOU_RESIST);
195         break;
196
197     case BEAM_POISON:
198         if (doEffects)
199         {
200             resist = poison_player(coinflip() ? 2 : 1, source, kaux) ? 0 : 1;
201
202             hurted = resist_adjust_damage(&you, flavour, resist,
203                                           hurted, true);
204             if (resist > 0)
205                 canned_msg(MSG_YOU_RESIST);
206         }
207         else
208         {
209             hurted = resist_adjust_damage(&you, flavour, player_res_poison(),
210                                           hurted, true);
211         }
212         break;
213
214     case BEAM_POISON_ARROW:
215         // [dshaligram] NOT importing uber-poison arrow from 4.1. Giving no
216         // bonus to poison resistant players seems strange and unnecessarily
217         // arbitrary.
218
219         resist = player_res_poison();
220
221         if (doEffects)
222         {
223             int poison_amount = 2 + random2(3);
224             poison_amount += (resist ? 0 : 2);
225             poison_player(poison_amount, source, kaux, true);
226         }
227
228         hurted = resist_adjust_damage(&you, flavour, resist, hurted);
229         if (hurted < original && doEffects)
230             canned_msg(MSG_YOU_PARTIALLY_RESIST);
231         break;
232
233     case BEAM_NEG:
234         resist = player_prot_life();
235
236         // TSO's protection.
237         if (you.religion == GOD_SHINING_ONE && you.piety > resist * 50)
238         {
239             int unhurted = min(hurted, (you.piety * hurted) / 150);
240
241             if (unhurted > 0)
242                 hurted -= unhurted;
243         }
244         else if (resist > 0)
245             hurted -= (resist * hurted) / 3;
246
247         if (doEffects)
248         {
249             drain_exp(true, beam ? beam->beam_source : NON_MONSTER,
250                       !kaux.empty() ? kaux.c_str() : NULL);
251         }
252         break;
253
254     case BEAM_ICE:
255         hurted = resist_adjust_damage(&you, flavour, player_res_cold(),
256                                       hurted, true);
257
258         if (hurted < original && doEffects)
259             canned_msg(MSG_YOU_PARTIALLY_RESIST);
260         else if (hurted > original && doEffects)
261         {
262             mpr("You feel a painful chill!");
263             xom_is_stimulated(200);
264         }
265         break;
266
267     case BEAM_LAVA:
268         hurted = resist_adjust_damage(&you, flavour, player_res_fire(),
269                                       hurted, true);
270
271         if (hurted < original && doEffects)
272             canned_msg(MSG_YOU_PARTIALLY_RESIST);
273         else if (hurted > original && doEffects)
274         {
275             mpr("The lava burns you terribly!");
276             xom_is_stimulated(200);
277         }
278         break;
279
280     case BEAM_ACID:
281         if (player_res_acid())
282         {
283             if (doEffects)
284                 canned_msg(MSG_YOU_RESIST);
285             hurted = hurted * player_acid_resist_factor() / 100;
286         }
287         break;
288
289     case BEAM_MIASMA:
290         if (you.res_rotting())
291         {
292             if (doEffects)
293                 canned_msg(MSG_YOU_RESIST);
294             hurted = 0;
295         }
296         break;
297
298     case BEAM_HOLY:
299     {
300         // Cleansing flame.
301         const int rhe = you.res_holy_energy(NULL);
302         if (rhe > 0)
303             hurted = 0;
304         else if (rhe == 0)
305             hurted /= 2;
306         else if (rhe < -1)
307             hurted = (hurted * 3) / 2;
308
309         if (hurted == 0 && doEffects)
310             canned_msg(MSG_YOU_RESIST);
311         break;
312     }
313
314     case BEAM_BOLT_OF_ZIN:
315     {
316         // Damage to chaos and mutations.
317
318         // For mutation damage, we want to count innate mutations for
319         // the demonspawn, but not for other species.
320         int mutated = how_mutated(you.species == SP_DEMONSPAWN, true);
321         int multiplier = min(mutated * 3, 60);
322         if (you.is_chaotic() || player_is_shapechanged())
323             multiplier = 60; // full damage
324         else if (you.is_undead || is_chaotic_god(you.religion))
325             multiplier = max(multiplier, 20);
326
327         hurted = hurted * multiplier / 60;
328
329         if (doEffects)
330         {
331             if (hurted <= 0)
332                 canned_msg(MSG_YOU_RESIST);
333             else if (multiplier > 30)
334                 mpr("The blast sears you terribly!");
335             else
336                 mpr("The blast sears you!");
337
338             if (one_chance_in(3)
339                 // delete_mutation() handles MUT_MUTATION_RESISTANCE but not the amulet
340                 && (!you.rmut_from_item()
341                     || one_chance_in(10)))
342             {
343                 // silver stars only, if this ever changes we may want to give
344                 // aux as well
345                 delete_mutation(RANDOM_GOOD_MUTATION, source);
346             }
347         }
348         break;
349     }
350
351     case BEAM_LIGHT:
352         if (you.species == SP_VAMPIRE)
353             hurted += hurted / 2;
354
355         if (hurted > original && doEffects)
356         {
357             mpr("The light scorches you terribly!");
358             xom_is_stimulated(200);
359         }
360         break;
361
362     case BEAM_AIR:
363     {
364         // Airstrike.
365         if (you.res_wind() > 0)
366             hurted = 0;
367         else if (you.flight_mode())
368             hurted += hurted / 2;
369         break;
370     }
371
372     case BEAM_GHOSTLY_FLAME:
373     {
374         if (you.holiness() == MH_UNDEAD)
375         {
376             if (doEffects)
377             {
378                 you.heal(roll_dice(2, 9));
379                 mpr("You are bolstered by the flame.");
380             }
381             hurted = 0;
382         }
383         else
384         {
385             hurted = resist_adjust_damage(&you, flavour,
386                                           you.res_negative_energy(),
387                                           hurted, true);
388             if (hurted < original && doEffects)
389                 canned_msg(MSG_YOU_PARTIALLY_RESIST);
390         }
391     }
392
393     default:
394         break;
395     }                           // end switch
396
397     if (doEffects && hurted != original)
398         maybe_id_resist(flavour);
399
400     return hurted;
401 }
402
403 void splash_with_acid(int acid_strength, int death_source, bool corrode_items, string hurt_message)
404 {
405     int dam = 0;
406     const bool wearing_cloak = player_wearing_slot(EQ_CLOAK);
407
408     for (int slot = EQ_MIN_ARMOUR; slot <= EQ_MAX_ARMOUR; slot++)
409     {
410         const bool cloak_protects = wearing_cloak && coinflip()
411                                     && slot != EQ_SHIELD && slot != EQ_CLOAK;
412
413         if (!cloak_protects)
414         {
415             item_def *item = you.slot_item(static_cast<equipment_type>(slot));
416             if (!item && slot != EQ_SHIELD)
417                 dam++;
418
419             if (item && corrode_items && x_chance_in_y(acid_strength + 1, 20))
420                 corrode_item(*item, &you);
421         }
422     }
423
424     // Covers head, hands and feet.
425     if (player_equip_unrand(UNRAND_LEAR))
426         dam = !wearing_cloak;
427
428     // Without fur, clothed people have dam 0 (+2 later), Sp/Tr/Dr/Og ~1
429     // (randomized), Fe 5.  Fur helps only against naked spots.
430     const int fur = player_mutation_level(MUT_SHAGGY_FUR);
431     dam -= fur * dam / 5;
432
433     // two extra virtual slots so players can't be immune
434     dam += 2;
435     dam = roll_dice(dam, acid_strength);
436
437     const int post_res_dam = dam * player_acid_resist_factor() / 100;
438
439     if (post_res_dam > 0)
440     {
441         mpr(hurt_message.empty() ? "The acid burns!" : hurt_message);
442
443         if (post_res_dam < dam)
444             canned_msg(MSG_YOU_RESIST);
445
446         ouch(post_res_dam, death_source, KILLED_BY_ACID);
447     }
448 }
449
450 // Helper function for the expose functions below.
451 // This currently works because elements only target a single type each.
452 static int _get_target_class(beam_type flavour)
453 {
454     int target_class = OBJ_UNASSIGNED;
455
456     switch (flavour)
457     {
458     case BEAM_FIRE:
459     case BEAM_LAVA:
460     case BEAM_NAPALM:
461     case BEAM_HELLFIRE:
462         target_class = OBJ_SCROLLS;
463         break;
464
465     case BEAM_COLD:
466     case BEAM_ICE:
467     case BEAM_FRAG:
468         target_class = OBJ_POTIONS;
469         break;
470
471     case BEAM_SPORE:
472     case BEAM_DEVOUR_FOOD:
473         target_class = OBJ_FOOD;
474         break;
475
476     default:
477         break;
478     }
479
480     return target_class;
481 }
482
483 // XXX: These expose functions could use being reworked into a real system...
484 // the usage and implementation is currently very hacky.
485 // Handles the destruction of inventory items from the elements.
486 static bool _expose_invent_to_element(beam_type flavour, int strength)
487 {
488     int num_dest = 0;
489     int total_dest = 0;
490     int jiyva_block = 0;
491
492     const int target_class = _get_target_class(flavour);
493     if (target_class == OBJ_UNASSIGNED)
494         return false;
495
496     // Wisp form semi-melds all of inventory, making it unusable for you,
497     // but also immune to destruction.  No message is needed.
498     if (you.form == TRAN_WISP)
499         return false;
500
501     // Fedhas worshipers are exempt from the food destruction effect
502     // of spores.
503     if (flavour == BEAM_SPORE
504         && you.religion == GOD_FEDHAS)
505     {
506         simple_god_message(" protects your food from the spores.",
507                            GOD_FEDHAS);
508         return false;
509     }
510
511     // Currently we test against each stack (and item in the stack)
512     // independently at strength%... perhaps we don't want that either
513     // because it makes the system very fair and removes the protection
514     // factor of junk (which might be more desirable for game play).
515     for (int i = 0; i < ENDOFPACK; ++i)
516     {
517         if (!you.inv[i].defined())
518             continue;
519
520         if (you.inv[i].base_type == target_class
521             || target_class == OBJ_FOOD
522                && you.inv[i].base_type == OBJ_CORPSES)
523         {
524             // Conservation doesn't help against harpies' devouring food.
525             if (flavour != BEAM_DEVOUR_FOOD
526                 && you.conservation() && !one_chance_in(10))
527             {
528                 continue;
529             }
530
531             // These stack with conservation; they're supposed to be good.
532             if (target_class == OBJ_SCROLLS
533                 && you.mutation[MUT_CONSERVE_SCROLLS]
534                 && !one_chance_in(10))
535             {
536                 continue;
537             }
538
539             if (target_class == OBJ_POTIONS
540                 && you.mutation[MUT_CONSERVE_POTIONS]
541                 && !one_chance_in(10))
542             {
543                 continue;
544             }
545
546             if (you.religion == GOD_JIYVA && !player_under_penance()
547                 && x_chance_in_y(you.piety, MAX_PIETY))
548             {
549                 ++jiyva_block;
550                 continue;
551             }
552
553             // Get name and quantity before destruction.
554             const string item_name = you.inv[i].name(DESC_PLAIN);
555             const int quantity = you.inv[i].quantity;
556             num_dest = 0;
557
558             // Loop through all items in the stack.
559             for (int j = 0; j < you.inv[i].quantity; ++j)
560             {
561                 if (bernoulli(strength, 0.01))
562                 {
563                     num_dest++;
564
565                     if (i == you.equip[EQ_WEAPON])
566                         you.wield_change = true;
567
568                     if (dec_inv_item_quantity(i, 1))
569                         break;
570                     else if (is_blood_potion(you.inv[i]))
571                         remove_oldest_blood_potion(you.inv[i]);
572                 }
573             }
574
575             // Name destroyed items.
576             // TODO: Combine messages using a vector.
577             if (num_dest > 0)
578             {
579                 switch (target_class)
580                 {
581                 case OBJ_SCROLLS:
582                     mprf("%s %s catch%s fire!",
583                          part_stack_string(num_dest, quantity).c_str(),
584                          item_name.c_str(),
585                          (num_dest == 1) ? "es" : "");
586                     break;
587
588                 case OBJ_POTIONS:
589                     mprf("%s %s freeze%s and shatter%s!",
590                          part_stack_string(num_dest, quantity).c_str(),
591                          item_name.c_str(),
592                          (num_dest == 1) ? "s" : "",
593                          (num_dest == 1) ? "s" : "");
594                     break;
595
596                 case OBJ_FOOD:
597                     mprf("%s %s %s %s!",
598                          part_stack_string(num_dest, quantity).c_str(),
599                          item_name.c_str(),
600                          (num_dest == 1) ? "is" : "are",
601                          (flavour == BEAM_DEVOUR_FOOD) ?
602                              "devoured" : "covered with spores");
603                     break;
604
605                 default:
606                     mprf("%s %s %s destroyed!",
607                          part_stack_string(num_dest, quantity).c_str(),
608                          item_name.c_str(),
609                          (num_dest == 1) ? "is" : "are");
610                     break;
611                 }
612
613                 total_dest += num_dest;
614             }
615         }
616     }
617
618     if (jiyva_block)
619     {
620         simple_god_message(
621             make_stringf(" shields %s delectables from destruction.",
622                          (total_dest > 0) ? "some of your" : "your").c_str(),
623             GOD_JIYVA);
624     }
625
626     if (!total_dest)
627         return false;
628
629     // Message handled elsewhere.
630     if (flavour == BEAM_DEVOUR_FOOD)
631         return true;
632
633     xom_is_stimulated((num_dest > 1) ? 25 : 12);
634
635     return true;
636 }
637
638 bool expose_items_to_element(beam_type flavour, const coord_def& where,
639                              int strength)
640 {
641     int num_dest = 0;
642
643     const int target_class = _get_target_class(flavour);
644     if (target_class == OBJ_UNASSIGNED)
645         return false;
646
647     // Beams fly *over* water and lava.
648     if (grd(where) == DNGN_LAVA || grd(where) == DNGN_DEEP_WATER)
649         return false;
650
651     for (stack_iterator si(where); si; ++si)
652     {
653         if (!si->defined())
654             continue;
655
656         if (si->base_type == target_class
657             || target_class == OBJ_FOOD && si->base_type == OBJ_CORPSES)
658         {
659             for (int j = 0; j < si->quantity; ++j)
660             {
661                 if (x_chance_in_y(strength, 100))
662                 {
663                     num_dest++;
664                     if (!dec_mitm_item_quantity(si->index(), 1)
665                         && is_blood_potion(*si))
666                     {
667                        remove_oldest_blood_potion(*si);
668                     }
669                 }
670             }
671         }
672     }
673
674     if (!num_dest)
675         return false;
676
677     if (flavour == BEAM_DEVOUR_FOOD)
678         return true;
679
680     if (you.see_cell(where))
681     {
682         switch (target_class)
683         {
684         case OBJ_SCROLLS:
685             mprf("You see %s of smoke.",
686                  (num_dest > 1) ? "some puffs" : "a puff");
687             break;
688
689         case OBJ_POTIONS:
690             mprf("You see %s shatter.",
691                  (num_dest > 1) ? "some glass" : "glass");
692             break;
693
694         case OBJ_FOOD:
695             mprf("You see %s of spores.",
696                  (num_dest > 1) ? "some clouds" : "a cloud");
697             break;
698
699         default:
700             mprf("%s on the floor %s destroyed!",
701                  (num_dest > 1) ? "Some items" : "An item",
702                  (num_dest > 1) ? "were" : "was");
703             break;
704         }
705     }
706
707     xom_is_stimulated((num_dest > 1) ? 25 : 12);
708
709     return true;
710 }
711
712 // Handle side-effects for exposure to element other than damage.  This
713 // function exists because some code calculates its own damage instead
714 // of using check_your_resists() and we want to isolate all the special
715 // code they keep having to do... namely condensation shield checks,
716 // you really can't expect this function to even be called for much
717 // else.
718 //
719 // This function now calls _expose_invent_to_element() if strength > 0.
720 //
721 // XXX: This function is far from perfect and a work in progress.
722 bool expose_player_to_element(beam_type flavour, int strength,
723                               bool damage_inventory, bool slow_dracs)
724 {
725     _maybe_melt_player_enchantments(flavour, strength ? strength : 10);
726
727     if (flavour == BEAM_COLD && slow_dracs && player_genus(GENPC_DRACONIAN)
728         && you.res_cold() <= 0 && coinflip())
729     {
730         you.slow_down(0, strength);
731     }
732
733     if (strength <= 0 || !damage_inventory)
734         return false;
735
736     return _expose_invent_to_element(flavour, strength);
737 }
738
739 static void _lose_level_abilities()
740 {
741     if (you.attribute[ATTR_PERM_FLIGHT]
742         && !you.racial_permanent_flight()
743         && !you.wearing_ego(EQ_ALL_ARMOUR, SPARM_FLYING))
744     {
745         you.increase_duration(DUR_FLIGHT, 50, 100);
746         you.attribute[ATTR_PERM_FLIGHT] = 0;
747         mpr("You feel your flight won't last long.", MSGCH_WARN);
748     }
749 }
750
751 void lose_level(int death_source, const char *aux)
752 {
753     // Because you.experience is unsigned long, if it's going to be
754     // negative, must die straightaway.
755     if (you.experience_level == 1)
756     {
757         ouch(INSTANT_DEATH, death_source, KILLED_BY_DRAINING, aux);
758         // Return in case death was canceled via wizard mode
759         return;
760     }
761
762     you.experience_level--;
763
764     mprf(MSGCH_WARN,
765          "You are now level %d!", you.experience_level);
766
767     ouch(4, death_source, KILLED_BY_DRAINING, aux);
768     dec_mp(1);
769
770     calc_hp();
771     calc_mp();
772     _lose_level_abilities();
773
774     char buf[200];
775     sprintf(buf, "HP: %d/%d MP: %d/%d",
776             you.hp, you.hp_max, you.magic_points, you.max_magic_points);
777     take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0, buf));
778
779     you.redraw_title = true;
780     you.redraw_experience = true;
781 #ifdef USE_TILE_LOCAL
782     // In case of intrinsic ability changes.
783     tiles.layout_statcol();
784     redraw_screen();
785 #endif
786
787     xom_is_stimulated(200);
788
789     // Kill the player if maxhp <= 0.  We can't just move the ouch() call past
790     // dec_max_hp() since it would decrease hp twice, so here's another one.
791     ouch(0, death_source, KILLED_BY_DRAINING, aux);
792 }
793
794 bool drain_exp(bool announce_full, int death_source, const char *aux)
795 {
796     const int protection = player_prot_life();
797
798     if (protection == 3)
799     {
800         if (announce_full)
801             canned_msg(MSG_YOU_RESIST);
802
803         return false;
804     }
805
806     if (you.experience == 0)
807     {
808         mpr("You are drained of all life!");
809         ouch(INSTANT_DEATH, death_source, KILLED_BY_DRAINING, aux);
810
811         // Return in case death was escaped via wizard mode.
812         return true;
813     }
814
815     if (you.experience_level == 1)
816     {
817         mpr("You feel drained.");
818         you.experience = 0;
819
820         return true;
821     }
822
823     unsigned int total_exp = exp_needed(you.experience_level + 1)
824                                   - exp_needed(you.experience_level);
825     unsigned int exp_drained = (total_exp * (5 + random2(11))) / 100;
826
827     // TSO's protection.
828     if (you.religion == GOD_SHINING_ONE && you.piety > protection * 50)
829     {
830         unsigned int undrained = min(exp_drained,
831                                      (you.piety * exp_drained) / 150);
832
833         if (undrained > 0)
834         {
835             simple_god_message(" protects your life force!");
836             if (undrained > 0)
837                 exp_drained -= undrained;
838         }
839     }
840     else if (protection > 0)
841     {
842         canned_msg(MSG_YOU_PARTIALLY_RESIST);
843         exp_drained -= (protection * exp_drained) / 3;
844     }
845
846     if (exp_drained > 0)
847     {
848         mpr("You feel drained.");
849         xom_is_stimulated(15);
850         you.experience -= exp_drained;
851
852         dprf("You lose %d experience points.", exp_drained);
853
854         level_change(death_source, aux);
855
856         return true;
857     }
858
859     return false;
860 }
861
862 static void _xom_checks_damage(kill_method_type death_type,
863                                int dam, int death_source)
864 {
865     if (you.religion == GOD_XOM)
866     {
867         if (death_type == KILLED_BY_TARGETTING
868             || death_type == KILLED_BY_BOUNCE
869             || death_type == KILLED_BY_REFLECTION
870             || death_type == KILLED_BY_SELF_AIMED
871                && player_in_a_dangerous_place())
872         {
873             // Xom thinks the player accidentally hurting him/herself is funny.
874             // Deliberate damage is only amusing if it's dangerous.
875             int amusement = 200 * dam / (dam + you.hp);
876             if (death_type == KILLED_BY_SELF_AIMED)
877                 amusement /= 5;
878             xom_is_stimulated(amusement);
879             return;
880         }
881         else if (death_type == KILLED_BY_FALLING_DOWN_STAIRS
882                  || death_type == KILLED_BY_FALLING_THROUGH_GATE)
883         {
884             // Xom thinks falling down the stairs is hilarious.
885             xom_is_stimulated(200);
886             return;
887         }
888         else if (death_type == KILLED_BY_DISINT)
889         {
890             // flying chunks...
891             xom_is_stimulated(100);
892             return;
893         }
894         else if (death_type != KILLED_BY_MONSTER
895                     && death_type != KILLED_BY_BEAM
896                     && death_type != KILLED_BY_DISINT
897                  || invalid_monster_index(death_source))
898         {
899             return;
900         }
901
902         int amusementvalue = 1;
903         const monster* mons = &menv[death_source];
904
905         if (!mons->alive())
906             return;
907
908         if (mons->wont_attack())
909         {
910             // Xom thinks collateral damage is funny.
911             xom_is_stimulated(200 * dam / (dam + you.hp));
912             return;
913         }
914
915         int leveldif = mons->hit_dice - you.experience_level;
916         if (leveldif == 0)
917             leveldif = 1;
918
919         // Note that Xom is amused when you are significantly hurt by a
920         // creature of higher level than yourself, as well as by a
921         // creature of lower level than yourself.
922         amusementvalue += leveldif * leveldif * dam;
923
924         if (!mons->visible_to(&you))
925             amusementvalue += 8;
926
927         if (mons->speed < 100/player_movement_speed())
928             amusementvalue += 7;
929
930         if (death_type != KILLED_BY_BEAM
931             && you.skill(SK_THROWING) <= (you.experience_level / 4))
932         {
933             amusementvalue += 2;
934         }
935         else if (you.skill(SK_FIGHTING) <= (you.experience_level / 4))
936             amusementvalue += 2;
937
938         if (player_in_a_dangerous_place())
939             amusementvalue += 2;
940
941         amusementvalue /= (you.hp > 0) ? you.hp : 1;
942
943         xom_is_stimulated(amusementvalue);
944     }
945 }
946
947 static void _yred_mirrors_injury(int dam, int death_source)
948 {
949     if (yred_injury_mirror())
950     {
951         // Cap damage to what was enough to kill you.  Can matter if
952         // Yred saves your life or you have an extra kitty.
953         if (you.hp < 0)
954             dam += you.hp;
955
956         if (dam <= 0 || invalid_monster_index(death_source))
957             return;
958
959         (new mirror_damage_fineff(&menv[death_source], &you, dam))->schedule();
960     }
961 }
962
963 static void _maybe_spawn_jellies(int dam, const char* aux,
964                                   kill_method_type death_type, int death_source)
965 {
966     // We need to exclude acid damage and similar things or this function
967     // will crash later.
968     if (death_source == NON_MONSTER)
969         return;
970
971     monster_type mon = royal_jelly_ejectable_monster();
972
973     // Exclude torment damage.
974     const bool torment = aux && strstr(aux, "torment");
975     if (you.religion == GOD_JIYVA && you.piety > 160 && !torment)
976     {
977         int how_many = 0;
978         if (dam >= you.hp_max * 3 / 4)
979             how_many = random2(4) + 2;
980         else if (dam >= you.hp_max / 2)
981             how_many = random2(2) + 2;
982         else if (dam >= you.hp_max / 4)
983             how_many = 1;
984
985         if (how_many > 0)
986         {
987             if (x_chance_in_y(how_many, 8)
988                 && !lose_stat(STAT_STR, 1, true, "spawning slimes"))
989             {
990                 canned_msg(MSG_NOTHING_HAPPENS);
991                 return;
992             }
993
994             int count_created = 0;
995             for (int i = 0; i < how_many; ++i)
996             {
997                 int foe = death_source;
998                 if (invalid_monster_index(foe))
999                     foe = MHITNOT;
1000                 mgen_data mg(mon, BEH_FRIENDLY, &you, 2, 0, you.pos(),
1001                              foe, 0, GOD_JIYVA);
1002
1003                 if (create_monster(mg))
1004                     count_created++;
1005             }
1006
1007             if (count_created > 0)
1008             {
1009                 mprf("You shudder from the %s and a %s!",
1010                      death_type == KILLED_BY_MONSTER ? "blow" : "blast",
1011                      count_created > 1 ? "flood of jellies pours out from you"
1012                                        : "jelly pops out");
1013             }
1014         }
1015     }
1016 }
1017
1018 static void _powered_by_pain(int dam)
1019 {
1020     const int level = player_mutation_level(MUT_POWERED_BY_PAIN);
1021
1022     if (you.mutation[MUT_POWERED_BY_PAIN]
1023         && (random2(dam) > 2 + 3 * level
1024             || dam >= you.hp_max / 2))
1025     {
1026         switch (random2(4))
1027         {
1028         case 0:
1029         case 1:
1030         {
1031             if (you.magic_points < you.max_magic_points)
1032             {
1033                 mpr("You focus on the pain.");
1034                 int mp = roll_dice(3, 2 + 3 * level);
1035                 mpr("You feel your power returning.");
1036                 inc_mp(mp);
1037                 break;
1038             }
1039             break;
1040         }
1041         case 2:
1042             mpr("You focus on the pain.");
1043             potion_effect(POT_MIGHT, level * 20);
1044             break;
1045         case 3:
1046             mpr("You focus on the pain.");
1047             potion_effect(POT_AGILITY, level * 20);
1048             break;
1049         }
1050     }
1051 }
1052
1053 static void _place_player_corpse(bool explode)
1054 {
1055     if (!in_bounds(you.pos()))
1056         return;
1057
1058     item_def corpse;
1059     if (fill_out_corpse(0, player_mons(), corpse) == MONS_NO_MONSTER)
1060         return;
1061
1062     if (explode && explode_corpse(corpse, you.pos()))
1063         return;
1064
1065     int o = get_mitm_slot();
1066     if (o == NON_ITEM)
1067     {
1068         item_was_destroyed(corpse);
1069         return;
1070     }
1071
1072     corpse.props[MONSTER_HIT_DICE].get_short() = you.experience_level;
1073     corpse.props[CORPSE_NAME_KEY] = you.your_name;
1074     corpse.props[CORPSE_NAME_TYPE_KEY].get_int64() = 0;
1075     corpse.props["ev"].get_int() = player_evasion(static_cast<ev_ignore_type>(
1076                                    EV_IGNORE_HELPLESS | EV_IGNORE_PHASESHIFT));
1077     // mostly mutations here.  At least there's no need to handle armour.
1078     corpse.props["ac"].get_int() = you.armour_class();
1079     mitm[o] = corpse;
1080
1081     move_item_to_grid(&o, you.pos(), !you.in_water());
1082 }
1083
1084
1085 #if defined(WIZARD) || defined(DEBUG)
1086 static void _wizard_restore_life()
1087 {
1088     if (you.hp_max <= 0)
1089         unrot_hp(9999);
1090     if (you.hp <= 0)
1091         set_hp(you.hp_max);
1092     for (int i = 0; i < NUM_STATS; ++i)
1093     {
1094         if (you.stat(static_cast<stat_type>(i)) <= 0)
1095         {
1096             you.stat_loss[i] = 0;
1097             you.stat_zero[i] = 0;
1098             you.redraw_stats[i] = true;
1099         }
1100     }
1101 }
1102 #endif
1103
1104 void reset_damage_counters()
1105 {
1106     you.turn_damage = 0;
1107     you.damage_source = NON_MONSTER;
1108     you.source_damage = 0;
1109 }
1110
1111 // death_source should be set to NON_MONSTER for non-monsters. {dlb}
1112 void ouch(int dam, int death_source, kill_method_type death_type,
1113           const char *aux, bool see_source, const char *death_source_name)
1114 {
1115     ASSERT(!crawl_state.game_is_arena());
1116     if (you.duration[DUR_TIME_STEP])
1117         return;
1118
1119     if (you.dead) // ... but eligible for revival
1120         return;
1121
1122     if (dam != INSTANT_DEATH && !invalid_monster_index(death_source)
1123         && menv[death_source].has_ench(ENCH_WRETCHED))
1124     {
1125         // An abstract boring simulation of reduced stats/etc due to bad muts
1126         // reducing the monster's melee damage, spell power, etc.  This is
1127         // wrong eg. for clouds that would be smaller and with a shorter
1128         // duration but do the same damage, etc.
1129         int degree = menv[death_source].get_ench(ENCH_WRETCHED).degree;
1130         dam = div_rand_round(dam * (10 - min(degree, 5)), 10);
1131     }
1132
1133     if (dam != INSTANT_DEATH && you.species == SP_DEEP_DWARF)
1134     {
1135         // Deep Dwarves get to shave any hp loss.
1136         int shave = 1 + random2(2 + random2(1 + you.experience_level / 3));
1137         dprf("HP shaved: %d.", shave);
1138         dam -= shave;
1139         if (dam <= 0)
1140         {
1141             // Rotting and costs may lower hp directly.
1142             if (you.hp > 0)
1143                 return;
1144             dam = 0;
1145         }
1146     }
1147
1148     if (dam != INSTANT_DEATH)
1149     {
1150         if (you.petrified())
1151             dam /= 2;
1152         else if (you.petrifying())
1153             dam = dam * 10 / 15;
1154     }
1155     ait_hp_loss hpl(dam, death_type);
1156     interrupt_activity(AI_HP_LOSS, &hpl);
1157
1158     if (dam > 0)
1159         you.check_awaken(500);
1160
1161     const bool non_death = death_type == KILLED_BY_QUITTING
1162         || death_type == KILLED_BY_WINNING
1163         || death_type == KILLED_BY_LEAVING;
1164
1165     if (you.duration[DUR_DEATHS_DOOR] && death_type != KILLED_BY_LAVA
1166         && death_type != KILLED_BY_WATER && !non_death && you.hp_max > 0)
1167     {
1168         return;
1169     }
1170
1171     if (dam != INSTANT_DEATH)
1172     {
1173         if (you.spirit_shield() && death_type != KILLED_BY_POISON
1174             && !(aux && strstr(aux, "flay_damage")))
1175         {
1176             // round off fairly (important for taking 1 damage at a time)
1177             int mp = div_rand_round(dam * you.magic_points,
1178                                     you.hp + you.magic_points);
1179             // but don't kill the player with round-off errors
1180             mp = max(mp, dam + 1 - you.hp);
1181             mp = min(mp, you.magic_points);
1182
1183             dam -= mp;
1184             dec_mp(mp);
1185             if (dam <= 0)
1186                 return;
1187         }
1188
1189         if (dam >= you.hp && you.hp_max > 0 && god_protects_from_harm())
1190         {
1191             simple_god_message(" protects you from harm!");
1192             return;
1193         }
1194
1195         you.turn_damage += dam;
1196         if (you.damage_source != death_source)
1197         {
1198             you.damage_source = death_source;
1199             you.source_damage = 0;
1200         }
1201         you.source_damage += dam;
1202
1203         dec_hp(dam, true);
1204
1205         // Even if we have low HP messages off, we'll still give a
1206         // big hit warning (in this case, a hit for half our HPs) -- bwr
1207         if (dam > 0 && you.hp_max <= dam * 2)
1208             mpr("Ouch! That really hurt!", MSGCH_DANGER);
1209
1210         if (you.hp > 0)
1211         {
1212             if (Options.hp_warning
1213                 && you.hp <= (you.hp_max * Options.hp_warning) / 100)
1214             {
1215                 mpr("* * * LOW HITPOINT WARNING * * *", MSGCH_DANGER);
1216                 dungeon_events.fire_event(DET_HP_WARNING);
1217             }
1218
1219             hints_healing_check();
1220
1221             _xom_checks_damage(death_type, dam, death_source);
1222
1223             // for note taking
1224             string damage_desc;
1225             if (!see_source)
1226                 damage_desc = make_stringf("something (%d)", dam);
1227             else
1228             {
1229                 damage_desc = scorefile_entry(dam, death_source,
1230                                               death_type, aux, true)
1231                     .death_description(scorefile_entry::DDV_TERSE);
1232             }
1233
1234             take_note(Note(NOTE_HP_CHANGE, you.hp, you.hp_max,
1235                            damage_desc.c_str()));
1236
1237             _yred_mirrors_injury(dam, death_source);
1238             _maybe_spawn_jellies(dam, aux, death_type, death_source);
1239             _powered_by_pain(dam);
1240
1241             return;
1242         } // else hp <= 0
1243     }
1244
1245     // Is the player being killed by a direct act of Xom?
1246     if (crawl_state.is_god_acting()
1247         && crawl_state.which_god_acting() == GOD_XOM
1248         && crawl_state.other_gods_acting().empty())
1249     {
1250         you.escaped_death_cause = death_type;
1251         you.escaped_death_aux   = aux == NULL ? "" : aux;
1252
1253         // Xom should only kill his worshippers if they're under penance
1254         // or Xom is bored.
1255         if (you.religion == GOD_XOM && !you.penance[GOD_XOM]
1256             && you.gift_timeout > 0)
1257         {
1258             return;
1259         }
1260
1261         // Also don't kill wizards testing Xom acts.
1262         if ((crawl_state.repeat_cmd == CMD_WIZARD
1263                 || crawl_state.prev_cmd == CMD_WIZARD)
1264             && you.religion != GOD_XOM)
1265         {
1266             return;
1267         }
1268
1269         // Okay, you *didn't* escape death.
1270         you.reset_escaped_death();
1271
1272         // Ensure some minimal information about Xom's involvement.
1273         if (aux == NULL || !*aux)
1274         {
1275             if (death_type != KILLED_BY_XOM)
1276                 aux = "Xom";
1277         }
1278         else if (strstr(aux, "Xom") == NULL)
1279             death_type = KILLED_BY_XOM;
1280     }
1281     // Xom may still try to save your life.
1282     else if (xom_saves_your_life(dam, death_source, death_type, aux,
1283                                  see_source))
1284     {
1285         return;
1286     }
1287
1288 #if defined(WIZARD) || defined(DEBUG)
1289     if (!non_death && crawl_state.disables[DIS_DEATH])
1290     {
1291         _wizard_restore_life();
1292         return;
1293     }
1294 #endif
1295
1296     crawl_state.cancel_cmd_all();
1297
1298     // Construct scorefile entry.
1299     scorefile_entry se(dam, death_source, death_type, aux, false,
1300                        death_source_name);
1301
1302 #ifdef WIZARD
1303     if (!non_death)
1304     {
1305         if (crawl_state.test || you.wizard)
1306         {
1307             const string death_desc
1308                 = se.death_description(scorefile_entry::DDV_VERBOSE);
1309
1310             dprf("Damage: %d; Hit points: %d", dam, you.hp);
1311
1312             if (crawl_state.test || !yesno("Die?", false, 'n'))
1313             {
1314                 mpr("Thought so.");
1315                 take_note(Note(NOTE_DEATH, you.hp, you.hp_max,
1316                                 death_desc.c_str()), true);
1317                 _wizard_restore_life();
1318                 return;
1319             }
1320         }
1321     }
1322 #endif  // WIZARD
1323
1324     if (crawl_state.game_is_tutorial())
1325     {
1326         crawl_state.need_save = false;
1327         if (!non_death)
1328             tutorial_death_message();
1329
1330         screen_end_game("");
1331     }
1332
1333     // Okay, so you're dead.
1334     take_note(Note(NOTE_DEATH, you.hp, you.hp_max,
1335                     se.death_description(scorefile_entry::DDV_NORMAL).c_str()),
1336               true);
1337     if (you.lives && !non_death)
1338     {
1339         mark_milestone("death", lowercase_first(se.long_kill_message()).c_str());
1340
1341         you.deaths++;
1342         you.lives--;
1343         you.dead = true;
1344
1345         stop_delay(true);
1346
1347         // You wouldn't want to lose this accomplishment to a crash, would you?
1348         // Especially if you manage to trigger one via lua somehow...
1349         save_game(false);
1350
1351         mprnojoin("You die...");
1352         xom_death_message((kill_method_type) se.get_death_type());
1353         more();
1354
1355         _place_player_corpse(death_type == KILLED_BY_DISINT);
1356         return;
1357     }
1358
1359     // The game's over.
1360     crawl_state.need_save       = false;
1361     crawl_state.updating_scores = true;
1362
1363     // Prevent bogus notes.
1364     activate_notes(false);
1365
1366 #ifndef SCORE_WIZARD_CHARACTERS
1367     if (!you.wizard)
1368 #endif
1369     {
1370         // Add this highscore to the score file.
1371         hiscores_new_entry(se);
1372         logfile_new_entry(se);
1373     }
1374
1375     // Never generate bones files of wizard or tutorial characters -- bwr
1376     if (!non_death && !crawl_state.game_is_tutorial() && !you.wizard)
1377         save_ghost();
1378
1379     _end_game(se);
1380 }
1381
1382 string morgue_name(string char_name, time_t when_crawl_got_even)
1383 {
1384     string name = "morgue-" + char_name;
1385
1386     string time = make_file_time(when_crawl_got_even);
1387     if (!time.empty())
1388         name += "-" + time;
1389
1390     return name;
1391 }
1392
1393 // Delete save files on game end.
1394 static void _delete_files()
1395 {
1396     crawl_state.need_save = false;
1397     you.save->unlink();
1398     delete you.save;
1399     you.save = 0;
1400 }
1401
1402 void screen_end_game(string text)
1403 {
1404     crawl_state.cancel_cmd_all();
1405     _delete_files();
1406
1407     if (!text.empty())
1408     {
1409         clrscr();
1410         linebreak_string(text, get_number_of_cols());
1411         display_tagged_block(text);
1412
1413         if (!crawl_state.seen_hups)
1414             get_ch();
1415     }
1416
1417     game_ended();
1418 }
1419
1420 void _end_game(scorefile_entry &se)
1421 {
1422     for (int i = 0; i < ENDOFPACK; i++)
1423         if (you.inv[i].defined() && item_type_unknown(you.inv[i]))
1424             add_inscription(you.inv[i], "unknown");
1425
1426     for (int i = 0; i < ENDOFPACK; i++)
1427     {
1428         if (!you.inv[i].defined())
1429             continue;
1430         set_ident_flags(you.inv[i], ISFLAG_IDENT_MASK);
1431         set_ident_type(you.inv[i], ID_KNOWN_TYPE);
1432     }
1433
1434     _delete_files();
1435
1436     // death message
1437     if (se.get_death_type() != KILLED_BY_LEAVING
1438         && se.get_death_type() != KILLED_BY_QUITTING
1439         && se.get_death_type() != KILLED_BY_WINNING)
1440     {
1441         mprnojoin("You die...");      // insert player name here? {dlb}
1442         xom_death_message((kill_method_type) se.get_death_type());
1443
1444         switch (you.religion)
1445         {
1446         case GOD_FEDHAS:
1447             simple_god_message(" appreciates your contribution to the "
1448                                "ecosystem.");
1449             break;
1450
1451         case GOD_NEMELEX_XOBEH:
1452             nemelex_death_message();
1453             break;
1454
1455         case GOD_KIKUBAAQUDGHA:
1456         {
1457             const mon_holy_type holi = you.holiness();
1458
1459             if (holi == MH_NONLIVING || holi == MH_UNDEAD)
1460             {
1461                 simple_god_message(" rasps: \"You have failed me! "
1462                                    "Welcome... oblivion!\"");
1463             }
1464             else
1465             {
1466                 simple_god_message(" rasps: \"You have failed me! "
1467                                    "Welcome... death!\"");
1468             }
1469             break;
1470         }
1471
1472         case GOD_YREDELEMNUL:
1473             if (you.is_undead)
1474                 simple_god_message(" claims you as an undead slave.");
1475             else if (se.get_death_type() != KILLED_BY_DISINT
1476                      && se.get_death_type() != KILLED_BY_LAVA)
1477             {
1478                 mpr("Your body rises from the dead as a mindless zombie.",
1479                     MSGCH_GOD);
1480             }
1481             // No message if you're not undead and your corpse is lost.
1482             break;
1483
1484         default:
1485             break;
1486         }
1487
1488         flush_prev_message();
1489         viewwindow(); // don't do for leaving/winning characters
1490
1491         if (crawl_state.game_is_hints())
1492             hints_death_screen();
1493     }
1494
1495     if (!dump_char(morgue_name(you.your_name, se.get_death_time()),
1496                    true, true, &se))
1497     {
1498         mpr("Char dump unsuccessful! Sorry about that.");
1499         if (!crawl_state.seen_hups)
1500             more();
1501         clrscr();
1502     }
1503
1504 #ifdef DGL_WHEREIS
1505     whereis_record(se.get_death_type() == KILLED_BY_QUITTING? "quit" :
1506                    se.get_death_type() == KILLED_BY_WINNING ? "won"  :
1507                    se.get_death_type() == KILLED_BY_LEAVING ? "bailed out"
1508                                                             : "dead");
1509 #endif
1510
1511     if (!crawl_state.seen_hups)
1512         more();
1513
1514     if (!crawl_state.disables[DIS_CONFIRMATIONS])
1515         browse_inventory();
1516     textcolor(LIGHTGREY);
1517
1518     // Prompt for saving macros.
1519     if (crawl_state.unsaved_macros && yesno("Save macros?", true, 'n'))
1520         macro_save();
1521
1522     clrscr();
1523     cprintf("Goodbye, %s.", you.your_name.c_str());
1524     cprintf("\n\n    "); // Space padding where # would go in list format
1525
1526     string hiscore = hiscores_format_single_long(se, true);
1527
1528     const int lines = count_occurrences(hiscore, "\n") + 1;
1529
1530     cprintf("%s", hiscore.c_str());
1531
1532     cprintf("\nBest Crawlers - %s\n",
1533             crawl_state.game_type_name().c_str());
1534
1535     // "- 5" gives us an extra line in case the description wraps on a line.
1536     hiscores_print_list(get_number_of_lines() - lines - 5);
1537
1538 #ifndef DGAMELAUNCH
1539     cprintf("\nYou can find your morgue file in the '%s' directory.",
1540             morgue_directory().c_str());
1541 #endif
1542
1543     // just to pause, actual value returned does not matter {dlb}
1544     if (!crawl_state.seen_hups && !crawl_state.disables[DIS_CONFIRMATIONS])
1545         get_ch();
1546
1547     if (se.get_death_type() == KILLED_BY_WINNING)
1548         crawl_state.last_game_won = true;
1549
1550     game_ended();
1551 }
1552
1553 int actor_to_death_source(const actor* agent)
1554 {
1555     if (agent->is_player())
1556         return NON_MONSTER;
1557     else if (agent->is_monster())
1558         return (agent->as_monster()->mindex());
1559     else
1560         return NON_MONSTER;
1561 }
1562
1563 int timescale_damage(const actor *act, int damage)
1564 {
1565     if (damage < 0)
1566         damage = 0;
1567     // Can we have a uniform player/monster speed system yet?
1568     if (act->is_player())
1569         return div_rand_round(damage * you.time_taken, BASELINE_DELAY);
1570     else
1571     {
1572         const monster *mons = act->as_monster();
1573         const int speed = mons->speed > 0? mons->speed : BASELINE_DELAY;
1574         return div_rand_round(damage * BASELINE_DELAY, speed);
1575     }
1576 }