bool player::feed_battery_with( item &it ) { if( !can_feed_battery_with( it ) ) { return false; } const int energy = get_acquirable_energy( it, rechargeable_cbm::battery ); const int profitable_energy = std::min( energy, max_power_level - power_level ); if( profitable_energy <= 0 ) { add_msg_player_or_npc( m_info, _( "Your internal power storage is fully powered." ), _( "<npcname>'s internal power storage is fully powered." ) ); return false; } charge_power( it.charges ); it.charges -= profitable_energy; add_msg_player_or_npc( m_info, _( "You recharge your battery system with the %s." ), _( "<npcname> recharges their battery system with the %s." ), it.tname().c_str() ); mod_moves( -250 ); return true; }
bool player::feed_furnace_with( item &it ) { if( !can_feed_furnace_with( it ) ) { return false; } const int energy = get_acquirable_energy( it, rechargeable_cbm::furnace ); if( energy == 0 ) { add_msg_player_or_npc( m_info, _( "You digest your %s, but fail to acquire energy from it." ), _( "<npcname> digests their %s for energy, but fails to acquire energy from it." ), it.tname().c_str() ); } else if( power_level >= max_power_level ) { add_msg_player_or_npc( _( "You digest your %s, but you're fully powered already, so the energy is wasted." ), _( "<npcname> digests a %s for energy, they're fully powered already, so the energy is wasted." ), it.tname().c_str() ); } else { const int profitable_energy = std::min( energy, max_power_level - power_level ); add_msg_player_or_npc( m_info, ngettext( "You digest your %s and recharge %d point of energy.", "You digest your %s and recharge %d points of energy.", profitable_energy ), ngettext( "<npcname> digests a %s and recharges %d point of energy.", "<npcname> digests a %s and recharges %d points of energy.", profitable_energy ), it.tname().c_str(), profitable_energy ); charge_power( profitable_energy ); } it.charges = 0; mod_moves( -250 ); return true; }
bool player::feed_reactor_with( item &it ) { if( !can_feed_reactor_with( it ) ) { return false; } const auto iter = plut_charges.find( it.typeId() ); const int max_amount = iter != plut_charges.end() ? iter->second : 0; const int amount = std::min( get_acquirable_energy( it, rechargeable_cbm::reactor ), max_amount ); if( amount >= PLUTONIUM_CHARGES * 10 && !query_yn( _( "Thats a LOT of plutonium. Are you sure you want that much?" ) ) ) { return false; } add_msg_player_or_npc( _( "You add your %s to your reactor's tank." ), _( "<npcname> pours %s into their reactor's tank." ), it.tname().c_str() ); tank_plut += amount; // @todo Encapsulate it.charges -= 1; mod_moves( -250 ); return true; }
/** * Attempts to harm a creature with a projectile. * * @param source Pointer to the creature who shot the projectile. * @param attack A structure describing the attack and its results. */ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) { const double missed_by = attack.missed_by; if( missed_by >= 1.0 ) { // Total miss return; } const projectile &proj = attack.proj; dealt_damage_instance &dealt_dam = attack.dealt_dam; const auto &proj_effects = proj.proj_effects; const bool u_see_this = g->u.sees(*this); const int avoid_roll = dodge_roll(); // Do dice(10, speed) instead of dice(speed, 10) because speed could potentially be > 10000 const int diff_roll = dice( 10, proj.speed ); // Partial dodge, capped at [0.0, 1.0], added to missed_by const double dodge_rescaled = avoid_roll / static_cast<double>( diff_roll ); const double goodhit = missed_by + std::max( 0.0, std::min( 1.0, dodge_rescaled ) ) ; if( goodhit >= 1.0 ) { // "Avoid" rather than "dodge", because it includes removing self from the line of fire // rather than just Matrix-style bullet dodging if( source != nullptr && g->u.sees( *source ) ) { add_msg_player_or_npc( m_warning, _("You avoid %s projectile!"), _("<npcname> avoids %s projectile."), source->disp_name(true).c_str() ); } else { add_msg_player_or_npc( m_warning, _("You avoid an incoming projectile!"), _("<npcname> avoids an incoming projectile.") ); } attack.missed_by = 1.0; // Arbitrary value return; } // Bounce applies whether it does damage or not. if( proj.proj_effects.count( "BOUNCE" ) ) { add_effect( effect_bounced, 1_turns ); } body_part bp_hit; double hit_value = missed_by + rng_float(-0.5, 0.5); // Headshots considered elsewhere if( hit_value <= 0.4 ) { bp_hit = bp_torso; } else if (one_in(4)) { if( one_in(2)) { bp_hit = bp_leg_l; } else { bp_hit = bp_leg_r; } } else { if( one_in(2)) { bp_hit = bp_arm_l; } else { bp_hit = bp_arm_r; } } double damage_mult = 1.0; std::string message = ""; game_message_type gmtSCTcolor = m_neutral; if( goodhit < accuracy_headshot ) { message = _("Headshot!"); gmtSCTcolor = m_headshot; damage_mult *= rng_float(1.95, 2.05); bp_hit = bp_head; // headshot hits the head, of course } else if( goodhit < accuracy_critical ) { message = _("Critical!"); gmtSCTcolor = m_critical; damage_mult *= rng_float(1.5, 2.0); } else if( goodhit < accuracy_goodhit ) { message = _("Good hit!"); gmtSCTcolor = m_good; damage_mult *= rng_float(1, 1.5); } else if( goodhit < accuracy_standard ) { damage_mult *= rng_float(0.5, 1); } else if( goodhit < accuracy_grazing ) { message = _("Grazing hit."); gmtSCTcolor = m_grazing; damage_mult *= rng_float(0, .25); } if( source != nullptr && !message.empty() ) { source->add_msg_if_player(m_good, message.c_str()); } attack.missed_by = goodhit; // copy it, since we're mutating damage_instance impact = proj.impact; if( damage_mult > 0.0f && proj_effects.count( "NO_DAMAGE_SCALING" ) ) { damage_mult = 1.0f; } impact.mult_damage(damage_mult); if( proj_effects.count( "NOGIB" ) > 0 ) { float dmg_ratio = (float)impact.total_damage() / get_hp_max( player::bp_to_hp( bp_hit ) ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } dealt_dam = deal_damage(source, bp_hit, impact); dealt_dam.bp_hit = bp_hit; // Apply ammo effects to target. if (proj.proj_effects.count("FLAME")) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, rng( 8_turns, 20_turns ), bp_hit ); } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) { add_effect( effect_onfire, rng( 5_turns, 10_turns ), bp_hit ); } } else if (proj.proj_effects.count("INCENDIARY") ) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, rng( 2_turns, 6_turns ), bp_hit ); } else if ( (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) && one_in(4) ) { add_effect( effect_onfire, rng( 1_turns, 4_turns ), bp_hit ); } } else if (proj.proj_effects.count("IGNITE")) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, 6_turns, bp_hit ); } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) { add_effect( effect_onfire, 10_turns, bp_hit ); } } if( bp_hit == bp_head && proj_effects.count( "BLINDS_EYES" ) ) { // TODO: Change this to require bp_eyes add_env_effect( effect_blind, bp_eyes, 5, rng( 3_turns, 10_turns ) ); } if( proj_effects.count( "APPLY_SAP" ) ) { add_effect( effect_sap, 1_turns * dealt_dam.total_damage() ); } int stun_strength = 0; if (proj.proj_effects.count("BEANBAG")) { stun_strength = 4; } if (proj.proj_effects.count("LARGE_BEANBAG")) { stun_strength = 16; } if( stun_strength > 0 ) { switch( get_size() ) { case MS_TINY: stun_strength *= 4; break; case MS_SMALL: stun_strength *= 2; break; case MS_MEDIUM: default: break; case MS_LARGE: stun_strength /= 2; break; case MS_HUGE: stun_strength /= 4; break; } add_effect( effect_stunned, 1_turns * rng( stun_strength / 2, stun_strength ) ); } if(u_see_this) { if( damage_mult == 0 ) { if( source != nullptr ) { add_msg( source->is_player() ? _("You miss!") : _("The shot misses!") ); } } else if( dealt_dam.total_damage() == 0 ) { //~ 1$ - monster name, 2$ - character's bodypart or monster's skin/armor add_msg( _("The shot reflects off %1$s %2$s!"), disp_name(true).c_str(), is_monster() ? skin_name().c_str() : body_part_name_accusative(bp_hit).c_str() ); } else if( is_player() ) { //monster hits player ranged //~ Hit message. 1$s is bodypart name in accusative. 2$d is damage value. add_msg_if_player(m_bad, _( "You were hit in the %1$s for %2$d damage." ), body_part_name_accusative(bp_hit).c_str(), dealt_dam.total_damage()); } else if( source != nullptr ) { if( source->is_player() ) { //player hits monster ranged SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(dealt_dam.total_damage(), get_hp_max(), true).first, m_good, message, gmtSCTcolor); if (get_hp() > 0) { SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(get_hp(), get_hp_max(), true).first, m_good, //~ "hit points", used in scrolling combat text _("hp"), m_neutral, "hp"); } else { SCT.removeCreatureHP(); } add_msg(m_good, _("You hit %s for %d damage."), disp_name().c_str(), dealt_dam.total_damage()); } else if( u_see_this ) { //~ 1$ - shooter, 2$ - target add_msg(_("%1$s shoots %2$s."), source->disp_name().c_str(), disp_name().c_str()); } } } check_dead_state(); attack.hit_critter = this; attack.missed_by = goodhit; }
bool Character::move_effects(bool attacking) { if (has_effect("downed")) { ///\EFFECT_DEX increases chance to stand up when knocked down ///\EFFECT_STR increases chance to stand up when knocked down, slightly if (rng(0, 40) > get_dex() + get_str() / 2) { add_msg_if_player(_("You struggle to stand.")); } else { add_msg_player_or_npc(m_good, _("You stand up."), _("<npcname> stands up.")); remove_effect("downed"); } return false; } if (has_effect("webbed")) { ///\EFFECT_STR increases chance to escape webs if (x_in_y(get_str(), 6 * get_effect_int("webbed"))) { add_msg_player_or_npc(m_good, _("You free yourself from the webs!"), _("<npcname> frees themselves from the webs!")); remove_effect("webbed"); } else { add_msg_if_player(_("You try to free yourself from the webs, but can't get loose!")); } return false; } if (has_effect("lightsnare")) { ///\EFFECT_STR increases chance to escape light snare ///\EFFECT_DEX increases chance to escape light snare if(x_in_y(get_str(), 12) || x_in_y(get_dex(), 8)) { remove_effect("lightsnare"); add_msg_player_or_npc(m_good, _("You free yourself from the light snare!"), _("<npcname> frees themselves from the light snare!")); item string("string_36", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), string); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the light snare, but can't get loose!")); } return false; } if (has_effect("heavysnare")) { ///\EFFECT_STR increases chance to escape heavy snare, slightly ///\EFFECT_DEX increases chance to escape light snare if(x_in_y(get_str(), 32) || x_in_y(get_dex(), 16)) { remove_effect("heavysnare"); add_msg_player_or_npc(m_good, _("You free yourself from the heavy snare!"), _("<npcname> frees themselves from the heavy snare!")); item rope("rope_6", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), rope); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the heavy snare, but can't get loose!")); } return false; } if (has_effect("beartrap")) { /* Real bear traps can't be removed without the proper tools or immense strength; eventually this should allow normal players two options: removal of the limb or removal of the trap from the ground (at which point the player could later remove it from the leg with the right tools). As such we are currently making it a bit easier for players and NPC's to get out of bear traps. */ ///\EFFECT_STR increases chance to escape bear trap if(x_in_y(get_str(), 100)) { remove_effect("beartrap"); add_msg_player_or_npc(m_good, _("You free yourself from the bear trap!"), _("<npcname> frees themselves from the bear trap!")); item beartrap("beartrap", calendar::turn); g->m.add_item_or_charges(posx(), posy(), beartrap); } else { add_msg_if_player(m_bad, _("You try to free yourself from the bear trap, but can't get loose!")); } return false; } if (has_effect("crushed")) { ///\EFFECT_STR increases chance to escape crushing rubble ///\EFFECT_DEX increases chance to escape crushing rubble, slightly if(x_in_y(get_str() + get_dex() / 4, 100)) { remove_effect("crushed"); add_msg_player_or_npc(m_good, _("You free yourself from the rubble!"), _("<npcname> frees themselves from the rubble!")); } else { add_msg_if_player(m_bad, _("You try to free yourself from the rubble, but can't get loose!")); } return false; } // Below this point are things that allow for movement if they succeed // Currently we only have one thing that forces movement if you succeed, should we get more // than this will need to be reworked to only have success effects if /all/ checks succeed if (has_effect("in_pit")) { ///\EFFECT_STR increases chance to escape pit ///\EFFECT_DEX increases chance to escape pit, slightly if (rng(0, 40) > get_str() + get_dex() / 2) { add_msg_if_player(m_bad, _("You try to escape the pit, but slip back in.")); return false; } else { add_msg_player_or_npc(m_good, _("You escape the pit!"), _("<npcname> escapes the pit!")); remove_effect("in_pit"); } } if( has_effect( "grabbed" ) && !attacking ) { int zed_number = 0; for( auto &&dest : g->m.points_in_radius( pos(), 1, 0 ) ){ if( g->mon_at( dest ) != -1 && ( g->zombie( g->mon_at( dest ) ).has_flag( MF_GRABS ) || g->zombie( g->mon_at( dest ) ).type->has_special_attack( "GRAB" ) ) ) { zed_number ++; } } if( zed_number == 0 ) { add_msg_player_or_npc( m_good, _( "You find yourself no longer grabbed." ), _( "<npcname> finds themselves no longer grabbed." ) ); remove_effect( "grabbed" ); ///\EFFECT_DEX increases chance to escape grab, if >STR ///\EFFECT_STR increases chance to escape grab, if >DEX } else if( rng( 0, std::max( get_dex(), get_str() ) ) < rng( get_effect_int( "grabbed" ), 8 ) ) { // Randomly compare higher of dex or str to grab intensity. add_msg_player_or_npc( m_bad, _( "You try break out of the grab, but fail!" ), _( "<npcname> tries to break out of the grab, but fails!" ) ); return false; } else { add_msg_player_or_npc( m_good, _( "You break out of the grab!" ), _( "<npcname> breaks out of the grab!" ) ); remove_effect( "grabbed" ); } } return Creature::move_effects( attacking ); }
void game::fire(player &p, int tarx, int tary, std::vector<point> &trajectory, bool burst) { item ammotmp; item* gunmod = p.weapon.active_gunmod(); it_ammo *curammo = NULL; item *weapon = NULL; if (p.weapon.has_flag("CHARGE")) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = p.weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); tmpammo->range = 5 + charges * 5; if (charges <= 4) tmpammo->dispersion = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->dispersion = charges * (charges - 4); tmpammo->recoil = tmpammo->dispersion * .8; if (charges == 8) { tmpammo->ammo_effects.insert("EXPLOSIVE_BIG"); } else if (charges >= 6) { tmpammo->ammo_effects.insert("EXPLOSIVE"); } if (charges >= 5){ tmpammo->ammo_effects.insert("FLAME"); } else if (charges >= 4) { tmpammo->ammo_effects.insert("INCENDIARY"); } if (gunmod != NULL) { weapon = gunmod; } else { weapon = &p.weapon; } curammo = tmpammo; weapon->curammo = tmpammo; weapon->active = false; weapon->charges = 0; } else if (gunmod != NULL) { weapon = gunmod; curammo = weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = p.weapon.curammo; weapon = &p.weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!weapon->is_gun() && !weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", p.name.c_str(), weapon->tname().c_str()); return; } bool is_bolt = false; std::set<std::string> *effects = &curammo->ammo_effects; // Bolts and arrows are silent if (curammo->type == "bolt" || curammo->type == "arrow") is_bolt = true; int x = p.posx, y = p.posy; // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); if (p.has_trait(PF_TRIGGERHAPPY) && one_in(30)) burst = true; if (burst && weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto // Use different amounts of time depending on the type of gun and our skill if (!effects->count("BOUNCE")) { p.moves -= time_to_fire(p, firing); } // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = weapon->burst_size(); if (num_shots > weapon->num_charges() && !weapon->has_flag("CHARGE")) num_shots = weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); // Set up a timespec for use in the nanosleep function below timespec ts; ts.tv_sec = 0; ts.tv_nsec = BULLET_SPEED; // Use up some ammunition int trange = rl_dist(p.posx, p.posy, tarx, tary); if (trange < int(firing->volume / 3) && firing->ammo != "shot") trange = int(firing->volume / 3); else if (p.has_bionic("bio_targeting")) { if (trange > LONG_RANGE) trange = int(trange * .65); else trange = int(trange * .8); } if (firing->skill_used == Skill::skill("rifle") && trange > LONG_RANGE) trange = LONG_RANGE + .6 * (trange - LONG_RANGE); std::string message = ""; bool missed = false; int tart; const bool debug_retarget = false; // this will inevitably be needed const bool wildly_spraying = false; // stub for now. later, rng based on stress/skill/etc at the start, int weaponrange = p.weapon.range(); // this is expensive, let's cache. todo: figure out if we need p.weapon.range(&p); for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first if ( curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0) ) { std::vector<point> new_targets; new_targets.clear(); if ( debug_retarget == true ) { mvprintz(curshot,5,c_red,"[%d] %s: retarget: mon_at(%d,%d)",curshot,p.name.c_str(),tarx,tary); if(mon_at(tarx, tary) == -1) { printz(c_red, " = -1"); } else { printz(c_red, ".hp=%d", z[mon_at(tarx, tary)].hp ); } } for ( int radius = 0; /* range from last target, not shooter! */ radius <= 2 + p.skillLevel("gun") && /* more skill: wider burst area? */ radius <= weaponrange && /* this seems redundant */ ( new_targets.empty() || /* got target? stop looking. However this breaks random selection, aka, wildly spraying, so: */ wildly_spraying == true ); /* lets set this based on rng && stress or whatever elsewhere */ radius++ ) { /* iterate from last target's position: makes sense for burst fire.*/ for (std::vector<monster>::iterator it = z.begin(); it != z.end(); it++) { int nt_range_to_me = rl_dist(p.posx, p.posy, it->posx, it->posy); int dummy; if (nt_range_to_me == 0 || nt_range_to_me > weaponrange || !pl_sees(&p, &(*it), dummy)) { /* reject out of range and unseen targets as well as MY FACE */ continue; } int nt_range_to_lt = rl_dist(tarx,tary,it->posx,it->posy); /* debug*/ if ( debug_retarget && nt_range_to_lt <= 5 ) printz(c_red, " r:%d/l:%d/m:%d ..", radius, nt_range_to_lt, nt_range_to_me ); if (nt_range_to_lt != radius) { continue; /* we're spiralling outward, catch you next iteration (maybe) */ } if (it->hp >0 && it->friendly == 0) { new_targets.push_back(point(it->posx, it->posy)); /* oh you're not dead and I don't like you. Hello! */ } } } if ( new_targets.empty() == false ) { /* new victim! or last victim moved */ int target_picked = rng(0, new_targets.size() - 1); /* 1 victim list unless wildly spraying */ tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) { trajectory = line_to(p.posx, p.posy, tarx, tary, tart); } else { trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } /* debug */ if (debug_retarget) printz(c_ltgreen, " NEW:(%d:%d,%d) %d,%d (%s)[%d] hp: %d", target_picked, new_targets[target_picked].x, new_targets[target_picked].y, tarx, tary, z[mon_at(tarx, tary)].name().c_str(), mon_at(tarx, tary), z[mon_at(tarx, tary)].hp); } else if ( ( !p.has_trait(PF_TRIGGERHAPPY) || /* double tap. TRIPLE TAP! wait, no... */ one_in(3) /* on second though...everyone double-taps at times. */ ) && ( p.skillLevel("gun") >= 7 || /* unless trained */ one_in(7 - p.skillLevel("gun")) /* ...sometimes */ ) ) { return; // No targets, so return } else if (debug_retarget) { printz(c_red, " new targets.empty()!"); } } else if (debug_retarget) { mvprintz(curshot,5,c_red,"[%d] %s: target == mon_at(%d,%d)[%d] %s hp %d",curshot, p.name.c_str(), tarx ,tary, mon_at(tarx, tary), z[mon_at(tarx, tary)].name().c_str(), z[mon_at(tarx, tary)].hp); } // Drop a shell casing if appropriate. itype_id casing_type = "null"; if( curammo->type == "shot" ) casing_type = "shot_hull"; else if( curammo->type == "9mm" ) casing_type = "9mm_casing"; else if( curammo->type == "22" ) casing_type = "22_casing"; else if( curammo->type == "38" ) casing_type = "38_casing"; else if( curammo->type == "40" ) casing_type = "40_casing"; else if( curammo->type == "44" ) casing_type = "44_casing"; else if( curammo->type == "45" ) casing_type = "45_casing"; else if( curammo->type == "454" ) casing_type = "454_casing"; else if( curammo->type == "500" ) casing_type = "500_casing"; else if( curammo->type == "57" ) casing_type = "57mm_casing"; else if( curammo->type == "46" ) casing_type = "46mm_casing"; else if( curammo->type == "762" ) casing_type = "762_casing"; else if( curammo->type == "223" ) casing_type = "223_casing"; else if( curammo->type == "3006" ) casing_type = "3006_casing"; else if( curammo->type == "308" ) casing_type = "308_casing"; else if( curammo->type == "40mm" ) casing_type = "40mm_casing"; if (casing_type != "null") { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; if( weapon->has_gunmod("brass_catcher") != -1 ) { p.i_add( casing ); } else { int x = p.posx - 1 + rng(0, 2); int y = p.posy - 1 + rng(0, 2); m.add_item_or_charges(x, y, casing); } } // Use up a round (or 100) if (weapon->has_flag("FIRE_100")) weapon->charges -= 100; else weapon->charges--; if (firing->skill_used != Skill::skill("archery") && firing->skill_used != Skill::skill("throw")) { // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (one_in(2 << firing->durability)) { add_msg_player_or_npc( &p, _("Your weapon misfires!"), _("<npcname>'s weapon misfires!") ); return; } } make_gun_sound_effect(this, p, burst, weapon); int trange = calculate_range(p, tarx, tary); double missed_by = calculate_missed_by(p, trange, weapon); // Calculate a penalty based on the monster's speed double monster_speed_penalty = 1.; int target_index = mon_at(tarx, tary); if (target_index != -1) { monster_speed_penalty = double(z[target_index].speed) / 80.; if (monster_speed_penalty < 1.) monster_speed_penalty = 1.; } if (curshot > 0) { if (recoil_add(p) % 2 == 1) p.recoil++; p.recoil += recoil_add(p) / 2; } else p.recoil += recoil_add(p); if (missed_by >= 1.) { // We missed D: // Shoot a random nearby space? int mtarx = tarx + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); int mtary = tary + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, x, y, -1, tart)) trajectory = line_to(p.posx, p.posy, mtarx, mtary, tart); else trajectory = line_to(p.posx, p.posy, mtarx, mtary, 0); missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You miss!"), _("<npcname> misses!") ); } } else if (missed_by >= .8 / monster_speed_penalty) { // Hit the space, but not necessarily the monster there missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You barely miss!"), _("<npcname> barely misses!") ); } } int dam = weapon->gun_damage(); int tx = trajectory[0].x; int ty = trajectory[0].y; int px = trajectory[0].x; int py = trajectory[0].y; for (int i = 0; i < trajectory.size() && (dam > 0 || (effects->count("FLAME"))); i++) { px = tx; py = ty; tx = trajectory[i].x; ty = trajectory[i].y; // Drawing the bullet uses player u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if (u_see(tx, ty)) { if (i > 0) { m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true, u.posx + u.view_offset_x, u.posy + u.view_offset_y); } char bullet = '*'; if (effects->count("FLAME")) bullet = '#'; mvwputch(w_terrain, ty + VIEWY - u.posy - u.view_offset_y, tx + VIEWX - u.posx - u.view_offset_x, c_red, bullet); wrefresh(w_terrain); if (&p == &u) nanosleep(&ts, NULL); } if (dam <= 0 && !(effects->count("FLAME"))) { // Ran out of momentum. ammo_effects(this, tx, ty, *effects); if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(4)) || (curammo->m1 != "wood" && !one_in(15)))) m.add_item_or_charges(tx, ty, ammotmp); if (weapon->num_charges() == 0) weapon->curammo = NULL; return; } // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it int mondex = mon_at(tx, ty); // If we shot us a monster... if (mondex != -1 && (!z[mondex].has_flag(MF_DIGS) || rl_dist(p.posx, p.posy, z[mondex].posx, z[mondex].posy) <= 1) && ((!missed && i == trajectory.size() - 1) || one_in((5 - int(z[mondex].type->size))))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; // Penalize for the monster's speed if (z[mondex].speed > 80) goodhit *= double( double(z[mondex].speed) / 80.); std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam, &z[mondex]); shoot_monster(this, p, z[mondex], dam, goodhit, weapon); } else if ((!missed || one_in(3)) && (npc_at(tx, ty) != -1 || (u.posx == tx && u.posy == ty))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; player *h; if (u.posx == tx && u.posy == ty) h = &u; else h = active_npc[npc_at(tx, ty)]; if (h->power_level >= 10 && h->uncanny_dodge()) { h->power_level -= 7; // dodging bullets costs extra } else { std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam); shoot_player(this, p, h, dam, goodhit); } } else m.shoot(this, tx, ty, dam, i == trajectory.size() - 1, *effects); } // Done with the trajectory! ammo_effects(this, tx, ty, *effects); if (effects->count("BOUNCE")) { for (int i = 0; i < z.size(); i++) { // search for monsters in radius 4 around impact site if (rl_dist(z[i].posx, z[i].posy, tx, ty) <= 4) { // don't hit targets that have already been hit if (!z[i].has_effect(ME_BOUNCED) && !z[i].dead) { add_msg(_("The attack bounced to %s!"), z[i].name().c_str()); trajectory = line_to(tx, ty, z[i].posx, z[i].posy, 0); if (weapon->charges > 0) fire(p, z[i].posx, z[i].posy, trajectory, false); break; } } } } if (m.move_cost(tx, ty) == 0) { tx = px; ty = py; } if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(5)) || (curammo->m1 != "wood" && !one_in(15)) )) m.add_item_or_charges(tx, ty, ammotmp); } if (weapon->num_charges() == 0) weapon->curammo = NULL; }
void player::remove_mutation( const trait_id &mut ) { const auto &mdata = mut.obj(); // Check if there's a prerequisite we should shrink back into trait_id replacing = trait_id::NULL_ID(); std::vector<trait_id> originals = mdata.prereqs; for (size_t i = 0; !replacing && i < originals.size(); i++) { trait_id pre = originals[i]; const auto &p = pre.obj(); for (size_t j = 0; !replacing && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } trait_id replacing2 = trait_id::NULL_ID(); std::vector<trait_id> originals2 = mdata.prereqs2; for (size_t i = 0; !replacing2 && i < originals2.size(); i++) { trait_id pre2 = originals2[i]; const auto &p = pre2.obj(); for (size_t j = 0; !replacing2 && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } // See if this mutation is canceled by a base trait //Only if there's no prerequisite to shrink to, thus we're at the bottom of the trait line if( !replacing ) { //Check each mutation until we reach the end or find a trait to revert to for( auto &iter : mutation_branch::get_all() ) { //See if it's in our list of base traits but not active if (has_base_trait(iter.first) && !has_trait(iter.first)) { //See if that base trait cancels the mutation we are using std::vector<trait_id> traitcheck = iter.second.cancels; if (!traitcheck.empty()) { for (size_t j = 0; !replacing && j < traitcheck.size(); j++) { if (traitcheck[j] == mut) { replacing = (iter.first); } } } } if( replacing ) { break; } } } // Duplicated for prereq2 if( !replacing2 ) { //Check each mutation until we reach the end or find a trait to revert to for( auto &iter : mutation_branch::get_all() ) { //See if it's in our list of base traits but not active if (has_base_trait(iter.first) && !has_trait(iter.first)) { //See if that base trait cancels the mutation we are using std::vector<trait_id> traitcheck = iter.second.cancels; if (!traitcheck.empty()) { for (size_t j = 0; !replacing2 && j < traitcheck.size(); j++) { if (traitcheck[j] == mut && (iter.first) != replacing) { replacing2 = (iter.first); } } } } if( replacing2 ) { break; } } } // make sure we don't toggle a mutation or trait twice, or it will cancel itself out. if(replacing == replacing2) { replacing2 = trait_id::NULL_ID(); } // This should revert back to a removed base trait rather than simply removing the mutation unset_mutation(mut); bool mutation_replaced = false; game_message_type rating; if( replacing ) { const auto &replace_mdata = replacing.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points > 0) { rating = m_good; } else if(mdata.points - replace_mdata.points > 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("Your %1$s mutation turns into %2$s."), _("<npcname>'s %1$s mutation turns into %2$s."), mdata.name.c_str(), replace_mdata.name.c_str() ); set_mutation(replacing); mutation_loss_effect(mut); mutation_effect(replacing); mutation_replaced = true; } if( replacing2 ) { const auto &replace_mdata = replacing2.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points > 0) { rating = m_good; } else if(mdata.points - replace_mdata.points > 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("Your %1$s mutation turns into %2$s."), _("<npcname>'s %1$s mutation turns into %2$s."), mdata.name.c_str(), replace_mdata.name.c_str() ); set_mutation(replacing2); mutation_loss_effect(mut); mutation_effect(replacing2); mutation_replaced = true; } if(!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_bad; } else if(mdata.points < 0) { rating = m_good; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("You lose your %s mutation."), _("<npcname> loses their %s mutation."), mdata.name.c_str() ); mutation_loss_effect(mut); } set_highest_cat_level(); drench_mut_calc(); }
bool player::mutate_towards( const trait_id &mut ) { if (has_child_flag(mut)) { remove_child_flag(mut); return true; } const mutation_branch &mdata = mut.obj(); bool has_prereqs = false; bool prereq1 = false; bool prereq2 = false; std::vector<trait_id> canceltrait; std::vector<trait_id> prereq = mdata.prereqs; std::vector<trait_id> prereqs2 = mdata.prereqs2; std::vector<trait_id> cancel = mdata.cancels; for (size_t i = 0; i < cancel.size(); i++) { if (!has_trait( cancel[i] )) { cancel.erase(cancel.begin() + i); i--; } else if (has_base_trait( cancel[i] )) { //If we have the trait, but it's a base trait, don't allow it to be removed normally canceltrait.push_back( cancel[i]); cancel.erase(cancel.begin() + i); i--; } } for (size_t i = 0; i < cancel.size(); i++) { if (!cancel.empty()) { trait_id removed = cancel[i]; remove_mutation(removed); cancel.erase(cancel.begin() + i); i--; // This checks for cases where one trait knocks out several others // Probably a better way, but gets it Fixed Now--KA101 return mutate_towards(mut); } } for (size_t i = 0; (!prereq1) && i < prereq.size(); i++) { if (has_trait(prereq[i])) { prereq1 = true; } } for (size_t i = 0; (!prereq2) && i < prereqs2.size(); i++) { if (has_trait(prereqs2[i])) { prereq2 = true; } } if (prereq1 && prereq2) { has_prereqs = true; } if (!has_prereqs && (!prereq.empty() || !prereqs2.empty())) { if (!prereq1 && !prereq.empty()) { return mutate_towards( random_entry( prereq ) ); } else if (!prereq2 && !prereqs2.empty()) { return mutate_towards( random_entry( prereqs2 ) ); } } // Check for threshold mutation, if needed bool threshold = mdata.threshold; bool profession = mdata.profession; bool has_threshreq = false; std::vector<trait_id> threshreq = mdata.threshreq; // It shouldn't pick a Threshold anyway--they're supposed to be non-Valid // and aren't categorized. This can happen if someone makes a threshold mutation into a prerequisite. if (threshold) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } if (profession) { // Profession picks fail silently return false; } for (size_t i = 0; !has_threshreq && i < threshreq.size(); i++) { if (has_trait(threshreq[i])) { has_threshreq = true; } } // No crossing The Threshold by simply not having it if (!has_threshreq && !threshreq.empty()) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } // Check if one of the prerequisites that we have TURNS INTO this one trait_id replacing = trait_id::NULL_ID(); prereq = mdata.prereqs; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre = elem; const auto &p = pre.obj(); for (size_t j = 0; !replacing && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } } // Loop through again for prereqs2 trait_id replacing2 = trait_id::NULL_ID(); prereq = mdata.prereqs2; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre2 = elem; const auto &p = pre2.obj(); for (size_t j = 0; !replacing2 && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } } set_mutation(mut); bool mutation_replaced = false; game_message_type rating; if( replacing ) { const auto &replace_mdata = replacing.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit this to visible mutations // TODO: In case invisible mutation turns into visible or vice versa // print only the visible mutation appearing/disappearing add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing); mutation_loss_effect(replacing); mutation_effect(mut); mutation_replaced = true; } if( replacing2 ) { const auto &replace_mdata = replacing2.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing2); mutation_loss_effect(replacing2); mutation_effect(mut); mutation_replaced = true; } for (size_t i = 0; i < canceltrait.size(); i++) { const auto &cancel_mdata = canceltrait[i].obj(); if(mdata.mixed_effect || cancel_mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points < cancel_mdata.points) { rating = m_bad; } else if(mdata.points > cancel_mdata.points) { rating = m_good; } else if(mdata.points == cancel_mdata.points) { rating = m_neutral; } else { rating = m_mixed; } // If this new mutation cancels a base trait, remove it and add the mutation at the same time add_msg_player_or_npc( rating, _("Your innate %1$s trait turns into %2$s!"), _("<npcname>'s innate %1$s trait turns into %2$s!"), cancel_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), cancel_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(canceltrait[i]); mutation_loss_effect(canceltrait[i]); mutation_effect(mut); mutation_replaced = true; } if (!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_good; } else if(mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit to visible mutations add_msg_player_or_npc( rating, _("You gain a mutation called %s!"), _("<npcname> gains a mutation called %s!"), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "Gained the mutation '%s'."), pgettext("memorial_female", "Gained the mutation '%s'."), mdata.name.c_str()); mutation_effect(mut); } set_highest_cat_level(); drench_mut_calc(); return true; }
void Character::mutation_effect( const trait_id &mut ) { if( mut == "GLASSJAW" ) { recalc_hp(); } else if (mut == trait_STR_ALPHA) { ///\EFFECT_STR_MAX determines bonus from STR mutation if (str_max <= 6) { str_max = 8; } else if (str_max <= 7) { str_max = 11; } else if (str_max <= 14) { str_max = 15; } else { str_max = 18; } recalc_hp(); } else if (mut == trait_DEX_ALPHA) { ///\EFFECT_DEX_MAX determines bonus from DEX mutation if (dex_max <= 6) { dex_max = 8; } else if (dex_max <= 7) { dex_max = 11; } else if (dex_max <= 14) { dex_max = 15; } else { dex_max = 18; } } else if (mut == trait_INT_ALPHA) { ///\EFFECT_INT_MAX determines bonus from INT mutation if (int_max <= 6) { int_max = 8; } else if (int_max <= 7) { int_max = 11; } else if (int_max <= 14) { int_max = 15; } else { int_max = 18; } } else if (mut == trait_INT_SLIME) { int_max *= 2; // Now, can you keep it? :-) } else if (mut == trait_PER_ALPHA) { ///\EFFECT_PER_MAX determines bonus from PER mutation if (per_max <= 6) { per_max = 8; } else if (per_max <= 7) { per_max = 11; } else if (per_max <= 14) { per_max = 15; } else { per_max = 18; } } else { apply_mods(mut, true); } const auto &branch = mut.obj(); if( branch.hp_modifier != 0.0f || branch.hp_modifier_secondary != 0.0f || branch.hp_adjustment != 0.0f ) { recalc_hp(); } remove_worn_items_with( [&]( item& armor ) { static const std::string mutation_safe = "OVERSIZE"; if( armor.has_flag( mutation_safe ) ) { return false; } if( !branch.conflicts_with_item( armor ) ) { return false; } if( branch.destroys_gear ) { add_msg_player_or_npc( m_bad, _("Your %s is destroyed!"), _("<npcname>'s %s is destroyed!"), armor.tname().c_str() ); for( item& remain : armor.contents ) { g->m.add_item_or_charges( pos(), remain ); } } else { add_msg_player_or_npc( m_bad, _("Your %s is pushed off!"), _("<npcname>'s %s is pushed off!"), armor.tname().c_str() ); g->m.add_item_or_charges( pos(), armor ); } return true; } ); if( branch.starts_active ) { my_mutations[mut].powered = true; } on_mutation_gain( mut ); }
bool Character::move_effects(bool attacking) { if (has_effect("downed")) { if (rng(0, 40) > get_dex() + int(get_str() / 2)) { add_msg_if_player(_("You struggle to stand.")); } else { add_msg_player_or_npc(m_good, _("You stand up."), _("<npcname> stands up.")); remove_effect("downed"); } return false; } if (has_effect("webbed")) { if (x_in_y(get_str(), 6 * get_effect_int("webbed"))) { add_msg_player_or_npc(m_good, _("You free yourself from the webs!"), _("<npcname> frees themselves from the webs!")); remove_effect("webbed"); } else { add_msg_if_player(_("You try to free yourself from the webs, but can't get loose!")); } return false; } if (has_effect("lightsnare")) { if(x_in_y(get_str(), 12) || x_in_y(get_dex(), 8)) { remove_effect("lightsnare"); add_msg_player_or_npc(m_good, _("You free yourself from the light snare!"), _("<npcname> frees themselves from the light snare!")); item string("string_36", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), string); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the light snare, but can't get loose!")); } return false; } if (has_effect("heavysnare")) { if(x_in_y(get_str(), 32) || x_in_y(get_dex(), 16)) { remove_effect("heavysnare"); add_msg_player_or_npc(m_good, _("You free yourself from the heavy snare!"), _("<npcname> frees themselves from the heavy snare!")); item rope("rope_6", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), rope); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the heavy snare, but can't get loose!")); } return false; } if (has_effect("beartrap")) { /* Real bear traps can't be removed without the proper tools or immense strength; eventually this should allow normal players two options: removal of the limb or removal of the trap from the ground (at which point the player could later remove it from the leg with the right tools). As such we are currently making it a bit easier for players and NPC's to get out of bear traps. */ if(x_in_y(get_str(), 100)) { remove_effect("beartrap"); add_msg_player_or_npc(m_good, _("You free yourself from the bear trap!"), _("<npcname> frees themselves from the bear trap!")); item beartrap("beartrap", calendar::turn); g->m.add_item_or_charges(posx(), posy(), beartrap); } else { add_msg_if_player(m_bad, _("You try to free yourself from the bear trap, but can't get loose!")); } return false; } if (has_effect("crushed")) { // Strength helps in getting free, but dex also helps you worm your way out of the rubble if(x_in_y(get_str() + get_dex() / 4, 100)) { remove_effect("crushed"); add_msg_player_or_npc(m_good, _("You free yourself from the rubble!"), _("<npcname> frees themselves from the rubble!")); } else { add_msg_if_player(m_bad, _("You try to free yourself from the rubble, but can't get loose!")); } return false; } // Below this point are things that allow for movement if they succeed // Currently we only have one thing that forces movement if you succeed, should we get more // than this will need to be reworked to only have success effects if /all/ checks succeed if (has_effect("in_pit")) { if (rng(0, 40) > get_str() + int(get_dex() / 2)) { add_msg_if_player(m_bad, _("You try to escape the pit, but slip back in.")); return false; } else { add_msg_player_or_npc(m_good, _("You escape the pit!"), _("<npcname> escapes the pit!")); remove_effect("in_pit"); } } if (has_effect("grabbed")){ int zed_number = 0; for( auto &&dest : g->m.points_in_radius( pos(), 1, 0 ) ){ if (g->mon_at(dest) != -1){ zed_number ++; } } if (attacking == true || zed_number == 0){ return true; } if (get_dex() > get_str() ? rng(0, get_dex()) : rng( 0, get_str()) < rng( get_effect_int("grabbed") , 8) ){ add_msg_player_or_npc(m_bad, _("You try break out of the grab, but fail!"), _("<npcname> tries to break out of the grab, but fails!")); return false; } else { add_msg_player_or_npc(m_good, _("You break out of the grab!"), _("<npcname> breaks out of the grab!")); remove_effect("grabbed"); } } return Creature::move_effects(attacking); }
bool Character::move_effects() { if (has_effect("downed")) { if (rng(0, 40) > get_dex() + int(get_str() / 2)) { add_msg_if_player(_("You struggle to stand.")); } else { add_msg_player_or_npc(m_good, _("You stand up."), _("<npcname> stands up.")); remove_effect("downed"); } return false; } if (has_effect("webbed")) { if (x_in_y(get_str(), 6 * get_effect_int("webbed"))) { add_msg_player_or_npc(m_good, _("You free yourself from the webs!"), _("<npcname> frees themselves from the webs!")); remove_effect("webbed"); } else { add_msg_if_player(_("You try to free yourself from the webs, but can't get loose!")); } return false; } if (has_effect("lightsnare")) { if(x_in_y(get_str(), 12) || x_in_y(get_dex(), 8)) { remove_effect("lightsnare"); add_msg_player_or_npc(m_good, _("You free yourself from the light snare!"), _("<npcname> frees themselves from the light snare!")); item string("string_36", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), string); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the light snare, but can't get loose!")); } return false; } if (has_effect("heavysnare")) { if(x_in_y(get_str(), 32) || x_in_y(get_dex(), 16)) { remove_effect("heavysnare"); add_msg_player_or_npc(m_good, _("You free yourself from the heavy snare!"), _("<npcname> frees themselves from the heavy snare!")); item rope("rope_6", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), rope); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the heavy snare, but can't get loose!")); } return false; } if (has_effect("beartrap")) { /* Real bear traps can't be removed without the proper tools or immense strength; eventually this should allow normal players two options: removal of the limb or removal of the trap from the ground (at which point the player could later remove it from the leg with the right tools). As such we are currently making it a bit easier for players and NPC's to get out of bear traps. */ if(x_in_y(get_str(), 100)) { remove_effect("beartrap"); add_msg_player_or_npc(m_good, _("You free yourself from the bear trap!"), _("<npcname> frees themselves from the bear trap!")); item beartrap("beartrap", calendar::turn); g->m.add_item_or_charges(posx(), posy(), beartrap); } else { add_msg_if_player(m_bad, _("You try to free yourself from the bear trap, but can't get loose!")); } return false; } if (has_effect("crushed")) { // Strength helps in getting free, but dex also helps you worm your way out of the rubble if(x_in_y(get_str() + get_dex() / 4, 100)) { remove_effect("crushed"); add_msg_player_or_npc(m_good, _("You free yourself from the rubble!"), _("<npcname> frees themselves from the rubble!")); } else { add_msg_if_player(m_bad, _("You try to free yourself from the rubble, but can't get loose!")); } return false; } if (has_effect("amigara")) { int curdist = 999, newdist = 999; for (int cx = 0; cx < SEEX * MAPSIZE; cx++) { for (int cy = 0; cy < SEEY * MAPSIZE; cy++) { if (g->m.ter(cx, cy) == t_fault) { int dist = rl_dist(cx, cy, posx(), posy()); if (dist < curdist) { curdist = dist; } dist = rl_dist(cx, cy, posx(), posy()); if (dist < newdist) { newdist = dist; } } } } if (newdist > curdist) { add_msg_if_player(m_info, _("You cannot pull yourself away from the faultline...")); return false; } } // Below this point are things that allow for movement if they succeed // Currently we only have one thing that forces movement if you succeed, should we get more // than this will need to be reworked to only have success effects if /all/ checks succeed if (has_effect("in_pit")) { if (rng(0, 40) > get_str() + int(get_dex() / 2)) { add_msg_if_player(m_bad, _("You try to escape the pit, but slip back in.")); return false; } else { add_msg_player_or_npc(m_good, _("You escape the pit!"), _("<npcname> escapes the pit!")); remove_effect("in_pit"); } } return Creature::move_effects(); }
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; }
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 ); }