/** * @file * @brief Functions used when Bad Things happen to the player. **/ #include "AppHdr.h" #include #include #include #include #include #include #include #ifdef UNIX #include #include #include #endif #include "ouch.h" #include "externs.h" #include "options.h" #include "art-enum.h" #include "artefact.h" #include "beam.h" #include "chardump.h" #include "delay.h" #include "describe.h" #include "dgnevent.h" #include "effects.h" #include "end.h" #include "env.h" #include "files.h" #include "fight.h" #include "fineff.h" #include "godabil.h" #include "godpassive.h" #include "hints.h" #include "hiscores.h" #include "invent.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "libutil.h" #include "macro.h" #include "message.h" #include "mgen_data.h" #include "misc.h" #include "mon-death.h" #include "mon-util.h" #include "mon-place.h" #include "mutation.h" #include "notes.h" #include "output.h" #include "player.h" #include "player-stats.h" #include "potion.h" #include "prompt.h" #include "random.h" #include "religion.h" #include "shopping.h" #include "skills2.h" #include "spl-clouds.h" #include "spl-selfench.h" #include "spl-other.h" #include "state.h" #include "strings.h" #include "transform.h" #include "tutorial.h" #include "view.h" #include "shout.h" #include "xom.h" void maybe_melt_player_enchantments(beam_type flavour, int damage) { if (flavour == BEAM_FIRE || flavour == BEAM_LAVA || flavour == BEAM_HELLFIRE || flavour == BEAM_STICKY_FLAME || flavour == BEAM_STEAM) { if (you.duration[DUR_CONDENSATION_SHIELD] > 0) { you.duration[DUR_CONDENSATION_SHIELD] -= damage * BASELINE_DELAY; if (you.duration[DUR_CONDENSATION_SHIELD] <= 0) remove_condensation_shield(); else you.props["melt_shield"] = true; } if (you.mutation[MUT_ICEMAIL]) { if (!you.duration[DUR_ICEMAIL_DEPLETED]) mprf(MSGCH_DURATION, "Your icy envelope dissipates!"); you.duration[DUR_ICEMAIL_DEPLETED] = ICEMAIL_TIME; you.redraw_armour_class = true; } if (you.duration[DUR_ICY_ARMOUR] > 0) { you.duration[DUR_ICY_ARMOUR] -= damage * BASELINE_DELAY; if (you.duration[DUR_ICY_ARMOUR] <= 0) remove_ice_armour(); else you.props[MELT_ARMOUR_KEY] = true; } } } // NOTE: DOES NOT check for hellfire!!! int check_your_resists(int hurted, beam_type flavour, string source, bolt *beam, bool doEffects) { int resist; int original = hurted; dprf("checking resistance: flavour=%d", flavour); string kaux = ""; if (beam) { source = beam->get_source_name(); kaux = beam->name; } if (doEffects) maybe_melt_player_enchantments(flavour, hurted); switch (flavour) { case BEAM_WATER: hurted = resist_adjust_damage(&you, flavour, you.res_water_drowning(), hurted, true); if (!hurted && doEffects) mpr("You shrug off the wave."); else if (hurted > original && doEffects) { mpr("The water douses you terribly!"); xom_is_stimulated(200); } break; case BEAM_STEAM: hurted = resist_adjust_damage(&you, flavour, player_res_steam(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_RESIST); else if (hurted > original && doEffects) { mpr("The steam scalds you terribly!"); xom_is_stimulated(200); } break; case BEAM_FIRE: hurted = resist_adjust_damage(&you, flavour, player_res_fire(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_RESIST); else if (hurted > original && doEffects) { mpr("The fire burns you terribly!"); xom_is_stimulated(200); } break; case BEAM_HELLFIRE: #if TAG_MAJOR_VERSION == 34 if (you.species == SP_DJINNI) { hurted = 0; if (doEffects) mpr("You resist completely."); } #endif // Inconsistency: no penalty for rF-, unlike monsters. That's // probably good, and monsters should be changed. break; case BEAM_COLD: hurted = resist_adjust_damage(&you, flavour, player_res_cold(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_RESIST); else if (hurted > original && doEffects) { mpr("You feel a terrible chill!"); xom_is_stimulated(200); } break; case BEAM_ELECTRICITY: hurted = resist_adjust_damage(&you, flavour, player_res_electricity(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_RESIST); else if (hurted > original && doEffects) { mpr("You are shocked senseless!"); xom_is_stimulated(200); } break; case BEAM_POISON: if (doEffects) { // Ensure that we received a valid beam object before proceeding. // See also melee_attack.cc:_print_resist_messages() which cannot be // used with this beam type (as it does not provide a valid beam). ASSERT(beam); int pois = div_rand_round(beam->damage.num * beam->damage.size, 3); pois = 3 + random_range(pois * 2 / 3, pois * 4 / 3); resist = poison_player(pois, source, kaux) ? 0 : 1; hurted = resist_adjust_damage(&you, flavour, resist, hurted, true); if (resist > 0) canned_msg(MSG_YOU_RESIST); } else { hurted = resist_adjust_damage(&you, flavour, player_res_poison(), hurted, true); } break; case BEAM_POISON_ARROW: resist = player_res_poison(); if (doEffects) { // Ensure that we received a valid beam object before proceeding. // See also melee_attack.cc:_print_resist_messages() which cannot be // used with this beam type (as it does not provide a valid beam). ASSERT(beam); int pois = div_rand_round(beam->damage.num * beam->damage.size, 3); pois = 3 + random_range(pois * 2 / 3, pois * 4 / 3); poison_player((resist ? pois / 2 : pois), source, kaux, true); } hurted = resist_adjust_damage(&you, flavour, resist, hurted); if (hurted < original && doEffects) canned_msg(MSG_YOU_PARTIALLY_RESIST); break; case BEAM_NEG: hurted = resist_adjust_damage(&you, flavour, player_prot_life(), hurted, true); if (doEffects) drain_player(min(75, 35 + original * 2 / 3), true); break; case BEAM_ICE: hurted = resist_adjust_damage(&you, flavour, player_res_cold(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_PARTIALLY_RESIST); else if (hurted > original && doEffects) { mpr("You feel a painful chill!"); xom_is_stimulated(200); } break; case BEAM_LAVA: hurted = resist_adjust_damage(&you, flavour, player_res_fire(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_PARTIALLY_RESIST); else if (hurted > original && doEffects) { mpr("The lava burns you terribly!"); xom_is_stimulated(200); } break; case BEAM_ACID: hurted = resist_adjust_damage(&you, flavour, player_res_acid(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_RESIST); break; case BEAM_MIASMA: if (you.res_rotting()) { if (doEffects) canned_msg(MSG_YOU_RESIST); hurted = 0; } break; case BEAM_HOLY: { // Cleansing flame. const int rhe = you.res_holy_energy(NULL); if (rhe > 0) hurted = 0; else if (rhe == 0) hurted /= 2; else if (rhe < -1) hurted = hurted * 3 / 2; if (hurted == 0 && doEffects) canned_msg(MSG_YOU_RESIST); break; } case BEAM_AIR: { // Airstrike. if (you.res_wind() > 0) hurted = 0; else if (you.flight_mode()) hurted += hurted / 2; break; } case BEAM_GHOSTLY_FLAME: { if (you.holiness() == MH_UNDEAD) { if (doEffects) { you.heal(roll_dice(2, 9)); mpr("You are bolstered by the flame."); } hurted = 0; } else { hurted = resist_adjust_damage(&you, flavour, you.res_negative_energy(), hurted, true); if (hurted < original && doEffects) canned_msg(MSG_YOU_PARTIALLY_RESIST); } } default: break; } // end switch return hurted; } /** * Attempts to apply corrosion to the player and deals acid damage. * * Each full equipment slot gives a chance of applying the corrosion debuff, * and each empty equipment slot increases the amount of acid damage taken. * * @param acid_strength The strength of the acid. * @param death_source The monster index of the acid's source. * @param allow_corrosion Whether to try and apply the corrosion debuff. * @param hurt_msg A message to display when dealing damage. */ void splash_with_acid(int acid_strength, int death_source, bool allow_corrosion, const char* hurt_msg) { int dam = 0; bool do_corrosion = false; const bool wearing_cloak = player_wearing_slot(EQ_CLOAK); for (int slot = EQ_MIN_ARMOUR; slot <= EQ_MAX_ARMOUR; slot++) { const bool cloak_protects = wearing_cloak && coinflip() && slot != EQ_SHIELD && slot != EQ_CLOAK; if (!cloak_protects) { item_def *item = you.slot_item(static_cast(slot)); if (!item && slot != EQ_SHIELD) dam++; if (item && allow_corrosion && x_chance_in_y(acid_strength + 1, 30)) do_corrosion = true; } } if (do_corrosion) corrode_actor(&you); // Covers head, hands and feet. if (player_equip_unrand(UNRAND_LEAR)) dam = !wearing_cloak; // Without fur, clothed people have dam 0 (+2 later), Sp/Tr/Dr/Og ~1 // (randomized), Fe 5. Fur helps only against naked spots. const int fur = player_mutation_level(MUT_SHAGGY_FUR); dam -= fur * dam / 5; // two extra virtual slots so players can't be immune dam += 2; dam = roll_dice(dam, acid_strength); const int post_res_dam = resist_adjust_damage(&you, BEAM_ACID, you.res_acid(), dam); if (post_res_dam > 0) { mpr(hurt_msg ? hurt_msg : "The acid burns!"); if (post_res_dam < dam) canned_msg(MSG_YOU_RESIST); ouch(post_res_dam, death_source, KILLED_BY_ACID); } } /** * Handle side-effects for exposure to element other than damage. * * @param flavour The beam type. * @param strength The strength, which is interpreted as a number of player turns. * @param slow_cold_blooded If True, the beam_type is BEAM_COLD, and the player * is cold-blooded and not cold-resistant, slow the * player 50% of the time. */ void expose_player_to_element(beam_type flavour, int strength, bool slow_cold_blooded) { maybe_melt_player_enchantments(flavour, strength ? strength : 10); qazlal_element_adapt(flavour, strength); if (flavour == BEAM_COLD && slow_cold_blooded && player_mutation_level(MUT_COLD_BLOODED) && you.res_cold() <= 0 && coinflip()) { you.slow_down(0, strength); } if (flavour == BEAM_WATER && you.duration[DUR_LIQUID_FLAMES]) { mprf(MSGCH_WARN, "The flames go out!"); you.duration[DUR_LIQUID_FLAMES] = 0; you.props.erase("sticky_flame_source"); you.props.erase("sticky_flame_aux"); } } static void _lose_level_abilities() { if (you.attribute[ATTR_PERM_FLIGHT] && !you.racial_permanent_flight() && !you.wearing_ego(EQ_ALL_ARMOUR, SPARM_FLYING)) { you.increase_duration(DUR_FLIGHT, 50, 100); you.attribute[ATTR_PERM_FLIGHT] = 0; mprf(MSGCH_WARN, "You feel your flight won't last long."); } } void lose_level(int death_source, const char *aux) { // Because you.experience is unsigned long, if it's going to be // negative, must die straightaway. if (you.experience_level == 1) { ouch(INSTANT_DEATH, death_source, KILLED_BY_DRAINING, aux); // Return in case death was cancelled via wizard mode return; } you.experience_level--; mprf(MSGCH_WARN, "You are now level %d!", you.experience_level); ouch(4, death_source, KILLED_BY_DRAINING, aux); dec_mp(1); calc_hp(); calc_mp(); _lose_level_abilities(); char buf[200]; sprintf(buf, "HP: %d/%d MP: %d/%d", you.hp, you.hp_max, you.magic_points, you.max_magic_points); take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0, buf)); you.redraw_title = true; you.redraw_experience = true; #ifdef USE_TILE_LOCAL // In case of intrinsic ability changes. tiles.layout_statcol(); redraw_screen(); #endif xom_is_stimulated(200); // Kill the player if maxhp <= 0. We can't just move the ouch() call past // dec_max_hp() since it would decrease hp twice, so here's another one. ouch(0, death_source, KILLED_BY_DRAINING, aux); } /** * Drain the player. * * @param power The amount by which to drain the player. * @param announce_full Whether to print messages even when fully resisting * the drain. * @param ignore_protection Whether to ignore the player's rN. * @return Whether draining occurred. */ bool drain_player(int power, bool announce_full, bool ignore_protection) { const int protection = player_prot_life(); if (protection == 3 && !ignore_protection) { if (announce_full) canned_msg(MSG_YOU_RESIST); return false; } if (protection > 0 && !ignore_protection) { canned_msg(MSG_YOU_PARTIALLY_RESIST); power /= (protection * 2); } if (power > 0) { mpr("You feel drained."); xom_is_stimulated(15); you.attribute[ATTR_XP_DRAIN] += power; // Losing skills may affect AC/EV. you.redraw_armour_class = true; you.redraw_evasion = true; dprf("Drained by %d points (%d total)", power, you.attribute[ATTR_XP_DRAIN]); return true; } return false; } static void _xom_checks_damage(kill_method_type death_type, int dam, int death_source) { if (you_worship(GOD_XOM)) { if (death_type == KILLED_BY_TARGETING || death_type == KILLED_BY_BOUNCE || death_type == KILLED_BY_REFLECTION || death_type == KILLED_BY_SELF_AIMED && player_in_a_dangerous_place()) { // Xom thinks the player accidentally hurting him/herself is funny. // Deliberate damage is only amusing if it's dangerous. int amusement = 200 * dam / (dam + you.hp); if (death_type == KILLED_BY_SELF_AIMED) amusement /= 5; xom_is_stimulated(amusement); return; } else if (death_type == KILLED_BY_FALLING_DOWN_STAIRS || death_type == KILLED_BY_FALLING_THROUGH_GATE) { // Xom thinks falling down the stairs is hilarious. xom_is_stimulated(200); return; } else if (death_type == KILLED_BY_DISINT) { // flying chunks... xom_is_stimulated(100); return; } else if (death_type != KILLED_BY_MONSTER && death_type != KILLED_BY_BEAM && death_type != KILLED_BY_DISINT || invalid_monster_index(death_source)) { return; } int amusementvalue = 1; const monster* mons = &menv[death_source]; if (!mons->alive()) return; if (mons->wont_attack()) { // Xom thinks collateral damage is funny. xom_is_stimulated(200 * dam / (dam + you.hp)); return; } int leveldif = mons->get_experience_level() - you.experience_level; if (leveldif == 0) leveldif = 1; // Note that Xom is amused when you are significantly hurt by a // creature of higher level than yourself, as well as by a // creature of lower level than yourself. amusementvalue += leveldif * leveldif * dam; if (!mons->visible_to(&you)) amusementvalue += 8; if (mons->speed < 100/player_movement_speed()) amusementvalue += 7; if (death_type != KILLED_BY_BEAM && you.skill(SK_THROWING) <= (you.experience_level / 4)) { amusementvalue += 2; } else if (you.skill(SK_FIGHTING) <= (you.experience_level / 4)) amusementvalue += 2; if (player_in_a_dangerous_place()) amusementvalue += 2; amusementvalue /= (you.hp > 0) ? you.hp : 1; xom_is_stimulated(amusementvalue); } } static void _yred_mirrors_injury(int dam, int death_source) { if (yred_injury_mirror()) { // Cap damage to what was enough to kill you. Can matter if // Yred saves your life or you have an extra kitty. if (you.hp < 0) dam += you.hp; if (dam <= 0 || invalid_monster_index(death_source)) return; (new mirror_damage_fineff(&menv[death_source], &you, dam))->schedule(); } } static void _maybe_spawn_jellies(int dam, const char* aux, kill_method_type death_type, int death_source) { // We need to exclude acid damage and similar things or this function // will crash later. if (death_source == NON_MONSTER) return; monster_type mon = royal_jelly_ejectable_monster(); // Exclude torment damage. const bool torment = aux && strstr(aux, "torment"); if (you_worship(GOD_JIYVA) && you.piety >= piety_breakpoint(5) && !torment) { int how_many = 0; if (dam >= you.hp_max * 3 / 4) how_many = random2(4) + 2; else if (dam >= you.hp_max / 2) how_many = random2(2) + 2; else if (dam >= you.hp_max / 4) how_many = 1; if (how_many > 0) { int count_created = 0; for (int i = 0; i < how_many; ++i) { int foe = death_source; if (invalid_monster_index(foe)) foe = MHITNOT; mgen_data mg(mon, BEH_FRIENDLY, &you, 2, 0, you.pos(), foe, 0, GOD_JIYVA); if (create_monster(mg)) count_created++; } if (count_created > 0) { mprf("You shudder from the %s and a %s!", death_type == KILLED_BY_MONSTER ? "blow" : "blast", count_created > 1 ? "flood of jellies pours out from you" : "jelly pops out"); } } } } static void _powered_by_pain(int dam) { const int level = player_mutation_level(MUT_POWERED_BY_PAIN); if (you.mutation[MUT_POWERED_BY_PAIN] && (random2(dam) > 2 + 3 * level || dam >= you.hp_max / 2)) { switch (random2(4)) { case 0: case 1: { if (you.magic_points < you.max_magic_points) { mpr("You focus on the pain."); int mp = roll_dice(3, 2 + 3 * level); mpr("You feel your power returning."); inc_mp(mp); break; } break; } case 2: mpr("You focus on the pain."); potion_effect(POT_MIGHT, level * 20); break; case 3: mpr("You focus on the pain."); potion_effect(POT_AGILITY, level * 20); break; } } } static void _maybe_fog(int dam) { const int upper_threshold = you.hp_max / 2; const int lower_threshold = upper_threshold - upper_threshold * (you.piety - piety_breakpoint(2)) / (MAX_PIETY - piety_breakpoint(2)); if (you_worship(GOD_DITHMENOS) && you.piety >= piety_breakpoint(2) && (dam > 0 && you.form == TRAN_SHADOW || dam >= lower_threshold && x_chance_in_y(dam - lower_threshold, upper_threshold - lower_threshold))) { mpr("You emit a cloud of dark smoke."); big_cloud(CLOUD_BLACK_SMOKE, &you, you.pos(), 50, 4 + random2(5)); } } static void _place_player_corpse(bool explode) { if (!in_bounds(you.pos())) return; item_def corpse; if (fill_out_corpse(0, player_mons(), corpse) == MONS_NO_MONSTER) return; if (explode && explode_corpse(corpse, you.pos())) return; int o = get_mitm_slot(); if (o == NON_ITEM) { item_was_destroyed(corpse); return; } corpse.props[MONSTER_HIT_DICE].get_short() = you.experience_level; corpse.props[CORPSE_NAME_KEY] = you.your_name; corpse.props[CORPSE_NAME_TYPE_KEY].get_int64() = 0; corpse.props["ev"].get_int() = player_evasion(static_cast( EV_IGNORE_HELPLESS | EV_IGNORE_PHASESHIFT)); // mostly mutations here. At least there's no need to handle armour. corpse.props["ac"].get_int() = you.armour_class(); mitm[o] = corpse; move_item_to_grid(&o, you.pos(), !you.in_water()); } #if defined(WIZARD) || defined(DEBUG) static void _wizard_restore_life() { if (you.hp_max <= 0) unrot_hp(9999); while (you.hp_max <= 0) you.hp_max_adj_perm++, calc_hp(); if (you.hp <= 0) set_hp(you.hp_max); } #endif void reset_damage_counters() { you.turn_damage = 0; you.damage_source = NON_MONSTER; you.source_damage = 0; } // death_source should be set to NON_MONSTER for non-monsters. {dlb} void ouch(int dam, int death_source, kill_method_type death_type, const char *aux, bool see_source, const char *death_source_name, bool attacker_effects) { ASSERT(!crawl_state.game_is_arena()); if (you.duration[DUR_TIME_STEP]) return; if (you.dead) // ... but eligible for revival return; int drain_amount = 0; if (attacker_effects && dam != INSTANT_DEATH && !invalid_monster_index(death_source) && menv[death_source].has_ench(ENCH_WRETCHED)) { // An abstract boring simulation of reduced stats/etc due to bad muts // reducing the monster's melee damage, spell power, etc. This is // wrong eg. for clouds that would be smaller and with a shorter // duration but do the same damage, etc. int degree = menv[death_source].get_ench(ENCH_WRETCHED).degree; dam = div_rand_round(dam * (10 - min(degree, 5)), 10); } if ((you.duration[DUR_FORTITUDE] || you.species == SP_DEEP_DWARF) && dam != INSTANT_DEATH && death_type != KILLED_BY_POISON) { if (you.species == SP_DEEP_DWARF) { // Deep Dwarves get to shave any hp loss. int shave = 1 + random2(2 + random2(1 + you.experience_level / 3)); dprf("HP shaved: %d.", shave); dam -= shave; } if (you.duration[DUR_FORTITUDE]) dam -= random2(10); if (dam <= 0) { // Rotting and costs may lower hp directly. if (you.hp > 0) return; dam = 0; } } if (dam != INSTANT_DEATH) { if (you.form == TRAN_SHADOW) { drain_amount = (dam - (dam / 2)); dam /= 2; } if (you.petrified()) dam /= 2; else if (you.petrifying()) dam = dam * 10 / 15; } ait_hp_loss hpl(dam, death_type); interrupt_activity(AI_HP_LOSS, &hpl); if (dam > 0 && death_type != KILLED_BY_POISON) you.check_awaken(500); const bool non_death = death_type == KILLED_BY_QUITTING || death_type == KILLED_BY_WINNING || death_type == KILLED_BY_LEAVING; if (you.duration[DUR_DEATHS_DOOR] && death_type != KILLED_BY_LAVA && death_type != KILLED_BY_WATER && !non_death && you.hp_max > 0) { return; } if (dam != INSTANT_DEATH) { if (you.spirit_shield() && death_type != KILLED_BY_POISON && !(aux && strstr(aux, "flay_damage"))) { // round off fairly (important for taking 1 damage at a time) int mp = div_rand_round(dam * you.magic_points, max(you.hp + you.magic_points, 1)); // but don't kill the player with round-off errors mp = max(mp, dam + 1 - you.hp); mp = min(mp, you.magic_points); dam -= mp; dec_mp(mp); if (dam <= 0 && you.hp > 0) return; } if (dam >= you.hp && you.hp_max > 0 && god_protects_from_harm()) { simple_god_message(" protects you from harm!"); return; } you.turn_damage += dam; if (you.damage_source != death_source) { you.damage_source = death_source; you.source_damage = 0; } you.source_damage += dam; dec_hp(dam, true); // Even if we have low HP messages off, we'll still give a // big hit warning (in this case, a hit for half our HPs) -- bwr if (dam > 0 && you.hp_max <= dam * 2) mprf(MSGCH_DANGER, "Ouch! That really hurt!"); if (you.hp > 0 && dam > 0) { if (Options.hp_warning && you.hp <= (you.hp_max * Options.hp_warning) / 100 && (death_type != KILLED_BY_POISON || poison_is_lethal())) { mprf(MSGCH_DANGER, "* * * LOW HITPOINT WARNING * * *"); dungeon_events.fire_event(DET_HP_WARNING); } hints_healing_check(); _xom_checks_damage(death_type, dam, death_source); // for note taking string damage_desc; if (!see_source) damage_desc = make_stringf("something (%d)", dam); else { damage_desc = scorefile_entry(dam, death_source, death_type, aux, true) .death_description(scorefile_entry::DDV_TERSE); } take_note(Note(NOTE_HP_CHANGE, you.hp, you.hp_max, damage_desc.c_str())); _yred_mirrors_injury(dam, death_source); _maybe_spawn_jellies(dam, aux, death_type, death_source); _maybe_fog(dam); _powered_by_pain(dam); if (drain_amount > 0) drain_player(drain_amount, true, true); } if (you.hp > 0) return; } // Is the player being killed by a direct act of Xom? if (crawl_state.is_god_acting() && crawl_state.which_god_acting() == GOD_XOM && crawl_state.other_gods_acting().empty()) { you.escaped_death_cause = death_type; you.escaped_death_aux = aux == NULL ? "" : aux; // Xom should only kill his worshippers if they're under penance // or Xom is bored. if (you_worship(GOD_XOM) && !you.penance[GOD_XOM] && you.gift_timeout > 0) { return; } // Also don't kill wizards testing Xom acts. if ((crawl_state.repeat_cmd == CMD_WIZARD || crawl_state.prev_cmd == CMD_WIZARD) && !you_worship(GOD_XOM)) { return; } // Okay, you *didn't* escape death. you.reset_escaped_death(); // Ensure some minimal information about Xom's involvement. if (aux == NULL || !*aux) { if (death_type != KILLED_BY_XOM) aux = "Xom"; } else if (strstr(aux, "Xom") == NULL) death_type = KILLED_BY_XOM; } // Xom may still try to save your life. else if (xom_saves_your_life(death_type, aux)) return; #if defined(WIZARD) || defined(DEBUG) if (!non_death && crawl_state.disables[DIS_DEATH]) { _wizard_restore_life(); return; } #endif crawl_state.cancel_cmd_all(); // Construct scorefile entry. scorefile_entry se(dam, death_source, death_type, aux, false, death_source_name); #ifdef WIZARD if (!non_death) { if (crawl_state.test || you.wizard) { const string death_desc = se.death_description(scorefile_entry::DDV_VERBOSE); dprf("Damage: %d; Hit points: %d", dam, you.hp); if (crawl_state.test || !yesno("Die?", false, 'n')) { mpr("Thought so."); take_note(Note(NOTE_DEATH, you.hp, you.hp_max, death_desc.c_str()), true); _wizard_restore_life(); return; } } } #endif // WIZARD if (crawl_state.game_is_tutorial()) { crawl_state.need_save = false; if (!non_death) tutorial_death_message(); screen_end_game(""); } // Okay, so you're dead. take_note(Note(NOTE_DEATH, you.hp, you.hp_max, se.death_description(scorefile_entry::DDV_NORMAL).c_str()), true); if (you.lives && !non_death) { mark_milestone("death", lowercase_first(se.long_kill_message()).c_str()); you.deaths++; you.lives--; you.dead = true; stop_delay(true); // You wouldn't want to lose this accomplishment to a crash, would you? // Especially if you manage to trigger one via lua somehow... if (!crawl_state.disables[DIS_SAVE_CHECKPOINTS]) save_game(false); canned_msg(MSG_YOU_DIE); xom_death_message((kill_method_type) se.get_death_type()); more(); _place_player_corpse(death_type == KILLED_BY_DISINT); return; } // The game's over. crawl_state.need_save = false; crawl_state.updating_scores = true; // Prevent bogus notes. activate_notes(false); #ifndef SCORE_WIZARD_CHARACTERS if (!you.wizard) #endif { // Add this highscore to the score file. hiscores_new_entry(se); logfile_new_entry(se); } // Never generate bones files of wizard or tutorial characters -- bwr if (!non_death && !crawl_state.game_is_tutorial() && !you.wizard) save_ghost(); end_game(se); } string morgue_name(string char_name, time_t when_crawl_got_even) { string name = "morgue-" + char_name; string time = make_file_time(when_crawl_got_even); if (!time.empty()) name += "-" + time; return name; } int actor_to_death_source(const actor* agent) { if (agent->is_player()) return NON_MONSTER; else if (agent->is_monster()) return agent->as_monster()->mindex(); else return NON_MONSTER; } int timescale_damage(const actor *act, int damage) { if (damage < 0) damage = 0; // Can we have a uniform player/monster speed system yet? if (act->is_player()) return div_rand_round(damage * you.time_taken, BASELINE_DELAY); else { const monster *mons = act->as_monster(); const int speed = mons->speed > 0? mons->speed : BASELINE_DELAY; return div_rand_round(damage * BASELINE_DELAY, speed); } }