summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/food.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/food.cc')
-rw-r--r--crawl-ref/source/food.cc488
1 files changed, 439 insertions, 49 deletions
diff --git a/crawl-ref/source/food.cc b/crawl-ref/source/food.cc
index ece51f4295..1f2e9e4175 100644
--- a/crawl-ref/source/food.cc
+++ b/crawl-ref/source/food.cc
@@ -58,7 +58,6 @@ static int _determine_chunk_effect(int which_chunk_type, bool rotten_chunk);
static void _eat_chunk(int 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 _food_change(bool suppress_message);
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);
@@ -89,7 +88,7 @@ void make_hungry(int hunger_amount, bool suppress_msg,
you.hunger = 0;
// So we don't get two messages, ever.
- bool state_message = _food_change(false);
+ bool state_message = food_change();
if (!suppress_msg && !state_message)
_describe_food_change( -hunger_amount );
@@ -106,7 +105,7 @@ void lessen_hunger(int satiated_amount, bool suppress_msg)
you.hunger = 12000;
// So we don't get two messages, ever.
- bool state_message = _food_change(false);
+ bool state_message = food_change();
if (!suppress_msg && !state_message)
_describe_food_change(satiated_amount);
@@ -654,6 +653,7 @@ bool butchery(int which_corpse)
}
break;
+ case ESCAPE:
case 'q':
canned_msg(MSG_OK);
_terminate_butchery(wpn_switch, removed_gloves, old_weapon,
@@ -742,7 +742,7 @@ static bool _userdef_eat_food()
#endif
}
-bool prompt_eat_from_inventory(int slot)
+bool prompt_eat_inventory_item(int slot)
{
if (inv_count() < 1)
{
@@ -800,7 +800,7 @@ bool prompt_eat_from_inventory(int slot)
return (false);
}
- eat_from_inventory(which_inventory_slot);
+ eat_inventory_item(which_inventory_slot);
burden_change();
you.turn_is_over = true;
@@ -833,14 +833,14 @@ bool eat_food(bool run_hook, int slot)
if (igrd(you.pos()) != NON_ITEM && slot == -1)
{
- const int res = eat_from_floor();
+ const int res = eat_from_floor(false);
if (res == 1)
return (true);
if (res == -1)
return (false);
}
- return (prompt_eat_from_inventory(slot));
+ return (prompt_eat_inventory_item(slot));
}
/*
@@ -869,7 +869,7 @@ static bool _player_has_enough_food()
if (is_mutagenic(item))
continue;
- if (causes_rot(item) && you.species != SP_GHOUL)
+ if (causes_rot(item))
continue;
// Vampires can only drain corpses.
@@ -918,7 +918,7 @@ static std::string _how_hungry()
return ("hungry");
}
-static bool _food_change(bool suppress_message)
+bool food_change(bool suppress_message)
{
char newstate = HS_ENGORGED;
bool state_changed = false;
@@ -927,7 +927,7 @@ static bool _food_change(bool suppress_message)
you.hunger = std::max(you_min_hunger(), you.hunger);
you.hunger = std::min(you_max_hunger(), you.hunger);
- // get new hunger state
+ // Get new hunger state.
if (you.hunger <= 1000)
newstate = HS_STARVING;
else if (you.hunger <= 1533)
@@ -989,7 +989,6 @@ static bool _food_change(bool suppress_message)
print_stats();
mpr("You can't stomach any more blood right now.");
}
-
}
if (!suppress_message)
@@ -1092,15 +1091,17 @@ static bool _player_can_eat_rotten_meat(bool need_msg = false)
}
// Should really be merged into function below. -- FIXME
-void eat_from_inventory(int which_inventory_slot)
+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;
}
- else if (food.sub_type == FOOD_CHUNK)
+
+ if (food.sub_type == FOOD_CHUNK)
{
const int mons_type = food.plus;
const bool cannibal = is_player_same_species(mons_type);
@@ -1117,6 +1118,7 @@ void eat_from_inventory(int which_inventory_slot)
else
_eating( food.base_type, food.sub_type );
+ you.turn_is_over = true;
dec_inv_item_quantity( which_inventory_slot, 1 );
}
@@ -1154,22 +1156,79 @@ void eat_floor_item(int item_link)
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()
+int eat_from_floor(bool skip_chunks)
{
if (you.flight_mode() == FL_LEVITATE)
- return 0;
+ 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<item_def *> food_items;
for (stack_iterator si(you.pos()); si; ++si )
{
- if (you.species != SP_VAMPIRE && si->base_type != OBJ_FOOD)
- continue;
-
if (you.species == SP_VAMPIRE)
{
if (si->base_type != OBJ_CORPSES || si->sub_type != CORPSE_BODY)
@@ -1181,7 +1240,18 @@ int eat_from_floor()
continue;
}
}
- else if (food_is_rotten(*si) && !_player_can_eat_rotten_meat())
+ 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;
@@ -1207,40 +1277,56 @@ int eat_from_floor()
}
found_valid = true;
- std::string item_name = get_message_colour_tags(*si, DESC_NOCAP_A,
- MSGCH_PROMPT);
-
- mprf(MSGCH_PROMPT, "%s %s%s? (ye/n/q/i?)",
- (you.species == SP_VAMPIRE ? "Drink blood from" : "Eat"),
- ((si->quantity > 1) ? "one of " : ""),
- item_name.c_str());
+ food_items.push_back(&(*si));
+ }
- int keyin = tolower(getchm(KC_CONFIRM));
- switch (keyin)
+ if (found_valid)
+ {
+ std::sort(food_items.begin(), food_items.end(), compare_by_freshness());
+ for (unsigned int i = 0; i < food_items.size(); ++i)
{
- case 'q':
- canned_msg(MSG_OK);
- return -1;
- case 'e':
- case 'y':
- if (can_ingest(si->base_type, si->sub_type, false))
+ 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(KC_CONFIRM));
+ switch (keyin)
{
- eat_floor_item(si->index());
- return 1;
+ case ESCAPE:
+ case 'q':
+ canned_msg(MSG_OK);
+ return -1;
+ case 'e':
+ case 'y':
+ 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;
}
- need_more = true;
- break;
- case 'i':
- case '?':
- // Directly skip ahead to inventory.
- return (0);
- default:
- // Else no: try next one.
- break;
}
}
-
- if (!found_valid)
+ else
{
// Give a message about why these food items can not actually be eaten.
if (unusable_corpse)
@@ -1279,6 +1365,275 @@ int eat_from_floor()
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;
+ bool found_valid = false;
+
+ std::vector<item_def *> food_items;
+ for (int i = 0; i < ENDOFPACK; ++i)
+ {
+ if (!is_valid_item(you.inv[i]))
+ 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(is_valid_item(*wonteat));
+ 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(KC_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(is_valid_item(*wonteat));
+ // 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);
+}
+
+bool eat_chunks()
+{
+ // Herbivores cannot eat chunks.
+ if (player_mutation_level(MUT_HERBIVOROUS) == 3)
+ return (false);
+
+ bool found_valid = false;
+ std::vector<item_def *> chunks;
+
+ // First search the stash on the floor, unless levitating.
+ if (you.flight_mode() != FL_LEVITATE)
+ {
+ for (stack_iterator si(you.pos()); 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 (!is_valid_item(you.inv[i]))
+ 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);
+ }
+
+ 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);
+
+ // Excempt 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.
+ if (Options.easy_eat_chunks && !you.is_undead
+ && !_is_bad_food(*item) && !is_contaminated(*item))
+ {
+ // If this chunk is safe to eat, just do so without prompting.
+ autoeat = true;
+ }
+ else
+ {
+ 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 = autoeat ? 'y' : tolower(getchm(KC_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))
+ {
+ 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 (true);
+ }
+ else
+ {
+ int ilink = item_on_floor(*item, you.pos());
+
+ if (ilink != NON_ITEM)
+ {
+ eat_floor_item(ilink);
+ return (true);
+ }
+ return (false);
+ }
+ }
+ break;
+ default:
+ // Else no: try next one.
+ break;
+ }
+ }
+ }
+
+ return (false);
+}
+
static const char *_chunk_flavour_phrase(bool likes_chunks)
{
const char *phrase =
@@ -1358,8 +1713,8 @@ static void _say_chunk_flavour(bool likes_chunks)
// through food::_determine_chunk_effect() first. {dlb}:
static void _eat_chunk(int chunk_effect, bool cannibal, int mon_intel)
{
- bool likes_chunks = (you.omnivorous() ||
- player_mutation_level(MUT_CARNIVOROUS));
+ bool likes_chunks = (you.omnivorous()
+ || player_mutation_level(MUT_CARNIVOROUS));
int nutrition = _chunk_nutrition(likes_chunks);
int hp_amt = 0;
bool suppress_msg = false; // do we display the chunk nutrition message?
@@ -1939,6 +2294,10 @@ 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);
}
@@ -1948,6 +2307,10 @@ 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);
}
@@ -1957,6 +2320,10 @@ 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);
}
@@ -2065,6 +2432,29 @@ bool is_preferred_food(const item_def &food)
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 can_ingest(int what_isit, int kindof_thing, bool suppress_msg, bool reqid,
bool check_hunger)
{