/* * File: ouch.cc * Summary: Functions used when Bad Things happen to the player. * Written by: Linley Henzell */ #include "AppHdr.h" #include #include #include #include #include #include #ifdef TARGET_OS_DOS #include #endif #ifdef UNIX #include #include #include #endif #include "ouch.h" #ifdef TARGET_COMPILER_MINGW #include #endif #include "externs.h" #include "options.h" #include "artefact.h" #include "beam.h" #include "chardump.h" #include "delay.h" #include "effects.h" #include "env.h" #include "files.h" #include "fight.h" #include "godabil.h" #include "hiscores.h" #include "invent.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "macro.h" #include "message.h" #include "misc.h" #include "mon-util.h" #include "mon-place.h" #include "mgen_data.h" #include "mon-stuff.h" #include "notes.h" #include "output.h" #include "player.h" #include "random.h" #include "religion.h" #include "shopping.h" #include "skills2.h" #include "spells4.h" #include "state.h" #include "stuff.h" #include "tutorial.h" #include "view.h" #include "shout.h" #include "xom.h" static void end_game( scorefile_entry &se ); static void _item_corrode(int slot); // NOTE: DOES NOT check for hellfire!!! int check_your_resists(int hurted, beam_type flavour) { int resist; int original = hurted; dprf("checking resistance: flavour=%d", flavour ); if (flavour == BEAM_FIRE || flavour == BEAM_LAVA || flavour == BEAM_HELLFIRE || flavour == BEAM_FRAG) { if (you.duration[DUR_CONDENSATION_SHIELD] > 0) remove_condensation_shield(); } switch (flavour) { case BEAM_WATER: hurted = resist_adjust_damage(&you, flavour, you.res_water_drowning(), hurted, true); if (!hurted) mpr("You shrug off the wave."); break; case BEAM_STEAM: hurted = resist_adjust_damage(&you, flavour, player_res_steam(), hurted, true); if (hurted < original) canned_msg(MSG_YOU_RESIST); else if (hurted > original) { 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) canned_msg(MSG_YOU_RESIST); else if (hurted > original) { mpr("The fire burns you terribly!"); xom_is_stimulated(200); } break; case BEAM_COLD: hurted = resist_adjust_damage(&you, flavour, player_res_cold(), hurted, true); if (hurted < original) canned_msg(MSG_YOU_RESIST); else if (hurted > original) { 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) canned_msg(MSG_YOU_RESIST); break; case BEAM_POISON: resist = player_res_poison(); if (resist <= 0) poison_player( coinflip() ? 2 : 1 ); hurted = resist_adjust_damage(&you, flavour, resist, hurted, true); if (resist > 0) canned_msg(MSG_YOU_RESIST); break; case BEAM_POISON_ARROW: // [dshaligram] NOT importing uber-poison arrow from 4.1. Giving no // bonus to poison resistant players seems strange and unnecessarily // arbitrary. resist = player_res_poison(); if (!resist) poison_player( 4 + random2(3), true ); else if (!you.is_undead) poison_player( 2 + random2(3), true ); hurted = resist_adjust_damage(&you, flavour, resist, hurted); if (hurted < original) canned_msg(MSG_YOU_PARTIALLY_RESIST); break; case BEAM_NEG: resist = player_prot_life(); // TSO's protection. if (you.religion == GOD_SHINING_ONE && you.piety > resist * 50) { int unhurted = std::min(hurted, (you.piety * hurted) / 150); if (unhurted > 0) hurted -= unhurted; } else if (resist > 0) hurted -= (resist * hurted) / 3; drain_exp(); break; case BEAM_ICE: hurted = resist_adjust_damage(&you, flavour, player_res_cold(), hurted, true); if (hurted < original) canned_msg(MSG_YOU_PARTIALLY_RESIST); else if (hurted > original) { 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) canned_msg(MSG_YOU_PARTIALLY_RESIST); else if (hurted > original) { mpr("The lava burns you terribly!"); xom_is_stimulated(200); } break; case BEAM_ACID: if (player_res_acid()) { canned_msg(MSG_YOU_RESIST); hurted = hurted * player_acid_resist_factor() / 100; } break; case BEAM_MIASMA: if (you.res_rotting()) { 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) canned_msg(MSG_YOU_RESIST); break; } default: break; } // end switch return (hurted); } void splash_with_acid(int acid_strength, bool corrode_items) { int dam = 0; const bool wearing_cloak = player_wearing_slot(EQ_CLOAK); for (int slot = EQ_CLOAK; slot <= EQ_BODY_ARMOUR; slot++) { if (!player_wearing_slot(slot)) { if (!wearing_cloak || coinflip()) dam += roll_dice(1, acid_strength); } else if (corrode_items && x_chance_in_y(acid_strength + 1, 20)) _item_corrode(you.equip[slot]); } if (dam > 0) { const int post_res_dam = dam * player_acid_resist_factor() / 100; if (post_res_dam > 0) { mpr("The acid burns!"); if (post_res_dam < dam) canned_msg(MSG_YOU_RESIST); ouch(post_res_dam, NON_MONSTER, KILLED_BY_ACID); } } } void weapon_acid(int acid_strength) { int hand_thing = you.equip[EQ_WEAPON]; if (hand_thing == -1 && you_tran_can_wear(EQ_GLOVES, true)) hand_thing = you.equip[EQ_GLOVES]; if (hand_thing == -1) { msg::stream << "Your " << your_hand(true) << " burn!" << std::endl; ouch(roll_dice(1, acid_strength), NON_MONSTER, KILLED_BY_ACID); } else if (x_chance_in_y(acid_strength + 1, 20)) _item_corrode(hand_thing); } void _item_corrode(int slot) { bool it_resists = false; bool suppress_msg = false; item_def& item = you.inv[slot]; // Artefacts don't corrode. if (is_artefact(item)) return; // Anti-corrosion items protect against 90% of corrosion. if (wearing_amulet(AMU_RESIST_CORROSION) && !one_chance_in(10)) { dprf("Amulet protects."); return; } if (you.religion == GOD_JIYVA && x_chance_in_y(you.piety, MAX_PIETY)) return; int how_rusty = ((item.base_type == OBJ_WEAPONS) ? item.plus2 : item.plus); // Already very rusty. if (how_rusty < -5) return; // determine possibility of resistance by object type {dlb}: switch (item.base_type) { case OBJ_ARMOUR: if ((item.sub_type == ARM_CRYSTAL_PLATE_MAIL || get_equip_race(item) == ISFLAG_DWARVEN) && !one_chance_in(5)) { it_resists = true; suppress_msg = false; } break; case OBJ_WEAPONS: case OBJ_MISSILES: if (get_equip_race(item) == ISFLAG_DWARVEN && !one_chance_in(5)) { it_resists = true; suppress_msg = false; } break; default: // Other items can't corrode. return; } // determine chance of corrosion {dlb}: if (!it_resists) { const int chance = abs(how_rusty); // The embedded equation may look funny, but it actually works well // to generate a pretty probability ramp {6%, 18%, 34%, 58%, 98%} // for values [0,4] which closely matches the original, ugly switch. // {dlb} if (chance >= 0 && chance <= 4) it_resists = x_chance_in_y(2 + (4 << chance) + chance * 8, 100); else it_resists = true; // If the checks get this far, you should hear about it. {dlb} suppress_msg = false; } // handle message output and item damage {dlb}: if (!suppress_msg) { if (it_resists) mprf("%s resists.", item.name(DESC_CAP_YOUR).c_str()); else mprf("The acid corrodes %s!", item.name(DESC_NOCAP_YOUR).c_str()); } if (!it_resists) { how_rusty--; xom_is_stimulated(64); if (item.base_type == OBJ_WEAPONS) item.plus2 = how_rusty; else item.plus = how_rusty; if (item.base_type == OBJ_ARMOUR) you.redraw_armour_class = true; if (you.equip[EQ_WEAPON] == slot) you.wield_change = true; } } // Helper function for the expose functions below. // This currently works because elements only target a single type each. static int _get_target_class(beam_type flavour) { int target_class = OBJ_UNASSIGNED; switch (flavour) { case BEAM_FIRE: case BEAM_LAVA: case BEAM_NAPALM: case BEAM_HELLFIRE: target_class = OBJ_SCROLLS; break; case BEAM_COLD: case BEAM_FRAG: target_class = OBJ_POTIONS; break; case BEAM_SPORE: case BEAM_STEAL_FOOD: target_class = OBJ_FOOD; break; default: break; } return (target_class); } // XXX: These expose functions could use being reworked into a real system... // the usage and implementation is currently very hacky. // Handles the destruction of inventory items from the elements. static bool _expose_invent_to_element(beam_type flavour, int strength) { int num_dest = 0; const int target_class = _get_target_class( flavour ); if (target_class == OBJ_UNASSIGNED) return (false); // Fedhas worshipers are exempt from the food destruction effect // of spores. if(flavour == BEAM_SPORE && you.religion == GOD_FEDHAS) { return false; } // Currently we test against each stack (and item in the stack) // independently at strength%... perhaps we don't want that either // because it makes the system very fair and removes the protection // factor of junk (which might be more desirable for game play). for (int i = 0; i < ENDOFPACK; ++i) { if (!you.inv[i].is_valid()) continue; if (you.inv[i].base_type == target_class || target_class == OBJ_FOOD && you.inv[i].base_type == OBJ_CORPSES) { // Conservation doesn't help against harpies stealing food. if (flavour != BEAM_STEAL_FOOD && player_item_conserve() && !one_chance_in(10)) { continue; } // These stack with conservation; they're supposed to be good. if (target_class == OBJ_SCROLLS && you.mutation[MUT_CONSERVE_SCROLLS] && !one_chance_in(10)) { continue; } if (target_class == OBJ_POTIONS && you.mutation[MUT_CONSERVE_POTIONS] && !one_chance_in(10)) { continue; } // Loop through all items in the stack. for (int j = 0; j < you.inv[i].quantity; ++j) { if (x_chance_in_y(strength, 100)) { num_dest++; if (i == you.equip[EQ_WEAPON]) you.wield_change = true; if (dec_inv_item_quantity(i, 1)) break; else if (is_blood_potion(you.inv[i])) remove_oldest_blood_potion(you.inv[i]); } } } } if (!num_dest) return (false); // Message handled elsewhere. if (flavour == BEAM_STEAL_FOOD) return (true); switch (target_class) { case OBJ_SCROLLS: mprf("%s you are carrying %s fire!", (num_dest > 1) ? "Some of the scrolls" : "A scroll", (num_dest > 1) ? "catch" : "catches" ); break; case OBJ_POTIONS: mprf("%s you are carrying %s and %s!", (num_dest > 1) ? "Some of the potions" : "A potion", (num_dest > 1) ? "freeze" : "freezes", (num_dest > 1) ? "shatter" : "shatters" ); break; case OBJ_FOOD: mpr("Some of your food is covered with spores!"); break; default: mprf("%s you are carrying %s destroyed!", (num_dest > 1) ? "Some items" : "An item", (num_dest > 1) ? "were" : "was" ); break; } xom_is_stimulated((num_dest > 1) ? 32 : 16); return (true); } bool expose_items_to_element(beam_type flavour, const coord_def& where, int strength) { int num_dest = 0; const int target_class = _get_target_class(flavour); if (target_class == OBJ_UNASSIGNED) return (false); // Beams fly *over* water and lava. if (grd(where) == DNGN_LAVA || grd(where) == DNGN_DEEP_WATER) return (false); for (stack_iterator si(where); si; ++si) { if (!si->is_valid()) continue; if (si->base_type == target_class || target_class == OBJ_FOOD && si->base_type == OBJ_CORPSES) { if (x_chance_in_y(strength, 100)) { num_dest++; if (!dec_mitm_item_quantity(si->index(), 1) && is_blood_potion(*si)) { remove_oldest_blood_potion(*si); } } } } if (!num_dest) return (false); if (flavour == BEAM_STEAL_FOOD) return (true); if (you.see_cell(where)) { switch (target_class) { case OBJ_SCROLLS: mprf("You see %s of smoke.", (num_dest > 1) ? "some puffs" : "a puff"); break; case OBJ_POTIONS: mprf("You see %s shatter.", (num_dest > 1) ? "some glass" : "glass"); break; case OBJ_FOOD: mprf("You see %s of spores.", (num_dest > 1) ? "some clouds" : "a cloud"); break; default: mprf("%s on the floor %s destroyed!", (num_dest > 1) ? "Some items" : "An item", (num_dest > 1) ? "were" : "was" ); break; } } xom_is_stimulated((num_dest > 1) ? 32 : 16); return (true); } // Handle side-effects for exposure to element other than damage. This // function exists because some code calculates its own damage instead // of using check_your_resists() and we want to isolate all the special // code they keep having to do... namely condensation shield checks, // you really can't expect this function to even be called for much // else. // // This function now calls _expose_invent_to_element() if strength > 0. // // XXX: This function is far from perfect and a work in progress. bool expose_player_to_element(beam_type flavour, int strength) { // Note that BEAM_TELEPORT is sent here when the player // blinks or teleports. if (flavour == BEAM_FIRE || flavour == BEAM_LAVA || flavour == BEAM_HELLFIRE || flavour == BEAM_FRAG || flavour == BEAM_TELEPORT || flavour == BEAM_NAPALM || flavour == BEAM_STEAM) { if (you.duration[DUR_CONDENSATION_SHIELD] > 0) remove_condensation_shield(); if (you.mutation[MUT_ICEMAIL]) { mpr("Your icy envelope dissipates!", MSGCH_DURATION); you.duration[DUR_ICEMAIL_DEPLETED] = ICEMAIL_TIME; you.redraw_armour_class = true; } } if (strength <= 0) return (false); return (_expose_invent_to_element( flavour, strength )); } 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, NON_MONSTER, KILLED_BY_DRAINING); // Return in case death was canceled via wizard mode return; } you.experience = exp_needed( you.experience_level + 1 ) - 1; you.experience_level--; mprf(MSGCH_WARN, "You are now level %d!", you.experience_level); // Constant value to avoid grape jelly trick... see level_change() for // where these HPs and MPs are given back. -- bwr ouch(4, NON_MONSTER, KILLED_BY_DRAINING); dec_max_hp(4); dec_mp(1); dec_max_mp(1); calc_hp(); calc_mp(); 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)); redraw_skill(you.your_name, player_title()); you.redraw_experience = true; xom_is_stimulated(255); } bool drain_exp(bool announce_full) { const int protection = player_prot_life(); if (protection == 3) { if (announce_full) canned_msg(MSG_YOU_RESIST); return (false); } if (you.experience == 0) { ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_DRAINING); // Return in case death was escaped via wizard mode. return (true); } if (you.experience_level == 1) { you.experience = 0; return (true); } unsigned long total_exp = exp_needed(you.experience_level + 2) - exp_needed(you.experience_level + 1); unsigned long exp_drained = (total_exp * (10 + random2(11))) / 100; unsigned long pool_drained = std::min(exp_drained, (unsigned long)you.exp_available); // TSO's protection. if (you.religion == GOD_SHINING_ONE && you.piety > protection * 50) { unsigned long undrained = std::min(exp_drained, (you.piety * exp_drained) / 150); unsigned long pool_undrained = std::min(pool_drained, (you.piety * pool_drained) / 150); if (undrained > 0 || pool_undrained > 0) { simple_god_message(" protects your life force!"); if (undrained > 0) exp_drained -= undrained; if (pool_undrained > 0) pool_drained -= pool_undrained; } } else if (protection > 0) { canned_msg(MSG_YOU_PARTIALLY_RESIST); exp_drained -= (protection * exp_drained) / 3; pool_drained -= (protection * pool_drained) / 3; } if (exp_drained > 0) { mpr("You feel drained."); xom_is_stimulated(20); you.experience -= exp_drained; you.exp_available -= pool_drained; you.exp_available = std::max(0, you.exp_available); dprf("You lose %ld experience points, %ld from pool.", exp_drained, pool_drained); you.redraw_experience = true; if (you.experience < exp_needed(you.experience_level + 1)) lose_level(); return (true); } return (false); } static void _xom_checks_damage(kill_method_type death_type, int dam, int death_source) { if (you.religion == GOD_XOM) { if (death_type == KILLED_BY_TARGETTING || 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 = 255 * 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(255); return; } else if (death_type == KILLED_BY_DISINT) { // flying chunks... xom_is_stimulated(128); 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 monsters *monster = &menv[death_source]; if (!monster->alive()) return; if (monster->wont_attack()) { // Xom thinks collateral damage is funny. xom_is_stimulated(255 * dam / (dam + you.hp)); return; } int leveldif = monster->hit_dice - 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 (!monster->visible_to(&you)) amusementvalue += 10; if (monster->speed < 100/player_movement_speed()) amusementvalue += 8; if (death_type != KILLED_BY_BEAM && you.skills[SK_THROWING] <= (you.experience_level / 4)) { amusementvalue += 2; } else if (you.skills[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()) { if (dam <= 0 || invalid_monster_index(death_source)) return; monsters *mon = &menv[death_source]; if (!mon->alive()) return; simple_god_message(" mirrors your injury!"); #ifndef USE_TILE flash_monster_colour(mon, RED, 200); #endif mon->hurt(&you, dam); if (mon->alive()) print_wounds(mon); lose_piety(integer_sqrt(dam)); } } static void _passive_freeze(kill_method_type death_type, const char *aux, int death_source) { const char *ptr = aux ? strstr(aux, "torment") : NULL; if (you.mutation[MUT_PASSIVE_FREEZE] && death_type == KILLED_BY_MONSTER && ptr == NULL) { if (invalid_monster_index(death_source)) return; monsters *mon = &menv[death_source]; if (!mon->alive()) return; bolt beam; beam.flavour = BEAM_COLD; beam.thrower = KILL_YOU; const int orig_hurted = roll_dice(1, 11); int hurted = mons_adjust_flavoured(mon, beam, orig_hurted); if (!hurted) return; simple_monster_message(mon, " is very cold."); #ifndef USE_TILE flash_monster_colour(mon, LIGHTBLUE, 200); #endif mon->hurt(&you, hurted); if (mon->alive()) { mon->expose_to_element(BEAM_COLD, orig_hurted); print_wounds(mon); const int cold_res = mon->res_cold(); if (cold_res <= 0) { const int stun = (1 - cold_res) * random2(7); mon->speed_increment -= stun; } } } } 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 char *ptr = strstr(aux, "torment"); if (you.religion == GOD_JIYVA && you.piety > 160 && ptr == NULL) { 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) { if (x_chance_in_y(how_many, 8) && !lose_stat(STAT_STRENGTH, 1, true, "spawning slimes")) { canned_msg(MSG_NOTHING_HAPPENS); return; } int count_created = 0; for (int i = 0; i < how_many; ++i) { mgen_data mg(mon, BEH_STRICT_NEUTRAL, &you, 0, 0, you.pos(), MHITNOT, 0, GOD_JIYVA); if (create_monster(mg) != -1) 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"); } } } } #ifdef WIZARD static void _wizard_restore_life() { if (you.hp <= 0) set_hp(you.hp_max, false); if (you.strength <= 0) { you.strength = you.max_strength; you.redraw_strength = true; } if (you.dex <= 0) { you.dex = you.max_dex; you.redraw_dexterity = true; you.redraw_evasion = true; } if (you.intel <= 0) { you.intel = you.max_intel; you.redraw_intelligence = true; } } #endif // 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) { ASSERT(!crawl_state.arena); if (you.duration[DUR_TIME_STEP]) return; if (dam != INSTANT_DEATH && 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 (dam <= 0) return; } ait_hp_loss hpl(dam, death_type); interrupt_activity(AI_HP_LOSS, &hpl); if (dam > 0) you.check_awaken(500); if (you.duration[DUR_DEATHS_DOOR] && death_type != KILLED_BY_LAVA && death_type != KILLED_BY_WATER) { return; } if (dam != INSTANT_DEATH) { if (player_spirit_shield() && death_type != KILLED_BY_POISON) { if (dam <= you.magic_points) { dec_mp(dam); return; } dam -= you.magic_points; dec_mp(you.magic_points); } if (dam >= you.hp) { if (harm_protection_type hpt = god_protects_from_harm(you.religion)) { simple_god_message(" protects you from harm!"); if (you.duration[DUR_PRAYER] && hpt == HPT_RELIABLE_PRAYING_PLUS_ANYTIME) { lose_piety(21 + random2(20)); } return; } } 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) mpr( "Ouch! That really hurt!", MSGCH_DANGER ); if (you.hp > 0) { if (Options.hp_warning && you.hp <= (you.hp_max * Options.hp_warning) / 100) { mpr( "* * * LOW HITPOINT WARNING * * *", MSGCH_DANGER ); } _xom_checks_damage(death_type, dam, death_source); // for note taking std::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); _passive_freeze(death_type, aux, death_source); _maybe_spawn_jellies(dam, aux, death_type, death_source); return; } // else hp <= 0 } // 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().size() == 0) { 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.religion == 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.religion != GOD_XOM) { return; } // Okay, you *didn't* escape death. you.reset_escaped_death(); // Ensure some minimal information about Xom's involvement. if (aux == NULL || strlen(aux) == 0) { 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(dam, death_source, death_type, aux, see_source)) { return; } #if WIZARD || DEBUG if (you.never_die) { if (you.hp <= 0) you.hp = you.hp_max; if (you.strength <= 0) you.strength = you.max_strength; if (you.dex <= 0) you.dex = you.max_dex; if (you.intel <= 0) you.intel = you.max_intel; return; } #endif // Construct scorefile entry. scorefile_entry se(dam, death_source, death_type, aux); #ifdef WIZARD if (death_type != KILLED_BY_QUITTING && death_type != KILLED_BY_WINNING && death_type != KILLED_BY_LEAVING) { if (crawl_state.test || you.wizard) { const std::string death_desc = se.death_description(scorefile_entry::DDV_VERBOSE); #ifdef USE_OPTIONAL_WIZARD_DEATH dprf("Damage: %d; Hit points: %d", dam, you.hp); if (crawl_state.test || !yesno("Die?", false, 'n')) { take_note(Note( NOTE_DEATH, you.hp, you.hp_max, death_desc.c_str()), true); _wizard_restore_life(); return; } #else // !def USE_OPTIONAL_WIZARD_DEATH mpr("Since you're a debugger, I'll let you live."); mpr("Be more careful next time, okay?"); take_note(Note( NOTE_DEATH, you.hp, you.hp_max, death_desc.c_str()), true); _wizard_restore_life(); return; #endif // USE_OPTIONAL_WIZARD_DEATH } } #endif // WIZARD // Okay, so you're dead. crawl_state.need_save = false; crawl_state.updating_scores = true; take_note(Note( NOTE_DEATH, you.hp, you.hp_max, se.death_description(scorefile_entry::DDV_NORMAL).c_str()), true); // Prevent bogus notes. activate_notes(false); #ifdef SCORE_WIZARD_CHARACTERS // Add this highscore to the score file. hiscores_new_entry(se); logfile_new_entry(se); #else // Only add non-wizards to the score file. // Never generate bones files of wizard characters -- bwr if (!you.wizard) { hiscores_new_entry(se); logfile_new_entry(se); if (death_type != KILLED_BY_LEAVING && death_type != KILLED_BY_WINNING && death_type != KILLED_BY_QUITTING) { save_ghost(); } } #endif end_game(se); } static std::string morgue_name(time_t when_crawl_got_even) { #ifdef SHORT_FILE_NAMES return "morgue"; #else // !SHORT_FILE_NAMES std::string name = "morgue-" + you.your_name; std::string time = make_file_time(when_crawl_got_even); if (!time.empty()) name += "-" + time; return (name); #endif // SHORT_FILE_NAMES } // Delete save files on game end. static void delete_files() { // clean all levels that we think we have ever visited for (level_id_set::const_iterator i = Generated_Levels.begin(); i != Generated_Levels.end(); ++i) { const level_id &place(*i); unlink( make_filename(you.your_name, place.absdepth(), place.branch, place.level_type, false).c_str()); } // temp levels, if any unlink( make_filename( you.your_name, 0, BRANCH_MAIN_DUNGEON, LEVEL_ABYSS, false ).c_str() ); unlink( make_filename( you.your_name, 0, BRANCH_MAIN_DUNGEON, LEVEL_PANDEMONIUM, false ).c_str() ); unlink( make_filename( you.your_name, 0, BRANCH_MAIN_DUNGEON, LEVEL_LABYRINTH, false ).c_str() ); unlink( make_filename( you.your_name, 0, BRANCH_MAIN_DUNGEON, LEVEL_PORTAL_VAULT, false ).c_str() ); // create base file name std::string basename = get_savedir_filename(you.your_name, "", ""); const char* suffixes[] = { #ifdef CLUA_BINDINGS ".lua", #endif #ifdef PACKAGE_SUFFIX PACKAGE_SUFFIX , #endif ".st", ".kil", ".tc", ".nts", ".tut", ".sav", ".msg" }; const int num_suffixes = sizeof(suffixes) / sizeof(const char*); for (int i = 0; i < num_suffixes; ++i) { std::string tmpname = basename + suffixes[i]; unlink( tmpname.c_str() ); } } void end_game(scorefile_entry &se) { bool dead = true; for (int i = 0; i < ENDOFPACK; i++) set_ident_flags( you.inv[i], ISFLAG_IDENT_MASK ); for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].base_type != 0) set_ident_type( you.inv[i], ID_KNOWN_TYPE ); } delete_files(); if (!dump_char( morgue_name(se.death_time), !dead, true, &se )) { mpr("Char dump unsuccessful! Sorry about that."); if (!crawl_state.seen_hups) more(); clrscr(); } if (se.death_type == KILLED_BY_LEAVING || se.death_type == KILLED_BY_QUITTING || se.death_type == KILLED_BY_WINNING) { dead = false; } // death message if (dead) { mpr("You die..."); // insert player name here? {dlb} xom_death_message((kill_method_type) se.death_type); flush_prev_message(); viewwindow(false); // don't do for leaving/winning characters if (Tutorial.tutorial_left) tutorial_death_screen(); } #ifdef DGL_WHEREIS whereis_record( se.death_type == KILLED_BY_QUITTING? "quit" : se.death_type == KILLED_BY_WINNING ? "won" : se.death_type == KILLED_BY_LEAVING ? "bailed out" : "dead" ); #endif if (!crawl_state.seen_hups) more(); browse_inventory(true); textcolor( LIGHTGREY ); clrscr(); clrscr(); cprintf("Goodbye, %s.", you.your_name.c_str()); cprintf( EOL EOL " " ); // Space padding where # would go in list format std::string hiscore = hiscores_format_single_long( se, true ); const int lines = count_occurrences(hiscore, EOL) + 1; cprintf( "%s", hiscore.c_str() ); cprintf( EOL "Best Crawlers -" EOL ); // "- 5" gives us an extra line in case the description wraps on a line. hiscores_print_list( get_number_of_lines() - lines - 5 ); // just to pause, actual value returned does not matter {dlb} if (!crawl_state.seen_hups) get_ch(); end(0); } int actor_to_death_source(const actor* agent) { if (agent->atype() == ACT_PLAYER) return (NON_MONSTER); else if (agent->atype() == ACT_MONSTER) return (dynamic_cast(agent)->mindex()); else return (NON_MONSTER); }