/* * File: misc.cc * Summary: Misc functions. * Written by: Linley Henzell */ #include "AppHdr.h" #include "misc.h" #include "notes.h" #include #include #if !defined(__IBMCPP__) && !defined(TARGET_COMPILER_VC) #include #endif #ifdef TARGET_COMPILER_MINGW #include #endif #include #include #include #include "externs.h" #include "options.h" #include "misc.h" #include "abyss.h" #include "branch.h" #include "chardump.h" #include "clua.h" #include "cloud.h" #include "coordit.h" #include "database.h" #include "delay.h" #include "dgn-shoals.h" #include "directn.h" #include "dgnevent.h" #include "directn.h" #include "fprop.h" #include "fight.h" #include "files.h" #include "food.h" #include "hiscores.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "item_use.h" #include "lev-pand.h" #include "macro.h" #include "makeitem.h" #include "mapmark.h" #include "message.h" #include "mon-place.h" #include "coord.h" #include "mon-pathfind.h" #include "mon-iter.h" #include "mon-util.h" #include "mon-stuff.h" #include "ouch.h" #include "output.h" #include "overmap.h" #include "place.h" #include "player.h" #include "random.h" #include "religion.h" #include "shopping.h" #include "skills.h" #include "skills2.h" #include "spells1.h" #include "spells3.h" #include "stash.h" #include "state.h" #include "stuff.h" #include "env.h" #include "areas.h" #include "tiles.h" #include "terrain.h" #include "transform.h" #include "traps.h" #include "travel.h" #include "tutorial.h" #include "view.h" #include "shout.h" #include "viewchar.h" #include "xom.h" static void _create_monster_hide(const item_def corpse) { // receive_corpses() in spells3.cc creates corpses that // are easily scummed for hides. We prevent this by setting // "DoNotDropHide" as an item property of corpses it creates. if (corpse.props.exists("DoNotDropHide")) return; int mons_class = corpse.plus; int o = get_item_slot(); if (o == NON_ITEM) return; item_def& item = mitm[o]; // These values are common to all: {dlb} item.base_type = OBJ_ARMOUR; item.quantity = 1; item.plus = 0; item.plus2 = 0; item.special = 0; item.flags = 0; item.colour = mons_class_colour(mons_class); // These values cannot be set by a reasonable formula: {dlb} switch (mons_class) { case MONS_DRAGON: item.sub_type = ARM_DRAGON_HIDE; break; case MONS_TROLL: item.sub_type = ARM_TROLL_HIDE; break; case MONS_ICE_DRAGON: item.sub_type = ARM_ICE_DRAGON_HIDE; break; case MONS_STEAM_DRAGON: item.sub_type = ARM_STEAM_DRAGON_HIDE; break; case MONS_MOTTLED_DRAGON: item.sub_type = ARM_MOTTLED_DRAGON_HIDE; break; case MONS_STORM_DRAGON: item.sub_type = ARM_STORM_DRAGON_HIDE; break; case MONS_GOLDEN_DRAGON: item.sub_type = ARM_GOLD_DRAGON_HIDE; break; case MONS_SWAMP_DRAGON: item.sub_type = ARM_SWAMP_DRAGON_HIDE; break; case MONS_SHEEP: case MONS_YAK: default: item.sub_type = ARM_ANIMAL_SKIN; break; } monster_type mtype = static_cast(corpse.orig_monnum - 1); if (!invalid_monster_type(mtype) && mons_is_unique(mtype)) item.inscription = mons_type_name(mtype, DESC_PLAIN); move_item_to_grid(&o, you.pos()); } void turn_corpse_into_skeleton(item_def &item) { ASSERT(item.base_type == OBJ_CORPSES && item.sub_type == CORPSE_BODY); // Some monsters' corpses lack the structure to leave skeletons // behind. if (!mons_skeleton(item.plus)) return; // While it is possible to distinguish draconian corpses by colour, // their skeletons are indistinguishable. if (mons_genus(item.plus) == MONS_DRACONIAN && item.plus != MONS_DRACONIAN) item.plus = MONS_DRACONIAN; // The same goes for rat corpses. else if (mons_genus(item.plus) == MONS_RAT && item.plus != MONS_RAT) item.plus = MONS_RAT; item.sub_type = CORPSE_SKELETON; item.special = 200; // reset rotting counter item.colour = LIGHTGREY; } void turn_corpse_into_chunks(item_def &item) { ASSERT(item.base_type == OBJ_CORPSES && item.sub_type == CORPSE_BODY); const monster_type montype = static_cast(item.plus); const int max_chunks = mons_weight(montype) / 150; // Only fresh corpses bleed enough to colour the ground. if (!food_is_rotten(item)) bleed_onto_floor(you.pos(), montype, max_chunks, true); item.base_type = OBJ_FOOD; item.sub_type = FOOD_CHUNK; item.quantity = 1 + random2(max_chunks); item.quantity = stepdown_value(item.quantity, 4, 4, 12, 12); if (you.species != SP_VAMPIRE) item.flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); // Happens after the corpse has been butchered. if (monster_descriptor(item.plus, MDSC_LEAVES_HIDE) && !one_chance_in(3)) _create_monster_hide(item); } void turn_corpse_into_skeleton_and_chunks(item_def &item) { item_def chunks = item; if (mons_skeleton(item.plus)) turn_corpse_into_skeleton(item); int o = get_item_slot(); if (o != NON_ITEM) { turn_corpse_into_chunks(chunks); copy_item_to_grid(chunks, you.pos()); } } // Initialise blood potions with a vector of timers. void init_stack_blood_potions(item_def &stack, int age) { ASSERT(is_blood_potion(stack)); CrawlHashTable &props = stack.props; props.clear(); // sanity measure props["timer"].new_vector(SV_LONG, SFLAG_CONST_TYPE); CrawlVector &timer = props["timer"].get_vector(); if (age == -1) { if (stack.sub_type == POT_BLOOD) age = 2500; else // coagulated blood age = 500; } // For a newly created stack, all potions use the same timer. const long max_age = you.num_turns + age; #ifdef DEBUG_BLOOD_POTIONS mprf(MSGCH_DIAGNOSTICS, "newly created stack will time out at turn %d", max_age); #endif for (int i = 0; i < stack.quantity; i++) timer.push_back(max_age); stack.special = 0; ASSERT(timer.size() == stack.quantity); props.assert_validity(); } // Sort a CrawlVector, should probably be done properly with templates. static void _long_sort(CrawlVector &vec) { std::vector help; while (!vec.empty()) { help.push_back(vec[vec.size()-1].get_long()); vec.pop_back(); } std::sort(help.begin(), help.end()); while (!help.empty()) { vec.push_back(help[help.size()-1]); help.pop_back(); } } static void _compare_blood_quantity(item_def &stack, int timer_size) { if (timer_size != stack.quantity) { mprf(MSGCH_WARN, "ERROR: blood potion quantity (%d) doesn't match timer (%d)", stack.quantity, timer_size); // sanity measure stack.quantity = timer_size; } } void maybe_coagulate_blood_potions_floor(int obj) { item_def &blood = mitm[obj]; ASSERT(blood.is_valid()); ASSERT(is_blood_potion(blood)); CrawlHashTable &props = blood.props; if (!props.exists("timer")) init_stack_blood_potions(blood, blood.special); ASSERT(props.exists("timer")); CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); _compare_blood_quantity(blood, timer.size()); // blood.sub_type could be POT_BLOOD or POT_BLOOD_COAGULATED // -> need different handling int rot_limit = you.num_turns; int coag_limit = you.num_turns + 500; // check 500 turns later // First count whether coagulating is even necessary. int rot_count = 0; int coag_count = 0; std::vector age_timer; long current; while (!timer.empty()) { current = timer[timer.size()-1].get_long(); if (current > coag_limit || blood.sub_type == POT_BLOOD_COAGULATED && current > rot_limit) { // Still some time until rotting/coagulating. break; } timer.pop_back(); if (current <= rot_limit) rot_count++; else if (blood.sub_type == POT_BLOOD && current <= coag_limit) { coag_count++; age_timer.push_back(current); } } if (!rot_count && !coag_count) // Nothing to be done. return; #ifdef DEBUG_BLOOD_POTIONS mprf(MSGCH_DIAGNOSTICS, "in maybe_coagulate_blood_potions_FLOOR " "(turns: %d)", you.num_turns); mprf(MSGCH_DIAGNOSTICS, "Something happened at pos (%d, %d)!", blood.x, blood.y); mprf(MSGCH_DIAGNOSTICS, "coagulated: %d, rotted: %d, total: %d", coag_count, rot_count, blood.quantity); more(); #endif if (!coag_count) // Some potions rotted away. { dec_mitm_item_quantity(obj, rot_count); // Timer is already up to date. return; } // Coagulated blood cannot coagulate any further... ASSERT(blood.sub_type == POT_BLOOD); if (!blood.held_by_monster()) { // Now that coagulating is necessary, check square for // !coagulated blood. ASSERT(blood.pos.x >= 0 && blood.pos.y >= 0); for (stack_iterator si(blood.pos); si; ++si) { if (si->base_type == OBJ_POTIONS && si->sub_type == POT_BLOOD_COAGULATED) { // Merge with existing stack. CrawlHashTable &props2 = si->props; if (!props2.exists("timer")) init_stack_blood_potions(*si, si->special); ASSERT(props2.exists("timer")); CrawlVector &timer2 = props2["timer"].get_vector(); ASSERT(timer2.size() == si->quantity); // Update timer -> push(pop). while (!age_timer.empty()) { const long val = age_timer.back(); age_timer.pop_back(); timer2.push_back(val); } _long_sort(timer2); inc_mitm_item_quantity(si.link(), coag_count); ASSERT(timer2.size() == si->quantity); dec_mitm_item_quantity(obj, rot_count + coag_count); return; } } } // If we got here, nothing was found! (Or it's in a monster's // inventory.) // Entire stack is gone, rotted or coagulated. // -> Change potions to coagulated type. if (rot_count + coag_count == blood.quantity) { ASSERT(timer.empty()); // Update subtype. blood.sub_type = POT_BLOOD_COAGULATED; item_colour(blood); // Re-fill vector. long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer.push_back(val); } dec_mitm_item_quantity(obj, rot_count); _compare_blood_quantity(blood, timer.size()); return; } // Else, create a new stack of potions. int o = get_item_slot(20); if (o == NON_ITEM) return; item_def &item = mitm[o]; item.base_type = OBJ_POTIONS; item.sub_type = POT_BLOOD_COAGULATED; item.quantity = coag_count; item.plus = 0; item.plus2 = 0; item.special = 0; item.flags = 0; item_colour(item); CrawlHashTable &props_new = item.props; props_new["timer"].new_vector(SV_LONG, SFLAG_CONST_TYPE); CrawlVector &timer_new = props_new["timer"].get_vector(); long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer_new.push_back(val); } ASSERT(timer_new.size() == coag_count); props_new.assert_validity(); if (blood.held_by_monster()) move_item_to_grid(&o, blood.holding_monster()->pos()); else move_item_to_grid(&o, blood.pos); dec_mitm_item_quantity(obj, rot_count + coag_count); _compare_blood_quantity(blood, timer.size()); } static std::string _get_desc_quantity(const int quant, const int total) { if (total == quant) return "Your"; else if (quant == 1) return "One of your"; else if (quant == 2) return "Two of your"; else if (quant >= (total * 3) / 4) return "Most of your"; else return "Some of your"; } // Prints messages for blood potions coagulating in inventory (coagulate = true) // or whenever potions are cursed into potions of decay (coagulate = false). static void _potion_stack_changed_message(item_def &potion, int num_changed, std::string verb) { ASSERT(num_changed > 0); verb = replace_all(verb, "%s", num_changed == 1 ? "s" : ""); mprf(MSGCH_ROTTEN_MEAT, "%s %s %s.", _get_desc_quantity(num_changed, potion.quantity).c_str(), potion.name(DESC_PLAIN, false).c_str(), verb.c_str()); } // Returns true if "equipment weighs less" message needed. // Also handles coagulation messages. bool maybe_coagulate_blood_potions_inv(item_def &blood) { ASSERT(blood.is_valid()); ASSERT(is_blood_potion(blood)); CrawlHashTable &props = blood.props; if (!props.exists("timer")) init_stack_blood_potions(blood, blood.special); ASSERT(props.exists("timer")); CrawlVector &timer = props["timer"].get_vector(); _compare_blood_quantity(blood, timer.size()); ASSERT(!timer.empty()); // blood.sub_type could be POT_BLOOD or POT_BLOOD_COAGULATED // -> need different handling int rot_limit = you.num_turns; int coag_limit = you.num_turns + 500; // check 500 turns later // First count whether coagulating is even necessary. int rot_count = 0; int coag_count = 0; std::vector age_timer; long current; const int size = timer.size(); for (int i = 0; i < size; i++) { current = timer[timer.size()-1].get_long(); if (current > coag_limit || blood.sub_type == POT_BLOOD_COAGULATED && current > rot_limit) { // Still some time until rotting/coagulating. break; } timer.pop_back(); if (current <= rot_limit) rot_count++; else if (blood.sub_type == POT_BLOOD && current <= coag_limit) { coag_count++; age_timer.push_back(current); } } if (!rot_count && !coag_count) return (false); // Nothing to be done. #ifdef DEBUG_BLOOD_POTIONS mprf(MSGCH_DIAGNOSTICS, "in maybe_coagulate_blood_potions_INV " "(turns: %d)", you.num_turns); mprf(MSGCH_DIAGNOSTICS, "coagulated: %d, rotted: %d, total: %d", coag_count, rot_count, blood.quantity); more(); #endif // just in case you.wield_change = true; you.redraw_quiver = true; const bool knew_coag = (get_ident_type(OBJ_POTIONS, POT_BLOOD_COAGULATED) == ID_KNOWN_TYPE); if (!coag_count) // Some potions rotted away, but none coagulated. { // Only coagulated blood can rot. ASSERT(blood.sub_type == POT_BLOOD_COAGULATED); _potion_stack_changed_message(blood, rot_count, "rot%s away"); blood.quantity -= rot_count; if (!knew_coag) { set_ident_type( OBJ_POTIONS, POT_BLOOD_COAGULATED, ID_KNOWN_TYPE ); if (blood.quantity >= 1) mpr(blood.name(DESC_INVENTORY).c_str()); } if (blood.quantity < 1) { if (you.equip[EQ_WEAPON] == blood.link) you.equip[EQ_WEAPON] = -1; destroy_item(blood); } else _compare_blood_quantity(blood, timer.size()); return (true); } // Coagulated blood cannot coagulate any further... ASSERT(blood.sub_type == POT_BLOOD); const bool knew_blood = get_ident_type(OBJ_POTIONS, POT_BLOOD) == ID_KNOWN_TYPE; _potion_stack_changed_message(blood, coag_count, "coagulate%s"); // Identify both blood and coagulated blood, if necessary. if (!knew_blood) set_ident_type( OBJ_POTIONS, POT_BLOOD, ID_KNOWN_TYPE ); if (!knew_coag) set_ident_type( OBJ_POTIONS, POT_BLOOD_COAGULATED, ID_KNOWN_TYPE ); // Now that coagulating is necessary, check inventory for !coagulated blood. for (int m = 0; m < ENDOFPACK; m++) { if (!you.inv[m].is_valid()) continue; if (you.inv[m].base_type == OBJ_POTIONS && you.inv[m].sub_type == POT_BLOOD_COAGULATED) { CrawlHashTable &props2 = you.inv[m].props; if (!props2.exists("timer")) init_stack_blood_potions(you.inv[m], you.inv[m].special); ASSERT(props2.exists("timer")); CrawlVector &timer2 = props2["timer"].get_vector(); blood.quantity -= coag_count + rot_count; if (blood.quantity < 1) { if (you.equip[EQ_WEAPON] == blood.link) you.equip[EQ_WEAPON] = -1; destroy_item(blood); } else { _compare_blood_quantity(blood, timer.size()); if (!knew_blood) mpr(blood.name(DESC_INVENTORY).c_str()); } // Update timer -> push(pop). long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer2.push_back(val); } you.inv[m].quantity += coag_count; ASSERT(timer2.size() == you.inv[m].quantity); if (!knew_coag) mpr(you.inv[m].name(DESC_INVENTORY).c_str()); // re-sort timer _long_sort(timer2); return (rot_count > 0); } } // If entire stack has coagulated, simply change subtype. if (rot_count + coag_count == blood.quantity) { ASSERT(timer.empty()); // Update subtype. blood.sub_type = POT_BLOOD_COAGULATED; item_colour(blood); // Re-fill vector. long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer.push_back(val); } blood.quantity -= rot_count; // Stack still exists because of coag_count. _compare_blood_quantity(blood, timer.size()); if (!knew_coag) mpr(blood.name(DESC_INVENTORY).c_str()); return (rot_count > 0); } // Else, create new stack in inventory. int freeslot = find_free_slot(blood); if (freeslot >= 0 && freeslot < ENDOFPACK) { item_def &item = you.inv[freeslot]; item.clear(); item.link = freeslot; item.slot = index_to_letter(item.link); item.base_type = OBJ_POTIONS; item.sub_type = POT_BLOOD_COAGULATED; item.quantity = coag_count; item.plus = 0; item.plus2 = 0; item.special = 0; item.flags = (ISFLAG_KNOW_TYPE & ISFLAG_BEEN_IN_INV); item.pos.set(-1, -1); item_colour(item); CrawlHashTable &props_new = item.props; props_new["timer"].new_vector(SV_LONG, SFLAG_CONST_TYPE); CrawlVector &timer_new = props_new["timer"].get_vector(); long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer_new.push_back(val); } ASSERT(timer_new.size() == coag_count); props_new.assert_validity(); blood.quantity -= coag_count + rot_count; _compare_blood_quantity(blood, timer.size()); if (!knew_blood) mpr(blood.name(DESC_INVENTORY).c_str()); if (!knew_coag) mpr(item.name(DESC_INVENTORY).c_str()); return (rot_count > 0); } // No space in inventory, check floor. int o = igrd(you.pos()); while (o != NON_ITEM) { if (mitm[o].base_type == OBJ_POTIONS && mitm[o].sub_type == POT_BLOOD_COAGULATED) { // Merge with existing stack. CrawlHashTable &props2 = mitm[o].props; if (!props2.exists("timer")) init_stack_blood_potions(mitm[o], mitm[o].special); ASSERT(props2.exists("timer")); CrawlVector &timer2 = props2["timer"].get_vector(); ASSERT(timer2.size() == mitm[o].quantity); // Update timer -> push(pop). long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer2.push_back(val); } _long_sort(timer2); inc_mitm_item_quantity(o, coag_count); ASSERT(timer2.size() == mitm[o].quantity); dec_inv_item_quantity(blood.link, rot_count + coag_count); _compare_blood_quantity(blood, timer.size()); if (!knew_blood) mpr(blood.name(DESC_INVENTORY).c_str()); return (true); } o = mitm[o].link; } // If we got here nothing was found! // Create a new stack of potions. o = get_item_slot(); if (o == NON_ITEM) return (false); // These values are common to all: {dlb} mitm[o].base_type = OBJ_POTIONS; mitm[o].sub_type = POT_BLOOD_COAGULATED; mitm[o].quantity = coag_count; mitm[o].plus = 0; mitm[o].plus2 = 0; mitm[o].special = 0; mitm[o].flags = (ISFLAG_KNOW_TYPE & ISFLAG_BEEN_IN_INV); item_colour(mitm[o]); CrawlHashTable &props_new = mitm[o].props; props_new["timer"].new_vector(SV_LONG, SFLAG_CONST_TYPE); CrawlVector &timer_new = props_new["timer"].get_vector(); long val; while (!age_timer.empty()) { val = age_timer[age_timer.size() - 1]; age_timer.pop_back(); timer_new.push_back(val); } ASSERT(timer_new.size() == coag_count); props_new.assert_validity(); move_item_to_grid(&o, you.pos()); blood.quantity -= rot_count + coag_count; if (blood.quantity < 1) { if (you.equip[EQ_WEAPON] == blood.link) you.equip[EQ_WEAPON] = -1; destroy_item(blood); } else { _compare_blood_quantity(blood, timer.size()); if (!knew_blood) mpr(blood.name(DESC_INVENTORY).c_str()); } return (true); } // Removes the oldest timer of a stack of blood potions. // Mostly used for (q)uaff, (f)ire, and Evaporate. long remove_oldest_blood_potion(item_def &stack) { ASSERT(stack.is_valid()); ASSERT(is_blood_potion(stack)); CrawlHashTable &props = stack.props; if (!props.exists("timer")) init_stack_blood_potions(stack, stack.special); ASSERT(props.exists("timer")); CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); // Assuming already sorted, and first (oldest) potion valid. const long val = timer[timer.size() - 1].get_long(); timer.pop_back(); // The quantity will be decreased elsewhere. return (val); } // Used whenever copies of blood potions have to be cleaned up. void remove_newest_blood_potion(item_def &stack, int quant) { ASSERT(stack.is_valid()); ASSERT(is_blood_potion(stack)); CrawlHashTable &props = stack.props; if (!props.exists("timer")) init_stack_blood_potions(stack, stack.special); ASSERT(props.exists("timer")); CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); if (quant == -1) quant = timer.size() - stack.quantity; // Overwrite newest potions with oldest ones. int repeats = stack.quantity; if (repeats > quant) repeats = quant; for (int i = 0; i < repeats; i++) { timer[i] = timer[timer.size() - 1]; timer.pop_back(); } // Now remove remaining oldest potions... repeats = quant - repeats; for (int i = 0; i < repeats; i++) timer.pop_back(); // ... and re-sort. _long_sort(timer); } void merge_blood_potion_stacks(item_def &source, item_def &dest, int quant) { if (!source.is_valid() || !dest.is_valid()) return; ASSERT(quant > 0 && quant <= source.quantity); ASSERT(is_blood_potion(source) && is_blood_potion(dest)); CrawlHashTable &props = source.props; if (!props.exists("timer")) init_stack_blood_potions(source, source.special); ASSERT(props.exists("timer")); CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); CrawlHashTable &props2 = dest.props; if (!props2.exists("timer")) init_stack_blood_potions(dest, dest.special); ASSERT(props2.exists("timer")); CrawlVector &timer2 = props2["timer"].get_vector(); // Update timer -> push(pop). for (int i = 0; i < quant; i++) { timer2.push_back(timer[timer.size() - 1].get_long()); timer.pop_back(); } // Re-sort timer. _long_sort(timer2); } // Deliberately don't check for rottenness here, so this check // can also be used to verify whether you *could* have bottled // a now rotten corpse. bool can_bottle_blood_from_corpse(int mons_type) { if (you.species != SP_VAMPIRE || you.experience_level < 6 || !mons_has_blood(mons_type)) { return (false); } int chunk_type = mons_corpse_effect( mons_type ); if (chunk_type == CE_CLEAN || chunk_type == CE_CONTAMINATED) return (true); return (false); } int num_blood_potions_from_corpse(int mons_class, int chunk_type) { if (chunk_type == -1) chunk_type = mons_corpse_effect(mons_class); // Max. amount is about one third of the max. amount for chunks. const int max_chunks = mons_weight( mons_class ) / 150; // Max. amount is about one third of the max. amount for chunks. int pot_quantity = max_chunks/3; pot_quantity = stepdown_value( pot_quantity, 2, 2, 6, 6 ); // Halve number of potions obtained from contaminated chunk type corpses. if (chunk_type == CE_CONTAMINATED) pot_quantity /= 2; if (pot_quantity < 1) pot_quantity = 1; return (pot_quantity); } // If autopickup is active, the potions are auto-picked up after creation. void turn_corpse_into_blood_potions(item_def &item) { ASSERT(item.base_type == OBJ_CORPSES); ASSERT(!food_is_rotten(item)); item_def corpse = item; const int mons_class = corpse.plus; ASSERT(can_bottle_blood_from_corpse(mons_class)); item.base_type = OBJ_POTIONS; item.sub_type = POT_BLOOD; item_colour(item); item.flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); item.quantity = num_blood_potions_from_corpse(mons_class); // Initialise timer depending on corpse age: // almost rotting: age = 100 --> potion timer = 500 --> will coagulate soon // freshly killed: age = 200 --> potion timer = 2500 --> fresh !blood init_stack_blood_potions(item, (item.special - 100) * 20 + 500); // Happens after the blood has been bottled. if (monster_descriptor(mons_class, MDSC_LEAVES_HIDE) && !one_chance_in(3)) _create_monster_hide(corpse); } void turn_corpse_into_skeleton_and_blood_potions(item_def &item) { item_def blood_potions = item; if (mons_skeleton(item.plus)) turn_corpse_into_skeleton(item); int o = get_item_slot(); if (o != NON_ITEM) { turn_corpse_into_blood_potions(blood_potions); copy_item_to_grid(blood_potions, you.pos()); } } // A variation of the mummy curse: // Instead of trashing the entire stack, split the stack and only turn part // of it into POT_DECAY. void split_potions_into_decay( int obj, int amount, bool need_msg ) { ASSERT(obj != -1); item_def &potion = you.inv[obj]; ASSERT(potion.is_valid()); ASSERT(potion.base_type == OBJ_POTIONS); ASSERT(amount > 0); ASSERT(amount <= potion.quantity); // Output decay message. if (need_msg && get_ident_type(OBJ_POTIONS, POT_DECAY) == ID_KNOWN_TYPE) _potion_stack_changed_message(potion, amount, "decay%s"); if (you.equip[EQ_WEAPON] == obj) you.wield_change = true; you.redraw_quiver = true; if (is_blood_potion(potion)) { // We're being nice here, and only decay the *oldest* potions. for (int i = 0; i < amount; i++) remove_oldest_blood_potion(potion); } // Try to merge into existing stacks of decayed potions. for (int m = 0; m < ENDOFPACK; m++) { if (you.inv[m].base_type == OBJ_POTIONS && you.inv[m].sub_type == POT_DECAY) { if (potion.quantity == amount) { if (you.equip[EQ_WEAPON] == obj) you.equip[EQ_WEAPON] = -1; destroy_item(potion); } else you.inv[obj].quantity -= amount; you.inv[m].quantity += amount; return; } } // Else, if entire stack affected just change subtype. if (amount == potion.quantity) { you.inv[obj].sub_type = POT_DECAY; unset_ident_flags( you.inv[obj], ISFLAG_IDENT_MASK ); // all different return; } // Else, create new stack in inventory. int freeslot = find_free_slot(you.inv[obj]); if (freeslot >= 0 && freeslot < ENDOFPACK && !you.inv[freeslot].is_valid()) { item_def &item = you.inv[freeslot]; item.link = freeslot; item.slot = index_to_letter(item.link); item.base_type = OBJ_POTIONS; item.sub_type = POT_DECAY; item.quantity = amount; // Keep description as it was. item.plus = potion.plus; item.plus2 = 0; item.special = 0; item.flags = 0; item.colour = potion.colour; item.inscription.clear(); item.pos.set(-1, -1); you.inv[obj].quantity -= amount; return; } // Okay, inventory is full. // Check whether we can merge with an existing stack on the floor. for (stack_iterator si(you.pos()); si; ++si) { if (si->base_type == OBJ_POTIONS && si->sub_type == POT_DECAY) { dec_inv_item_quantity(obj, amount); inc_mitm_item_quantity(si->index(), amount); return; } } item_def potion2; potion2.base_type = OBJ_POTIONS; potion2.sub_type = POT_DECAY; // Keep description as it was. potion2.plus = potion.plus; potion2.quantity = amount; potion2.colour = potion.colour; potion2.plus2 = 0; potion2.flags = 0; potion2.special = 0; copy_item_to_grid(potion2, you.pos()); // Is decreased even if the decay stack goes splat. dec_inv_item_quantity(obj, amount); } static bool allow_bleeding_on_square(const coord_def& where) { // No bleeding onto sanctuary ground, please. // Also not necessary if already covered in blood. if (is_bloodcovered(where) || is_sanctuary(where)) return (false); // No spattering into lava or water. if (grd(where) >= DNGN_LAVA && grd(where) < DNGN_FLOOR) return (false); // No spattering into fountains (other than blood). if (grd(where) == DNGN_FOUNTAIN_BLUE || grd(where) == DNGN_FOUNTAIN_SPARKLING) { return (false); } // The good gods like to keep their altars pristine. if (is_good_god(feat_altar_god(grd(where)))) return (false); return (true); } bool maybe_bloodify_square(const coord_def& where) { if (!allow_bleeding_on_square(where)) return (false); env.pgrid(where) |= FPROP_BLOODY; return(true); } static void _maybe_bloodify_square(const coord_def& where, int amount, bool spatter = false, bool smell_alert = true) { if (amount < 1) return; bool may_bleed = allow_bleeding_on_square(where); if (!spatter && !may_bleed) return; if (x_chance_in_y(amount, 20)) { dprf("might bleed now; square: (%d, %d); amount = %d", where.x, where.y, amount); if (may_bleed) { env.pgrid(where) |= FPROP_BLOODY; if (smell_alert) blood_smell(12, where); } if (spatter) { // Smaller chance of spattering surrounding squares. for (adjacent_iterator ai(where); ai; ++ai) { // Spattering onto walls etc. less likely. if (grd(*ai) < DNGN_MINMOVE && !one_chance_in(3)) continue; _maybe_bloodify_square(*ai, amount/15); } } } } // Currently flavour only: colour ground (and possibly adjacent squares) red. // "damage" depends on damage taken (or hitpoints, if damage higher), // or, for sacrifices, on the number of chunks possible to get out of a corpse. void bleed_onto_floor(const coord_def& where, monster_type montype, int damage, bool spatter, bool smell_alert) { ASSERT(in_bounds(where)); if (montype == MONS_PLAYER && !you.can_bleed()) return; if (montype != NUM_MONSTERS && montype != MONS_PLAYER) { monsters m; m.type = montype; if (!m.can_bleed()) return; } _maybe_bloodify_square(where, damage, spatter, smell_alert); } void blood_spray(const coord_def& origin, monster_type montype, int level) { los_def ld(origin, opc_solid); ld.update(); int tries = 0; for (int i = 0; i < level; ++i) { // Blood drops are small and light and suffer a lot of wind // resistance. int range = random2(8) + 1; while (tries < 5000) { ++tries; coord_def bloody = origin; bloody.x += random_range(-range, range); bloody.y += random_range(-range, range); if (in_bounds(bloody) && ld.see_cell(bloody)) { if (feat_is_solid(grd(bloody)) && coinflip()) continue; bleed_onto_floor(bloody, montype, 99); break; } } } } static void _spatter_neighbours(const coord_def& where, int chance) { for (adjacent_iterator ai(where, false); ai; ++ai) { if (!allow_bleeding_on_square(*ai)) continue; if (grd(*ai) < DNGN_MINMOVE && !one_chance_in(3)) continue; if (one_chance_in(chance)) { env.pgrid(*ai) |= FPROP_BLOODY; _spatter_neighbours(*ai, chance+1); } } } void generate_random_blood_spatter_on_level() { int startprob; // startprob is used to initialise the chance for neighbours being // spattered, which will be decreased by 1 per recursion round. // We then use one_chance_in(chance) to determine whether to spatter a // given grid or not. Thus, startprob = 1 means that initially all // surrounding grids will be spattered (3x3), and the _higher_ startprob // the _lower_ the overall chance for spattering and the _smaller_ the // bloodshed area. const int max_cluster = 7 + random2(9); // Lower chances for large bloodshed areas if we have many clusters, // but increase chances if we have few. // Chances for startprob are [1..3] for 7-9 clusters, // ... [1..4] for 10-12 clusters, and // ... [2..5] for 13-15 clusters. int min_prob = 1; int max_prob = 4; if (max_cluster < 10) max_prob--; else if (max_cluster > 12) min_prob++; for (int i = 0; i < max_cluster; ++i) { coord_def c = random_in_bounds(); startprob = min_prob + random2(max_prob); maybe_bloodify_square(c); _spatter_neighbours(c, startprob); } } void search_around(bool only_adjacent) { ASSERT(!crawl_state.arena); // Traps and doors stepdown skill: // skill/(2x-1) for squares at distance x int max_dist = (you.skills[SK_TRAPS_DOORS] + 1) / 2; if (max_dist > 5) max_dist = 5; if (only_adjacent && max_dist > 1 || max_dist < 1) max_dist = 1; for (radius_iterator ri(you.pos(), max_dist); ri; ++ri ) { // Must have LOS, with no translucent walls in the way. if (you.see_cell_no_trans(*ri)) { // Maybe we want distance() instead of feat_distance()? int dist = grid_distance(*ri, you.pos()); // Don't exclude own square; may be levitating. // XXX: Currently, levitating over a trap will always detect it. if (dist == 0) ++dist; // Making this harsher by removing the old +1... int effective = you.skills[SK_TRAPS_DOORS] / (2*dist - 1); if (grd(*ri) == DNGN_SECRET_DOOR && x_chance_in_y(effective+1, 17)) { mpr("You found a secret door!"); reveal_secret_door(*ri); exercise(SK_TRAPS_DOORS, (coinflip() ? 2 : 1)); } else if (grd(*ri) == DNGN_UNDISCOVERED_TRAP && x_chance_in_y(effective + 1, 17)) { trap_def* ptrap = find_trap(*ri); if (ptrap) { ptrap->reveal(); mpr("You found a trap!"); learned_something_new(TUT_SEEN_TRAP, *ri); exercise(SK_TRAPS_DOORS, (coinflip() ? 2 : 1)); } else { // Maybe we shouldn't kill the trap for debugging // purposes - oh well. grd(*ri) = DNGN_FLOOR; dprf("You found a buggy trap! It vanishes!"); } } } } } bool merfolk_change_is_safe(bool quiet) { // If already transformed, no subsequent transformation necessary. if (!you.airborne() && feat_is_water(grd(you.pos()))) return (true); std::set r; r.insert(EQ_BOOTS); if (check_transformation_stat_loss(r, quiet)) return (false); return (true); } void merfolk_start_swimming() { if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE) untransform(); remove_one_equip(EQ_BOOTS); you.redraw_evasion = true; } static void exit_stair_message(dungeon_feature_type stair, bool /* going_up */) { if (feat_is_escape_hatch(stair)) mpr("The hatch slams shut behind you."); } static void climb_message(dungeon_feature_type stair, bool going_up, level_area_type old_level_type) { if (old_level_type != LEVEL_DUNGEON) return; if (feat_is_portal(stair)) mpr("The world spins around you as you enter the gateway."); else if (feat_is_escape_hatch(stair)) { if (going_up) mpr("A mysterious force pulls you upwards."); else { mprf("You %s downwards.", you.flight_mode() == FL_FLY ? "fly" : (you.airborne() ? "float" : "slide")); } } else if (feat_is_gate(stair)) { mprf("You %s %s through the gate.", you.flight_mode() == FL_FLY ? "fly" : (you.airborne() ? "float" : "go"), going_up ? "up" : "down"); } else { mprf("You %s %swards.", you.flight_mode() == FL_FLY ? "fly" : (you.airborne() ? "float" : "climb"), going_up ? "up" : "down"); } } static void leaving_level_now() { // Note the name ahead of time because the events may cause markers // to be discarded. const std::string newtype = env.markers.property_at(you.pos(), MAT_ANY, "dst"); // Extension to use for bones files. const std::string newext = env.markers.property_at(you.pos(), MAT_ANY, "dstext"); const std::string oldname = you.level_type_name; std::string newname = env.markers.property_at(you.pos(), MAT_ANY, "dstname"); std::string neworigin = env.markers.property_at(you.pos(), MAT_ANY, "dstorigin"); const std::string oldname_abbrev = you.level_type_name_abbrev; std::string newname_abbrev = env.markers.property_at(you.pos(), MAT_ANY, "dstname_abbrev"); dungeon_events.fire_position_event(DET_PLAYER_CLIMBS, you.pos()); dungeon_events.fire_event(DET_LEAVING_LEVEL); // Lua scripts explicitly set level_type_name, so use that. if (you.level_type_name != oldname) newname = you.level_type_name; // Lua scripts explicitly set level_type_name_abbrev, so use that. if (you.level_type_name_abbrev != oldname_abbrev) newname_abbrev = you.level_type_name_abbrev; if (newname_abbrev.length() > MAX_NOTE_PLACE_LEN) { mprf(MSGCH_ERROR, "'%s' is too long for a portal vault name " "abbreviation, truncating"); newname_abbrev = newname_abbrev.substr(0, MAX_NOTE_PLACE_LEN); } you.level_type_origin = ""; // Lua scripts explicitly set level_type_origin, so use that. if (!you.level_type_origin.empty()) neworigin = you.level_type_origin; // Don't clobber level_type_name for stairs in portal vaults. if (you.level_type_name.empty() || !newname.empty() || you.level_type != LEVEL_PORTAL_VAULT) { you.level_type_name = newname; } // Don't clobber level_type_name_abbrev for stairs in portal vaults. if (you.level_type_name_abbrev.empty() || !newname_abbrev.empty() || you.level_type != LEVEL_PORTAL_VAULT) { you.level_type_name_abbrev = newname_abbrev; } if (you.level_type_tag.empty() || !newtype.empty() || you.level_type != LEVEL_PORTAL_VAULT) { you.level_type_tag = newtype; } const std::string spaced_tag = replace_all(you.level_type_tag, "_", " "); if (!you.level_type_tag.empty() && you.level_type_name.empty()) you.level_type_name = spaced_tag; if (!you.level_type_name.empty() && you.level_type_name_abbrev.empty()) { if (you.level_type_name.length() <= MAX_NOTE_PLACE_LEN) you.level_type_name_abbrev = you.level_type_name; else if (you.level_type_tag.length() <= MAX_NOTE_PLACE_LEN) you.level_type_name_abbrev = spaced_tag; else { const std::string shorter = you.level_type_name.length() < you.level_type_tag.length() ? you.level_type_name : spaced_tag; you.level_type_name_abbrev = shorter.substr(0, MAX_NOTE_PLACE_LEN); } } if (!newext.empty()) you.level_type_ext = newext; else if (!you.level_type_tag.empty()) you.level_type_ext = lowercase_string(you.level_type_tag); if (you.level_type_ext.length() > 3) you.level_type_ext = you.level_type_ext.substr(0, 3); if (!neworigin.empty()) you.level_type_origin = neworigin; else if (!you.level_type_name.empty()) { std::string lname = lowercase_string(you.level_type_name); std::string article, prep; if (starts_with(lname, "level ") || lname.find(":") != std::string::npos) { prep = "on "; } else prep = "in "; if (starts_with(lname, "a ") || starts_with(lname, "an ") || starts_with(lname, "the ") || starts_with(lname, "level ") || lname.find(":") != std::string::npos) { ; // Doesn't need an article } else { char letter = you.level_type_name[0]; if (isupper(letter)) article = "the "; else if (is_vowel(letter)) article = "an "; else article = "a "; } you.level_type_origin = prep + article + you.level_type_name; } } static void set_entry_cause(entry_cause_type default_cause, level_area_type old_level_type) { ASSERT(default_cause != NUM_ENTRY_CAUSE_TYPES); if (old_level_type == you.level_type && you.entry_cause != EC_UNKNOWN) return; if (crawl_state.is_god_acting()) { if (crawl_state.is_god_retribution()) you.entry_cause = EC_GOD_RETRIBUTION; else you.entry_cause = EC_GOD_ACT; you.entry_cause_god = crawl_state.which_god_acting(); } else if (default_cause != EC_UNKNOWN) { you.entry_cause = default_cause; you.entry_cause_god = GOD_NO_GOD; } else { you.entry_cause = EC_SELF_EXPLICIT; you.entry_cause_god = GOD_NO_GOD; } } static int runes_in_pack(std::vector &runes) { int num_runes = 0; for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && you.inv[i].base_type == OBJ_MISCELLANY && you.inv[i].sub_type == MISC_RUNE_OF_ZOT) { num_runes += you.inv[i].quantity; for (int q = 1; runes.size() < 3 && q <= you.inv[i].quantity; ++q) { runes.push_back(i); } } } return num_runes; } bool check_annotation_exclusion_warning() { // Players might not realise the implications of teleport mutations // in the labyrinth. if (grd(you.pos()) == DNGN_ENTER_LABYRINTH && player_mutation_level(MUT_TELEPORT)) { mpr("Within the labyrinth you'll only be able to teleport away from " "the exit!"); if (!yesno("Continue anyway?", false, 'N', true, false)) { canned_msg(MSG_OK); interrupt_activity( AI_FORCE_INTERRUPT ); return (false); } return (true); } level_id next_level_id = level_id::get_next_level_id(you.pos()); crawl_state.level_annotation_shown = false; bool might_be_dangerous = false; if (level_annotation_has("!", next_level_id) && next_level_id != level_id::current() && next_level_id.level_type == LEVEL_DUNGEON) { mpr("Warning: level annotation for next level is:", MSGCH_PROMPT); mpr(get_level_annotation(next_level_id).c_str(), MSGCH_PLAIN, YELLOW); might_be_dangerous = true; crawl_state.level_annotation_shown = true; } else if (is_exclude_root(you.pos())) { mpr("This staircase is marked as excluded!", MSGCH_WARN); might_be_dangerous = true; } if (might_be_dangerous && !yesno("Enter next level anyway?", true, 'n', true, false)) { canned_msg(MSG_OK); interrupt_activity( AI_FORCE_INTERRUPT ); crawl_state.level_annotation_shown = false; return (false); } return (true); } static void _player_change_level_reset() { you.prev_targ = MHITNOT; if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; you.prev_grd_targ.reset(); } static level_id _stair_destination_override() { const std::string force_place = env.markers.property_at(you.pos(), MAT_ANY, "dstplace"); if (!force_place.empty()) { try { const level_id place = level_id::parse_level_id(force_place); return (place); } catch (const std::string &err) { end(1, false, "Marker set with invalid level name: %s", force_place.c_str()); } } const level_id invalid; return (invalid); } static bool _stair_force_destination(const level_id &override) { if (override.is_valid()) { if (override.level_type == LEVEL_DUNGEON) { you.where_are_you = override.branch; you.your_level = override.absdepth(); you.level_type = override.level_type; } else { you.level_type = override.level_type; } return (true); } return (false); } static void _player_change_level_upstairs(dungeon_feature_type stair_find, const level_id &place_override) { if (_stair_force_destination(place_override)) return; if (you.level_type == LEVEL_DUNGEON) you.your_level--; // Make sure we return to our main dungeon level... labyrinth entrances // in the abyss or pandemonium are a bit trouble (well the labyrinth does // provide a way out of those places, its really not that bad I suppose). if (level_type_exits_up(you.level_type)) you.level_type = LEVEL_DUNGEON; if (player_in_branch( BRANCH_VESTIBULE_OF_HELL )) { you.where_are_you = BRANCH_MAIN_DUNGEON; you.your_level = you.hell_exit; } if (player_in_hell()) { you.where_are_you = BRANCH_VESTIBULE_OF_HELL; you.your_level = 27; } // Did we take a branch stair? for (int i = 0; i < NUM_BRANCHES; ++i) { if (branches[i].exit_stairs == stair_find && you.where_are_you == i) { you.where_are_you = branches[i].parent_branch; // If leaving a branch which wasn't generated in this // particular game (like the Swamp or Shoals), then // its startdepth is set to -1; compensate for that, // so we don't end up on "level -1". if (branches[i].startdepth == -1) you.your_level += 2; break; } } } static bool _marker_vetoes_level_change() { return (marker_vetoes_operation("veto_level_change")); } static bool _stair_moves_pre(dungeon_feature_type stair) { if (crawl_state.prev_cmd == CMD_WIZARD) return (false); if (stair != grd(you.pos())) return (false); if (feat_stair_direction(stair) == CMD_NO_CMD) return (false); if (!you.duration[DUR_REPEL_STAIRS_CLIMB]) return (false); int pct; if (you.duration[DUR_REPEL_STAIRS_MOVE]) pct = 29; else pct = 50; // When the effect is still strong, the chance to actually catch a stair // is smaller. (Assuming the duration starts out at 500.) const int dur = std::max(0, you.duration[DUR_REPEL_STAIRS_CLIMB] - 200); pct += dur/20; if (!x_chance_in_y(pct, 100)) return (false); // Get feature name before sliding stair over. std::string stair_str = feature_description(you.pos(), false, DESC_CAP_THE, false); if (!slide_feature_over(you.pos(), coord_def(-1, -1), false)) return (false); std::string verb = stair_climb_verb(stair); mprf("%s moves away as you attempt to %s it!", stair_str.c_str(), verb.c_str()); you.turn_is_over = true; return (true); } static bool _check_carrying_orb() { // We never picked up the Orb, no problem. if (you.char_direction != GDT_ASCENDING) return (true); // So we did pick up the Orb. Now check whether we're carrying it. for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && you.inv[i].base_type == OBJ_ORBS && you.inv[i].sub_type == ORB_ZOT) { return (true); } } return (yes_or_no("You're not carrying the Orb! Leave anyway")); } void up_stairs(dungeon_feature_type force_stair, entry_cause_type entry_cause) { dungeon_feature_type stair_find = (force_stair ? force_stair : grd(you.pos())); const branch_type old_where = you.where_are_you; const level_area_type old_level_type = you.level_type; // Up and down both work for shops. if (stair_find == DNGN_ENTER_SHOP) { shop(); return; } // Up and down both work for portals. if (get_feature_dchar(stair_find) == DCHAR_ARCH && feat_stair_direction(stair_find) != CMD_NO_CMD && stair_find != DNGN_ENTER_ZOT && stair_find != DNGN_RETURN_FROM_ZOT && stair_find != DNGN_EXIT_HELL) { down_stairs(you.your_level, force_stair, entry_cause); return; } // Probably still need this check here (teleportation) -- bwr else if (feat_stair_direction(stair_find) != CMD_GO_UPSTAIRS) { if (stair_find == DNGN_STONE_ARCH) mpr("There is nothing on the other side of the stone arch."); else if (stair_find == DNGN_ABANDONED_SHOP) mpr("This shop appears to be closed."); else mpr("You can't go up here."); return; } if (_stair_moves_pre(stair_find)) return; // Since the overloaded message set turn_is_over, I'm assuming that // the overloaded character makes an attempt... so we're doing this // check before that one. -- bwr if (!you.airborne() && you.confused() && old_level_type == LEVEL_DUNGEON && !feat_is_escape_hatch(stair_find) && coinflip()) { const char* fall_where = "down the stairs"; if (!feat_is_staircase(stair_find)) fall_where = "through the gate"; mprf("In your confused state, you trip and fall back %s.", fall_where); if (!feat_is_staircase(stair_find)) ouch(1, NON_MONSTER, KILLED_BY_FALLING_THROUGH_GATE); else ouch(1, NON_MONSTER, KILLED_BY_FALLING_DOWN_STAIRS); you.turn_is_over = true; return; } if (you.burden_state == BS_OVERLOADED && !feat_is_escape_hatch(stair_find) && !feat_is_gate(stair_find)) { mpr("You are carrying too much to climb upwards."); you.turn_is_over = true; return; } const level_id destination_override(_stair_destination_override()); const bool leaving_dungeon = level_id::current() == level_id(BRANCH_MAIN_DUNGEON, 1) && !destination_override.is_valid(); if (leaving_dungeon && (!yesno("Are you sure you want to leave the Dungeon?", false, 'n') || !_check_carrying_orb())) { if (Tutorial.tutorial_left) { if (!yesno("Are you *sure*? Doing so will end the game!", false, 'n')) { mpr("Alright."); return; } } mpr("Alright, then stay!"); return; } // Bail if any markers veto the move. if (_marker_vetoes_level_change()) return; // Magical level changes (don't exist yet in this direction) // need this. clear_trapping_net(); // Checks are done, the character is committed to moving between levels. leaving_level_now(); const int old_level = you.your_level; // Interlevel travel data. const bool collect_travel_data = can_travel_interlevel(); level_id old_level_id = level_id::current(); LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id); if (collect_travel_data) old_level_info.update(); _player_change_level_reset(); _player_change_level_upstairs(stair_find, destination_override); if (you.your_level < 0) { mpr("You have escaped!"); for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && you.inv[i].base_type == OBJ_ORBS) { ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_WINNING); } } ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_LEAVING); } if (old_level_id.branch == BRANCH_VESTIBULE_OF_HELL && !player_in_branch( BRANCH_VESTIBULE_OF_HELL )) { mpr("Thank you for visiting Hell. Please come again soon."); } // Fixup exits from the Hell branches. if (player_in_branch(BRANCH_VESTIBULE_OF_HELL)) { switch (old_level_id.branch) { case BRANCH_COCYTUS: stair_find = DNGN_ENTER_COCYTUS; break; case BRANCH_DIS: stair_find = DNGN_ENTER_DIS; break; case BRANCH_GEHENNA: stair_find = DNGN_ENTER_GEHENNA; break; case BRANCH_TARTARUS: stair_find = DNGN_ENTER_TARTARUS; break; default: break; } } const dungeon_feature_type stair_taken = stair_find; if (you.flight_mode() == FL_LEVITATE && !feat_is_gate(stair_find)) mpr("You float upwards... And bob straight up to the ceiling!"); else if (you.flight_mode() == FL_FLY && !feat_is_gate(stair_find)) mpr("You fly upwards."); else climb_message(stair_find, true, old_level_type); exit_stair_message(stair_find, true); if (old_where != you.where_are_you && you.level_type == LEVEL_DUNGEON) { mprf("Welcome back to %s!", branches[you.where_are_you].longname); } const coord_def stair_pos = you.pos(); load(stair_taken, LOAD_ENTER_LEVEL, old_level_type, old_level, old_where); set_entry_cause(entry_cause, old_level_type); entry_cause = you.entry_cause; you.turn_is_over = true; save_game_state(); new_level(); viewwindow(true); // Left Zot without enough runes to get back in (because they were // destroyed), but need to get back in Zot to get the Orb? // Xom finds that funny. if (stair_find == DNGN_RETURN_FROM_ZOT && branches[BRANCH_HALL_OF_ZOT].branch_flags & BFLAG_HAS_ORB) { int runes_avail = you.attribute[ATTR_UNIQUE_RUNES] + you.attribute[ATTR_DEMONIC_RUNES] + you.attribute[ATTR_ABYSSAL_RUNES]; if (runes_avail < NUMBER_OF_RUNES_NEEDED) xom_is_stimulated(255, "Xom snickers loudly.", true); } if (you.skills[SK_TRANSLOCATIONS] > 0 && !allow_control_teleport( true )) mpr( "You sense a powerful magical force warping space.", MSGCH_WARN ); if (collect_travel_data) { // Update stair information for the stairs we just ascended, and the // down stairs we're currently on. level_id new_level_id = level_id::current(); if (can_travel_interlevel()) { LevelInfo &new_level_info = travel_cache.get_level_info(new_level_id); new_level_info.update(); // First we update the old level's stair. level_pos lp; lp.id = new_level_id; lp.pos = you.pos(); bool guess = false; // Ugly hack warning: // The stairs in the Vestibule of Hell exhibit special behaviour: // they always lead back to the dungeon level that the player // entered the Vestibule from. This means that we need to pretend // we don't know where the upstairs from the Vestibule go each time // we take it. If we don't, interlevel travel may try to use portals // to Hell as shortcuts between dungeon levels, which won't work, // and will confuse the dickens out of the player (well, it confused // the dickens out of me when it happened). if (new_level_id == BRANCH_MAIN_DUNGEON && old_level_id == BRANCH_VESTIBULE_OF_HELL) { old_level_info.clear_stairs(DNGN_EXIT_HELL); } else { old_level_info.update_stair(stair_pos, lp, guess); } // We *guess* that going up a staircase lands us on a downstair, // and that we can descend that downstair and get back to where we // came from. This assumption is guaranteed false when climbing out // of one of the branches of Hell. if (new_level_id != BRANCH_VESTIBULE_OF_HELL) { // Set the new level's stair, assuming arbitrarily that going // downstairs will land you on the same upstairs you took to // begin with (not necessarily true). lp.id = old_level_id; lp.pos = stair_pos; new_level_info.update_stair(you.pos(), lp, true); } } } request_autopickup(); } // Adds a dungeon marker at the point of the level where returning from // a labyrinth or portal vault should drop the player. static void _mark_portal_return_point(const coord_def &pos) { // First toss all markers of this type. Stale markers are possible // if the player goes to the Abyss from a portal vault / // labyrinth, thus returning to this level without activating a // previous portal vault exit marker. const std::vector markers = env.markers.get_all(MAT_FEATURE); for (int i = 0, size = markers.size(); i < size; ++i) { if (dynamic_cast(markers[i])->feat == DNGN_EXIT_PORTAL_VAULT) { env.markers.remove(markers[i]); } } if (!env.markers.find(pos, MAT_FEATURE)) { map_feature_marker *mfeat = new map_feature_marker(pos, DNGN_EXIT_PORTAL_VAULT); env.markers.add(mfeat); } } // All changes to you.level_type, you.where_are_you and you.your_level // for descending stairs should happen here. static void _player_change_level_downstairs(dungeon_feature_type stair_find, const level_id &place_override, bool shaft, int shaft_level, const level_id &shaft_dest) { if (_stair_force_destination(place_override)) return; const level_area_type original_level_type(you.level_type); if (you.level_type != LEVEL_DUNGEON && (you.level_type != LEVEL_PANDEMONIUM || stair_find != DNGN_TRANSIT_PANDEMONIUM) && (you.level_type != LEVEL_PORTAL_VAULT || !feat_is_stone_stair(stair_find))) { you.level_type = LEVEL_DUNGEON; } if (stair_find == DNGN_ENTER_HELL) { you.where_are_you = BRANCH_VESTIBULE_OF_HELL; you.hell_exit = you.your_level; you.your_level = 26; } // Welcome message. // Try to find a branch stair. for (int i = 0; i < NUM_BRANCHES; ++i) { if (branches[i].entry_stairs == stair_find) { you.where_are_you = branches[i].id; break; } } if (stair_find == DNGN_ENTER_LABYRINTH) you.level_type = LEVEL_LABYRINTH; else if (stair_find == DNGN_ENTER_ABYSS) you.level_type = LEVEL_ABYSS; else if (stair_find == DNGN_ENTER_PANDEMONIUM) you.level_type = LEVEL_PANDEMONIUM; else if (stair_find == DNGN_ENTER_PORTAL_VAULT) you.level_type = LEVEL_PORTAL_VAULT; if (shaft) { you.your_level = shaft_level; you.where_are_you = shaft_dest.branch; } else if (original_level_type == LEVEL_DUNGEON && you.level_type == LEVEL_DUNGEON) { you.your_level++; } } void _maybe_destroy_trap(const coord_def &p) { trap_def* trap = find_trap(p); if (trap) trap->destroy(); } void down_stairs( int old_level, dungeon_feature_type force_stair, entry_cause_type entry_cause ) { const level_area_type old_level_type = you.level_type; const dungeon_feature_type stair_find = force_stair? force_stair : grd(you.pos()); branch_type old_where = you.where_are_you; const bool shaft = (!force_stair && get_trap_type(you.pos()) == TRAP_SHAFT || force_stair == DNGN_TRAP_NATURAL); level_id shaft_dest; int shaft_level = -1; // Up and down both work for shops. if (stair_find == DNGN_ENTER_SHOP) { shop(); return; } // Up and down both work for portals. if (get_feature_dchar(stair_find) == DCHAR_ARCH && feat_stair_direction(stair_find) != CMD_NO_CMD && stair_find != DNGN_ENTER_ZOT && stair_find != DNGN_RETURN_FROM_ZOT && stair_find != DNGN_EXIT_HELL) { ; } // Probably still need this check here (teleportation) -- bwr else if (feat_stair_direction(stair_find) != CMD_GO_DOWNSTAIRS && !shaft) { if (stair_find == DNGN_STONE_ARCH) mpr("There is nothing on the other side of the stone arch."); else if (stair_find == DNGN_ABANDONED_SHOP) mpr("This shop appears to be closed."); else mpr("You can't go down here!"); return; } if (stair_find == DNGN_ENTER_HELL && you.level_type != LEVEL_DUNGEON) { mpr("You can't enter Hell from outside the dungeon!", MSGCH_ERROR); return; } if (stair_find >= DNGN_ENTER_LABYRINTH && stair_find <= DNGN_ESCAPE_HATCH_DOWN && player_in_branch( BRANCH_VESTIBULE_OF_HELL )) { // Down stairs in vestibule are one-way! mpr("A mysterious force prevents you from descending the staircase."); return; } if (stair_find == DNGN_STONE_ARCH) { mpr("There is nothing on the other side of the stone arch."); return; } if (!force_stair && you.flight_mode() == FL_LEVITATE && !feat_is_gate(stair_find)) { mpr("You're floating high up above the floor!"); return; } if (_stair_moves_pre(stair_find)) return; if (shaft) { const bool known_trap = (grd(you.pos()) != DNGN_UNDISCOVERED_TRAP && !force_stair); if (you.flight_mode() == FL_LEVITATE && !force_stair) { if (known_trap) mpr("You can't fall through a shaft while levitating."); return; } if (!is_valid_shaft_level()) { if (known_trap) mpr("The shaft disappears is a puff of logic!"); _maybe_destroy_trap(you.pos()); return; } shaft_dest = you.shaft_dest(known_trap); if (shaft_dest == level_id::current()) { if (known_trap) { mpr("Strange, the shaft seems to lead back to this level."); mpr("The strain on the space-time continuum destroys the " "shaft!"); } _maybe_destroy_trap(you.pos()); return; } shaft_level = absdungeon_depth(shaft_dest.branch, shaft_dest.depth); #ifdef DGL_MILESTONES if (!known_trap && shaft_level - you.your_level > 1) mark_milestone("shaft", "fell down a shaft to " + short_place_name(shaft_dest) + "."); #endif if (you.flight_mode() != FL_FLY || force_stair) mpr("You fall through a shaft!"); // Shafts are one-time-use. mpr("The shaft crumbles and collapses."); _maybe_destroy_trap(you.pos()); } if (stair_find == DNGN_ENTER_ZOT && !you.opened_zot) { std::vector runes; const int num_runes = runes_in_pack(runes); if (num_runes < NUMBER_OF_RUNES_NEEDED) { switch (NUMBER_OF_RUNES_NEEDED) { case 1: mpr("You need a rune to enter this place."); break; default: mprf("You need at least %d runes to enter this place.", NUMBER_OF_RUNES_NEEDED); } return; } ASSERT(runes.size() >= 3); mprf("You insert %s into the lock.", you.inv[runes[0]].name(DESC_NOCAP_THE).c_str()); #ifdef USE_TILE tiles.add_overlay(you.pos(), tileidx_zap(GREEN)); update_screen(); #else flash_view(LIGHTGREEN); #endif mpr("The lock glows an eerie green colour!"); more(); mprf("You insert %s into the lock.", you.inv[runes[1]].name(DESC_NOCAP_THE).c_str()); big_cloud(CLOUD_BLUE_SMOKE, KC_YOU, you.pos(), 20, 7 + random2(7)); viewwindow(false, true); mpr("Heavy smoke blows from the lock!"); more(); mprf("You insert %s into the lock.", you.inv[runes[2]].name(DESC_NOCAP_THE).c_str()); if (silenced(you.pos())) mpr("The gate opens wide!"); else mpr("With a loud hiss the gate opens wide!"); more(); you.opened_zot = true; } // Bail if any markers veto the move. if (_marker_vetoes_level_change()) return; const level_id destination_override(_stair_destination_override()); // All checks are done, the player is on the move now. // Magical level changes (Portal, Banishment) need this. clear_trapping_net(); // Fire level-leaving trigger. leaving_level_now(); #ifdef DGL_MILESTONES if (!force_stair && !crawl_state.arena) { // Not entirely accurate - the player could die before // reaching the Abyss. if (grd(you.pos()) == DNGN_ENTER_ABYSS) mark_milestone("abyss.enter", "entered the Abyss!"); else if (grd(you.pos()) == DNGN_EXIT_ABYSS && you.char_direction != GDT_GAME_START) { mark_milestone("abyss.exit", "escaped from the Abyss!"); } } #endif // Interlevel travel data. const bool collect_travel_data = can_travel_interlevel(); const level_id old_level_id = level_id::current(); LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id); const coord_def stair_pos = you.pos(); if (collect_travel_data) old_level_info.update(); // Preserve abyss uniques now, since this Abyss level will be deleted. if (you.level_type == LEVEL_ABYSS) save_abyss_uniques(); if (stair_find == DNGN_ENTER_LABYRINTH) dungeon_terrain_changed(you.pos(), DNGN_STONE_ARCH); if (stair_find == DNGN_ENTER_LABYRINTH || stair_find == DNGN_ENTER_PORTAL_VAULT) { _mark_portal_return_point(you.pos()); } const int shaft_depth = (shaft ? shaft_level - you.your_level : 1); _player_change_level_reset(); _player_change_level_downstairs(stair_find, destination_override, shaft, shaft_level, shaft_dest); // When going downstairs into a special level, delete any previous // instances of it. if (you.level_type != LEVEL_DUNGEON) { std::string lname = make_filename(you.your_name, you.your_level, you.where_are_you, you.level_type, false); #if DEBUG_DIAGNOSTICS mprf( MSGCH_DIAGNOSTICS, "Deleting: %s", lname.c_str() ); #endif unlink(lname.c_str()); } // Did we enter a new branch. const bool entered_branch( you.where_are_you != old_level_id.branch && branches[you.where_are_you].parent_branch == old_level_id.branch); if (stair_find == DNGN_EXIT_ABYSS || stair_find == DNGN_EXIT_PANDEMONIUM) { mpr("You pass through the gate."); if (!you.wizard || !crawl_state.is_replaying_keys()) more(); } if (old_level_type != you.level_type && you.level_type == LEVEL_DUNGEON) mprf("Welcome back to %s!", branches[you.where_are_you].longname); if (!you.airborne() && you.confused() && !feat_is_escape_hatch(stair_find) && force_stair != DNGN_ENTER_ABYSS && coinflip()) { const char* fall_where = "down the stairs"; if (!feat_is_staircase(stair_find)) fall_where = "through the gate"; mprf("In your confused state, you trip and fall %s.", fall_where); // Note that this only does damage; it doesn't cancel the level // transition. if (!feat_is_staircase(stair_find)) ouch(1, NON_MONSTER, KILLED_BY_FALLING_THROUGH_GATE); else ouch(1, NON_MONSTER, KILLED_BY_FALLING_DOWN_STAIRS); } dungeon_feature_type stair_taken = stair_find; if (you.level_type == LEVEL_ABYSS) stair_taken = DNGN_FLOOR; if (you.level_type == LEVEL_PANDEMONIUM) stair_taken = DNGN_TRANSIT_PANDEMONIUM; if (shaft) stair_taken = DNGN_ESCAPE_HATCH_DOWN; switch (you.level_type) { case LEVEL_LABYRINTH: if (you.species == SP_MINOTAUR) mpr("You feel right at home here."); else mpr("You enter a dark and forbidding labyrinth."); break; case LEVEL_ABYSS: if (!force_stair) mpr("You enter the Abyss!"); mpr("To return, you must find a gate leading back."); if (you.religion == GOD_CHEIBRIADOS) mpr("You feel Cheibriados slowing down the madness of this place.", MSGCH_GOD, GOD_CHEIBRIADOS); learned_something_new(TUT_ABYSS); break; case LEVEL_PANDEMONIUM: if (old_level_type == LEVEL_PANDEMONIUM) mpr("You pass into a different region of Pandemonium."); else { mpr("You enter the halls of Pandemonium!"); mpr("To return, you must find a gate leading back."); } break; default: if (shaft) { if (you.flight_mode() == FL_FLY && !force_stair) mpr("You dive down through the shaft."); handle_items_on_shaft(you.pos(), false); } else climb_message(stair_find, false, old_level_type); break; } if (!shaft) exit_stair_message(stair_find, false); if (entered_branch) { if (branches[you.where_are_you].entry_message) mpr(branches[you.where_are_you].entry_message); else mprf("Welcome to %s!", branches[you.where_are_you].longname); } if (stair_find == DNGN_ENTER_HELL) { mpr("Welcome to Hell!"); mpr("Please enjoy your stay."); // Kill -more- prompt if we're traveling. if (!you.running && !force_stair) more(); } const bool newlevel = load(stair_taken, LOAD_ENTER_LEVEL, old_level_type, old_level, old_where); set_entry_cause(entry_cause, old_level_type); entry_cause = you.entry_cause; if (newlevel) { // When entering a new level, reset friendly_pickup to default. you.friendly_pickup = Options.default_friendly_pickup; switch (you.level_type) { case LEVEL_DUNGEON: // Xom thinks it's funny if you enter a new level via shaft // or escape hatch, for shafts it's funnier the deeper you fell. if (shaft || feat_is_escape_hatch(stair_find)) xom_is_stimulated(shaft_depth * 50); else xom_is_stimulated(14); break; case LEVEL_PORTAL_VAULT: // Portal vaults aren't as interesting. xom_is_stimulated(25); break; case LEVEL_LABYRINTH: // Finding the way out of a labyrinth interests Xom, // but less so for Minotaurs. (though not now, as they cannot // map the labyrinth any more {due}) xom_is_stimulated(98); break; case LEVEL_ABYSS: case LEVEL_PANDEMONIUM: { // Paranoia if (old_level_type == you.level_type) break; PlaceInfo &place_info = you.get_place_info(); generate_random_blood_spatter_on_level(); // Entering voluntarily only stimulates Xom if you've never // been there before if ((place_info.num_visits == 1 && place_info.levels_seen == 1) || entry_cause != EC_SELF_EXPLICIT) { if (crawl_state.is_god_acting()) xom_is_stimulated(255); else if (entry_cause == EC_SELF_EXPLICIT) { // Entering Pandemonium or the Abyss for the first // time *voluntarily* stimulates Xom much more than // entering a normal dungeon level for the first time. xom_is_stimulated(128, XM_INTRIGUED); } else if (entry_cause == EC_SELF_RISKY) xom_is_stimulated(128); else xom_is_stimulated(255); } break; } default: ASSERT(false); } } unsigned char pc = 0; unsigned char pt = random2avg(28, 3); switch (you.level_type) { case LEVEL_ABYSS: grd(you.pos()) = DNGN_FLOOR; init_pandemonium(); // colours only if (player_in_hell()) { you.where_are_you = BRANCH_MAIN_DUNGEON; you.your_level = you.hell_exit - 1; } break; case LEVEL_PANDEMONIUM: if (old_level_type == LEVEL_PANDEMONIUM) { init_pandemonium(); for (pc = 0; pc < pt; pc++) pandemonium_mons(); } else { init_pandemonium(); for (pc = 0; pc < pt; pc++) pandemonium_mons(); if (player_in_hell()) { you.where_are_you = BRANCH_MAIN_DUNGEON; you.hell_exit = 26; you.your_level = 26; } } break; default: break; } you.turn_is_over = true; save_game_state(); new_level(); // Clear list of beholding monsters. you.clear_beholders(); if (you.skills[SK_TRANSLOCATIONS] > 0 && !allow_control_teleport( true )) mpr( "You sense a powerful magical force warping space.", MSGCH_WARN ); trackers_init_new_level(true); viewwindow(true); maybe_update_stashes(); if (collect_travel_data) { // Update stair information for the stairs we just descended, and the // upstairs we're currently on. level_id new_level_id = level_id::current(); if (can_travel_interlevel()) { LevelInfo &new_level_info = travel_cache.get_level_info(new_level_id); new_level_info.update(); // First we update the old level's stair. level_pos lp; lp.id = new_level_id; lp.pos = you.pos(); old_level_info.update_stair(stair_pos, lp); // Then the new level's stair, assuming arbitrarily that going // upstairs will land you on the same downstairs you took to begin // with (not necessarily true). lp.id = old_level_id; lp.pos = stair_pos; new_level_info.update_stair(you.pos(), lp, true); } } request_autopickup(); } void trackers_init_new_level(bool transit) { travel_init_new_level(); if (transit) stash_init_new_level(); } void new_level(void) { if (you.level_type == LEVEL_PORTAL_VAULT) { // This here because place_name can't find the name of a level that you // *are no longer on* when it spits out the new notes list. std::string desc = "Entered " + place_name(get_packed_place(), true, true); take_note(Note(NOTE_DUNGEON_LEVEL_CHANGE, 0, 0, NULL, desc.c_str())); } else take_note(Note(NOTE_DUNGEON_LEVEL_CHANGE)); print_stats_level(); #ifdef DGL_WHEREIS whereis_record(); #endif } std::string weird_glowing_colour() { return getMiscString("glowing_colour_name"); } std::string weird_writing() { return getMiscString("writing_name"); } std::string weird_smell() { return getMiscString("smell_name"); } std::string weird_sound() { return getMiscString("sound_name"); } bool scramble(void) { ASSERT(!crawl_state.arena); // Statues are too stiff and heavy to scramble out of the water. if (you.attribute[ATTR_TRANSFORMATION] == TRAN_STATUE) return (false); int max_carry = carrying_capacity(); // When highly encumbered, scrambling out is hard to do. if ((max_carry / 2) + random2(max_carry / 2) <= you.burden) return (false); else return (true); } bool go_berserk(bool intentional, bool potion) { ASSERT(!crawl_state.arena); if (!you.can_go_berserk(intentional, potion)) return (false); if (stasis_blocks_effect(true, "%s thrums violently and saps your rage.", 3, "%s vibrates violently and saps your rage.")) { return (false); } if (Tutorial.tutorial_left) Tutorial.tut_berserk_counter++; mpr("A red film seems to cover your vision as you go berserk!"); mpr("You feel yourself moving faster!"); mpr("You feel mighty!"); // Cutting the duration in half since berserk causes haste and hasted // actions have half the usual delay. This keeps player turns // approximately consistent withe previous versions. -cao int berserk_duration = (20 + random2avg(19,2)) / 2; you.increase_duration(DUR_BERSERKER, berserk_duration); calc_hp(); you.hp = you.hp * 3 / 2; deflate_hp(you.hp_max, false); if (!you.duration[DUR_MIGHT]) modify_stat(STAT_STRENGTH, 5, true, "going berserk"); you.increase_duration(DUR_MIGHT, berserk_duration); /// doubling the duration here since haste_player already cuts input // durations in half haste_player(berserk_duration * 2); did_god_conduct(DID_HASTY, 8, intentional); if (you.berserk_penalty != NO_BERSERK_PENALTY) you.berserk_penalty = 0; return (true); } // Returns true if the monster has a path to the player, or it has to be // assumed that this is the case. static bool _mons_has_path_to_player(const monsters *mon) { // Don't consider sleeping monsters safe, in case the player would // rather retreat and try another path for maximum stabbing chances. // TODO: This doesn't cover monsters encaged in glass. if (mon->asleep()) return (true); // If the monster is awake and knows a path towards the player // (even though the player cannot know this) treat it as unsafe. if (mon->travel_target == MTRAV_PLAYER) return (true); if (mon->travel_target == MTRAV_KNOWN_UNREACHABLE) return (false); // Try to find a path from monster to player, using the map as it's // known to the player and assuming unknown terrain to be traversable. monster_pathfind mp; const int range = mons_tracking_range(mon); if (range > 0) mp.set_range(range); if (mp.init_pathfind(mon, you.pos(), true, false, true)) return (true); // Now we know the monster cannot possibly reach the player. mon->travel_target = MTRAV_KNOWN_UNREACHABLE; return (false); } bool mons_can_hurt_player(const monsters *mon) { // FIXME: This takes into account whether the player knows the map! // It also always returns true for sleeping monsters, but that's okay // for its current purposes. (Travel interruptions and tension.) if (_mons_has_path_to_player(mon)) return (true); // The monster need only see you to hurt you. if (mons_has_los_attack(mon)) return (true); // Even if the monster can not actually reach the player it might // still use some ranged form of attack. if (you.see_cell_no_trans(mon->pos()) && mons_has_ranged_ability(mon)) return (true); return (false); } bool mons_is_safe(const monsters *mon, bool want_move, bool consider_user_options) { int dist = grid_distance(you.pos(), mon->pos()); bool is_safe = (mon->wont_attack() || mons_class_flag(mon->type, M_NO_EXP_GAIN) && mon->type != MONS_KRAKEN_TENTACLE || mon->pacified() && dist > 1 || mon->type == MONS_BALLISTOMYCETE && mon->number == 0 #ifdef WIZARD // Wizmode skill setting enforces hiddenness. || you.skills[SK_STEALTH] > 27 && dist > 2 #endif // Only seen through glass walls or within water? // Assuming that there are no water-only/lava-only // monsters capable of throwing or zapping wands. || (!you.see_cell_no_trans(mon->pos()) || mons_class_habitat(mon->type) == HT_WATER || mons_class_habitat(mon->type) == HT_LAVA) && !mons_can_hurt_player(mon)); #ifdef CLUA_BINDINGS if (consider_user_options) { bool moving = (!you.delay_queue.empty() && is_run_delay(you.delay_queue.front().type) && you.delay_queue.front().type != DELAY_REST || you.running < RMODE_NOT_RUNNING || want_move); bool result = is_safe; if (clua.callfn("ch_mon_is_safe", "Mbbd>b", mon, is_safe, moving, dist, &result)) { is_safe = result; } } #endif return (is_safe); } // Return all nearby monsters in range (default: LOS) that the player // is able to recognise as being monsters (i.e. no unknown mimics or // submerged creatures.) // // want_move (??) Somehow affects what monsters are considered dangerous // just_check Return zero or one monsters only // dangerous_only Return only "dangerous" monsters // require_visible Require that monsters be visible to the player // range search radius (defaults: LOS) // std::vector get_nearby_monsters(bool want_move, bool just_check, bool dangerous_only, bool consider_user_options, bool require_visible, int range) { ASSERT(!crawl_state.arena); if (range == -1) range = LOS_RADIUS; std::vector mons; // Sweep every visible square within range. for (radius_iterator ri(you.pos(), range); ri; ++ri) { if (monsters* mon = monster_at(*ri)) { if (mon->alive() && (!require_visible || mon->visible_to(&you)) && !mon->submerged() && !mons_is_unknown_mimic(mon) && (!dangerous_only || !mons_is_safe(mon, want_move, consider_user_options))) { mons.push_back(mon); if (just_check) // stop once you find one break; } } } return mons; } bool i_feel_safe(bool announce, bool want_move, bool just_monsters, int range) { if (!just_monsters) { // check clouds if (in_bounds(you.pos()) && env.cgrid(you.pos()) != EMPTY_CLOUD) { const int cloudidx = env.cgrid(you.pos()); const cloud_type type = env.cloud[cloudidx].type; if (is_damaging_cloud(type, want_move)) { if (announce) { mprf(MSGCH_WARN, "You're standing in a cloud of %s!", cloud_name(cloudidx).c_str()); } return (false); } } // No monster will attack you inside a sanctuary, // so presence of monsters won't matter -- until it starts shrinking... if (is_sanctuary(you.pos()) && env.sanctuary_time >= 5) return (true); } // Monster check. std::vector visible = get_nearby_monsters(want_move, !announce, true, true, true, range); // No monsters found. if (visible.empty()) return (true); // Announce the presence of monsters (Eidolos). if (visible.size() == 1) { const monsters &m = *visible[0]; if (announce) { std::string monname = (mons_is_mimic(m.type)) ? "a mimic" : m.name(DESC_NOCAP_A); mprf(MSGCH_WARN, "Not with %s in view!", monname.c_str()); } } else if (announce && visible.size() > 1) mprf(MSGCH_WARN, "Not with these monsters around!"); return (false); } bool there_are_monsters_nearby(bool dangerous_only, bool require_visible, bool consider_user_options) { return (!get_nearby_monsters(false, true, dangerous_only, consider_user_options, require_visible).empty()); } static const char *shop_types[] = { "weapon", "armour", "antique weapon", "antique armour", "antiques", "jewellery", "wand", "book", "food", "distillery", "scroll", "general" }; int str_to_shoptype(const std::string &s) { if (s == "random" || s == "any") return (SHOP_RANDOM); for (unsigned i = 0; i < sizeof(shop_types) / sizeof (*shop_types); ++i) { if (s == shop_types[i]) return (i); } return (-1); } // General threat = sum_of_logexpervalues_of_nearby_unfriendly_monsters. // Highest threat = highest_logexpervalue_of_nearby_unfriendly_monsters. static void monster_threat_values(double *general, double *highest, bool *invis) { *invis = false; double sum = 0; int highest_xp = -1; for (monster_iterator mi(&you.get_los()); mi; ++mi) { if (!mi->friendly()) { const int xp = exper_value(*mi); const double log_xp = log((double)xp); sum += log_xp; if (xp > highest_xp) { highest_xp = xp; *highest = log_xp; } if (!you.can_see(*mi)) *invis = true; } } *general = sum; } bool player_in_a_dangerous_place(bool *invis) { bool junk; if (invis == NULL) invis = &junk; const double logexp = log((double)you.experience); double gen_threat = 0.0, hi_threat = 0.0; monster_threat_values(&gen_threat, &hi_threat, invis); return (gen_threat > logexp * 1.3 || hi_threat > logexp / 2); } //////////////////////////////////////////////////////////////////////////// // Living breathing dungeon stuff. // static std::vector sfx_seeds; void setup_environment_effects() { sfx_seeds.clear(); for (int x = X_BOUND_1; x <= X_BOUND_2; ++x) { for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y) { if (!in_bounds(x, y)) continue; const int grid = grd[x][y]; if (grid == DNGN_LAVA || (grid == DNGN_SHALLOW_WATER && you.where_are_you == BRANCH_SWAMP)) { const coord_def c(x, y); sfx_seeds.push_back(c); } } } #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "%u environment effect seeds", sfx_seeds.size()); #endif } static void apply_environment_effect(const coord_def &c) { const dungeon_feature_type grid = grd(c); // Don't apply if if the feature doesn't want it. if (testbits(env.pgrid(c), FPROP_NO_CLOUD_GEN)) return; if (grid == DNGN_LAVA) check_place_cloud(CLOUD_BLACK_SMOKE, c, random_range(4, 8), KC_OTHER); else if (grid == DNGN_SHALLOW_WATER) check_place_cloud(CLOUD_MIST, c, random_range(2, 5), KC_OTHER); } static const int Base_Sfx_Chance = 5; void run_environment_effects() { if (!you.time_taken) return; dungeon_events.fire_event(DET_TURN_ELAPSED); // Each square in sfx_seeds has this chance of doing something special // per turn. const int sfx_chance = Base_Sfx_Chance * you.time_taken / 10; const int nseeds = sfx_seeds.size(); // If there are a large number of seeds, speed things up by fudging the // numbers. if (nseeds > 50) { int nsels = div_rand_round( sfx_seeds.size() * sfx_chance, 100 ); if (one_chance_in(5)) nsels += random2(nsels * 3); for (int i = 0; i < nsels; ++i) apply_environment_effect( sfx_seeds[ random2(nseeds) ] ); } else { for (int i = 0; i < nseeds; ++i) { if (random2(100) >= sfx_chance) continue; apply_environment_effect( sfx_seeds[i] ); } } run_corruption_effects(you.time_taken); shoals_apply_tides(div_rand_round(you.time_taken, 10)); } coord_def pick_adjacent_free_square(const coord_def& p) { int num_ok = 0; coord_def result(-1, -1); for (adjacent_iterator ai(p); ai; ++ai) if (grd(*ai) == DNGN_FLOOR && monster_at(*ai) == NULL) if (one_chance_in(++num_ok)) result = *ai; return result; } // Converts a movement speed to a duration. i.e., answers the // question: if the monster is so fast, how much time has it spent in // its last movement? // // If speed is 10 (normal), one movement is a duration of 10. // If speed is 1 (very slow), each movement is a duration of 100. // If speed is 15 (50% faster than normal), each movement is a duration of // 6.6667. int speed_to_duration(int speed) { if (speed < 1) speed = 10; else if (speed > 100) speed = 100; return div_rand_round(100, speed); } void reveal_secret_door(const coord_def& p) { ASSERT(grd(p) == DNGN_SECRET_DOOR); dungeon_feature_type door = grid_secret_door_appearance(p); // Former secret doors become known but are still hidden to monsters // until opened. grd(p) = feat_is_opaque(door) ? DNGN_DETECTED_SECRET_DOOR : DNGN_OPEN_DOOR; viewwindow(false); learned_something_new(TUT_SEEN_SECRET_DOOR, p); // If a transparent secret door was forced open to preserve LOS, // check if it had an opening prompt. if (grd(p) == DNGN_OPEN_DOOR) { std::string door_open_prompt = env.markers.property_at(p, MAT_ANY, "door_open_prompt"); if (!door_open_prompt.empty()) { mprf("That secret door had a prompt on it:", MSGCH_PROMPT); mprf(MSGCH_PROMPT, "%s", door_open_prompt.c_str()); if (!is_exclude_root(p)) { if (yesno("Put travel exclusion on door? (Y/n)", true, 'y')) // Zero radius exclusion right on top of door. set_exclude(p, 0); } } } } // A feeble attempt at Nethack-like completeness for cute messages. std::string your_hand(bool plural) { std::string result; switch (you.attribute[ATTR_TRANSFORMATION]) { default: mpr("ERROR: unknown transformation in your_hand() (spells4.cc)", MSGCH_ERROR); case TRAN_NONE: case TRAN_STATUE: case TRAN_LICH: result = (you.has_usable_claws()) ? "claw" : "hand"; break; case TRAN_ICE_BEAST: result = "hand"; break; case TRAN_SPIDER: case TRAN_PIG: result = "front leg"; break; case TRAN_DRAGON: case TRAN_BAT: result = "foreclaw"; break; case TRAN_BLADE_HANDS: result = "scythe-like blade"; break; } if (plural) result += 's'; return result; } bool stop_attack_prompt(const monsters *mon, bool beam_attack, coord_def beam_target) { ASSERT(!crawl_state.arena); if (you.confused() || !you.can_see(mon)) return (false); bool retval = false; bool prompt = false; const bool mon_target = (beam_target == mon->pos()); const bool inSanctuary = (is_sanctuary(you.pos()) || is_sanctuary(mon->pos())); const bool wontAttack = mon->wont_attack(); const bool isFriendly = mon->friendly(); const bool isNeutral = mon->neutral(); const bool isUnchivalric = is_unchivalric_attack(&you, mon); const bool isHoly = mon->is_holy() && (mon->attitude != ATT_HOSTILE || testbits(mon->flags, MF_NO_REWARD) || testbits(mon->flags, MF_WAS_NEUTRAL)); if (isFriendly) { // Listed in the form: "your rat", "Blork the orc". std::string verb = ""; bool need_mon_name = true; if (beam_attack) { verb = "fire "; if (mon_target) verb += "at "; else if (you.pos() < beam_target && beam_target < mon->pos() || you.pos() > beam_target && beam_target > mon->pos()) { verb += "in " + mon->name(DESC_NOCAP_THE) + "'s direction"; need_mon_name = false; } else verb += "through "; } else verb = "attack "; if (need_mon_name) verb += mon->name(DESC_NOCAP_THE); snprintf(info, INFO_SIZE, "Really %s%s?", verb.c_str(), (inSanctuary) ? ", despite your sanctuary" : ""); prompt = true; } else if (inSanctuary || wontAttack || (you.religion == GOD_JIYVA && mons_is_slime(mon)) || (isNeutral || isHoly) && is_good_god(you.religion) || isUnchivalric && you.religion == GOD_SHINING_ONE && !tso_unchivalric_attack_safe_monster(mon)) { snprintf(info, INFO_SIZE, "Really %s the %s%s%s%s%s?", (beam_attack) ? (mon_target) ? "fire at" : "fire through" : "attack", (isUnchivalric) ? "helpless " : "", (isFriendly) ? "friendly " : (wontAttack) ? "non-hostile " : (isNeutral) ? "neutral " : "", (isHoly) ? "holy " : "", mon->name(DESC_PLAIN).c_str(), (inSanctuary) ? ", despite your sanctuary" : ""); prompt = true; } if (prompt) retval = !yesno(info, false, 'n'); return (retval); } bool is_orckind(const actor *act) { if (mons_genus(act->mons_species()) == MONS_ORC) return (true); if (act->atype() == ACT_MONSTER) { const monsters* mon = dynamic_cast(act); if (mons_is_zombified(mon) && mons_genus(mon->base_monster) == MONS_ORC) { return (true); } } return (false); } bool is_dragonkind(const actor *act) { if (mons_genus(act->mons_species()) == MONS_DRAGON || mons_genus(act->mons_species()) == MONS_DRACONIAN) { return (true); } if (act->atype() == ACT_PLAYER) return (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON); // Else the actor is a monster. const monsters* mon = dynamic_cast(act); if (mon->type == MONS_SERPENT_OF_HELL) return (true); if (mons_is_zombified(mon) && (mons_genus(mon->base_monster) == MONS_DRAGON || mons_genus(mon->base_monster) == MONS_DRACONIAN)) { return (true); } return (false); } // Make the player swap positions with a given monster. void swap_with_monster(monsters *mon_to_swap) { monsters& mon(*mon_to_swap); ASSERT(mon.alive()); const coord_def newpos = mon.pos(); // Be nice: no swapping into uninhabitable environments. if (!you.is_habitable(newpos) || !mon.is_habitable(you.pos())) { mpr("You spin around."); return; } const bool mon_caught = mon.caught(); const bool you_caught = you.attribute[ATTR_HELD]; // If it was submerged, it surfaces first. mon.del_ench(ENCH_SUBMERGED); mprf("You swap places with %s.", mon.name(DESC_NOCAP_THE).c_str()); // Pick the monster up. mgrd(newpos) = NON_MONSTER; mon.moveto(you.pos()); // Plunk it down. mgrd(mon.pos()) = mon_to_swap->mindex(); if (you_caught) { check_net_will_hold_monster(&mon); if (!mon_caught) you.attribute[ATTR_HELD] = 0; } // Move you to its previous location. move_player_to_grid(newpos, false, true, true, false); if (mon_caught) { if (you.body_size(PSIZE_BODY) >= SIZE_GIANT) { mpr("The net rips apart!"); you.attribute[ATTR_HELD] = 0; int net = get_trapping_net(you.pos()); if (net != NON_ITEM) destroy_item(net); } else { you.attribute[ATTR_HELD] = 10; mpr("You become entangled in the net!"); // Xom thinks this is hilarious if you trap yourself this way. if (you_caught) xom_is_stimulated(16); else xom_is_stimulated(255); } if (!you_caught) mon.del_ench(ENCH_HELD, true); } } // AutoID an equipped ring of teleport. // Code copied from fire/ice in spl-cast.cc void maybe_id_ring_TC() { if (you.duration[DUR_CONTROL_TELEPORT] || player_mutation_level(MUT_TELEPORT_CONTROL)) { return; } int num_unknown = 0; for (int i = EQ_LEFT_RING; i <= EQ_RIGHT_RING; ++i) { if (player_wearing_slot(i) && !item_ident(you.inv[you.equip[i]], ISFLAG_KNOW_PROPERTIES)) { ++num_unknown; } } if (num_unknown != 1) return; for (int i = EQ_LEFT_RING; i <= EQ_RIGHT_RING; ++i) if (player_wearing_slot(i)) { item_def& ring = you.inv[you.equip[i]]; if (!item_ident(ring, ISFLAG_KNOW_PROPERTIES) && ring.sub_type == RING_TELEPORT_CONTROL) { set_ident_type( ring.base_type, ring.sub_type, ID_KNOWN_TYPE ); set_ident_flags(ring, ISFLAG_KNOW_PROPERTIES); mprf("You are wearing: %s", ring.name(DESC_INVENTORY_EQUIP).c_str()); } } }