/* * File: food.cc * Summary: Functions for eating and butchering. * Written by: Linley Henzell */ #include "AppHdr.h" #include "food.h" #include #include #include #include #include "externs.h" #include "options.h" #include "artefact.h" #include "cio.h" #include "clua.h" #include "command.h" #include "debug.h" #include "delay.h" #include "effects.h" #include "env.h" #include "invent.h" #include "items.h" #include "itemname.h" #include "itemprop.h" #include "item_use.h" #include "it_use2.h" #include "macro.h" #include "message.h" #include "misc.h" #include "mon-util.h" #include "mutation.h" #include "output.h" #include "player.h" #include "random.h" #include "religion.h" #include "skills2.h" #include "spells2.h" #include "state.h" #include "stuff.h" #include "transform.h" #include "tutorial.h" #include "xom.h" static corpse_effect_type _determine_chunk_effect(corpse_effect_type chunktype, bool rotten_chunk); static void _eat_chunk(corpse_effect_type chunk_effect, bool cannibal, int mon_intel = 0); static void _eating(unsigned char item_class, int item_type); static void _describe_food_change(int hunger_increment); static bool _vampire_consume_corpse(int slot, bool invent); static void _heal_from_food(int hp_amt, int mp_amt = 0, bool unrot = false, bool restore_str = false); /* * BEGIN PUBLIC FUNCTIONS */ void make_hungry(int hunger_amount, bool suppress_msg, bool allow_reducing) { if (you.is_undead == US_UNDEAD) return; if (allow_reducing) hunger_amount = calc_hunger(hunger_amount); if (hunger_amount == 0 && !suppress_msg) return; #if DEBUG_DIAGNOSTICS set_redraw_status( REDRAW_HUNGER ); #endif you.hunger -= hunger_amount; if (you.hunger < 0) you.hunger = 0; // So we don't get two messages, ever. bool state_message = food_change(); if (!suppress_msg && !state_message) _describe_food_change( -hunger_amount ); } void lessen_hunger(int satiated_amount, bool suppress_msg) { if (you.is_undead == US_UNDEAD) return; you.hunger += satiated_amount; if (you.hunger > 12000) you.hunger = 12000; // So we don't get two messages, ever. bool state_message = food_change(); if (!suppress_msg && !state_message) _describe_food_change(satiated_amount); } void set_hunger(int new_hunger_level, bool suppress_msg) { if (you.is_undead == US_UNDEAD) return; int hunger_difference = (new_hunger_level - you.hunger); if (hunger_difference < 0) make_hungry(-hunger_difference, suppress_msg); else if (hunger_difference > 0) lessen_hunger(hunger_difference, suppress_msg); } // More of a "weapon_switch back from butchering" function, switching // to a weapon is done using the wield_weapon code. // special cases like staves of power or other special weps are taken // care of by calling wield_effects(). {gdl} void weapon_switch(int targ) { // Give the player an option to abort. if (you.weapon() && !check_old_item_warning(*you.weapon(), OPER_WIELD)) return; if (targ == -1) // Unarmed Combat. { // Already unarmed? if (!you.weapon()) return; mpr( "You switch back to your bare hands." ); } else { // Possibly not valid anymore (dropped, etc.). if (!you.inv[targ].is_valid()) return; // Already wielding this weapon? if (you.equip[EQ_WEAPON] == you.inv[targ].link) return; mprf("Switching back to %s.", you.inv[targ].name(DESC_INVENTORY).c_str()); } // Unwield the old weapon and wield the new. // XXX This is a pretty dangerous hack; I don't like it.--GDL // // Well yeah, but that's because interacting with the wielding // code is a mess... this whole function's purpose was to // isolate this hack until there's a proper way to do things. -- bwr if (you.weapon()) unwield_item(false); you.equip[EQ_WEAPON] = targ; // Special checks: staves of power, etc. if (targ != -1) wield_effects(targ, false); if (Options.chunks_autopickup || you.species == SP_VAMPIRE) autopickup(); // Same amount of time as normal wielding. // FIXME: this duplicated code is begging for a bug. if (you.weapon()) you.time_taken /= 2; else // Swapping to empty hands is faster. { you.time_taken *= 3; you.time_taken /= 10; } you.wield_change = true; you.turn_is_over = true; } // Look for a butchering implement. If fallback is true, // prompt the user if no obvious options exist. // Returns whether a weapon was switched. static bool _find_butchering_implement(int &butcher_tool) { // When berserk, you can't change weapons. Sanity check! if (!can_wield(NULL, true)) return (false); // If wielding a distortion weapon, never attempt to switch away // automatically. if (const item_def *wpn = you.weapon()) { // Otherwise we wouldn't be here. ASSERT(!can_cut_meat( *wpn )); if (wpn->base_type == OBJ_WEAPONS && item_type_known(*wpn) && get_weapon_brand(*wpn) == SPWPN_DISTORTION) { mprf(MSGCH_WARN, "You're wielding a weapon of distortion, will not autoswap " "for butchering."); return (false); } } bool potential_candidate = false; // Look for a butchering implement in your pack. for (int i = 0; i < ENDOFPACK; ++i) { item_def& tool(you.inv[i]); if (tool.is_valid() && tool.base_type == OBJ_WEAPONS && can_cut_meat( tool ) && can_wield( &tool ) // Don't even suggest autocursing items. // Note that unknown autocursing is OK. && (!is_artefact(tool) || (artefact_known_wpn_property(tool, ARTP_CURSED) <= 0))) { if (Options.easy_butcher && item_known_uncursed(tool) && item_type_known(tool) && get_weapon_brand(tool) != SPWPN_DISTORTION // Don't even ask! && !has_warning_inscription(tool, OPER_WIELD)) { butcher_tool = i; return (true); } else potential_candidate = true; } } if (!potential_candidate) { mpr("You don't carry any weapon you could use for butchering."); if (Tutorial.tutorial_left) { mpr("You should pick up the first knife, dagger, sword or axe " "you find so you can use it to butcher corpses.", MSGCH_TUTORIAL); } return (false); } const int item_slot = prompt_invent_item( "What would you like to use? (- for none)?", MT_INVLIST, OSEL_BUTCHERY, true, true, true, '-', -1, NULL, OPER_WIELD); if (prompt_failed(item_slot)) { canned_msg(MSG_OK); return (false); } else if (item_slot == PROMPT_GOT_SPECIAL) { if (you.has_claws() || transform_can_butcher_barehanded( static_cast( you.attribute[ATTR_TRANSFORMATION]))) { butcher_tool = -1; return (true); } else { mpr("You can't butcher without a weapon!"); return (false); } } else if (item_slot == you.equip[EQ_WEAPON]) { mpr("You are already wielding that!"); return (false); } if (you.inv[item_slot].is_valid() && you.inv[item_slot].base_type == OBJ_WEAPONS && can_cut_meat(you.inv[item_slot])) { if (can_wield(&you.inv[item_slot])) { butcher_tool = item_slot; return (true); } mpr("You can't wield this item!"); return (false); } mpr("That item isn't sharp enough!"); return (false); } static bool _prepare_butchery(bool can_butcher, bool removed_gloves, bool wpn_switch, int butchering_tool) { // No preparation necessary. if (can_butcher) return (true); // At least one of these has to be true, else what are we doing // here? ASSERT(removed_gloves || wpn_switch); // If you can butcher by taking off your gloves, don't prompt. if (removed_gloves) { // Actually take off the gloves; this creates a delay. We // assume later on that gloves have a 1-turn takeoff delay! if (!takeoff_armour(you.equip[EQ_GLOVES])) return (false); // Ensure that the gloves are taken off by now by finishing the // DELAY_ARMOUR_OFF delay started by takeoff_armour() above. // FIXME: get rid of this hack! finish_last_delay(); } if (wpn_switch) { mprf("Switching to %s.", butchering_tool == -1 ? "unarmed" : "a butchering implement"); if (!wield_weapon(true, butchering_tool, false, true)) return (false); } // Switched to a good butchering tool. return (true); } static bool _butcher_corpse(int corpse_id, bool first_corpse = true, bool force_butcher = false) { ASSERT(corpse_id != -1); const bool rotten = food_is_rotten(mitm[corpse_id]); // Start work on the first corpse we butcher. if (first_corpse) mitm[corpse_id].plus2++; int work_req = std::max(0, 4 - mitm[corpse_id].plus2); delay_type dtype = DELAY_BUTCHER; if (!force_butcher && !rotten && can_bottle_blood_from_corpse(mitm[corpse_id].plus)) { dtype = DELAY_BOTTLE_BLOOD; } start_delay(dtype, work_req, corpse_id, mitm[corpse_id].special); you.turn_is_over = true; return (true); } static void _terminate_butchery(bool wpn_switch, bool removed_gloves, int old_weapon, int old_gloves) { // Switch weapon back. if (wpn_switch && you.equip[EQ_WEAPON] != old_weapon && (!you.weapon() || !you.weapon()->cursed())) { start_delay(DELAY_WEAPON_SWAP, 1, old_weapon); } // Put on the removed gloves. if (removed_gloves && you.equip[EQ_GLOVES] != old_gloves) start_delay(DELAY_ARMOUR_ON, 1, old_gloves); } static bool _have_corpses_in_pack(bool remind) { const bool can_bottle = (you.species == SP_VAMPIRE && you.experience_level > 5); int num = 0; int num_bottle = 0; for (int i = 0; i < ENDOFPACK; ++i) { item_def &obj(you.inv[i]); if (!obj.is_valid()) continue; // Only actually count corpses, not skeletons. if (obj.base_type != OBJ_CORPSES || obj.sub_type != CORPSE_BODY) continue; // Only saprovorous characters care about rotten food. if (food_is_rotten(obj) && (!player_mutation_level(MUT_SAPROVOROUS))) { continue; } num++; if (can_bottle && mons_has_blood(obj.plus)) num_bottle++; } if (num == 0) return (false); std::string verb = (num_bottle ? (num == num_bottle ? "bottle" : "bottle or butcher") : "butcher"); std::string noun, pronoun; if (num == 1) { noun = "corpse"; pronoun = "it"; } else { noun = "corpses"; pronoun = "them"; } if (remind) { mprf("You might want to also %s the %s in your pack.", verb.c_str(), noun.c_str()); } else { mprf("If you dropped the %s in your pack you could %s %s.", noun.c_str(), verb.c_str(), pronoun.c_str()); } return (true); } bool butchery(int which_corpse) { if (you.visible_igrd(you.pos()) == NON_ITEM) { if (!_have_corpses_in_pack(false)) mpr("There isn't anything here!"); return (false); } if (you.flight_mode() == FL_LEVITATE) { mpr("You can't reach the floor from up here."); return (false); } const transformation_type transform = static_cast(you.attribute[ATTR_TRANSFORMATION]); // Vampires' fangs are optimised for biting, not for tearing flesh. // (Not that they really need to.) Other species with this mutation // might still benefit from it. bool teeth_butcher = (player_mutation_level(MUT_FANGS) == 3 && you.species != SP_VAMPIRE); bool barehand_butcher = (transform_can_butcher_barehanded(transform) || you.has_claws()) && !player_wearing_slot(EQ_GLOVES); bool gloved_butcher = (you.has_claws() && player_wearing_slot(EQ_GLOVES) && !you.inv[you.equip[EQ_GLOVES]].cursed()); bool can_butcher = (teeth_butcher || barehand_butcher || you.weapon() && can_cut_meat(*you.weapon())); if (!Options.easy_butcher && !can_butcher) { mpr("Maybe you should try using a sharper implement."); return (false); } // It makes more sense that you first find out if there's anything // to butcher, *then* decide to actually butcher it. // The old code did it the other way. if (!can_butcher && you.berserk()) { mpr("You are too berserk to search for a butchering tool!"); return (false); } // First determine how many things there are to butcher. int num_corpses = 0; int corpse_id = -1; bool prechosen = (which_corpse != -1); for (stack_iterator si(you.pos(), true); si; ++si) { if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY) { corpse_id = si->index(); num_corpses++; // Return pre-chosen corpse if it exists. if (prechosen && corpse_id == which_corpse) break; } } if (num_corpses == 0) { if (!_have_corpses_in_pack(false)) { mprf("There isn't anything to %sbutcher here.", (you.species == SP_VAMPIRE && you.experience_level > 5) ? "bottle or " : ""); } return (false); } int old_weapon = you.equip[EQ_WEAPON]; int old_gloves = you.equip[EQ_GLOVES]; bool wpn_switch = false; bool removed_gloves = false; int butcher_tool = -1; if (!can_butcher) { // Try to find a butchering implement. if (!gloved_butcher) { if (!_find_butchering_implement(butcher_tool)) return (false); } if (butcher_tool == -1 && gloved_butcher) removed_gloves = true; else if (you.equip[EQ_WEAPON] != butcher_tool) wpn_switch = true; } // Butcher pre-chosen corpse, if found, or if there is only one corpse. bool success = false; if (prechosen && corpse_id == which_corpse || num_corpses == 1 && !Options.always_confirm_butcher) { if (!_prepare_butchery(can_butcher, removed_gloves, wpn_switch, butcher_tool)) { return (false); } success = _butcher_corpse(corpse_id, true); _terminate_butchery(wpn_switch, removed_gloves, old_weapon, old_gloves); // Remind player of corpses in pack that could be butchered or // bottled. _have_corpses_in_pack(true); return (success); } // Now pick what you want to butcher. This is only a problem // if there are several corpses on the square. bool butcher_all = false; bool bottle_all = false; // for Vampires bool force_butcher = false; bool repeat_prompt = false; bool did_weap_swap = false; bool first_corpse = true; int keyin; for (stack_iterator si(you.pos(), true); si; ++si) { if (si->base_type != OBJ_CORPSES || si->sub_type != CORPSE_BODY) continue; if (bottle_all && !can_bottle_blood_from_corpse(si->plus)) continue; if (butcher_all || bottle_all) corpse_id = si->index(); else { corpse_id = -1; std::string corpse_name = si->name(DESC_NOCAP_A); // We don't need to check for undead because // * Mummies can't eat. // * Ghouls relish the bad things. // * Vampires won't bottle bad corpses. if (!you.is_undead) { corpse_name = get_message_colour_tags(*si, DESC_NOCAP_A, MSGCH_PROMPT); } // Shall we butcher this corpse? do { mprf(MSGCH_PROMPT, "%s %s? (yc/n/a/q/?)", (!can_bottle_blood_from_corpse(si->plus)) ? "Butcher" : "Bottle", corpse_name.c_str()); repeat_prompt = false; keyin = tolower(getchm(KMC_CONFIRM)); switch (keyin) { case 'b': force_butcher = true; // intentional fall-through case 'y': case 'c': case 'd': case 'a': if (!did_weap_swap) { if (_prepare_butchery(can_butcher, removed_gloves, wpn_switch, butcher_tool)) { did_weap_swap = true; } else return (false); } corpse_id = si->index(); if (keyin == 'a') { if (!force_butcher && can_bottle_blood_from_corpse(si->plus)) { bottle_all = true; } else butcher_all = true; } break; case ESCAPE: case 'q': canned_msg(MSG_OK); _terminate_butchery(wpn_switch, removed_gloves, old_weapon, old_gloves); return (false); case '?': show_butchering_help(); mesclr(); redraw_screen(); repeat_prompt = true; break; default: break; } } while (repeat_prompt); } if (corpse_id != -1) { if (_butcher_corpse(corpse_id, first_corpse, force_butcher || butcher_all)) { success = true; first_corpse = false; } } } if (!butcher_all && !bottle_all && corpse_id == -1) { mprf("There isn't anything else to %s here.", you.species == SP_VAMPIRE && you.experience_level >= 6 ? "bottle" : "butcher"); } _terminate_butchery(wpn_switch, removed_gloves, old_weapon, old_gloves); if (success) { // Remind player of corpses in pack that could be butchered or // bottled. _have_corpses_in_pack(true); } return (success); } bool prompt_eat_inventory_item(int slot) { if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return (false); } int which_inventory_slot; if (you.species != SP_VAMPIRE) { which_inventory_slot = (slot != -1) ? slot : prompt_invent_item("Eat which item?", MT_INVLIST, OBJ_FOOD, true, true, true, 0, -1, NULL, OPER_EAT); } else { which_inventory_slot = (slot != -1) ? slot : prompt_invent_item("Drain what?", MT_INVLIST, OSEL_VAMP_EAT, true, true, true, 0, -1, NULL, OPER_EAT); } if (prompt_failed(which_inventory_slot)) return (false); // This conditional can later be merged into food::can_ingest() when // expanded to handle more than just OBJ_FOOD 16mar200 {dlb} if (you.species != SP_VAMPIRE) { if (you.inv[which_inventory_slot].base_type != OBJ_FOOD) { mpr("You can't eat that!"); return (false); } } else { if (you.inv[which_inventory_slot].base_type != OBJ_CORPSES || you.inv[which_inventory_slot].sub_type != CORPSE_BODY) { mpr("You crave blood!"); return (false); } } if (!can_ingest( you.inv[which_inventory_slot].base_type, you.inv[which_inventory_slot].sub_type, false )) { return (false); } eat_inventory_item(which_inventory_slot); burden_change(); you.turn_is_over = true; return (true); } // [ds] Returns true if something was eaten. bool eat_food(int slot) { if (you.is_undead == US_UNDEAD) { mpr("You can't eat."); crawl_state.zero_turns_taken(); return (false); } if (you.hunger >= 11000) { mprf("You're too full to %s anything.", you.species == SP_VAMPIRE ? "drain" : "eat"); crawl_state.zero_turns_taken(); return (false); } int result; // Skip the prompts if we already know what we're eating. if (slot == -1) { result = prompt_eat_chunks(); if (result == 1 || result == -1) return (result > 0); if (result != -2) // else skip ahead to inventory { if (you.visible_igrd(you.pos()) != NON_ITEM) { result = eat_from_floor(true); if (result == 1) return (true); if (result == -1) return (false); } } } return (prompt_eat_inventory_item(slot)); } // END PUBLIC FUNCTIONS static bool _player_has_enough_food() { int food_value = 0; item_def item; for (unsigned slot = 0; slot < ENDOFPACK; ++slot) { item = you.inv[slot]; if (!item.is_valid()) continue; if (!can_ingest(item.base_type, item.sub_type, true, true, false)) continue; if (food_is_rotten(item) && !player_mutation_level(MUT_SAPROVOROUS)) continue; if (is_poisonous(item)) continue; if (is_mutagenic(item)) continue; if (causes_rot(item)) continue; // Vampires can only drain corpses. if (you.species == SP_VAMPIRE) food_value += 3; else { if (item.base_type != OBJ_FOOD) continue; switch (item.sub_type) { case FOOD_CHUNK: if (!player_mutation_level(MUT_HERBIVOROUS)) food_value += 2 * item.quantity; break; case FOOD_MEAT_RATION: if (!player_mutation_level(MUT_HERBIVOROUS)) food_value += 3 * item.quantity; break; case FOOD_BREAD_RATION: if (!player_mutation_level(MUT_CARNIVOROUS)) food_value += 3 * item.quantity; break; default: // Only count snacks if we really like them if (is_preferred_food(item)) food_value += item.quantity; break; } } } // You have "enough" food if you have, e.g. // 1 meat ration + 1 chunk, or 2 chunks for carnivores, or // 5 items of fruit, or 1 bread ration and 2 fruit items as a herbivore. return (food_value > 5); } static std::string _how_hungry() { if (you.hunger_state > HS_SATIATED) return ("full"); else if (you.species == SP_VAMPIRE) return ("thirsty"); return ("hungry"); } bool food_change(bool suppress_message) { char newstate = HS_ENGORGED; bool state_changed = false; bool less_hungry = false; you.hunger = std::max(you_min_hunger(), you.hunger); you.hunger = std::min(you_max_hunger(), you.hunger); // Get new hunger state. if (you.hunger <= 1000) newstate = HS_STARVING; else if (you.hunger <= 1533) newstate = HS_NEAR_STARVING; else if (you.hunger <= 2066) newstate = HS_VERY_HUNGRY; else if (you.hunger <= 2600) newstate = HS_HUNGRY; else if (you.hunger < 7000) newstate = HS_SATIATED; else if (you.hunger < 9000) newstate = HS_FULL; else if (you.hunger < 11000) newstate = HS_VERY_FULL; if (newstate != you.hunger_state) { state_changed = true; if (newstate > you.hunger_state) less_hungry = true; you.hunger_state = newstate; set_redraw_status( REDRAW_HUNGER ); if (newstate < HS_SATIATED) interrupt_activity( AI_HUNGRY ); if (you.species == SP_VAMPIRE) { if (newstate <= HS_SATIATED) { if (you.duration[DUR_BERSERKER] > 1) { mpr("Your blood-deprived body can't sustain your rage any " "longer.", MSGCH_DURATION); you.duration[DUR_BERSERKER] = 1; } int transform = you.attribute[ATTR_TRANSFORMATION]; if (transform != TRAN_NONE && transform != TRAN_BAT && you.duration[DUR_TRANSFORMATION] > 2 * BASELINE_DELAY) { mpr("Your blood-deprived body can't sustain your " "transformation much longer.", MSGCH_DURATION); you.set_duration(DUR_TRANSFORMATION, 2); } } else if (player_in_bat_form() && you.duration[DUR_TRANSFORMATION] > 5) { print_stats(); mpr("Your blood-filled body can't sustain your transformation " "much longer.", MSGCH_WARN); // Give more time because suddenly stopping flying can be fatal. you.set_duration(DUR_TRANSFORMATION, 5); } else if (newstate == HS_ENGORGED && is_vampire_feeding()) // Alive { print_stats(); mpr("You can't stomach any more blood right now."); } } if (!suppress_message) { std::string msg = "You "; switch (you.hunger_state) { case HS_STARVING: if (you.species == SP_VAMPIRE) msg += "feel devoid of blood!"; else msg += "are starving!"; mprf(MSGCH_FOOD, less_hungry, "%s", msg.c_str()); // Xom thinks this is funny if you're in a labyrinth // and are low on food. if (you.level_type == LEVEL_LABYRINTH && !_player_has_enough_food()) { xom_is_stimulated(64); } learned_something_new(TUT_YOU_STARVING); you.check_awaken(500); break; case HS_NEAR_STARVING: if (you.species == SP_VAMPIRE) msg += "feel almost devoid of blood!"; else msg += "are near starving!"; mprf(MSGCH_FOOD, less_hungry, "%s", msg.c_str()); learned_something_new(TUT_YOU_HUNGRY); break; case HS_VERY_HUNGRY: case HS_HUNGRY: msg += "are feeling "; if (you.hunger_state == HS_VERY_HUNGRY) msg += "very "; msg += _how_hungry(); msg += "."; mprf(MSGCH_FOOD, less_hungry, "%s", msg.c_str()); learned_something_new(TUT_YOU_HUNGRY); break; default: return (state_changed); } } } return (state_changed); } // food_increment is positive for eating, negative for hungering static void _describe_food_change(int food_increment) { int magnitude = (food_increment > 0)?food_increment:(-food_increment); std::string msg; if (magnitude == 0) return; msg = "You feel "; if (magnitude <= 100) msg += "slightly "; else if (magnitude <= 350) msg += "somewhat "; else if (magnitude <= 800) msg += "quite a bit "; else msg += "a lot "; if ((you.hunger_state > HS_SATIATED) ^ (food_increment < 0)) msg += "more "; else msg += "less "; msg += _how_hungry().c_str(); msg += "."; mpr(msg.c_str()); } static bool _player_can_eat_rotten_meat(bool need_msg = false) { if (player_mutation_level(MUT_SAPROVOROUS)) return (true); if (need_msg) mpr("You refuse to eat that rotten meat."); return (false); } // Should really be merged into function below. -- FIXME void eat_inventory_item(int which_inventory_slot) { item_def& food(you.inv[which_inventory_slot]); if (food.base_type == OBJ_CORPSES && food.sub_type == CORPSE_BODY) { _vampire_consume_corpse(which_inventory_slot, true); you.turn_is_over = true; return; } if (food.sub_type == FOOD_CHUNK) { const int mons_type = food.plus; const bool cannibal = is_player_same_species(mons_type); const int intel = mons_class_intel(mons_type) - I_ANIMAL; const bool rotten = food_is_rotten(food); const corpse_effect_type chunk_type = mons_corpse_effect(mons_type); if (rotten && !_player_can_eat_rotten_meat(true)) return; _eat_chunk(_determine_chunk_effect(chunk_type, rotten), cannibal, intel); } else _eating(food.base_type, food.sub_type); you.turn_is_over = true; dec_inv_item_quantity( which_inventory_slot, 1 ); } void eat_floor_item(int item_link) { item_def& food(mitm[item_link]); if (food.base_type == OBJ_CORPSES && food.sub_type == CORPSE_BODY) { if (you.species != SP_VAMPIRE) return; if (_vampire_consume_corpse(item_link, false)) you.turn_is_over = true; return; } else if (food.sub_type == FOOD_CHUNK) { const int intel = mons_class_intel(food.plus) - I_ANIMAL; const bool cannibal = is_player_same_species(food.plus); const bool rotten = food_is_rotten(food); const corpse_effect_type chunk_type = mons_corpse_effect(food.plus); if (rotten && !_player_can_eat_rotten_meat(true)) return; _eat_chunk(_determine_chunk_effect(chunk_type, rotten), cannibal, intel); } else _eating( food.base_type, food.sub_type ); you.turn_is_over = true; dec_mitm_item_quantity( item_link, 1 ); } static bool _is_bad_food(item_def food) { return (is_poisonous(food) || is_mutagenic(food) || is_forbidden_food(food) || causes_rot(food)); } // Returns which of two food items is older (true for first, else false). struct compare_by_freshness { bool operator()(item_def *food1, item_def *food2) { ASSERT(food1->base_type == OBJ_CORPSES || food1->base_type == OBJ_FOOD); ASSERT(food2->base_type == OBJ_CORPSES || food2->base_type == OBJ_FOOD); ASSERT(food1->base_type == food2->base_type); if (is_inedible(*food1)) return (false); if (is_inedible(*food2)) return (true); if (food1->base_type == OBJ_FOOD) { // Prefer chunks to non-chunks. (Herbivores handled above.) if (food1->sub_type != FOOD_CHUNK && food2->sub_type == FOOD_CHUNK) return (false); if (food2->sub_type != FOOD_CHUNK && food1->sub_type == FOOD_CHUNK) return (true); } // Both food types are edible (not rotten, or player is Saprovore). if (food1->base_type == OBJ_CORPSES || food1->sub_type == FOOD_CHUNK && food2->sub_type == FOOD_CHUNK) { // Always offer poisonous/mutagenic chunks last. if (_is_bad_food(*food1) && !_is_bad_food(*food2)) return (false); if (_is_bad_food(*food2) && !_is_bad_food(*food1)) return (true); if (Options.prefer_safe_chunks) { // Offer non-contaminated chunks first. if (is_contaminated(*food1) && !is_contaminated(*food2)) return (false); if (is_contaminated(*food2) && !is_contaminated(*food1)) return (true); } } return (food1->special < food2->special); } }; // Returns -1 for cancel, 1 for eaten, 0 for not eaten. int eat_from_floor(bool skip_chunks) { if (you.flight_mode() == FL_LEVITATE) return (0); // Corpses should have been handled before. if (you.species == SP_VAMPIRE && skip_chunks) return (0); bool need_more = false; int unusable_corpse = 0; int inedible_food = 0; item_def wonteat; bool found_valid = false; std::vector food_items; for (stack_iterator si(you.pos(), true); si; ++si ) { if (you.species == SP_VAMPIRE) { if (si->base_type != OBJ_CORPSES || si->sub_type != CORPSE_BODY) continue; if (!mons_has_blood(si->plus)) { unusable_corpse++; continue; } } else { if (si->base_type != OBJ_FOOD) continue; // Chunks should have been handled before. if (skip_chunks && si->sub_type == FOOD_CHUNK) continue; } if (!skip_chunks && food_is_rotten(*si) && !_player_can_eat_rotten_meat()) { unusable_corpse++; continue; } else if (!can_ingest(si->base_type, si->sub_type, true)) { if (!inedible_food) { wonteat = *si; inedible_food++; } else { // Increase only if we're dealing with different subtypes. // FIXME: Use a common check for herbivorous/carnivorous // dislikes, for e.g. "Blech! You need blood!" ASSERT(wonteat.is_valid()); if (wonteat.sub_type != si->sub_type) inedible_food++; } continue; } found_valid = true; food_items.push_back(&(*si)); } if (found_valid) { std::sort(food_items.begin(), food_items.end(), compare_by_freshness()); for (unsigned int i = 0; i < food_items.size(); ++i) { item_def *item = food_items[i]; std::string item_name = get_message_colour_tags(*item, DESC_NOCAP_A, MSGCH_PROMPT); mprf(MSGCH_PROMPT, "%s %s%s? (ye/n/q/i?)", (you.species == SP_VAMPIRE ? "Drink blood from" : "Eat"), ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); int keyin = tolower(getchm(KMC_CONFIRM)); switch (keyin) { case ESCAPE: case 'q': canned_msg(MSG_OK); return -1; case 'e': case 'y': if (!check_warning_inscriptions(*item, OPER_EAT)) break; if (can_ingest(item->base_type, item->sub_type, false)) { int ilink = item_on_floor(*item, you.pos()); if (ilink != NON_ITEM) { eat_floor_item(ilink); return (true); } return (false); } need_more = true; break; case 'i': case '?': // Directly skip ahead to inventory. return (0); default: // Else no: try next one. break; } } } else { // Give a message about why these food items can not actually be eaten. if (unusable_corpse) { if (you.species == SP_VAMPIRE) { mprf("%s devoid of blood.", unusable_corpse == 1 ? "This corpse is" : "These corpses are"); } else _player_can_eat_rotten_meat(true); need_more = true; } else if (inedible_food) { if (inedible_food == 1) { ASSERT(wonteat.is_valid()); // Use the normal cannot ingest message. if (can_ingest(wonteat.base_type, wonteat.sub_type, false)) { mprf(MSGCH_DIAGNOSTICS, "Error: Can eat %s after all?", wonteat.name(DESC_PLAIN).c_str() ); } } else // Several different food items. mpr("You refuse to eat these food items."); need_more = true; } } if (need_more && Options.auto_list) more(); return (0); } bool eat_from_inventory() { // Corpses should have been handled before. if (you.species == SP_VAMPIRE) return 0; int unusable_corpse = 0; int inedible_food = 0; item_def *wonteat = NULL; bool found_valid = false; std::vector food_items; for (int i = 0; i < ENDOFPACK; ++i) { if (!you.inv[i].is_valid()) continue; item_def *item = &you.inv[i]; if (you.species == SP_VAMPIRE) { if (item->base_type != OBJ_CORPSES || item->sub_type != CORPSE_BODY) continue; if (!mons_has_blood(item->plus)) { unusable_corpse++; continue; } } else { // Chunks should have been handled before. if (item->base_type != OBJ_FOOD || item->sub_type == FOOD_CHUNK) continue; } if (food_is_rotten(*item) && !_player_can_eat_rotten_meat()) { unusable_corpse++; continue; } else if (!can_ingest(item->base_type, item->sub_type, true)) { if (!inedible_food) { wonteat = item; inedible_food++; } else { // Increase only if we're dealing with different subtypes. // FIXME: Use a common check for herbivorous/carnivorous // dislikes, for e.g. "Blech! You need blood!" ASSERT(wonteat->is_valid()); if (wonteat->sub_type != item->sub_type) inedible_food++; } continue; } found_valid = true; food_items.push_back(item); } if (found_valid) { std::sort(food_items.begin(), food_items.end(), compare_by_freshness()); for (unsigned int i = 0; i < food_items.size(); ++i) { item_def *item = food_items[i]; std::string item_name = get_message_colour_tags(*item, DESC_NOCAP_A, MSGCH_PROMPT); mprf(MSGCH_PROMPT, "%s %s%s? (ye/n/q)", (you.species == SP_VAMPIRE ? "Drink blood from" : "Eat"), ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); int keyin = tolower(getchm(KMC_CONFIRM)); switch (keyin) { case ESCAPE: case 'q': canned_msg(MSG_OK); return (false); case 'e': case 'y': if (can_ingest(item->base_type, item->sub_type, false)) { eat_inventory_item(item->link); return (true); } break; default: // Else no: try next one. break; } } } else { // Give a message about why these food items can not actually be eaten. if (unusable_corpse) { if (you.species == SP_VAMPIRE) { mprf("%s devoid of blood.", unusable_corpse == 1 ? "The corpse you are carrying is" : "The corpses you are carrying are"); } else _player_can_eat_rotten_meat(true); } else if (inedible_food) { if (inedible_food == 1) { ASSERT(wonteat->is_valid()); // Use the normal cannot ingest message. if (can_ingest(wonteat->base_type, wonteat->sub_type, false)) { mprf(MSGCH_DIAGNOSTICS, "Error: Can eat %s after all?", wonteat->name(DESC_PLAIN).c_str() ); } } else // Several different food items. mpr("You refuse to eat these food items."); } } return (false); } // Returns -1 for cancel, 1 for eaten, 0 for not eaten, // -2 for skip to inventory. int prompt_eat_chunks() { // Full herbivores cannot eat chunks. if (player_mutation_level(MUT_HERBIVOROUS) == 3) return (0); // If we *know* the player can eat chunks, doesn't have the gourmand // effect and isn't hungry, don't prompt for chunks. if (you.species != SP_VAMPIRE && !player_likes_chunks(true) && !wearing_amulet(AMU_THE_GOURMAND, false) && you.hunger_state >= HS_SATIATED) { return (0); } bool found_valid = false; std::vector chunks; // First search the stash on the floor, unless levitating. if (you.flight_mode() != FL_LEVITATE) { for (stack_iterator si(you.pos(), true); si; ++si) { if (you.species == SP_VAMPIRE) { if (si->base_type != OBJ_CORPSES || si->sub_type != CORPSE_BODY) continue; if (!mons_has_blood(si->plus)) continue; } else if (si->base_type != OBJ_FOOD || si->sub_type != FOOD_CHUNK) continue; if (food_is_rotten(*si) && !_player_can_eat_rotten_meat()) continue; found_valid = true; chunks.push_back(&(*si)); } } // Then search through the inventory. for (int i = 0; i < ENDOFPACK; ++i) { if (!you.inv[i].is_valid()) continue; item_def *item = &you.inv[i]; if (you.species == SP_VAMPIRE) { if (item->base_type != OBJ_CORPSES || item->sub_type != CORPSE_BODY) continue; if (!mons_has_blood(item->plus)) continue; } else if (item->base_type != OBJ_FOOD || item->sub_type != FOOD_CHUNK) continue; if (food_is_rotten(*item) && !_player_can_eat_rotten_meat()) continue; found_valid = true; chunks.push_back(item); } const bool easy_eat = Options.easy_eat_chunks && !you.is_undead; const bool easy_contam = easy_eat && (Options.easy_eat_gourmand && wearing_amulet(AMU_THE_GOURMAND) || Options.easy_eat_contaminated); if (found_valid) { std::sort(chunks.begin(), chunks.end(), compare_by_freshness()); for (unsigned int i = 0; i < chunks.size(); ++i) { bool autoeat = false; item_def *item = chunks[i]; std::string item_name = get_message_colour_tags(*item, DESC_NOCAP_A, MSGCH_PROMPT); const bool contam = is_contaminated(*item); const bool bad = _is_bad_food(*item); // Exempt undead from auto-eating since: // * Mummies don't eat. // * Vampire feeding takes a lot more time than eating a chunk // and may have unintended consequences. // * Ghouls may want to wait until chunks become rotten // or until they have some hp rot to heal. if (easy_eat && !bad && !contam) { // If this chunk is safe to eat, just do so without prompting. autoeat = true; } else if (easy_contam && contam && !bad) autoeat = true; else { mprf(MSGCH_PROMPT, "%s %s%s? (ye/n/q/i?)", (you.species == SP_VAMPIRE ? "Drink blood from" : "Eat"), ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); } int keyin = autoeat ? 'y' : tolower(getchm(KMC_CONFIRM)); switch (keyin) { case ESCAPE: case 'q': canned_msg(MSG_OK); return (-1); case 'i': case '?': // Skip ahead to the inventory. return (-2); case 'e': case 'y': if (can_ingest(item->base_type, item->sub_type, false)) { if (autoeat) { mprf("%s %s%s.", (you.species == SP_VAMPIRE ? "Drinking blood from" : "Eating"), ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); } if (in_inventory(*item)) { eat_inventory_item(item->link); return (1); } else { int ilink = item_on_floor(*item, you.pos()); if (ilink != NON_ITEM) { eat_floor_item(ilink); return (1); } return (0); } } break; default: // Else no: try next one. break; } } } return (0); } static const char *_chunk_flavour_phrase(bool likes_chunks) { const char *phrase; const int level = player_mutation_level(MUT_SAPROVOROUS); switch (level) { case 1: case 2: phrase = "tastes unpleasant."; break; case 3: phrase = "tastes good."; break; default: phrase = "tastes terrible."; break; } if (likes_chunks) phrase = "tastes great."; else { const int gourmand = you.duration[DUR_GOURMAND]; if (gourmand >= GOURMAND_MAX) { phrase = one_chance_in(1000) ? "tastes like chicken!" : "tastes great."; } else if (gourmand > GOURMAND_MAX * 75 / 100) phrase = "tastes very good."; else if (gourmand > GOURMAND_MAX * 50 / 100) phrase = "tastes good."; else if (gourmand > GOURMAND_MAX * 25 / 100) phrase = "is not very appetising."; } return (phrase); } void chunk_nutrition_message(int nutrition) { int perc_nutrition = nutrition * 100 / CHUNK_BASE_NUTRITION; if (perc_nutrition < 15) mpr("That was extremely unsatisfying."); else if (perc_nutrition < 35) mpr("That was not very filling."); } static int _apply_herbivore_nutrition_effects(int nutrition) { int how_herbivorous = player_mutation_level(MUT_HERBIVOROUS); while (how_herbivorous--) nutrition = nutrition * 80 / 100; return (nutrition); } static int _apply_gourmand_nutrition_effects(int nutrition, int gourmand) { return (nutrition * (gourmand + GOURMAND_NUTRITION_BASE) / (GOURMAND_MAX + GOURMAND_NUTRITION_BASE)); } static int _chunk_nutrition(bool likes_chunks) { int nutrition = CHUNK_BASE_NUTRITION; if (likes_chunks || you.hunger_state < HS_SATIATED) { return (likes_chunks ? nutrition : _apply_herbivore_nutrition_effects(nutrition)); } const int gourmand = wearing_amulet(AMU_THE_GOURMAND) ? you.duration[DUR_GOURMAND] : 0; const int effective_nutrition = _apply_gourmand_nutrition_effects(nutrition, gourmand); #ifdef DEBUG_DIAGNOSTICS const int epercent = effective_nutrition * 100 / nutrition; mprf(MSGCH_DIAGNOSTICS, "Gourmand factor: %d, chunk base: %d, effective: %d, %%: %d", gourmand, nutrition, effective_nutrition, epercent); #endif return (_apply_herbivore_nutrition_effects(effective_nutrition)); } static void _say_chunk_flavour(bool likes_chunks) { mprf("This raw flesh %s", _chunk_flavour_phrase(likes_chunks)); } // Never called directly - chunk_effect values must pass // through food::_determine_chunk_effect() first. {dlb}: static void _eat_chunk(corpse_effect_type chunk_effect, bool cannibal, int mon_intel) { bool likes_chunks = player_likes_chunks(true); int nutrition = _chunk_nutrition(likes_chunks); int hp_amt = 0; bool suppress_msg = false; // do we display the chunk nutrition message? bool do_eat = false; if (you.species == SP_GHOUL) { nutrition = CHUNK_BASE_NUTRITION; hp_amt = 1 + random2(5) + random2(1 + you.experience_level); suppress_msg = true; } switch (chunk_effect) { case CE_MUTAGEN_RANDOM: mpr("This meat tastes really weird."); mutate(RANDOM_MUTATION); did_god_conduct( DID_DELIBERATE_MUTATING, 10); xom_is_stimulated(100); break; case CE_MUTAGEN_BAD: mpr("This meat tastes *really* weird."); give_bad_mutation(); did_god_conduct( DID_DELIBERATE_MUTATING, 10); xom_is_stimulated(random2(200)); break; case CE_HCL: you.rot(&you, 10 + random2(10)); if (you.sicken(50 + random2(100))) xom_is_stimulated(random2(100)); break; case CE_POISONOUS: mpr("Yeeuch - this meat is poisonous!"); if (poison_player(3 + random2(4))) xom_is_stimulated(random2(128)); break; case CE_ROTTEN: case CE_CONTAMINATED: if (player_mutation_level(MUT_SAPROVOROUS) == 3) { mprf("This %sflesh tastes delicious!", chunk_effect == CE_ROTTEN ? "rotting " : ""); if (you.species == SP_GHOUL) _heal_from_food(hp_amt, 0, !one_chance_in(4), one_chance_in(5)); do_eat = true; } else { mpr("There is something wrong with this meat."); if (you.sicken(50 + random2(100))) xom_is_stimulated(random2(100)); } break; case CE_CLEAN: if (player_mutation_level(MUT_SAPROVOROUS) == 3) { mpr("This raw flesh tastes good."); if (you.species == SP_GHOUL) { _heal_from_food((!one_chance_in(5) ? hp_amt : 0), 0, !one_chance_in(3)); } } else _say_chunk_flavour(likes_chunks); do_eat = true; break; case CE_MUTAGEN_GOOD: case CE_NOCORPSE: case CE_RANDOM: mprf(MSGCH_ERROR, "This flesh (%d) tastes buggy!", chunk_effect); break; } if (cannibal) did_god_conduct(DID_CANNIBALISM, 10); else if (mon_intel > 0) did_god_conduct(DID_EAT_SOULED_BEING, mon_intel); if (do_eat) { start_delay(DELAY_EAT, 2, (suppress_msg) ? 0 : nutrition, -1); lessen_hunger(nutrition, true); } } static void _eating(unsigned char item_class, int item_type) { int food_value = 0; int how_herbivorous = player_mutation_level(MUT_HERBIVOROUS); int how_carnivorous = player_mutation_level(MUT_CARNIVOROUS); int carnivore_modifier = 0; int herbivore_modifier = 0; switch (item_class) { case OBJ_FOOD: // apply base sustenance {dlb}: switch (item_type) { case FOOD_MEAT_RATION: case FOOD_ROYAL_JELLY: food_value = 5000; break; case FOOD_BREAD_RATION: food_value = 4400; break; case FOOD_HONEYCOMB: food_value = 2000; break; case FOOD_SNOZZCUMBER: // Maybe a nasty side-effect from RD's book? // I'd like that, but I don't dare. (jpeg) case FOOD_PIZZA: case FOOD_BEEF_JERKY: food_value = 1500; break; case FOOD_CHEESE: case FOOD_SAUSAGE: food_value = 1200; break; case FOOD_ORANGE: case FOOD_BANANA: case FOOD_LEMON: food_value = 1000; break; case FOOD_PEAR: case FOOD_APPLE: case FOOD_APRICOT: food_value = 700; break; case FOOD_CHOKO: case FOOD_RAMBUTAN: case FOOD_LYCHEE: food_value = 600; break; case FOOD_STRAWBERRY: food_value = 200; break; case FOOD_GRAPE: food_value = 100; break; case FOOD_SULTANA: food_value = 70; // Will not save you from starvation. break; default: break; } // Next, sustenance modifier for carnivores/herbivores {dlb}: switch (item_type) { case FOOD_MEAT_RATION: carnivore_modifier = 500; herbivore_modifier = -1500; break; case FOOD_BEEF_JERKY: case FOOD_SAUSAGE: carnivore_modifier = 200; herbivore_modifier = -200; break; case FOOD_BREAD_RATION: carnivore_modifier = -1000; herbivore_modifier = 500; break; case FOOD_BANANA: case FOOD_ORANGE: case FOOD_LEMON: carnivore_modifier = -300; herbivore_modifier = 300; break; case FOOD_PEAR: case FOOD_APPLE: case FOOD_APRICOT: case FOOD_CHOKO: case FOOD_SNOZZCUMBER: case FOOD_RAMBUTAN: case FOOD_LYCHEE: carnivore_modifier = -200; herbivore_modifier = 200; break; case FOOD_STRAWBERRY: carnivore_modifier = -50; herbivore_modifier = 50; break; case FOOD_GRAPE: case FOOD_SULTANA: carnivore_modifier = -20; herbivore_modifier = 20; break; default: carnivore_modifier = 0; herbivore_modifier = 0; break; } // Finally, modify player's hunger level {dlb}: if (carnivore_modifier && how_carnivorous > 0) food_value += (carnivore_modifier * how_carnivorous); if (herbivore_modifier && how_herbivorous > 0) food_value += (herbivore_modifier * how_herbivorous); if (food_value > 0) { int duration = 1; if (item_type == FOOD_MEAT_RATION || item_type == FOOD_BREAD_RATION) duration = 3; start_delay( DELAY_EAT, duration, 0, item_type ); lessen_hunger( food_value, true ); } break; default: break; } } // Handle messaging at the end of eating. // Some food types may not get a message. void finished_eating_message(int food_type) { bool herbivorous = player_mutation_level(MUT_HERBIVOROUS) > 0; bool carnivorous = player_mutation_level(MUT_CARNIVOROUS) > 0; if (herbivorous) { switch (food_type) { case FOOD_MEAT_RATION: case FOOD_BEEF_JERKY: case FOOD_SAUSAGE: mpr("Blech - you need greens!"); return; default: break; } } else { switch (food_type) { case FOOD_MEAT_RATION: mpr("That meat ration really hit the spot!"); return; case FOOD_BEEF_JERKY: mprf("That beef jerky was %s!", one_chance_in(4) ? "jerk-a-riffic" : "delicious"); return; case FOOD_SAUSAGE: mpr("That sausage was delicious!"); return; default: break; } } if (carnivorous) { switch (food_type) { case FOOD_BREAD_RATION: case FOOD_BANANA: case FOOD_ORANGE: case FOOD_LEMON: case FOOD_PEAR: case FOOD_APPLE: case FOOD_APRICOT: case FOOD_CHOKO: case FOOD_SNOZZCUMBER: case FOOD_RAMBUTAN: case FOOD_LYCHEE: case FOOD_STRAWBERRY: case FOOD_GRAPE: case FOOD_SULTANA: mpr("Blech - you need meat!"); return; default: break; } } else { switch (food_type) { case FOOD_BREAD_RATION: mpr("That bread ration really hit the spot!"); return; case FOOD_PEAR: case FOOD_APPLE: case FOOD_APRICOT: mprf("Mmmm... Yummy %s.", (food_type == FOOD_APPLE) ? "apple." : (food_type == FOOD_PEAR) ? "pear." : (food_type == FOOD_APRICOT) ? "apricot." : "fruit."); return; case FOOD_CHOKO: mpr("That choko was very bland."); return; case FOOD_SNOZZCUMBER: mpr("That snozzcumber tasted truly putrid!"); return; case FOOD_ORANGE: mprf("That orange was delicious!%s", one_chance_in(8) ? " Even the peel tasted good!" : ""); return; case FOOD_BANANA: mprf("That banana was delicious!%s", one_chance_in(8) ? " Even the peel tasted good!" : ""); return; case FOOD_STRAWBERRY: mpr("That strawberry was delicious!"); return; case FOOD_RAMBUTAN: mpr("That rambutan was delicious!"); return; case FOOD_LEMON: mpr("That lemon was rather sour... but delicious nonetheless!"); return; case FOOD_GRAPE: mpr("That grape was delicious!"); return; case FOOD_SULTANA: mpr("That sultana was delicious! (but very small)"); return; case FOOD_LYCHEE: mpr("That lychee was delicious!"); return; default: break; } } switch (food_type) { case FOOD_HONEYCOMB: mpr("That honeycomb was delicious."); break; case FOOD_ROYAL_JELLY: mpr("That royal jelly was delicious!"); restore_stat(STAT_ALL, 0, false); break; case FOOD_PIZZA: if (!Options.pizza.empty() && !one_chance_in(3)) mprf("Mmm... %s.", Options.pizza.c_str()); else { int temp_rand; if (carnivorous) // non-vegetable temp_rand = 5 + random2(4); else if (herbivorous) // non-meaty temp_rand = random2(6) + 2; else temp_rand = random2(9); mprf("Mmm... %s", (temp_rand == 0) ? "Ham and pineapple." : (temp_rand == 2) ? "Vegetable." : (temp_rand == 3) ? "Pepperoni." : (temp_rand == 4) ? "Yeuchh - Anchovies!" : (temp_rand == 5) ? "Cheesy." : (temp_rand == 6) ? "Supreme." : (temp_rand == 7) ? "Super Supreme!" : "Chicken."); } break; case FOOD_CHEESE: { int temp_rand = random2(9); mprf("Mmm...%s.", (temp_rand == 0) ? "Cheddar" : (temp_rand == 1) ? "Edam" : (temp_rand == 2) ? "Wensleydale" : (temp_rand == 3) ? "Camembert" : (temp_rand == 4) ? "Goat cheese" : (temp_rand == 5) ? "Fruit cheese" : (temp_rand == 6) ? "Mozzarella" : (temp_rand == 7) ? "Sheep cheese" : "Yak cheese"); break; } default: break; } } // Divide full nutrition by duration, so that each turn you get the same // amount of nutrition. Also, experimentally regenerate 1 hp per feeding turn // - this is likely too strong. // feeding is -1 at start, 1 when finishing, and 0 else // Here are some values for nutrition (quantity * 1000) and duration: // max_chunks quantity duration // 1 1 1 // 2 1 1 // 3 1 2 // 4 1 2 // 5 1 2 // 6 2 3 // 7 2 3 // 8 2 3 // 9 2 4 // 10 2 4 // 12 3 5 // 15 3 5 // 20 4 6 // 25 4 6 // 30 5 7 void vampire_nutrition_per_turn(const item_def &corpse, int feeding) { const int mons_type = corpse.plus; const int chunk_type = _determine_chunk_effect( mons_corpse_effect(mons_type), false); // Duration depends on corpse weight. const int max_chunks = mons_weight(mons_type)/150; int chunk_amount = 1 + max_chunks/3; chunk_amount = stepdown_value( chunk_amount, 6, 6, 12, 12 ); // Add 1 for the artificial extra call at the start of draining. const int duration = 1 + chunk_amount; // Use number of potions per corpse to calculate total nutrition, which // then gets distributed over the entire duration. int food_value = CHUNK_BASE_NUTRITION * num_blood_potions_from_corpse(mons_type, chunk_type); bool start_feeding = false; bool during_feeding = false; bool end_feeding = false; if (feeding < 0) start_feeding = true; else if (feeding > 0) end_feeding = true; else during_feeding = true; switch (mons_type) { case MONS_HUMAN: food_value += random2avg((you.experience_level * 10)/duration, 2); // Human may give a bit of healing during feeding as long as // the corpse is still fairly fresh. if (corpse.special > 150) { int hp_amt = 1 + you.experience_level/3; if (!end_feeding) { if (start_feeding) mpr("This warm blood tastes really delicious!"); if (hp_amt >= duration) hp_amt /= duration; else if (x_chance_in_y(hp_amt, duration)) hp_amt = 1; _heal_from_food(hp_amt); } else { // Give the remainder of healing at the end. if (hp_amt > duration) _heal_from_food(hp_amt % duration); } } break; case MONS_ELF: food_value += random2avg((you.experience_level * 10) / duration, 2); // Elven blood gives a bit of mana at the end of feeding, // but only from fairly fresh corpses. if (corpse.special > 150) { if (end_feeding) { const int mp_amt = 1 + random2(3); _heal_from_food(1, mp_amt); } else if (start_feeding) mpr("This warm blood tastes magically delicious!"); } break; default: switch (chunk_type) { case CE_CLEAN: if (start_feeding) mpr("This warm blood tastes delicious!"); else if (end_feeding && corpse.special > 150) _heal_from_food(1); break; case CE_CONTAMINATED: food_value /= 2; if (start_feeding) mpr("Somehow this blood was not very filling!"); else if (end_feeding && corpse.special > 150) _heal_from_food(1); break; case CE_POISONOUS: make_hungry(food_value / 2, false); // Always print this message - maybe you lost poison // resistance due to feeding. mpr("Blech - this blood tastes nasty!"); if (poison_player(1 + random2(3))) xom_is_stimulated(random2(128)); stop_delay(); return; case CE_MUTAGEN_RANDOM: food_value /= 2; if (start_feeding) mpr("This blood tastes really weird!"); mutate(RANDOM_MUTATION); did_god_conduct(DID_DELIBERATE_MUTATING, 10); xom_is_stimulated(100); // Sometimes heal by one hp. if (end_feeding && corpse.special > 150 && coinflip()) _heal_from_food(1); break; case CE_MUTAGEN_BAD: food_value /= 2; if (start_feeding) mpr("This blood tastes *really* weird."); give_bad_mutation(); did_god_conduct(DID_DELIBERATE_MUTATING, 10); xom_is_stimulated(random2(200)); // No healing from bad mutagenic blood. break; case CE_HCL: you.rot(&you, 5 + random2(5)); if (you.sicken(50 + random2(100))) xom_is_stimulated(random2(100)); stop_delay(); break; } } if (!end_feeding) lessen_hunger(food_value / duration, !start_feeding); } // Returns true if a food item (also corpses) is poisonous AND the player // is not (known to be) poison resistant. bool is_poisonous(const item_def &food) { if (food.base_type != OBJ_FOOD && food.base_type != OBJ_CORPSES) return (false); if (player_res_poison(false)) return (false); return (mons_corpse_effect(food.plus) == CE_POISONOUS); } // Returns true if a food item (also corpses) is mutagenic. bool is_mutagenic(const item_def &food) { if (food.base_type != OBJ_FOOD && food.base_type != OBJ_CORPSES) return (false); // Has no effect on ghouls. if (you.species == SP_GHOUL) return (false); return (mons_corpse_effect(food.plus) == CE_MUTAGEN_RANDOM); } // Returns true if a food item (also corpses) may cause sickness. bool is_contaminated(const item_def &food) { if (food.base_type != OBJ_FOOD && food.base_type != OBJ_CORPSES) return (false); // Has no (bad) effect on ghouls. if (you.species == SP_GHOUL) return (false); return (mons_corpse_effect(food.plus) == CE_CONTAMINATED); } // Returns true if a food item (also corpses) will cause rotting. bool causes_rot(const item_def &food) { if (food.base_type != OBJ_FOOD && food.base_type != OBJ_CORPSES) return (false); // Has no effect on ghouls. if (you.species == SP_GHOUL) return (false); return (mons_corpse_effect(food.plus) == CE_HCL); } // Returns 1 for herbivores, -1 for carnivores and 0 for either. static int _player_likes_food_type(int type) { switch (static_cast(type)) { case FOOD_BREAD_RATION: case FOOD_PEAR: case FOOD_APPLE: case FOOD_CHOKO: case FOOD_SNOZZCUMBER: case FOOD_PIZZA: case FOOD_APRICOT: case FOOD_ORANGE: case FOOD_BANANA: case FOOD_STRAWBERRY: case FOOD_RAMBUTAN: case FOOD_LEMON: case FOOD_GRAPE: case FOOD_SULTANA: case FOOD_LYCHEE: case FOOD_CHEESE: return 1; case FOOD_CHUNK: case FOOD_MEAT_RATION: case FOOD_SAUSAGE: case FOOD_BEEF_JERKY: return -1; case FOOD_HONEYCOMB: case FOOD_ROYAL_JELLY: return 0; case NUM_FOODS: mpr("Bad food type", MSGCH_ERROR); return 0; } mprf(MSGCH_ERROR, "Couldn't handle food type: %d", type); return 0; } // Returns true if an item of basetype FOOD or CORPSES cannot currently // be eaten (respecting species and mutations set). bool is_inedible(const item_def &item) { // Mummies don't eat. if (you.is_undead == US_UNDEAD) return (true); if (food_is_rotten(item) && !player_mutation_level(MUT_SAPROVOROUS)) { return (true); } if (item.base_type == OBJ_FOOD && !can_ingest(item.base_type, item.sub_type, true, true, false)) { return (true); } if (item.base_type == OBJ_CORPSES && (item.sub_type == CORPSE_SKELETON || you.species == SP_VAMPIRE && !mons_has_blood(item.plus))) { return (true); } return (false); } // As we want to avoid autocolouring the entire food selection, this should // be restricted to the absolute highlights, even though other stuff may // still be edible or even delicious. bool is_preferred_food(const item_def &food) { // Mummies don't eat. if (you.is_undead == US_UNDEAD) return (false); // Vampires don't really have a preferred food type, but they really // like blood potions. if (you.species == SP_VAMPIRE) return (is_blood_potion(food)); if (food.base_type == OBJ_POTIONS && food.sub_type == POT_PORRIDGE && item_type_known(food)) { return (!player_mutation_level(MUT_CARNIVOROUS)); } if (food.base_type != OBJ_FOOD) return (false); // Poisoned, mutagenic, etc. food should never be marked as "preferred". if (_is_bad_food(food)) return (false); // Honeycombs are tasty for everyone. if (food.sub_type == FOOD_HONEYCOMB || food.sub_type == FOOD_ROYAL_JELLY) return (true); // Ghouls specifically like rotten food. if (you.species == SP_GHOUL) return (food_is_rotten(food)); if (player_mutation_level(MUT_CARNIVOROUS) == 3) return (_player_likes_food_type(food.sub_type) < 0); if (player_mutation_level(MUT_HERBIVOROUS) == 3) return (_player_likes_food_type(food.sub_type) > 0); // No food preference. return (false); } bool is_forbidden_food(const item_def &food) { if (food.base_type != OBJ_CORPSES && (food.base_type != OBJ_FOOD || food.sub_type != FOOD_CHUNK)) { return (false); } // Some gods frown upon cannibalistic behaviour. if (god_hates_cannibalism(you.religion) && is_player_same_species(food.plus)) { return (true); } // Zin doesn't like it if you eat beings with a soul. if (you.religion == GOD_ZIN && mons_class_intel(food.plus) >= I_NORMAL) return (true); // Everything else is allowed. return (false); } bool check_amu_the_gourmand(bool reqid) { if (wearing_amulet(AMU_THE_GOURMAND, !reqid)) { const int amulet = you.equip[EQ_AMULET]; ASSERT(amulet != -1); if (!item_type_known(you.inv[amulet])) { // For artefact amulets, this will tell you its name and // subtype. Other properties may still be hidden. set_ident_flags(you.inv[amulet], ISFLAG_KNOW_TYPE); set_ident_type(OBJ_JEWELLERY, AMU_THE_GOURMAND, ID_KNOWN_TYPE); mpr(you.inv[amulet].name(DESC_INVENTORY, false).c_str()); } return (true); } return (false); } bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg, bool reqid, bool check_hunger) { bool survey_says = false; // [ds] These redundant checks are now necessary - Lua might be calling us. if (you.is_undead == US_UNDEAD) { if (!suppress_msg) mpr("You can't eat."); return (false); } if (check_hunger && you.hunger >= 11000) { if (!suppress_msg) mpr("You're too full to eat anything."); return (false); } if (you.species == SP_VAMPIRE) { if (what_isit == OBJ_CORPSES && kindof_thing == CORPSE_BODY) return (true); if (what_isit == OBJ_POTIONS && (kindof_thing == POT_BLOOD || kindof_thing == POT_BLOOD_COAGULATED)) { return (true); } if (!suppress_msg) mpr("Blech - you need blood!"); return (false); } bool ur_carnivorous = player_mutation_level(MUT_CARNIVOROUS) == 3; bool ur_herbivorous = player_mutation_level(MUT_HERBIVOROUS) == 3; // ur_chunkslover not defined in terms of ur_carnivorous because // a player could be one and not the other IMHO - 13mar2000 {dlb} bool ur_chunkslover = ((check_hunger ? you.hunger_state < HS_SATIATED : true) || player_likes_chunks(true)); switch (what_isit) { case OBJ_FOOD: { if (you.species == SP_VAMPIRE) { if (!suppress_msg) mpr("Blech - you need blood!"); return (false); } const int vorous = _player_likes_food_type(kindof_thing); if (vorous > 0) // Herbivorous food. { if (ur_carnivorous) { if (!suppress_msg) mpr("Sorry, you're a carnivore."); return (false); } else return (true); } else if (vorous < 0) // Carnivorous food. { if (ur_herbivorous) { if (!suppress_msg) mpr("Blech - you need greens!"); return (false); } else if (kindof_thing == FOOD_CHUNK) { if (ur_chunkslover) return (true); if (check_amu_the_gourmand(reqid)) return (true); if (!suppress_msg) mpr("You aren't quite hungry enough to eat that!"); return (false); } } // Any food types not specifically handled until here (e.g. meat // rations for non-herbivores) are okay. return (true); } case OBJ_CORPSES: if (you.species == SP_VAMPIRE) { if (kindof_thing == CORPSE_BODY) return (true); else { if (!suppress_msg) mpr("Blech - you need blood!"); return (false); } } return (false); case OBJ_POTIONS: // called by lua if (get_ident_type(OBJ_POTIONS, kindof_thing) != ID_KNOWN_TYPE) return (true); switch (kindof_thing) { case POT_BLOOD: case POT_BLOOD_COAGULATED: if (ur_herbivorous) { if (!suppress_msg) mpr("Urks, you're a herbivore!"); return (false); } return (true); case POT_WATER: if (you.species == SP_VAMPIRE) { if (!suppress_msg) mpr("Blech - you need blood!"); return (false); } return (true); case POT_PORRIDGE: if (you.species == SP_VAMPIRE) { if (!suppress_msg) mpr("Blech - you need blood!"); return (false); } else if (ur_carnivorous) { if (!suppress_msg) mpr("Sorry, you're a carnivore."); return (false); } default: return (true); } // Other object types are set to return false for now until // someone wants to recode the eating code to permit consumption // of things other than just food. default: return (false); } return (survey_says); } // See if you can follow along here -- except for the amulet of the gourmand // addition (long missing and requested), what follows is an expansion of how // chunks were handled in the codebase up to this date ... {dlb} static corpse_effect_type _determine_chunk_effect(corpse_effect_type chunktype, bool rotten_chunk) { // Determine the initial effect of eating a particular chunk. {dlb} switch (chunktype) { case CE_HCL: case CE_MUTAGEN_RANDOM: if (you.species == SP_GHOUL) chunktype = CE_CLEAN; break; case CE_POISONOUS: if (player_res_poison() > 0) chunktype = CE_CLEAN; break; case CE_CONTAMINATED: switch (player_mutation_level(MUT_SAPROVOROUS)) { case 1: if (!one_chance_in(15)) chunktype = CE_CLEAN; break; case 2: if (!one_chance_in(45)) chunktype = CE_CLEAN; break; default: if (!one_chance_in(3)) chunktype = CE_CLEAN; break; } break; default: break; } // Determine effects of rotting on base chunk effect {dlb}: if (rotten_chunk) { switch (chunktype) { case CE_CLEAN: case CE_CONTAMINATED: chunktype = CE_ROTTEN; break; case CE_MUTAGEN_RANDOM: chunktype = CE_MUTAGEN_BAD; break; default: break; } } // One last chance for some species to safely eat rotten food. {dlb} if (chunktype == CE_ROTTEN) { switch (player_mutation_level(MUT_SAPROVOROUS)) { case 1: if (!one_chance_in(5)) chunktype = CE_CLEAN; break; case 2: if (!one_chance_in(15)) chunktype = CE_CLEAN; break; default: break; } } // The amulet of the gourmand will permit consumption of // contaminated meat as though it were "clean" meat - level 3 // saprovores get rotting meat effect from clean chunks, since they // love rotting meat. if (wearing_amulet(AMU_THE_GOURMAND) && x_chance_in_y(you.duration[DUR_GOURMAND], GOURMAND_MAX)) { if (player_mutation_level(MUT_SAPROVOROUS) == 3) { // [dshaligram] Level 3 saprovores relish contaminated meat. if (chunktype == CE_CLEAN) chunktype = CE_CONTAMINATED; } else { // [dshaligram] New AotG behaviour - contaminated chunks become // clean, but rotten chunks remain rotten. if (chunktype == CE_CONTAMINATED) chunktype = CE_CLEAN; } } return (chunktype); } static bool _vampire_consume_corpse(int slot, bool invent) { ASSERT(you.species == SP_VAMPIRE); item_def &corpse = (invent ? you.inv[slot] : mitm[slot]); ASSERT(corpse.base_type == OBJ_CORPSES); ASSERT(corpse.sub_type == CORPSE_BODY); if (!mons_has_blood(corpse.plus)) { mpr("There is no blood in this body!"); return (false); } if (food_is_rotten(corpse)) { mpr("It's not fresh enough."); return (false); } // The delay for eating a chunk (mass 1000) is 2 // Here the base nutrition value equals that of chunks, // but the delay should be smaller. const int max_chunks = mons_weight(corpse.plus) / 150; int duration = 1 + max_chunks / 3; duration = stepdown_value(duration, 6, 6, 12, 12); // Get some nutrition right away, in case we're interrupted. // (-1 for the starting message.) vampire_nutrition_per_turn(corpse, -1); // The draining delay doesn't have a start action, and we only need // the continue/finish messages if it takes longer than 1 turn. start_delay(DELAY_FEED_VAMPIRE, duration, (int)invent, slot); return (true); } static void _heal_from_food(int hp_amt, int mp_amt, bool unrot, bool restore_str) { if (hp_amt > 0) inc_hp(hp_amt, false); if (mp_amt > 0) inc_mp(mp_amt, false); if (unrot && player_rotted()) { mpr("You feel more resilient."); unrot_hp(1); } if (restore_str && you.strength < you.max_strength) restore_stat(STAT_STRENGTH, 1, false); calc_hp(); calc_mp(); } int you_max_hunger() { // This case shouldn't actually happen. if (you.is_undead == US_UNDEAD) return (6000); // Take care of ghouls - they can never be 'full'. if (you.species == SP_GHOUL) return (6999); return (40000); } int you_min_hunger() { // This case shouldn't actually happen. if (you.is_undead == US_UNDEAD) return (6000); // Vampires can never starve to death. if (you.species == SP_VAMPIRE) return (701); return (0); }