Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1117 lines (960 sloc) 32.5 KB
/**
* @file
* @brief Functions used when Bad Things happen to the player.
**/
#include "AppHdr.h"
#include "ouch.h"
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#ifdef UNIX
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include "artefact.h"
#include "art-enum.h"
#include "beam.h"
#include "chardump.h"
#include "cloud.h"
#include "colour.h"
#include "delay.h"
#include "describe.h"
#include "dgn-event.h"
#include "end.h"
#include "env.h"
#include "fight.h"
#include "files.h"
#include "fineff.h"
#include "god-abil.h"
#include "god-conduct.h"
#include "god-passive.h"
#include "hints.h"
#include "hiscores.h"
#include "invent.h"
#include "item-name.h"
#include "item-prop.h"
#include "items.h"
#include "libutil.h"
#include "macro.h"
#include "message.h"
#include "mgen-data.h"
#include "mon-death.h"
#include "mon-place.h"
#include "mon-util.h"
#include "mutation.h"
#include "nearby-danger.h"
#include "notes.h"
#include "options.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 "shout.h"
#include "skills.h"
#include "spl-clouds.h"
#include "spl-other.h"
#include "spl-selfench.h"
#include "state.h"
#include "stringutil.h"
#include "teleport.h"
#include "transform.h"
#include "tutorial.h"
#include "view.h"
#include "xom.h"
void maybe_melt_player_enchantments(beam_type flavour, int damage)
{
if (flavour == BEAM_FIRE || flavour == BEAM_LAVA
|| flavour == BEAM_STICKY_FLAME || flavour == BEAM_STEAM)
{
if (you.has_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;
}
}
}
int check_your_resists(int hurted, beam_type flavour, string source,
bolt *beam, bool doEffects)
{
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, hurted);
if (!hurted && doEffects)
mpr("You shrug off the wave.");
break;
case BEAM_STEAM:
hurted = resist_adjust_damage(&you, flavour, hurted);
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, hurted);
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_DAMNATION:
break; // sucks to be you (:
case BEAM_COLD:
hurted = resist_adjust_damage(&you, flavour, hurted);
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, hurted);
if (hurted < original && doEffects)
canned_msg(MSG_YOU_RESIST);
break;
case BEAM_POISON:
hurted = resist_adjust_damage(&you, flavour, hurted);
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(pois, source, kaux);
if (player_res_poison() > 0)
canned_msg(MSG_YOU_RESIST);
}
break;
case BEAM_POISON_ARROW:
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);
const int resist = player_res_poison();
poison_player((resist ? pois / 2 : pois), source, kaux, true);
}
hurted = resist_adjust_damage(&you, flavour, hurted);
if (hurted < original && doEffects)
canned_msg(MSG_YOU_PARTIALLY_RESIST);
break;
case BEAM_NEG:
hurted = resist_adjust_damage(&you, flavour, hurted);
if (doEffects)
{
// drain_player handles the messaging here
drain_player(min(75, 35 + original * 2 / 3), true);
}
break;
case BEAM_ICE:
hurted = resist_adjust_damage(&you, flavour, hurted);
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, hurted);
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, hurted);
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:
{
hurted = resist_adjust_damage(&you, flavour, hurted);
if (hurted < original && doEffects)
canned_msg(MSG_YOU_RESIST);
else if (hurted > original && doEffects)
{
mpr("You writhe in agony!");
xom_is_stimulated(200);
}
break;
}
case BEAM_AIR:
{
// Airstrike.
if (you.airborne())
hurted += hurted / 2;
break;
}
default:
break;
} // end switch
return hurted;
}
/**
* Handle side-effects for exposure to element other than damage.
* Historically this handled item destruction, and melting meltable enchantments. Now it takes care of 3 things:
* - triggering qazlal's elemental adaptations
* - slowing cold-blooded players (draconians, hydra form)
* - putting out fires
* This function should be called exactly once any time a player is exposed to the
* following elements/beam types: cold, fire, elec, water, steam, lava, BEAM_FRAG. For the sake of Qazlal's
* elemental adaptation, it should also be called (exactly once) with BEAM_MISSILE when
* receiving physical damage. Hybrid damage (brands) should call it twice with appropriate
* flavours.
*
* @param flavour The beam type.
* @param strength The strength of the attack. Used in different ways for different side-effects.
* For qazlal_elemental_adapt: (i) it is used for the probability of triggering, and (ii) the resulting length of the effect.
* @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)
{
dprf("expose_player_to_element, strength %i, flavor %i, slow_cold_blooded is %i", strength, flavour, slow_cold_blooded);
qazlal_element_adapt(flavour, strength);
if (flavour == BEAM_COLD && slow_cold_blooded
&& you.get_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()
{
// Because you.experience is unsigned long, if it's going to be
// negative, must die straightaway.
if (you.experience_level == 1)
{
ouch(INSTANT_DEATH, KILLED_BY_DRAINING);
// Return in case death was cancelled via wizard mode
return;
}
you.experience_level--;
mprf(MSGCH_WARN,
"You are now level %d!", you.experience_level);
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, KILLED_BY_DRAINING);
}
/**
* 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)
{
if (crawl_state.disables[DIS_AFFLICTIONS])
return false;
const int protection = ignore_protection ? 0 : player_prot_life();
if (protection == 3)
{
if (announce_full)
canned_msg(MSG_YOU_RESIST);
return false;
}
if (protection > 0)
{
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, mid_t 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
|| !monster_by_mid(death_source))
{
return;
}
int amusementvalue = 1;
const monster* mons = monster_by_mid(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 (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, mid_t 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;
monster* mons = monster_by_mid(death_source);
if (dam <= 0 || !mons)
return;
mirror_damage_fineff::schedule(mons, &you, dam);
}
}
static void _maybe_ru_retribution(int dam, mid_t death_source)
{
if (will_ru_retaliate())
{
// Cap damage to what was enough to kill you. Can matter if
// you have an extra kitty.
if (you.hp < 0)
dam += you.hp;
monster* mons = monster_by_mid(death_source);
if (dam <= 0 || !mons)
return;
ru_retribution_fineff::schedule(mons, &you, dam);
}
}
static void _maybe_spawn_monsters(int dam, kill_method_type death_type,
mid_t death_source)
{
monster* damager = monster_by_mid(death_source);
// We need to exclude acid damage and similar things or this function
// will crash later.
if (!damager)
return;
monster_type mon;
int how_many = 0;
if (have_passive(passive_t::spawn_slimes_on_hit))
{
mon = royal_jelly_ejectable_monster();
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;
}
else if (you_worship(GOD_XOM)
&& dam >= you.hp_max / 4
&& x_chance_in_y(dam, 3 * you.hp_max))
{
mon = MONS_BUTTERFLY;
how_many = 2 + random2(5);
}
if (how_many > 0)
{
int count_created = 0;
for (int i = 0; i < how_many; ++i)
{
mgen_data mg(mon, BEH_FRIENDLY, you.pos(), damager->mindex());
mg.set_summoned(&you, 2, 0, you.religion);
if (create_monster(mg))
count_created++;
}
if (count_created > 0)
{
if (mon == MONS_BUTTERFLY)
{
mprf(MSGCH_GOD, "A shower of butterflies erupts from you!");
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "butterfly on damage"), true);
}
else
{
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 = you.get_mutation_level(MUT_POWERED_BY_PAIN);
if (level > 0
&& (random2(dam) > 4 + div_rand_round(you.experience_level, 4)
|| 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);
canned_msg(MSG_GAIN_MAGIC);
inc_mp(mp);
break;
}
break;
}
case 2:
mpr("You focus on the pain.");
potionlike_effect(POT_MIGHT, level * 20);
break;
case 3:
mpr("You focus on the pain.");
potionlike_effect(POT_AGILITY, level * 20);
break;
}
}
}
static void _maybe_fog(int dam)
{
const int minpiety = have_passive(passive_t::hit_smoke)
? piety_breakpoint(rank_for_passive(passive_t::hit_smoke) - 1)
: piety_breakpoint(2); // Xom
const int upper_threshold = you.hp_max / 2;
const int lower_threshold = upper_threshold
- upper_threshold
* (you.piety - minpiety)
/ (MAX_PIETY - minpiety);
if (have_passive(passive_t::hit_smoke)
&& (dam > 0 && you.form == transformation::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));
}
else if (you_worship(GOD_XOM) && x_chance_in_y(dam, 30 * upper_threshold))
{
mprf(MSGCH_GOD, "You emit a cloud of colourful smoke!");
big_cloud(CLOUD_XOM_TRAIL, &you, you.pos(), 50, 4 + random2(5), -1);
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "smoke on damage"), true);
}
}
static void _deteriorate(int dam)
{
if (x_chance_in_y(you.get_mutation_level(MUT_DETERIORATION), 4)
&& dam > you.hp_max / 10)
{
mprf(MSGCH_WARN, "Your body deteriorates!");
lose_stat(STAT_RANDOM, 1);
}
}
/**
* Maybe corrode the player after taking damage if they're wearing *Corrode.
**/
static void _maybe_corrode()
{
int corrosion_sources = you.scan_artefacts(ARTP_CORRODE);
int degree = binomial(corrosion_sources, 3);
if (degree > 0)
you.corrode_equipment("Your corrosive artefact", degree);
}
/**
* Maybe slow the player after taking damage if they're wearing *Slow.
**/
static void _maybe_slow()
{
int slow_sources = you.scan_artefacts(ARTP_SLOW);
if (x_chance_in_y(slow_sources, 100))
slow_player(10 + random2(5));
}
static void _place_player_corpse(bool explode)
{
if (!in_bounds(you.pos()))
return;
monster dummy;
dummy.type = player_mons(false);
define_monster(dummy); // assumes player_mons is not a zombie
dummy.position = you.pos();
dummy.props["always_corpse"] = true;
dummy.mname = you.your_name;
dummy.set_hit_dice(you.experience_level);
if (explode)
dummy.flags &= MF_EXPLODE_KILL;
if (you.form != transformation::none)
mpr("Your shape twists and changes as you die.");
place_monster_corpse(dummy, false);
}
#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
static int _apply_extra_harm(int dam, mid_t source)
{
monster* damager = monster_by_mid(source);
// Don't check for monster amulet if there source isn't a monster
if (damager && damager->extra_harm())
return dam * 13 / 10; // +30% damage when the opponent has harm
else if (you.extra_harm())
return dam * 6 / 5; // +20% damage when you have harm
return dam;
}
void reset_damage_counters()
{
you.turn_damage = 0;
you.damage_source = NON_MONSTER;
you.source_damage = 0;
}
bool can_shave_damage()
{
return you.species == SP_DEEP_DWARF;
}
int do_shave_damage(int dam)
{
if (!can_shave_damage())
return dam;
// 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;
return dam;
}
// Determine what's threatening for purposes of sacrifice drink and reading.
// the statuses are guaranteed not to happen if the incoming damage is less
// than 4% max hp. Otherwise, they scale up with damage taken and with lower
// health, becoming certain at 20% max health damage or <30% max health
// current hp.
static bool _is_damage_threatening (int damage_fraction_of_hp)
{
int hp_fraction = you.hp * 100 / you.hp_max;
return damage_fraction_of_hp > 5
&& hp_fraction <= 85
&& (damage_fraction_of_hp + random2(20) >= 20
|| random2(100) < hp_fraction);
}
/** Hurt the player. Isn't it fun?
*
* @param dam How much damage -- may be INSTANT_DEATH.
* @param death_type how did you get hurt?
* @param source who could do such a thing?
* @param aux what did they do it with?
* @param see_source whether the attacker was visible to you
* @param death_source_name the attacker's name if it is already dead.
*/
void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux,
bool see_source, const char *death_source_name)
{
ASSERT(!crawl_state.game_is_arena());
if (you.duration[DUR_TIME_STEP])
return;
if (you.pending_revival)
return;
int drain_amount = 0;
// Multiply damage if amulet of harm is in play
if (dam != INSTANT_DEATH)
dam = _apply_extra_harm(dam, source);
if (can_shave_damage() && dam != INSTANT_DEATH
&& death_type != KILLED_BY_POISON)
{
dam = max(0, do_shave_damage(dam));
}
if (dam != INSTANT_DEATH)
{
if (you.form == transformation::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(activity_interrupt::hp_loss, &hpl);
// Don't wake the player with fatal or poison damage.
if (dam > 0 && dam < you.hp && 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;
// certain effects (e.g. drowned souls) use KILLED_BY_WATER for flavour
// reasons (morgue messages?), with regrettable consequences if we don't
// double-check.
const bool env_death = source == MID_NOBODY
&& (death_type == KILLED_BY_LAVA
|| death_type == KILLED_BY_WATER);
// death's door protects against everything but falling into water/lava,
// excessive rot, leaving the dungeon, or quitting.
if (you.duration[DUR_DEATHS_DOOR] && !env_death && !non_death
&& you.hp_max > 0)
{
return;
}
if (dam > 0 && death_type != KILLED_BY_POISON)
{
int damage_fraction_of_hp = dam * 100 / you.hp_max;
// Check _is_damage_threatening separately for read and drink so they
// don't always trigger in unison when you have both.
if (you.get_mutation_level(MUT_NO_READ))
{
if (_is_damage_threatening(damage_fraction_of_hp))
{
if (!you.duration[DUR_NO_SCROLLS])
mpr("You feel threatened and lose the ability to read scrolls!");
you.increase_duration(DUR_NO_SCROLLS, 1 + random2(dam), 30);
}
}
if (you.get_mutation_level(MUT_NO_DRINK))
{
if (_is_damage_threatening(damage_fraction_of_hp))
{
if (!you.duration[DUR_NO_POTIONS])
mpr("You feel threatened and lose the ability to drink potions!");
you.increase_duration(DUR_NO_POTIONS, 1 + random2(dam), 30);
}
}
}
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);
// Wake players who took fatal damage exactly equal to current HP,
// but had it reduced below fatal threshhold by spirit shield.
if (dam < you.hp)
you.check_awaken(500);
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!");
// Ensure divine intervention wakes sleeping players. Necessary
// because we otherwise don't wake players who take fatal damage.
you.check_awaken(500);
return;
}
you.turn_damage += dam;
if (you.damage_source != source)
{
you.damage_source = 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()))
{
flash_view_delay(UA_HP, RED, 50);
mprf(MSGCH_DANGER, "* * * LOW HITPOINT WARNING * * *");
dungeon_events.fire_event(DET_HP_WARNING);
}
hints_healing_check();
_xom_checks_damage(death_type, dam, source);
// for note taking
string damage_desc;
if (!see_source)
damage_desc = make_stringf("something (%d)", dam);
else
{
damage_desc = scorefile_entry(dam, 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()));
_deteriorate(dam);
_yred_mirrors_injury(dam, source);
_maybe_ru_retribution(dam, source);
_maybe_spawn_monsters(dam, death_type, source);
_maybe_fog(dam);
_powered_by_pain(dam);
if (sanguine_armour_valid())
activate_sanguine_armour();
if (death_type != KILLED_BY_POISON)
{
_maybe_corrode();
_maybe_slow();
}
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 == nullptr ? "" : 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 == nullptr || !*aux)
{
if (death_type != KILLED_BY_XOM)
aux = "Xom";
}
else if (strstr(aux, "Xom") == nullptr)
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, source, death_type, aux, false,
death_source_name);
#ifdef WIZARD
if (!non_death)
{
if (crawl_state.test || you.wizard || you.suppress_wizard || (you.explore && !you.lives))
{
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.pending_revival = 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;
}
// Prevent bogus notes.
activate_notes(false);
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)
{
return agent ? agent->mindex() : 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);
}
}
You can’t perform that action at this time.