void player::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == trait_WEB_WEAVER ) { g->m.add_field( pos(), fd_web, 1 ); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } time_duration time_to_do = 0_turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. time_to_do = 30_minutes; } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { time_to_do = 10_minutes; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity( activity_id( "ACT_BURROW" ), to_moves<int>( time_to_do ), -1, 0 ); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if( mut == trait_SLIMESPAWNER ) { std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if (g->is_empty(dest)) { valid.push_back( dest ); } } // Oops, no room to divide! if( valid.empty() ) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if( monster * const slime = g->summon_mon( mtype_id( "mon_player_blob" ), target ) ) { slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if( mut == trait_NAUSEA || mut == trait_VOMITOUS ) { vomit(); tdata.powered = false; return; } else if( mut == trait_M_FERTILE ) { spores(); tdata.powered = false; return; } else if( mut == trait_M_BLOOM ) { blossoms(); tdata.powered = false; return; } else if( mut == trait_SELFAWARE ) { print_health(); tdata.powered = false; return; } else if( !mdata.spawn_item.empty() ) { item tmpitem( mdata.spawn_item ); i_add_or_drop( tmpitem ); add_msg_if_player( _( mdata.spawn_item_message.c_str() ) ); tdata.powered = false; return; } }
void player::power_mutations() { if( !is_player() ) { // TODO: Implement NPCs activating muts return; } std::vector <std::string> passive; std::vector <std::string> active; for( auto &mut : my_mutations ) { if (!mutation_branch::get( mut.first ).activated) { passive.push_back(mut.first); } else { active.push_back(mut.first); } // New mutations are initialized with no key at all, so we have to do this here. if( mut.second.key == ' ' ) { for( const auto &letter : mutation_chars ) { if( trait_by_invlet( letter ).empty() ) { mut.second.key = letter; break; } } } } // maximal number of rows in both columns const int mutations_count = std::max(passive.size(), active.size()); int TITLE_HEIGHT = 2; int DESCRIPTION_HEIGHT = 5; // Main window /** Total required height is: * top frame line: + 1 * height of title window: + TITLE_HEIGHT * line after the title: + 1 * line with active/passive mutation captions: + 1 * height of the biggest list of active/passive mutations: + mutations_count * line before mutation description: + 1 * height of description window: + DESCRIPTION_HEIGHT * bottom frame line: + 1 * TOTAL: TITLE_HEIGHT + mutations_count + DESCRIPTION_HEIGHT + 5 */ int HEIGHT = std::min(TERMY, std::max(FULL_SCREEN_HEIGHT, TITLE_HEIGHT + mutations_count + DESCRIPTION_HEIGHT + 5)); int WIDTH = FULL_SCREEN_WIDTH + (TERMX - FULL_SCREEN_WIDTH) / 2; int START_X = (TERMX - WIDTH) / 2; int START_Y = (TERMY - HEIGHT) / 2; WINDOW *wBio = newwin(HEIGHT, WIDTH, START_Y, START_X); // Description window @ the bottom of the bio window int DESCRIPTION_START_Y = START_Y + HEIGHT - DESCRIPTION_HEIGHT - 1; int DESCRIPTION_LINE_Y = DESCRIPTION_START_Y - START_Y - 1; WINDOW *w_description = newwin(DESCRIPTION_HEIGHT, WIDTH - 2, DESCRIPTION_START_Y, START_X + 1); // Title window int TITLE_START_Y = START_Y + 1; int HEADER_LINE_Y = TITLE_HEIGHT + 1; // + lines with text in titlebar, local WINDOW *w_title = newwin(TITLE_HEIGHT, WIDTH - 2, TITLE_START_Y, START_X + 1); int scroll_position = 0; int second_column = 32 + (TERMX - FULL_SCREEN_WIDTH) / 4; // X-coordinate of the list of active mutations input_context ctxt("MUTATIONS"); ctxt.register_updown(); ctxt.register_action("ANY_INPUT"); ctxt.register_action("TOGGLE_EXAMINE"); ctxt.register_action("REASSIGN"); ctxt.register_action("HELP_KEYBINDINGS"); bool redraw = true; std::string menu_mode = "activating"; while(true) { // offset for display: mutation with index i is drawn at y=list_start_y+i // drawing the mutation starts with mutation[scroll_position] const int list_start_y = HEADER_LINE_Y + 2 - scroll_position; int max_scroll_position = HEADER_LINE_Y + 2 + mutations_count - ((menu_mode == "examining") ? DESCRIPTION_LINE_Y : (HEIGHT - 1)); if(redraw) { redraw = false; werase(wBio); draw_border(wBio); // Draw line under title mvwhline(wBio, HEADER_LINE_Y, 1, LINE_OXOX, WIDTH - 2); // Draw symbols to connect additional lines to border mvwputch(wBio, HEADER_LINE_Y, 0, BORDER_COLOR, LINE_XXXO); // |- mvwputch(wBio, HEADER_LINE_Y, WIDTH - 1, BORDER_COLOR, LINE_XOXX); // -| // Captions mvwprintz(wBio, HEADER_LINE_Y + 1, 2, c_ltblue, _("Passive:")); mvwprintz(wBio, HEADER_LINE_Y + 1, second_column, c_ltblue, _("Active:")); draw_exam_window(wBio, DESCRIPTION_LINE_Y, menu_mode == "examining"); nc_color type; if (passive.empty()) { mvwprintz(wBio, list_start_y, 2, c_ltgray, _("None")); } else { for (size_t i = scroll_position; i < passive.size(); i++) { const auto &md = mutation_branch::get( passive[i] ); const auto &td = my_mutations[passive[i]]; if (list_start_y + static_cast<int>(i) == (menu_mode == "examining" ? DESCRIPTION_LINE_Y : HEIGHT - 1)) { break; } type = c_cyan; mvwprintz(wBio, list_start_y + i, 2, type, "%c %s", td.key, md.name.c_str()); } } if (active.empty()) { mvwprintz(wBio, list_start_y, second_column, c_ltgray, _("None")); } else { for (size_t i = scroll_position; i < active.size(); i++) { const auto &md = mutation_branch::get( active[i] ); const auto &td = my_mutations[active[i]]; if (list_start_y + static_cast<int>(i) == (menu_mode == "examining" ? DESCRIPTION_LINE_Y : HEIGHT - 1)) { break; } if (!td.powered) { type = c_red; }else if (td.powered) { type = c_ltgreen; } else { type = c_ltred; } // TODO: track resource(s) used and specify mvwputch( wBio, list_start_y + i, second_column, type, td.key ); std::stringstream mut_desc; mut_desc << md.name; if ( md.cost > 0 && md.cooldown > 0 ) { mut_desc << string_format( _(" - %d RU / %d turns"), md.cost, md.cooldown ); } else if ( md.cost > 0 ) { mut_desc << string_format( _(" - %d RU"), md.cost ); } else if ( md.cooldown > 0 ) { mut_desc << string_format( _(" - %d turns"), md.cooldown ); } if ( td.powered ) { mut_desc << _(" - Active"); } mvwprintz( wBio, list_start_y + i, second_column + 2, type, mut_desc.str().c_str() ); } } // Scrollbar if(scroll_position > 0) { mvwputch(wBio, HEADER_LINE_Y + 2, 0, c_ltgreen, '^'); } if(scroll_position < max_scroll_position && max_scroll_position > 0) { mvwputch(wBio, (menu_mode == "examining" ? DESCRIPTION_LINE_Y : HEIGHT - 1) - 1, 0, c_ltgreen, 'v'); } } wrefresh(wBio); show_mutations_titlebar(w_title, this, menu_mode); const std::string action = ctxt.handle_input(); const long ch = ctxt.get_raw_input().get_first_input(); if (menu_mode == "reassigning") { menu_mode = "activating"; const auto mut_id = trait_by_invlet( ch ); if( mut_id.empty() ) { // Selected an non-existing mutation (or escape, or ...) continue; } redraw = true; const long newch = popup_getkey(_("%s; enter new letter."), mutation_branch::get_name( mut_id ).c_str()); wrefresh(wBio); if(newch == ch || newch == ' ' || newch == KEY_ESCAPE) { continue; } if( !mutation_chars.valid( newch ) ) { popup( _("Invalid mutation letter. Only those characters are valid:\n\n%s"), mutation_chars.get_allowed_chars().c_str() ); continue; } const auto other_mut_id = trait_by_invlet( newch ); if( !other_mut_id.empty() ) { std::swap(my_mutations[mut_id].key, my_mutations[other_mut_id].key); } else { my_mutations[mut_id].key = newch; } // TODO: show a message like when reassigning a key to an item? } else if (action == "DOWN") { if(scroll_position < max_scroll_position) { scroll_position++; redraw = true; } } else if (action == "UP") { if(scroll_position > 0) { scroll_position--; redraw = true; } } else if (action == "REASSIGN") { menu_mode = "reassigning"; } else if (action == "TOGGLE_EXAMINE") { // switches between activation and examination menu_mode = menu_mode == "activating" ? "examining" : "activating"; werase(w_description); draw_exam_window(wBio, DESCRIPTION_LINE_Y, false); redraw = true; }else if (action == "HELP_KEYBINDINGS") { redraw = true; } else { const auto mut_id = trait_by_invlet( ch ); if( mut_id.empty() ) { // entered a key that is not mapped to any mutation, // -> leave screen break; } const auto &mut_data = mutation_branch::get( mut_id ); if (menu_mode == "activating") { if (mut_data.activated) { if (my_mutations[mut_id].powered) { add_msg_if_player(m_neutral, _("You stop using your %s."), mut_data.name.c_str()); deactivate_mutation( mut_id ); delwin(w_title); delwin(w_description); delwin(wBio); // Action done, leave screen break; } else if( (!mut_data.hunger || get_hunger() <= 400) && (!mut_data.thirst || get_thirst() <= 400) && (!mut_data.fatigue || get_fatigue() <= 400) ) { // this will clear the mutations menu for targeting purposes werase(wBio); wrefresh(wBio); delwin(w_title); delwin(w_description); delwin(wBio); g->draw(); add_msg_if_player( m_neutral, _("You activate your %s."), mut_data.name.c_str() ); activate_mutation( mut_id ); // Action done, leave screen break; } else { popup( _( "You don't have enough in you to activate your %s!" ), mut_data.name.c_str() ); redraw = true; continue; } } else { popup(_("\ You cannot activate %s! To read a description of \ %s, press '!', then '%c'."), mut_data.name.c_str(), mut_data.name.c_str(), my_mutations[mut_id].key ); redraw = true; } } if (menu_mode == "examining") { // Describing mutations, not activating them! draw_exam_window(wBio, DESCRIPTION_LINE_Y, true); // Clear the lines first werase(w_description); fold_and_print(w_description, 0, 0, WIDTH - 2, c_ltblue, mut_data.description); wrefresh(w_description); } } } //if we activated a mutation, already killed the windows if(!(menu_mode == "activating")) { werase(wBio); wrefresh(wBio); delwin(w_title); delwin(w_description); delwin(wBio); } }
void player::activate_mutation( const std::string &mut ) { const auto &mdata = mutation_branch::get( mut ); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == "WEB_WEAVER" ) { g->m.add_field(pos(), fd_web, 1, 0); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } int turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Takes about 100 minutes (not quite two hours) base time. // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. ///\EFFECT_CARPENTRY speeds up burrowing turns = (100000 - 5000 * skillLevel( skill_id( "carpentry" ) )); } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { turns = 18000; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity(ACT_BURROW, turns, -1, 0); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if (mut == "SLIMESPAWNER") { std::vector<tripoint> valid; for (int x = posx() - 1; x <= posx() + 1; x++) { for (int y = posy() - 1; y <= posy() + 1; y++) { tripoint dest(x, y, posz()); if (g->is_empty(dest)) { valid.push_back( dest ); } } } // Oops, no room to divide! if (valid.size() == 0) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if (g->summon_mon(mtype_id( "mon_player_blob" ), target)) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if ((mut == "NAUSEA") || (mut == "VOMITOUS") ){ vomit(); tdata.powered = false; return; } else if (mut == "M_FERTILE"){ spores(); tdata.powered = false; return; } else if (mut == "M_BLOOM"){ blossoms(); tdata.powered = false; return; } else if (mut == "VINES3"){ item newit( "vine_30", calendar::turn ); if (!can_pickVolume(newit.volume())) { //Accounts for result_mult add_msg_if_player(_("You detach a vine but don't have room to carry it, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else if (!can_pickWeight(newit.weight(), !OPTIONS["DANGEROUS_PICKUPS"])) { add_msg_if_player(_("Your freshly-detached vine is too heavy to carry, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else { inv.assign_empty_invlet(newit); newit = i_add(newit); add_msg_if_player(m_info, "%c - %s", newit.invlet == 0 ? ' ' : newit.invlet, newit.tname().c_str()); } tdata.powered = false; return; } else if( mut == "SELFAWARE" ) { print_health(); tdata.powered = false; return; } }
void player::consume_effects( const item &food ) { if( !food.is_comestible() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto &comest = *food.type->comestible; const int capacity = stomach_capacity(); if( has_trait( trait_id( "THRESH_PLANT" ) ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } // Rotten food causes health loss const float relative_rot = food.get_relative_rot(); if( relative_rot > 1.0f && !has_trait( trait_id( "SAPROPHAGE" ) ) && !has_trait( trait_id( "SAPROVORE" ) ) && !has_bionic( bio_digestion ) ) { const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); // ~-1 health per 1 nutrition at halfway-rotten-away, ~0 at "just got rotten" // But always round down int h_loss = -rottedness * comest.nutr; mod_healthy_mod( h_loss, -200 ); add_msg( m_debug, "%d health from %0.2f%% rotten food", h_loss, rottedness ); } const auto nutr = nutrition_for( food ); mod_hunger( -nutr ); mod_thirst( -comest.quench ); mod_stomach_food( nutr ); mod_stomach_water( comest.quench ); if( comest.healthy != 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest.healthy, ( comest.healthy >= 0 ) ? 200 : -200 ); } if( comest.stim != 0 && ( abs( stim ) < ( abs( comest.stim ) * 3 ) || sgn( stim ) != sgn( comest.stim ) ) ) { if( comest.stim < 0 ) { stim = std::max( comest.stim * 3, stim + comest.stim ); } else { stim = std::min( comest.stim * 3, stim + comest.stim ); } } add_addiction( comest.add, comest.addict ); if( addiction_craving( comest.add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest.add ) ); } // Morale is in minutes int morale_time = HOURS( 2 ) / MINUTES( 1 ); if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { morale_time = HOURS( 3 ) / MINUTES( 1 ); int clamped_nutr = std::max( 5, std::min( 20, nutr / 10 ) ); add_morale( MORALE_FOOD_HOT, clamped_nutr, 20, morale_time, morale_time / 2 ); } std::pair<int, int> fun = fun_for( food ); if( fun.first < 0 ) { add_morale( MORALE_FOOD_BAD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } else if( fun.first > 0 ) { add_morale( MORALE_FOOD_GOOD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest.quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest.quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest.quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest.quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( trait_id( "SLIMESPAWNER" ) ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( monster *const slime = g->summon_mon( mon_player_blob, target ) ) { slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( trait_id( "EATHEALTH" ) ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest.quench > 0 ); }
bool player::eat( item &food, bool force ) { if( !food.is_food() ) { return false; } // Check if it's rotten before eating! food.calc_rot( global_square_location() ); const auto ret = force ? can_eat( food ) : will_eat( food, is_player() ); if( !ret.success() ) { return false; } if( food.type->has_use() ) { if( food.type->invoke( *this, food, pos() ) <= 0 ) { return false; } } // Note: the block below assumes we decided to eat it // No coming back from here const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); const int nutr = nutrition_for( food ); const int quench = food.type->comestible->quench; const bool spoiled = food.rotten(); // The item is solid food const bool chew = food.type->comestible->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); // This item is a drink and not a solid food (and not a thick soup) const bool drinkable = !chew && food.type->comestible->comesttype == "DRINK"; // If neither of the above is true then it's a drug and shouldn't get mealtime penalty/bonus if( hibernate && ( get_hunger() > -60 && get_thirst() > -60 ) && ( get_hunger() - nutr < -60 || get_thirst() - quench < -60 ) ) { add_memorial_log( pgettext( "memorial_male", "Began preparing for hibernation." ), pgettext( "memorial_female", "Began preparing for hibernation." ) ); add_msg_if_player( _( "You've begun stockpiling calories and liquid for hibernation. You get the feeling that you should prepare for bed, just in case, but...you're hungry again, and you could eat a whole week's worth of food RIGHT NOW." ) ); } const bool will_vomit = get_hunger() < 0 && nutr >= 5 && !has_trait( trait_id( "GOURMAND" ) ) && !hibernate && !has_trait( trait_id( "SLIMESPAWNER" ) ) && !has_trait( trait_id( "EATHEALTH" ) ) && rng( -200, 0 ) > get_hunger() - nutr; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); if( spoiled && !saprophage ) { add_msg_if_player( m_bad, _( "Ick, this %s doesn't taste so good..." ), food.tname().c_str() ); if( !has_trait( trait_id( "SAPROVORE" ) ) && !has_trait( trait_id( "EATDEAD" ) ) && ( !has_bionic( bio_digestion ) || one_in( 3 ) ) ) { add_effect( effect_foodpoison, rng( 60, ( nutr + 1 ) * 60 ) ); } consume_effects( food ); } else if( spoiled && saprophage ) { add_msg_if_player( m_good, _( "Mmm, this %s tastes delicious..." ), food.tname().c_str() ); consume_effects( food ); } else { consume_effects( food ); } const bool amorphous = has_trait( trait_id( "AMORPHOUS" ) ); int mealtime = 250; if( drinkable || chew ) { // Those bonuses/penalties only apply to food // Not to smoking weed or applying bandages! if( has_trait( trait_id( "MOUTH_TENTACLES" ) ) || has_trait( trait_id( "MANDIBLES" ) ) ) { mealtime /= 2; } else if( has_trait( trait_id( "GOURMAND" ) ) ) { // Don't stack those two - that would be 25 moves per item mealtime -= 100; } if( has_trait( trait_id( "BEAK_HUM" ) ) && !drinkable ) { mealtime += 200; // Much better than PROBOSCIS but still optimized for fluids } else if( has_trait( trait_id( "SABER_TEETH" ) ) ) { mealtime += 250; // They get In The Way } if( amorphous ) { mealtime *= 1.1; // Minor speed penalty for having to flow around it // rather than just grab & munch } } moves -= mealtime; // If it's poisonous... poison us. // TODO: Move this to a flag if( food.poison > 0 && !has_trait( trait_id( "EATPOISON" ) ) && !has_trait( trait_id( "EATDEAD" ) ) ) { if( food.poison >= rng( 2, 4 ) ) { add_effect( effect_poison, food.poison * 100 ); } add_effect( effect_foodpoison, food.poison * 300 ); } if( amorphous ) { add_msg_player_or_npc( _( "You assimilate your %s." ), _( "<npcname> assimilates a %s." ), food.tname().c_str() ); } else if( drinkable ) { add_msg_player_or_npc( _( "You drink your %s." ), _( "<npcname> drinks a %s." ), food.tname().c_str() ); } else if( chew ) { add_msg_player_or_npc( _( "You eat your %s." ), _( "<npcname> eats a %s." ), food.tname().c_str() ); } if( item::find_type( food.type->comestible->tool )->tool ) { // Tools like lighters get used use_charges( food.type->comestible->tool, 1 ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL" ) ) { charge_power( rng( 50, 200 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_WEAK" ) ) { charge_power( rng( 25, 100 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_STRONG" ) ) { charge_power( rng( 75, 300 ) ); } if( food.has_flag( "CANNIBALISM" ) ) { // Sapiovores don't recognize humans as the same species. // But let them possibly feel cool about eating sapient stuff - treat like psycho const bool cannibal = has_trait( trait_id( "CANNIBAL" ) ); const bool psycho = has_trait( trait_id( "PSYCHOPATH" ) ) || has_trait( trait_id( "SAPIOVORE" ) ); const bool spiritual = has_trait( trait_id( "SPIRITUAL" ) ); if( cannibal && psycho && spiritual ) { add_msg_if_player( m_good, _( "You feast upon the human flesh, and in doing so, devour their spirit." ) ); // You're not really consuming anything special; you just think you are. add_morale( MORALE_CANNIBAL, 25, 300 ); } else if( cannibal && psycho ) { add_msg_if_player( m_good, _( "You feast upon the human flesh." ) ); add_morale( MORALE_CANNIBAL, 15, 200 ); } else if( cannibal && spiritual ) { add_msg_if_player( m_good, _( "You consume the sacred human flesh." ) ); // Boosted because you understand the philosophical implications of your actions, and YOU LIKE THEM. add_morale( MORALE_CANNIBAL, 15, 200 ); } else if( cannibal ) { add_msg_if_player( m_good, _( "You indulge your shameful hunger." ) ); add_morale( MORALE_CANNIBAL, 10, 50 ); } else if( psycho && spiritual ) { add_msg_if_player( _( "You greedily devour the taboo meat." ) ); // Small bonus for violating a taboo. add_morale( MORALE_CANNIBAL, 5, 50 ); } else if( psycho ) { add_msg_if_player( _( "Meh. You've eaten worse." ) ); } else if( spiritual ) { add_msg_if_player( m_bad, _( "This is probably going to count against you if there's still an afterlife." ) ); add_morale( MORALE_CANNIBAL, -60, -400, 600, 300 ); } else { add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) ); add_morale( MORALE_CANNIBAL, -60, -400, 600, 300 ); } } // Allergy check const auto allergy = allergy_type( food ); if( allergy != MORALE_NULL ) { add_msg_if_player( m_bad, _( "Yuck! How can anybody eat this stuff?" ) ); add_morale( allergy, -75, -400, 300, 240 ); } // Carnivores CAN eat junk food, but they won't like it much. // Pizza-scraping happens in consume_effects. if( has_trait( trait_id( "CARNIVORE" ) ) && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) ) { add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); add_morale( MORALE_NO_DIGEST, -25, -125, 300, 240 ); } if( !spoiled && chew && has_trait( trait_id( "SAPROPHAGE" ) ) ) { // It's OK to *drink* things that haven't rotted. Alternative is to ban water. D: add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); add_morale( MORALE_NO_DIGEST, -75, -400, 300, 240 ); } if( food.has_flag( "URSINE_HONEY" ) && ( !crossed_threshold() || has_trait( trait_id( "THRESH_URSINE" ) ) ) && mutation_category_level["MUTCAT_URSINE"] > 40 ) { //Need at least 5 bear mutations for effect to show, to filter out mutations in common with other mutcats int honey_fun = has_trait( trait_id( "THRESH_URSINE" ) ) ? std::min( mutation_category_level["MUTCAT_URSINE"] / 8, 20 ) : mutation_category_level["MUTCAT_URSINE"] / 12; if( honey_fun < 10 ) { add_msg_if_player( m_good, _( "You find the sweet taste of honey surprisingly palatable." ) ); } else { add_msg_if_player( m_good, _( "You feast upon the sweet honey." ) ); } add_morale( MORALE_HONEY, honey_fun, 100 ); } if( will_vomit ) { vomit(); } // chance to become parasitised if( !( has_bionic( bio_digestion ) || has_trait( trait_id( "PARAIMMUNE" ) ) ) ) { if( food.type->comestible->parasites > 0 && one_in( food.type->comestible->parasites ) ) { switch( rng( 0, 3 ) ) { case 0: if( !has_trait( trait_id( "EATHEALTH" ) ) ) { add_effect( effect_tapeworm, 1, num_bp, true ); } break; case 1: if( !has_trait( trait_id( "ACIDBLOOD" ) ) ) { add_effect( effect_bloodworms, 1, num_bp, true ); } break; case 2: add_effect( effect_brainworms, 1, num_bp, true ); break; case 3: add_effect( effect_paincysts, 1, num_bp, true ); } } } for( const auto &v : this->vitamins_from( food ) ) { auto qty = has_effect( effect_tapeworm ) ? v.second / 2 : v.second; // can never develop hypervitaminosis from consuming food vitamin_mod( v.first, qty ); } food.mod_charges( -1 ); return true; }
ret_val<edible_rating> player::will_eat( const item &food, bool interactive ) const { const auto ret = can_eat( food ); if( !ret.success() ) { if( interactive ) { add_msg_if_player( m_info, "%s", ret.c_str() ); } return ret; } std::vector<ret_val<edible_rating>> consequences; const auto add_consequence = [&consequences]( const std::string & msg, edible_rating code ) { consequences.emplace_back( ret_val<edible_rating>::make_failure( code, msg ) ); }; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); const auto &comest = food.type->comestible; if( food.rotten() ) { const bool saprovore = has_trait( trait_id( "SAPROVORE" ) ); if( !saprophage && !saprovore ) { add_consequence( _( "This is rotten and smells awful!" ), ROTTEN ); } } const bool carnivore = has_trait( trait_id( "CARNIVORE" ) ); if( food.has_flag( "CANNIBALISM" ) && !has_trait_flag( "CANNIBAL" ) ) { add_consequence( _( "The thought of eating human flesh makes you feel sick." ), CANNIBALISM ); } const bool edible = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( edible && has_effect( effect_nausea ) ) { add_consequence( _( "You still feel nauseous and will probably puke it all up again." ), NAUSEA ); } if( ( allergy_type( food ) != MORALE_NULL ) || ( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) ) ) { add_consequence( _( "Your stomach won't be happy (allergy)." ), ALLERGY ); } if( saprophage && edible && food.rotten() && !food.has_flag( "FERTILIZER" ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hard-coding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. add_consequence( _( "Your stomach won't be happy (not rotten enough)." ), ALLERGY_WEAK ); } const int nutr = nutrition_for( food ); const int quench = comest->quench; const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; if( !has_active_mutation( trait_id( "EATHEALTH" ) ) && !has_active_mutation( trait_id( "HIBERNATE" ) ) && !has_trait( trait_id( "SLIMESPAWNER" ) ) ) { if( get_hunger() < 0 && nutr >= 5 && !has_active_mutation( trait_id( "GOURMAND" ) ) ) { add_consequence( _( "You're full already and will be forcing yourself to eat." ), TOO_FULL ); } else if( ( ( nutr > 0 && temp_hunger < stomach_capacity() ) || ( comest->quench > 0 && temp_thirst < stomach_capacity() ) ) && !food.has_infinite_charges() ) { add_consequence( _( "You will not be able to finish it all." ), TOO_FULL ); } } if( !consequences.empty() ) { if( !interactive ) { return consequences.front(); } std::ostringstream req; for( const auto &elem : consequences ) { req << elem.str() << std::endl; } const bool eat_verb = food.has_flag( "USE_EAT_VERB" ); if( eat_verb || comest->comesttype == "FOOD" ) { req << string_format( _( "Eat your %s anyway?" ), food.tname().c_str() ); } else if( !eat_verb && comest->comesttype == "DRINK" ) { req << string_format( _( "Drink your %s anyway?" ), food.tname().c_str() ); } else { req << string_format( _( "Consume your %s anyway?" ), food.tname().c_str() ); } if( !query_yn( req.str() ) ) { return consequences.front(); } } // All checks ended, it's edible (or we're pretending it is) return ret_val<edible_rating>::make_success(); }
void player::consume_effects( item &food, bool rotten ) { if( !food.is_food() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto comest = food.type->comestible.get(); const int capacity = stomach_capacity(); if( has_trait( "THRESH_PLANT" ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } float factor = 1.0f; float hunger_factor = 1.0f; bool unhealthy_allowed = true; if( has_trait( "GIZZARD" ) ) { factor *= 0.6f; } if( has_trait( "CARNIVORE" ) && food.has_flag( "CARNIVORE_OK" ) ) { // At least partially edible if( food.has_any_flag( carnivore_blacklist ) ) { // Other things are in it, we only get partial benefits add_msg_if_player( _( "You pick out the edible parts and throw away the rest." ) ); factor *= 0.5f; } else { // Carnivores don't get unhealthy off pure meat diets unhealthy_allowed = false; } } // Saprophages get full nutrition from rotting food if( rotten && !has_trait( "SAPROPHAGE" ) ) { // everyone else only gets a portion of the nutrition hunger_factor *= rng_float( 0, 1 ); // and takes a health penalty if they aren't adapted if( !has_trait( "SAPROVORE" ) && !has_bionic( "bio_digestion" ) ) { mod_healthy_mod( -30, -200 ); } } // Bio-digestion gives extra nutrition if( has_bionic( "bio_digestion" ) ) { hunger_factor += rng_float( 0, 1 ); } const auto nutr = nutrition_for( food.type ); mod_hunger( -nutr * factor * hunger_factor ); mod_thirst( -comest->quench * factor ); mod_stomach_food( nutr * factor * hunger_factor ); mod_stomach_water( comest->quench * factor ); if( unhealthy_allowed || comest->healthy > 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest->healthy, ( comest->healthy >= 0 ) ? 200 : -200 ); } if( comest->stim != 0 && ( abs( stim ) < ( abs( comest->stim ) * 3 ) || sgn( stim ) != sgn( comest->stim ) ) ) { if( comest->stim < 0 ) { stim = std::max( comest->stim * 3, stim + comest->stim ); } else { stim = std::min( comest->stim * 3, stim + comest->stim ); } } add_addiction( comest->add, comest->addict ); if( addiction_craving( comest->add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest->add ) ); } if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { add_morale( MORALE_FOOD_HOT, 5, 10 ); } auto fun = comest->fun; if( food.has_flag( "COLD" ) && food.has_flag( "EATEN_COLD" ) && fun > 0 ) { if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 3, 60, 30, false, food.type ); } else { fun = 1; } } const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); if( gourmand ) { if( fun < -2 ) { add_morale( MORALE_FOOD_BAD, fun * 0.5, fun, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 6, 60, 30, false, food.type ); } } else if( fun < 0 ) { add_morale( MORALE_FOOD_BAD, fun, fun * 6, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun, fun * 4, 60, 30, false, food.type ); } if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest->quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest->quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest->quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest->quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( "SLIMESPAWNER" ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( g->summon_mon( mon_player_blob, target ) ) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( "EATHEALTH" ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest->quench > 0 ); }
edible_rating player::can_eat( const item &food, bool interactive, bool force ) const { if( is_npc() || force ) { // Just to be sure interactive = false; } const std::string &itname = food.tname(); // Helper to avoid ton of `if( interactive )` // Prints if interactive is true, does nothing otherwise const auto maybe_print = [interactive, &itname] ( game_message_type type, const char *str ) { if( interactive ) { add_msg( type, str, itname.c_str() ); } }; // As above, but for queries // Asks if interactive and not force // Always true if force // Never true otherwise const auto maybe_query = [force, interactive, &itname, this]( const char *str ) { if( force ) { return true; } else if( !interactive ) { return false; } return query_yn( str, itname.c_str() ); }; const auto comest = food.type->comestible.get(); if( comest == nullptr ) { maybe_print( m_info, _( "That doesn't look edible." ) ); return INEDIBLE; } if( comest->tool != "null" ) { bool has = has_amount( comest->tool, 1 ); if( item::count_by_charges( comest->tool ) ) { has = has_charges( comest->tool, 1 ); } if( !has ) { if( interactive ) { add_msg_if_player( m_info, _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ); } return NO_TOOL; } } if( is_underwater() ) { maybe_print( m_info, _( "You can't do that while underwater." ) ); return INEDIBLE; } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( "M_DEPENDENT" ) && food.type->id != "mycus_fruit" ) { maybe_print( m_info, _( "We can't eat that. It's not right for us." ) ); return INEDIBLE_MUTATION; } const bool drinkable = comest->comesttype == "DRINK" && !food.has_flag( "USE_EAT_VERB" ); // Here's why PROBOSCIS is such a negative trait. if( has_trait( "PROBOSCIS" ) && !drinkable ) { maybe_print( m_info, _( "Ugh, you can't drink that!" ) ); return INEDIBLE_MUTATION; } int capacity = stomach_capacity(); // TODO: Move this cache to a structure and pass it around // to speed up checking entire inventory for edibles const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); const bool eathealth = has_trait( "EATHEALTH" ); const bool slimespawner = has_trait( "SLIMESPAWNER" ); const int nutr = nutrition_for( food.type ); const int quench = comest->quench; bool spoiled = food.rotten(); const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; const bool overeating = get_hunger() < 0 && nutr >= 5 && !gourmand && !eathealth && !slimespawner && !hibernate; if( interactive && hibernate && ( get_hunger() >= -60 && get_thirst() >= -60 ) && ( temp_hunger < -60 || temp_thirst < -60 ) ) { if( !maybe_query( _( "You're adequately fueled. Prepare for hibernation?" ) ) ) { return TOO_FULL; } } const bool carnivore = has_trait( "CARNIVORE" ); if( carnivore && nutr > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { maybe_print( m_info, _( "Eww. Inedible plant stuff!" ) ); return INEDIBLE_MUTATION; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! maybe_print( m_info, _( "The thought of eating that makes you feel sick. You decide not to." ) ); return INEDIBLE_MUTATION; } if( food.has_flag( "CANNIBALISM" ) ) { if( !has_trait_flag( "CANNIBAL" ) && !maybe_query( _( "The thought of eating that makes you feel sick. Really do it?" ) ) ) { return CANNIBALISM; } } if( is_allergic( food ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } if( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } const bool saprophage = has_trait( "SAPROPHAGE" ); // The item is solid food const bool chew = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( spoiled ) { if( !saprophage && !has_trait( "SAPROVORE" ) && !maybe_query( _( "This %s smells awful! Eat it?" ) ) ) { return ROTTEN; } } else if( saprophage && chew && !food.has_flag( "FERTILIZER" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hardcoding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. maybe_print( m_info, _( "It's too fresh, let it age a little first." ) ); return ROTTEN; } // Print at most one of those bool overfull = false; if( overeating ) { overfull = !maybe_query( _( "You're full. Force yourself to eat?" ) ); } else if( ( ( nutr > 0 && temp_hunger < capacity ) || ( comest->quench > 0 && temp_thirst < capacity ) ) && !eathealth && !slimespawner ) { overfull = !maybe_query( _( "You will not be able to finish it all. Consume it?" ) ); } if( overfull ) { return TOO_FULL; } // All checks ended, it's edible (or we're pretending it is) return EDIBLE; }