bool player::can_arm_block() { martialart ma = martialarts[style_selected]; if (ma.arm_block < 0 || !(ma.arm_block_with_bio_armor_arms && has_bionic("bio_armor_arms"))) return false; int unarmed_skill = has_active_bionic("bio_cqb") ? 5 : (int)skillLevel("unarmed"); if (unarmed_skill < ma.arm_block && !(ma.arm_block_with_bio_armor_arms && has_bionic("bio_armor_arms"))) return false; if (hp_cur[hp_arm_l] > 0 || hp_cur[hp_arm_r] > 0) return true; else return false; }
int Character::volume_capacity() const { int ret = 2; // A small bonus (the overflow) for (auto &i : worn) { ret += i.get_storage(); } if (has_bionic("bio_storage")) { ret += 8; } if (has_trait("SHELL")) { ret += 16; } if (has_trait("SHELL2") && !has_active_mutation("SHELL2")) { ret += 24; } if (has_trait("PACKMULE")) { ret = int(ret * 1.4); } if (has_trait("DISORGANIZED")) { ret = int(ret * 0.6); } if (ret < 2) { ret = 2; } return ret; }
bool player::install_bionics(it_bionic *type) { if (type == NULL) { debugmsg("Tried to install NULL bionic"); return false; } if (bionics.count(type->id) == 0) { popup("invalid / unknown bionic id %s", type->id.c_str()); return false; } if (has_bionic(type->id)) { if (!(type->id == "bio_power_storage" || type->id == "bio_power_storage_mkII")) { popup(_("You have already installed this bionic.")); return false; } } int chance_of_success = bionic_manip_cos(int_cur, skillLevel("electronics"), skillLevel("firstaid"), skillLevel("mechanics"), type->difficulty); if (!query_yn( _("WARNING: %i percent chance of genetic damage, blood loss, or damage to existing bionics! Install anyway?"), 100 - chance_of_success)) { return false; } int pow_up = 0; if (type->id == "bio_power_storage" || type->id == "bio_power_storage_mkII") { pow_up = BATTERY_AMOUNT; if (type->id == "bio_power_storage_mkII") { pow_up = 250; } } practice( "electronics", int((100 - chance_of_success) * 1.5) ); practice( "firstaid", int((100 - chance_of_success) * 1.0) ); practice( "mechanics", int((100 - chance_of_success) * 0.5) ); int success = chance_of_success - rng(1, 100); if (success > 0) { add_memorial_log(pgettext("memorial_male", "Installed bionic: %s."), pgettext("memorial_female", "Installed bionic: %s."), bionics[type->id]->name.c_str()); if (pow_up) { max_power_level += pow_up; add_msg_if_player(m_good, _("Increased storage capacity by %i"), pow_up); } else { add_msg(m_good, _("Successfully installed %s."), bionics[type->id]->name.c_str()); add_bionic(type->id); } } else { add_memorial_log(pgettext("memorial_male", "Installed bionic: %s."), pgettext("memorial_female", "Installed bionic: %s."), bionics[type->id]->name.c_str()); bionics_install_failure(this, type, success); } g->refresh_all(); return true; }
// This must be called when any of the following change: // - effects // - bionics // - traits // - underwater // - clothes // With the exception of clothes, all changes to these character attributes must // occur through a function in this class which calls this function. Clothes are // typically added/removed with wear() and takeoff(), but direct access to the // 'wears' vector is still allowed due to refactor exhaustion. void Character::recalc_sight_limits() { sight_max = 9999; sight_boost = 0; sight_boost_cap = 0; // Set sight_max. if (has_effect("blind")) { sight_max = 0; } else if (has_effect("in_pit") || (has_effect("boomered") && (!(has_trait("PER_SLIME_OK")))) || (underwater && !has_bionic("bio_membrane") && !has_trait("MEMBRANE") && !worn_with_flag("SWIM_GOGGLES") && !has_trait("CEPH_EYES") && !has_trait("PER_SLIME_OK") ) ) { sight_max = 1; } else if (has_active_mutation("SHELL2")) { // You can kinda see out a bit. sight_max = 2; } else if ( (has_trait("MYOPIC") || has_trait("URSINE_EYE")) && !is_wearing("glasses_eye") && !is_wearing("glasses_monocle") && !is_wearing("glasses_bifocal") && !has_effect("contacts")) { sight_max = 4; } else if (has_trait("PER_SLIME")) { sight_max = 6; } // Set sight_boost and sight_boost_cap, based on night vision. // (A player will never have more than one night vision trait.) sight_boost_cap = 12; // Debug-only NV, by vache's request if (has_trait("DEBUG_NIGHTVISION")) { sight_boost = 59; sight_boost_cap = 59; } else if (has_nv() || is_wearing("rm13_armor_on") || has_active_mutation("NIGHTVISION3") || has_active_mutation("ELFA_FNV") || (has_active_mutation("CEPH_VISION")) ) { // Yes, I'm breaking the cap. I doubt the reality bubble shrinks at night. // BIRD_EYE represents excellent fine-detail vision so I think it works. if (has_trait("BIRD_EYE")) { sight_boost_cap = 13; } sight_boost = sight_boost_cap; } else if (has_active_mutation("ELFA_NV")) { sight_boost = 6; // Elf-a and Bird eyes shouldn't coexist } else if (has_active_mutation("NIGHTVISION2") || has_active_mutation("FEL_NV") || has_active_mutation("URSINE_EYE")) { if (has_trait("BIRD_EYE")) { sight_boost = 5; } else { sight_boost = 4; } } else if (has_active_mutation("NIGHTVISION")) { if (has_trait("BIRD_EYE")) { sight_boost = 2; } else { sight_boost = 1; } } }
// utility functions for projectile_attack double player::get_weapon_dispersion(item *weapon) { int weapon_skill_level = 0; if(weapon->is_gunmod()) { it_gunmod* firing = dynamic_cast<it_gunmod *>(weapon->type); weapon_skill_level = skillLevel(firing->skill_used); } else { it_gun* firing = dynamic_cast<it_gun *>(weapon->type); weapon_skill_level = skillLevel(firing->skill_used); } double dispersion = 0.; // Measured in quarter-degrees. // Up to 0.75 degrees for each skill point < 8. if (weapon_skill_level < 8) { dispersion += rng(0, 3 * (8 - weapon_skill_level)); } // Up to 0.25 deg per each skill point < 9. if (skillLevel("gun") < 9) { dispersion += rng(0, 9 - skillLevel("gun")); } dispersion += rng(0, ranged_dex_mod()); dispersion += rng(0, ranged_per_mod()); dispersion += rng(0, 2 * encumb(bp_arms)) + rng(0, 4 * encumb(bp_eyes)); dispersion += rng(0, weapon->curammo->dispersion); // item::dispersion() doesn't support gunmods. dispersion += rng(0, weapon->dispersion()); int adj_recoil = recoil + driving_recoil; dispersion += rng(int(adj_recoil / 4), adj_recoil); // this is what the total bonus USED to look like // rng(0,x) on each term in the sum // 3 * skill + skill + 2 * dex + 2 * per // - 2*p.encumb(bp_arms) - 4*p.encumb(bp_eyes) - 5/8 * recoil // old targeting bionic suddenly went from 0.8 to 0.65 when LONG_RANGE was // crossed, so increasing range by 1 would actually increase accuracy by a // lot. This is kind of a compromise if (has_bionic("bio_targeting")) dispersion *= 0.75; if ((is_underwater() && !weapon->has_flag("UNDERWATER_GUN")) || // Range is effectively four times longer when shooting unflagged guns underwater. (!is_underwater() && weapon->has_flag("UNDERWATER_GUN"))) { // Range is effectively four times longer when shooting flagged guns out of water. dispersion *= 4; } if (dispersion < 0) { return 0; } return dispersion; }
bool player::can_arm_block() { martialart ma = martialarts[style_selected]; int unarmed_skill = has_active_bionic("bio_cqb") ? 5 : (int)skillLevel("unarmed"); // Success conditions. if (hp_cur[hp_arm_l] > 0 || hp_cur[hp_arm_r] > 0) { if( unarmed_skill >= ma.arm_block ) { return true; } else if( ma.arm_block_with_bio_armor_arms && has_bionic("bio_armor_arms") ) { return true; } } // if not above, can't block. return false; }
bool player::can_arm_block() const { const martialart &ma = style_selected.obj(); ///\EFFECT_UNARMED increases ability to perform arm block int unarmed_skill = has_active_bionic( bionic_id( "bio_cqb" ) ) ? 5 : (int)get_skill_level(skill_id("unarmed")); // Success conditions. if (hp_cur[hp_arm_l] > 0 || hp_cur[hp_arm_r] > 0) { if( unarmed_skill >= ma.arm_block ) { return true; } else if( ma.arm_block_with_bio_armor_arms && has_bionic( bionic_id( "bio_armor_arms" ) ) ) { return true; } } // if not above, can't block. return false; }
// TODO: Move pizza scraping here. // Same for other kinds of nutrition alterations // This is used by item display, making actual nutrition available to player. int player::nutrition_for( const item &comest ) const { static const trait_id trait_CARNIVORE( "CARNIVORE" ); static const trait_id trait_GIZZARD( "GIZZARD" ); static const trait_id trait_SAPROPHAGE( "SAPROPHAGE" ); static const std::string flag_CARNIVORE_OK( "CARNIVORE_OK" ); if( !comest.is_comestible() ) { return 0; } // As float to avoid rounding too many times float nutr = comest.type->comestible->nutr; if( has_trait( trait_GIZZARD ) ) { nutr *= 0.6f; } if( has_trait( trait_CARNIVORE ) && comest.has_flag( flag_CARNIVORE_OK ) && comest.has_any_flag( carnivore_blacklist ) ) { // TODO: Comment pizza scrapping nutr *= 0.5f; } const float relative_rot = comest.get_relative_rot(); // Saprophages get full nutrition from rotting food if( relative_rot > 1.0f && !has_trait( trait_SAPROPHAGE ) ) { // everyone else only gets a portion of the nutrition // Scaling linearly from 100% at just-rotten to 0 at halfway-rotten-away const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); nutr *= ( 1.0f - rottedness ); } // Bio-digestion gives extra nutrition if( has_bionic( bio_digestion ) ) { nutr *= 1.5f; } return ( int )nutr; }
int Character::volume_capacity() const { int ret = 0; for (auto &i : worn) { ret += i.get_storage(); } if (has_bionic("bio_storage")) { ret += 8; } if (has_trait("SHELL")) { ret += 16; } if (has_trait("SHELL2") && !has_active_mutation("SHELL2")) { ret += 24; } if (has_trait("PACKMULE")) { ret = int(ret * 1.4); } if (has_trait("DISORGANIZED")) { ret = int(ret * 0.6); } ret = std::max(ret, 0); return ret; }
void Character::reset_stats() { // Bionic buffs if (has_active_bionic("bio_hydraulics")) mod_str_bonus(20); if (has_bionic("bio_eye_enhancer")) mod_per_bonus(2); if (has_bionic("bio_str_enhancer")) mod_str_bonus(2); if (has_bionic("bio_int_enhancer")) mod_int_bonus(2); if (has_bionic("bio_dex_enhancer")) mod_dex_bonus(2); // Trait / mutation buffs if (has_trait("THICK_SCALES")) { mod_dex_bonus(-2); } if (has_trait("CHITIN2") || has_trait("CHITIN3") || has_trait("CHITIN_FUR3")) { mod_dex_bonus(-1); } if (has_trait("BIRD_EYE")) { mod_per_bonus(4); } if (has_trait("INSECT_ARMS")) { mod_dex_bonus(-2); } if (has_trait("WEBBED")) { mod_dex_bonus(-1); } if (has_trait("ARACHNID_ARMS")) { mod_dex_bonus(-4); } if (has_trait("ARM_TENTACLES") || has_trait("ARM_TENTACLES_4") || has_trait("ARM_TENTACLES_8")) { mod_dex_bonus(1); } // Dodge-related effects if (has_trait("TAIL_LONG")) { mod_dodge_bonus(2); } if (has_trait("TAIL_CATTLE")) { mod_dodge_bonus(1); } if (has_trait("TAIL_RAT")) { mod_dodge_bonus(2); } if (has_trait("TAIL_THICK") && !(has_active_mutation("TAIL_THICK")) ) { mod_dodge_bonus(1); } if (has_trait("TAIL_RAPTOR")) { mod_dodge_bonus(3); } if (has_trait("TAIL_FLUFFY")) { mod_dodge_bonus(4); } if (has_trait("WINGS_BAT")) { mod_dodge_bonus(-3); } if (has_trait("WINGS_BUTTERFLY")) { mod_dodge_bonus(-4); } ///\EFFECT_STR_MAX above 15 decreases Dodge bonus by 1 (NEGATIVE) if (str_max >= 16) {mod_dodge_bonus(-1);} // Penalty if we're huge ///\EFFECT_STR_MAX below 6 increases Dodge bonus by 1 else if (str_max <= 5) {mod_dodge_bonus(1);} // Bonus if we're small nv_cached = false; // Reset our stats to normal levels // Any persistent buffs/debuffs will take place in effects, // player::suffer(), etc. // repopulate the stat fields str_cur = str_max + get_str_bonus(); dex_cur = dex_max + get_dex_bonus(); per_cur = per_max + get_per_bonus(); int_cur = int_max + get_int_bonus(); // Floor for our stats. No stat changes should occur after this! if( dex_cur < 0 ) { dex_cur = 0; } if( str_cur < 0 ) { str_cur = 0; } if( per_cur < 0 ) { per_cur = 0; } if( int_cur < 0 ) { int_cur = 0; } // Does nothing! TODO: Remove Creature::reset_stats(); }
// This must be called when any of the following change: // - effects // - bionics // - traits // - underwater // - clothes // With the exception of clothes, all changes to these character attributes must // occur through a function in this class which calls this function. Clothes are // typically added/removed with wear() and takeoff(), but direct access to the // 'wears' vector is still allowed due to refactor exhaustion. void Character::recalc_sight_limits() { sight_max = 9999; vision_mode_cache.reset(); // Set sight_max. if (has_effect("blind") || worn_with_flag("BLIND") || has_active_bionic("bio_blindfold")) { sight_max = 0; } else if( has_effect("boomered") && (!(has_trait("PER_SLIME_OK"))) ) { sight_max = 1; vision_mode_cache.set( BOOMERED ); } else if (has_effect("in_pit") || (underwater && !has_bionic("bio_membrane") && !has_trait("MEMBRANE") && !worn_with_flag("SWIM_GOGGLES") && !has_trait("CEPH_EYES") && !has_trait("PER_SLIME_OK") ) ) { sight_max = 1; } else if (has_active_mutation("SHELL2")) { // You can kinda see out a bit. sight_max = 2; } else if ( (has_trait("MYOPIC") || has_trait("URSINE_EYE")) && !is_wearing("glasses_eye") && !is_wearing("glasses_monocle") && !is_wearing("glasses_bifocal") && !has_effect("contacts")) { sight_max = 4; } else if (has_trait("PER_SLIME")) { sight_max = 6; } else if( has_effect( "darkness" ) ) { vision_mode_cache.set( DARKNESS ); sight_max = 10; } // Debug-only NV, by vache's request if( has_trait("DEBUG_NIGHTVISION") ) { vision_mode_cache.set( DEBUG_NIGHTVISION ); } if( has_nv() ) { vision_mode_cache.set( NV_GOGGLES ); } if( has_active_mutation("NIGHTVISION3") || is_wearing("rm13_armor_on") ) { vision_mode_cache.set( NIGHTVISION_3 ); } if( has_active_mutation("ELFA_FNV") ) { vision_mode_cache.set( FULL_ELFA_VISION ); } if( has_active_mutation("CEPH_VISION") ) { vision_mode_cache.set( CEPH_VISION ); } if (has_active_mutation("ELFA_NV")) { vision_mode_cache.set( ELFA_VISION ); } if( has_active_mutation("NIGHTVISION2") ) { vision_mode_cache.set( NIGHTVISION_2 ); } if( has_active_mutation("FEL_NV") ) { vision_mode_cache.set( FELINE_VISION ); } if( has_active_mutation("URSINE_EYE") ) { vision_mode_cache.set( URSINE_VISION ); } if (has_active_mutation("NIGHTVISION")) { vision_mode_cache.set(NIGHTVISION_1); } if( has_trait("BIRD_EYE") ) { vision_mode_cache.set( BIRD_EYE); } }
int player::hit_mon(game *g, monster *z) { bool is_u = (this == &(g->u)); // Affects how we'll display messages int j; bool can_see = (is_u || g->u_see(posx, posy, j)); std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); // Types of combat (may overlap!) bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), cutting = weapon.is_cutting_weapon(), stabbing = (weapon.has_weapon_flag(WF_SPEAR) || weapon.has_weapon_flag(WF_STAB)); // Recoil penalty if (recoil <= 30) recoil += 6; // Movement cost moves -= weapon.attack_time() + 20 * encumb(bp_torso); // Different sizes affect your chance to hit if (hit_roll() < z->dodge_roll()) {// A miss! stumble(g); return 0; } if (z->has_flag(MF_SHOCK) && !wearing_something_on(bp_hands) && (unarmed || weapon.conductive())) { if (is_u) g->add_msg("The %s's electric body shocks you!", z->name().c_str()); hurtall(rng(1, 3)); } // For very high hit rolls, we crit! bool critical_hit = (hit_roll() >= 50 + 10 * z->dodge_roll()); int dam = base_damage(true); int cutting_penalty = 0; // Moves lost from getting a cutting weapon stuck // Drunken Master damage bonuses if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) { // Remember, a single drink gives 600 levels of DI_DRUNK if (unarmed) dam += disease_level(DI_DRUNK) / 250; else dam += disease_level(DI_DRUNK) / 400; } if (unarmed) { // Unarmed bonuses dam += rng(0, sklevel[sk_unarmed]); if (has_trait(PF_TALONS) && z->type->armor - sklevel[sk_unarmed] < 10) { int z_armor = (z->type->armor - sklevel[sk_unarmed]); if (z_armor < 0) z_armor = 0; dam += 10 - z_armor; } } else if (rng(1, 45 - dex_cur) < 2 * sklevel[sk_unarmed] && rng(1, 65 - dex_cur) < 2 * sklevel[sk_unarmed] ) { // If we're not unarmed, there's still a possibility of getting in a bonus // unarmed attack. if (is_u || can_see) { switch (rng(1, 4)) { case 1: g->add_msg("%s kick%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 2: g->add_msg("%s headbutt%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 3: g->add_msg("%s elbow%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 4: g->add_msg("%s knee%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; } } dam += rng(1, sklevel[sk_unarmed]); practice(sk_unarmed, 2); } // Melee skill bonus dam += rng(0, sklevel[sk_melee]); // Bashing damage bonus int bash_dam = weapon.type->melee_dam, bash_cap = 5 + str_cur + sklevel[sk_bashing]; if (bash_dam > bash_cap)// Cap for weak characters bash_dam = (bash_cap * 3 + bash_dam) / 4; if (bashing) bash_dam += rng(0, sklevel[sk_bashing]) * sqrt(str_cur); int bash_min = bash_dam / 4; if (bash_min < sklevel[sk_bashing] * 2) bash_min = sklevel[sk_bashing] * 2; dam += rng(bash_dam / 4, bash_dam); // Take some moves away from the target; at this point it's skill & bash damage z->moves -= rng(0, dam * 2); // Spears treat cutting damage specially. if (weapon.has_weapon_flag(WF_SPEAR) && weapon.type->melee_cut > z->type->armor - int(sklevel[sk_stabbing])) { int z_armor = z->type->armor - int(sklevel[sk_stabbing]); dam += int(weapon.type->melee_cut / 5); int minstab = sklevel[sk_stabbing] * 8 + weapon.type->melee_cut * 2, maxstab = sklevel[sk_stabbing] * 20 + weapon.type->melee_cut * 4; int monster_penalty = rng(minstab, maxstab); if (monster_penalty >= 150) g->add_msg("You force the %s to the ground!", z->name().c_str()); else if (monster_penalty >= 80) g->add_msg("The %s is skewered and flinches!", z->name().c_str()); z->moves -= monster_penalty; cutting_penalty = weapon.type->melee_cut * 4 + z_armor * 8 - dice(sklevel[sk_stabbing], 10); practice(sk_stabbing, 2); // Cutting damage bonus } else if (weapon.type->melee_cut > z->type->armor - int(sklevel[sk_cutting] / 2)) { int z_armor = z->type->armor - int(sklevel[sk_cutting] / 2); if (z_armor < 0) z_armor = 0; dam += weapon.type->melee_cut - z_armor; cutting_penalty = weapon.type->melee_cut * 3 + z_armor * 8 - dice(sklevel[sk_cutting], 10); } if (weapon.has_weapon_flag(WF_MESSY)) { // e.g. chainsaws cutting_penalty /= 6; // Harder to get stuck for (int x = z->posx - 1; x <= z->posx + 1; x++) { for (int y = z->posy - 1; y <= z->posy + 1; y++) { if (!one_in(3)) { if (g->m.field_at(x, y).type == fd_blood && g->m.field_at(x, y).density < 3) g->m.field_at(x, y).density++; else g->m.add_field(g, x, y, fd_blood, 1); } } } } // Bonus attacks! bool shock_them = (!z->has_flag(MF_SHOCK) && has_bionic(bio_shock) && power_level >= 2 && unarmed && one_in(3)); bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 && !is_armed() && z->has_flag(MF_WARM)); bool bite_them = (has_trait(PF_FANGS) && z->armor() < 18 && one_in(20 - dex_cur - sklevel[sk_unarmed])); bool peck_them = (has_trait(PF_BEAK) && z->armor() < 16 && one_in(15 - dex_cur - sklevel[sk_unarmed])); if (drain_them) power_level--; drain_them &= one_in(2); // Only works half the time // Critical hit effects if (critical_hit) { bool headshot = (!z->has_flag(MF_NOHEAD) && !one_in(3)); // Second chance for shock_them, drain_them, bite_them and peck_them shock_them = (shock_them || (!z->has_flag(MF_SHOCK) && has_bionic(bio_shock)&& power_level >= 2 && unarmed && !one_in(3))); drain_them = (drain_them || (has_bionic(bio_heat_absorb) && !is_armed() && power_level >= 1 && z->has_flag(MF_WARM) && !one_in(3))); bite_them = ( bite_them || (has_trait(PF_FANGS) && z->armor() < 18 && one_in(5))); peck_them = ( peck_them || (has_trait(PF_BEAK) && z->armor() < 16 && one_in(4))); if (weapon.has_weapon_flag(WF_SPEAR) || weapon.has_weapon_flag(WF_STAB)) { dam += weapon.type->melee_cut; dam += weapon.type->melee_cut * double(sklevel[sk_stabbing] / 10); practice(sk_stabbing, 5); } if (unarmed) { dam += rng(2, 6) * sklevel[sk_unarmed]; if (sklevel[sk_unarmed] > 5) dam += 4 * (sklevel[sk_unarmed - 3]); z->moves -= dam; // Stunning blow if (weapon.type->id == itm_bio_claws) { if (sklevel[sk_cutting] >= 3) dam += 5; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s claws pierce the %s's skull!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s claws stab straight through the %s!", Your.c_str(), z->name().c_str()); } else if (has_trait(PF_TALONS)) { dam += 2; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s talons tear the %s's head open!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s bur%s %s talons into the %s!", You.c_str(),(is_u?"y":"ies"), your.c_str(), z->name().c_str()); } else { headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s crush%s the %s's skull in a single blow!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s deliver%s a crushing punch!",You.c_str(),(is_u ? "" : "s")); } if (z->hp > 0 && rng(1, 5) < sklevel[sk_unarmed]) z->add_effect(ME_STUNNED, 1 + sklevel[sk_unarmed]); } else { // Not unarmed if (bashing) { dam += 8 + (str_cur / 2); int turns_stunned = int(dam / 20) + int(sklevel[sk_bashing] / 2); if (turns_stunned > 6) turns_stunned = 6; z->add_effect(ME_STUNNED, turns_stunned); } if (cutting) { double cut_multiplier = double(sklevel[sk_cutting] / 12); if (cut_multiplier > 1.5) cut_multiplier = 1.5; dam += cut_multiplier * weapon.type->melee_cut; headshot &= z->hp < dam; if (stabbing) { if (headshot && can_see) g->add_msg("%s %s stabs through the %s's skull!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s stab %s %s through the %s!", You.c_str(), your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } else { if (headshot && can_see) g->add_msg("%s %s slices the %s's head off!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else g->add_msg("%s %s cuts the %s deeply!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } } else { // Not cutting, probably bashing headshot &= z->hp < dam; if (headshot && can_see) g->add_msg("%s crush%s the %s's skull!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s crush%s the %s's body!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); } } // End of not-unarmed } // End of critical hit if (shock_them) { power_level -= 2; if (can_see) g->add_msg("%s shock%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); int shock = rng(2, 5); dam += shock * rng(1, 3); z->moves -= shock * 180; } if (drain_them) { charge_power(rng(0, 4)); if (can_see) g->add_msg("%s drain%s the %s's body heat!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); dam += rng(4, 10); z->moves -= rng(80, 120); } if (bite_them) { if (can_see) g->add_msg("%s sink %s fangs into the %s!", You.c_str(), your.c_str(), z->name().c_str()); dam += 18 - z->armor(); } if (peck_them) { if (can_see) g->add_msg("%s peck%s the %s viciously!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); dam += 16 - z->armor(); } // Make a rather quiet sound, to alert any nearby monsters g->sound(posx, posy, 8, ""); // Glass weapons shatter sometimes if (weapon.made_of(GLASS) && rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { if (can_see) g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); g->sound(posx, posy, 16, ""); // Dump its contents on the ground for (int i = 0; i < weapon.contents.size(); i++) g->m.add_item(posx, posy, weapon.contents[i]); hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage if (weapon.is_two_handed(this))// Hurt left arm too, if it was big hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra remove_weapon(); } if (dam <= 0) { if (is_u) g->add_msg("You hit the %s, but do no damage.", z->name().c_str()); else if (can_see) g->add_msg("%s's %s hits the %s, but does no damage.", You.c_str(), weapon.tname(g).c_str(), z->name().c_str()); practice(sk_melee, rng(2, 5)); if (unarmed) practice(sk_unarmed, 2); if (bashing) practice(sk_bashing, 2); if (cutting) practice(sk_cutting, 2); if (stabbing) practice(sk_stabbing, 2); return 0; } if (is_u) g->add_msg("You hit the %s for %d damage.", z->name().c_str(), dam); else if (can_see) g->add_msg("%s hits the %s with %s %s.", You.c_str(), z->name().c_str(), (male ? "his" : "her"), (weapon.type->id == 0 ? "fists" : weapon.tname(g).c_str())); practice(sk_melee, rng(5, 10)); if (unarmed) practice(sk_unarmed, rng(5, 10)); if (bashing) practice(sk_bashing, rng(5, 10)); if (cutting) practice(sk_cutting, rng(5, 10)); if (stabbing) practice(sk_stabbing, rng(5, 10)); // Penalize the player if their cutting weapon got stuck if (!unarmed && dam < z->hp && cutting_penalty > dice(str_cur * 2, 20)) { if (is_u) g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!", weapon.tname().c_str(), z->type->name.c_str()); z->add_item(remove_weapon()); if (weapon.has_weapon_flag(WF_SPEAR) || weapon.has_weapon_flag(WF_STAB)) z->speed *= .7; else z->speed *= .85; } else { if (dam >= z->hp) { cutting_penalty /= 2; cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2); } if (cutting_penalty > 0) moves -= cutting_penalty; if (cutting_penalty >= 50 && is_u) g->add_msg("Your %s gets stuck in the %s, but you yank it free.", weapon.tname().c_str(), z->type->name.c_str()); if (weapon.has_weapon_flag(WF_SPEAR) || weapon.has_weapon_flag(WF_STAB)) z->speed *= .9; } return dam; }
void player::melee_special_effects(game *g, monster *z, player *p, bool crit, int &bash_dam, int &cut_dam, int &stab_dam) { if (z == NULL && p == NULL) return; bool mon = (z != NULL); int junk; bool is_u = (!is_npc()); bool can_see = (is_u || g->u_see(posx, posy, junk)); std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : name + "'s"); std::string target = (mon ? "the " + z->name() : (p->is_npc() ? p->name : "you")); std::string target_possessive = (mon ? "the " + z->name() + "'s" : (p->is_npc() ? p->name + "'s" : your)); int tarposx = (mon ? z->posx : p->posx), tarposy = (mon ? z->posy : p->posy); // Bashing effecs if (mon) z->moves -= rng(0, bash_dam * 2); else p->moves -= rng(0, bash_dam * 2); // Bashing crit if (crit && !unarmed_attack()) { int turns_stunned = int(bash_dam / 20) + rng(0, int(skillLevel("bashing") / 2)); if (turns_stunned > 6) turns_stunned = 6; if (turns_stunned > 0) { if (mon) z->add_effect(ME_STUNNED, turns_stunned); else p->add_disease(DI_STUNNED, 1 + turns_stunned / 2, g); } } // Stabbing effects int stab_moves = rng(stab_dam / 2, stab_dam * 1.5); if (crit) stab_moves *= 1.5; if (stab_moves >= 150) { if (can_see) g->add_msg("%s force%s the %s to the ground!", You.c_str(), (is_u ? "" : "s"), target.c_str()); if (mon) { z->add_effect(ME_DOWNED, 1); z->moves -= stab_moves / 2; } else { p->add_disease(DI_DOWNED, 1, g); p->moves -= stab_moves / 2; } } else if (mon) z->moves -= stab_moves; else p->moves -= stab_moves; // Bonus attacks! bool shock_them = (has_bionic("bio_shock") && power_level >= 2 && unarmed_attack() && (!mon || !z->has_flag(MF_ELECTRIC)) && one_in(3)); bool drain_them = (has_bionic("bio_heat_absorb") && power_level >= 1 && !is_armed() && (!mon || z->has_flag(MF_WARM))); if (drain_them) power_level--; drain_them &= one_in(2); // Only works half the time if (shock_them) { power_level -= 2; int shock = rng(2, 5); if (mon) { z->hurt( shock * rng(1, 3) ); z->moves -= shock * 180; if (can_see) g->add_msg("%s shock%s %s!", You.c_str(), (is_u ? "" : "s"), target.c_str()); } else { p->hurt(g, bp_torso, 0, shock * rng(1, 3)); p->moves -= shock * 80; } } if (drain_them) { charge_power(rng(0, 4)); if (can_see) g->add_msg("%s drain%s %s body heat!", You.c_str(), (is_u ? "" : "s"), target_possessive.c_str()); if (mon) { z->moves -= rng(80, 120); z->speed -= rng(4, 6); } else p->moves -= rng(80, 120); } bool conductive = !wearing_something_on(bp_hands) && weapon.conductive(); if (mon && z->has_flag(MF_ELECTRIC) && conductive) { hurtall(rng(0, 1)); moves -= rng(0, 50); if (is_u) g->add_msg("Contact with the %s shocks you!", z->name().c_str()); } // Glass weapons shatter sometimes if (weapon.made_of(GLASS) && rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { if (can_see) g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); g->sound(posx, posy, 16, ""); // Dump its contents on the ground for (int i = 0; i < weapon.contents.size(); i++) g->m.add_item(posx, posy, weapon.contents[i]); hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage if (weapon.is_two_handed(this))// Hurt left arm too, if it was big hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); cut_dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra remove_weapon(); } // Getting your weapon stuck int cutting_penalty = roll_stuck_penalty(z, stab_dam > cut_dam); if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws cutting_penalty /= 6; // Harder to get stuck for (int x = tarposx - 1; x <= tarposx + 1; x++) { for (int y = tarposy - 1; y <= tarposy + 1; y++) { if (!one_in(3)) { if (g->m.field_at(x, y).type == fd_blood && g->m.field_at(x, y).density < 3) g->m.field_at(x, y).density++; else g->m.add_field(g, x, y, fd_blood, 1); } } } } if (!unarmed_attack() && cutting_penalty > dice(str_cur * 2, 20)) { if (is_u) g->add_msg("Your %s gets stuck in %s, pulling it out of your hands!", weapon.tname().c_str(), target.c_str()); if (mon) { if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .7; else z->speed *= .85; z->add_item(remove_weapon()); } else g->m.add_item(posx, posy, remove_weapon()); } else { if (mon && (cut_dam >= z->hp || stab_dam >= z->hp)) { cutting_penalty /= 2; cutting_penalty -= rng(skillLevel("cutting"), skillLevel("cutting") * 2 + 2); } if (cutting_penalty > 0) moves -= cutting_penalty; if (cutting_penalty >= 50 && is_u) g->add_msg("Your %s gets stuck in %s, but you yank it free.", weapon.tname().c_str(), target.c_str()); if (mon && (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))) z->speed *= .9; } // Finally, some special effects for martial arts if(weapon.typeId() == "style_karate"){ dodges_left++; blocks_left += 2; } else if(weapon.typeId() == "style_aikido"){ bash_dam /= 2; } else if(weapon.typeId() == "style_capoeira"){ add_disease(DI_DODGE_BOOST, 2, g, 2); } else if(weapon.typeId() == "style_muay_thai"){ if ((mon && z->type->size >= MS_LARGE) || (!mon && p->str_max >= 12)) bash_dam += rng((mon ? z->type->size : (p->str_max - 8) / 4), 3 * (mon ? z->type->size : (p->str_max - 8) / 4)); } else if(weapon.typeId() == "style_tiger"){ add_disease(DI_DAMAGE_BOOST, 2, g, 2, 10); } else if(weapon.typeId() == "style_centipede"){ add_disease(DI_SPEED_BOOST, 2, g, 4, 40); } else if(weapon.typeId() == "style_venom_snake"){ if (has_disease(DI_VIPER_COMBO)) { if (disease_intensity(DI_VIPER_COMBO) == 1) { if (is_u) g->add_msg("Snakebite!"); int dambuf = bash_dam; bash_dam = stab_dam; stab_dam = dambuf; add_disease(DI_VIPER_COMBO, 2, g, 1, 2); // Upgrade to Viper Strike } else if (disease_intensity(DI_VIPER_COMBO) == 2) { if (hp_cur[hp_arm_l] >= hp_max[hp_arm_l] * .75 && hp_cur[hp_arm_r] >= hp_max[hp_arm_r] * .75 ) { if (is_u) g->add_msg("Viper STRIKE!"); bash_dam *= 3; } else if (is_u) g->add_msg("Your injured arms prevent a viper strike!"); rem_disease(DI_VIPER_COMBO); } } else if (crit) { if (is_u) g->add_msg("Tail whip! Viper Combo Intiated!"); bash_dam += 5; add_disease(DI_VIPER_COMBO, 2, g, 1, 2); } } else if(weapon.typeId() == "style_scorpion"){ if (crit) { if (!is_npc()) g->add_msg("Stinger Strike!"); if (mon) { z->add_effect(ME_STUNNED, 3); int zposx = z->posx, zposy = z->posy; z->knock_back_from(g, posx, posy); if (z->posx != zposx || z->posy != zposy) z->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked } else { p->add_disease(DI_STUNNED, 2, g); int pposx = p->posx, pposy = p->posy; p->knock_back_from(g, posx, posy); if (p->posx != pposx || p->posy != pposy) p->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked } } } else if(weapon.typeId() == "style_zui_quan"){ dodges_left = 50; // Basically, unlimited. } }
bool player::install_bionics(game *g, it_bionic* type) { if (type == NULL) { debugmsg("Tried to install NULL bionic"); return false; } std::string bio_name = type->name.substr(5); // Strip off "CBM: " WINDOW* w = newwin(FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY-FULL_SCREEN_HEIGHT)/2 : 0, (TERMX > FULL_SCREEN_WIDTH) ? (TERMX-FULL_SCREEN_WIDTH)/2 : 0); WINDOW* w_description = newwin(3, FULL_SCREEN_WIDTH-2, 21 + getbegy(w), 1 + getbegx(w)); werase(w); wborder(w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); int pl_skill = int_cur + skillLevel("electronics") * 4 + skillLevel("firstaid") * 3 + skillLevel("mechanics") * 2; int skint = int(pl_skill / 4); int skdec = int((pl_skill * 10) / 4) % 10; // Header text mvwprintz(w, 1, 1, c_white, "Installing bionics:"); mvwprintz(w, 1, 21, type->color, bio_name.c_str()); // Dividing bars for (int i = 1; i < 79; i++) { mvwputch(w, 2, i, c_ltgray, LINE_OXOX); mvwputch(w, 20, i, c_ltgray, LINE_OXOX); } mvwputch(w, 2, 0, c_ltgray, LINE_XXXO); // |- mvwputch(w, 2, 79, c_ltgray, LINE_XOXX); // -| mvwputch(w, 20, 0, c_ltgray, LINE_XXXO); // |- mvwputch(w, 20, 79, c_ltgray, LINE_XOXX); // -| // Init the list of bionics for (int i = 1; i < type->options.size(); i++) { bionic_id bio_id = type->options[i]; mvwprintz(w, i + 3, 1, (has_bionic(bio_id) ? c_ltred : c_ltblue), bionics[bio_id]->name.c_str()); } // Helper text mvwprintz(w, 3, 39, c_white, "Difficulty of this module: %d", type->difficulty); mvwprintz(w, 4, 39, c_white, "Your installation skill: %d.%d", skint, skdec); mvwprintz(w, 5, 39, c_white, "Installation requires high intelligence,"); mvwprintz(w, 6, 39, c_white, "and skill in electronics, first aid, and"); mvwprintz(w, 7, 39, c_white, "mechanics (in that order of importance)."); int chance_of_success = int((100 * pl_skill) / (pl_skill + 4 * type->difficulty)); mvwprintz(w, 9, 39, c_white, "Chance of success:"); nc_color col_suc; if (chance_of_success >= 95) col_suc = c_green; else if (chance_of_success >= 80) col_suc = c_ltgreen; else if (chance_of_success >= 60) col_suc = c_yellow; else if (chance_of_success >= 35) col_suc = c_ltred; else col_suc = c_red; mvwprintz(w, 9, 59, col_suc, "%d%%%%", chance_of_success); mvwprintz(w, 11, 39, c_white, "Failure may result in crippling damage,"); mvwprintz(w, 12, 39, c_white, "loss of existing bionics, genetic damage"); mvwprintz(w, 13, 39, c_white, "or faulty installation."); wrefresh(w); if (type->id == "bio_power_storage" || type->id == "bio_power_storage_mkII") { // No selection list; just confirm int pow_up = BATTERY_AMOUNT; if (type->id == "bio_power_storage_mkII") { pow_up = 10; } mvwprintz(w, 3, 1, h_ltblue, "Power Storage +%d", pow_up); mvwprintz(w_description, 0, 0, c_ltblue, "\ Installing this bionic will increase your total power storage by %d.\n\ Power is necessary for most bionics to function. You also require a\n\ charge mechanism, which must be installed from another CBM.", pow_up); InputEvent input; wrefresh(w_description); wrefresh(w); do input = get_input(); while (input != Confirm && input != Cancel); if (input == Confirm) { practice(g->turn, "electronics", (100 - chance_of_success) * 1.5); practice(g->turn, "firstaid", (100 - chance_of_success) * 1.0); practice(g->turn, "mechanics", (100 - chance_of_success) * 0.5); int success = chance_of_success - rng(1, 100); if (success > 0) { g->add_msg("Successfully installed batteries."); max_power_level += pow_up; } else bionics_install_failure(g, this, success); werase(w); delwin(w); g->refresh_all(); return true; } werase(w); delwin(w); g->refresh_all(); return false; }
void Character::reset_stats() { Creature::reset_stats(); // Bionic buffs if (has_active_bionic("bio_hydraulics")) mod_str_bonus(20); if (has_bionic("bio_eye_enhancer")) mod_per_bonus(2); if (has_bionic("bio_str_enhancer")) mod_str_bonus(2); if (has_bionic("bio_int_enhancer")) mod_int_bonus(2); if (has_bionic("bio_dex_enhancer")) mod_dex_bonus(2); // Trait / mutation buffs if (has_trait("THICK_SCALES")) { mod_dex_bonus(-2); } if (has_trait("CHITIN2") || has_trait("CHITIN3") || has_trait("CHITIN_FUR3")) { mod_dex_bonus(-1); } if (has_trait("BIRD_EYE")) { mod_per_bonus(4); } if (has_trait("INSECT_ARMS")) { mod_dex_bonus(-2); } if (has_trait("WEBBED")) { mod_dex_bonus(-1); } if (has_trait("ARACHNID_ARMS")) { mod_dex_bonus(-4); } if (has_trait("ARM_TENTACLES") || has_trait("ARM_TENTACLES_4") || has_trait("ARM_TENTACLES_8")) { mod_dex_bonus(1); } // Dodge-related effects if (has_trait("TAIL_LONG")) { mod_dodge_bonus(2); } if (has_trait("TAIL_CATTLE")) { mod_dodge_bonus(1); } if (has_trait("TAIL_RAT")) { mod_dodge_bonus(2); } if (has_trait("TAIL_THICK") && !(has_active_mutation("TAIL_THICK")) ) { mod_dodge_bonus(1); } if (has_trait("TAIL_RAPTOR")) { mod_dodge_bonus(3); } if (has_trait("TAIL_FLUFFY")) { mod_dodge_bonus(4); } if (has_trait("WINGS_BAT")) { mod_dodge_bonus(-3); } if (has_trait("WINGS_BUTTERFLY")) { mod_dodge_bonus(-4); } if (str_max >= 16) {mod_dodge_bonus(-1);} // Penalty if we're huge else if (str_max <= 5) {mod_dodge_bonus(1);} // Bonus if we're small nv_cached = false; }
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( 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::uninstall_bionic(bionic_id b_id) { // malfunctioning bionics don't have associated items and get a difficulty of 12 int difficulty = 12; if( item_controller->has_template(b_id) > 0) { const it_bionic *type = dynamic_cast<it_bionic *> (item_controller->find_template(b_id)); difficulty = type->difficulty; } if (!has_bionic(b_id)) { popup(_("You don't have this bionic installed.")); return false; } if (!(inv.has_items_with_quality("CUT", 1, 1) && has_amount("1st_aid", 1))) { popup(_("Removing bionics requires a cutting tool and a first aid kit.")); return false; } if ( b_id == "bio_blaster" ) { popup(_("Removing your Fusion Blaster Arm would leave you with a useless stump.")); return false; } // removal of bionics adds +2 difficulty over installation int chance_of_success = bionic_manip_cos(int_cur, skillLevel("electronics"), skillLevel("firstaid"), skillLevel("mechanics"), difficulty + 2); if (!query_yn(_("WARNING: %i percent chance of SEVERE bodily damage! Remove anyway?"), 100 - chance_of_success)) { return false; } use_charges("1st_aid", 1); practice( "electronics", int((100 - chance_of_success) * 1.5) ); practice( "firstaid", int((100 - chance_of_success) * 1.0) ); practice( "mechanics", int((100 - chance_of_success) * 0.5) ); int success = chance_of_success - rng(1, 100); if (success > 0) { add_memorial_log(pgettext("memorial_male", "Removed bionic: %s."), pgettext("memorial_female", "Removed bionic: %s."), bionics[b_id]->name.c_str()); // until bionics can be flagged as non-removable add_msg(m_neutral, _("You jiggle your parts back into their familiar places.")); add_msg(m_good, _("Successfully removed %s."), bionics[b_id]->name.c_str()); remove_bionic(b_id); g->m.spawn_item(posx, posy, "burnt_out_bionic", 1); } else { add_memorial_log(pgettext("memorial_male", "Removed bionic: %s."), pgettext("memorial_female", "Removed bionic: %s."), bionics[b_id]->name.c_str()); bionics_uninstall_failure(this); } g->refresh_all(); return true; }
int player::hit_mon(game *g, monster *z) { bool is_u = (this == &(g->u)); // Affects how we'll display messages if (is_u) z->add_effect(ME_HIT_BY_PLAYER, 100); // Flag as attacked by us int j; bool can_see = (is_u || g->u_see(posx, posy, j)); std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); // Types of combat (may overlap!) bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), cutting = weapon.is_cutting_weapon(), stabbing = (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB) ); bool can_poison = false; // Recoil penalty if (recoil <= 30) recoil += 6; // Movement cost int move_cost = weapon.attack_time() + 20 * encumb(bp_torso); if (has_trait(PF_LIGHT_BONES)) move_cost *= .9; if (has_trait(PF_HOLLOW_BONES)) move_cost *= .8; moves -= move_cost; // Different sizes affect your chance to hit if (hit_roll() < z->dodge_roll() || one_in(4 + dex_cur + weapon.type->m_to_hit)) {// A miss! stumble(g); return 0; } // For very high hit rolls, we crit! bool critical_hit = scored_crit(z->dodge_roll()); int dam = base_damage(true); int cutting_penalty = 0; // Moves lost from getting a cutting weapon stuck // Drunken Master damage bonuses if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) { // Remember, a single drink gives 600 levels of DI_DRUNK int mindrunk, maxdrunk; if (unarmed) { mindrunk = disease_level(DI_DRUNK) / 600; maxdrunk = disease_level(DI_DRUNK) / 250; } else { mindrunk = disease_level(DI_DRUNK) / 900; maxdrunk = disease_level(DI_DRUNK) / 400; } dam += rng(mindrunk, maxdrunk); } if (unarmed) { // Unarmed bonuses dam += rng(0, sklevel[sk_unarmed]); if (has_trait(PF_NAILS) && z->armor_cut() == 0 && !wearing_something_on(bp_hands)) { dam++; if (one_in(2)) can_poison = true; } if (has_trait(PF_CLAWS) && z->armor_cut() < 6 && !wearing_something_on(bp_hands)) { dam += 6; if (one_in(2)) can_poison = true; } if (has_trait(PF_TALONS) && z->armor_cut() - sklevel[sk_unarmed] < 10) { int z_armor = (z->armor_cut() - sklevel[sk_unarmed]); if (z_armor < 0) z_armor = 0; dam += 10 - z_armor; if (one_in(2)) can_poison = true; } if (has_trait(PF_THORNS) && z->armor_cut() < 4 && !wearing_something_on(bp_hands)) { dam += 4 - z->armor_cut(); if (one_in(2)) can_poison = true; } if (has_trait(PF_SLIME_HANDS) && !z->has_flag(MF_ACIDPROOF) && !wearing_something_on(bp_hands)) { dam += rng(4, 6); can_poison = true; } } if (rng(1, 45 - dex_cur) < 2 * sklevel[sk_unarmed] && rng(1, 65 - dex_cur) < 2 * sklevel[sk_unarmed] ) { // Bonus unarmed attack! if (is_u || can_see) { switch (rng(1, 2)) { case 1: g->add_msg("%s elbow%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 2: g->add_msg("%s knee%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; } } if (sklevel[sk_unarmed] >= 4) dam += rng(1, sklevel[sk_unarmed] / 2); else dam++; practice(sk_unarmed, 2); } // Melee skill bonus dam += rng(0, sklevel[sk_melee]); // Bashing damage bonus int bash_dam = weapon.damage_bash() - z->armor_bash(), bash_cap = 5 + str_cur + sklevel[sk_bashing]; if (bash_dam > bash_cap)// Cap for weak characters bash_dam = (bash_cap * 3 + bash_dam) / 4; if (bashing) bash_dam += rng(0, sklevel[sk_bashing] + sqrt(double(str_cur))); if (z->has_flag(MF_PLASTIC)) bash_dam /= rng(2, 4); int bash_min = bash_dam / 4; if (bash_min < sklevel[sk_bashing] ) bash_min = sklevel[sk_bashing]; dam += rng(bash_min, bash_dam); // Take some moves away from the target; at this point it's skill & bash damage z->moves -= rng(0, dam * 2); // Spears treat cutting damage specially. if (weapon.has_flag(IF_SPEAR) && weapon.damage_cut() > z->armor_cut() - int(sklevel[sk_stabbing])) { int z_armor = z->armor_cut() - int(sklevel[sk_stabbing]); dam += int(weapon.damage_cut() / 5); int minstab = sklevel[sk_stabbing] * 5 + weapon.volume() * 2, maxstab = sklevel[sk_stabbing] * 15 + weapon.volume() * 4; int monster_penalty = rng(minstab, maxstab); if (monster_penalty >= 150) g->add_msg("You force the %s to the ground!", z->name().c_str()); else if (monster_penalty >= 50) g->add_msg("The %s is skewered and flinches!", z->name().c_str()); z->moves -= monster_penalty; cutting_penalty = weapon.damage_cut() * 4 + z_armor * 8 - dice(sklevel[sk_stabbing], 10); practice(sk_stabbing, 2); // Cutting damage bonus } else if (weapon.damage_cut() > z->armor_cut() - int(sklevel[sk_cutting] / 2)) { int z_armor = z->armor_cut() - int(sklevel[sk_cutting] / 2); if (z_armor < 0) z_armor = 0; dam += weapon.damage_cut() - z_armor; cutting_penalty = weapon.damage_cut() * 3 + z_armor * 8 - dice(sklevel[sk_cutting], 10); } if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws cutting_penalty /= 6; // Harder to get stuck for (int x = z->posx - 1; x <= z->posx + 1; x++) { for (int y = z->posy - 1; y <= z->posy + 1; y++) { if (!one_in(3)) { if (g->m.field_at(x, y).type == fd_blood && g->m.field_at(x, y).density < 3) g->m.field_at(x, y).density++; else g->m.add_field(g, x, y, fd_blood, 1); } } } } // Critical hit effects if (critical_hit) { bool headshot = (!z->has_flag(MF_NOHEAD) && !one_in(3)); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) { dam += weapon.damage_cut(); dam += weapon.damage_cut() * double(sklevel[sk_stabbing] / 10); practice(sk_stabbing, 5); } if (unarmed) { dam += rng(1, 4) * sklevel[sk_unarmed]; z->moves -= dam; // Stunning blow if (weapon.type->id == itm_bio_claws) { if (sklevel[sk_cutting] >= 3) dam += 5; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s claws pierce the %s's skull!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s claws stab straight through the %s!", Your.c_str(), z->name().c_str()); } else if (has_trait(PF_TALONS)) { dam += 2; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s talons tear the %s's head open!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s bur%s %s talons into the %s!", You.c_str(),(is_u?"y":"ies"), your.c_str(), z->name().c_str()); } else { headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s crush%s the %s's skull in a single blow!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s deliver%s a crushing punch!",You.c_str(),(is_u ? "" : "s")); } if (z->hp > 0 && rng(1, 5) < sklevel[sk_unarmed]) z->add_effect(ME_STUNNED, 1 + sklevel[sk_unarmed]); } else { // Not unarmed if (bashing) { dam += (str_cur / 2); int turns_stunned = int(dam / 20) + rng(0, int(sklevel[sk_bashing] / 2)); if (turns_stunned > 6) turns_stunned = 6; z->add_effect(ME_STUNNED, turns_stunned); } if (cutting || stabbing) { double cut_multiplier; if (cutting) cut_multiplier = double(sklevel[sk_cutting] / 12); else cut_multiplier = double(sklevel[sk_stabbing] / 5); if (cut_multiplier > 1.5) cut_multiplier = 1.5; dam += cut_multiplier * weapon.damage_cut(); headshot &= z->hp < dam; if (stabbing) { if (headshot && can_see) g->add_msg("%s %s stabs through the %s's skull!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s stab %s %s through the %s!", You.c_str(), your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } else { if (headshot && can_see) g->add_msg("%s %s slices the %s's head off!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else g->add_msg("%s %s cuts the %s deeply!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } } else if (bashing) { headshot &= z->hp < dam; if (headshot && can_see) g->add_msg("%s crush%s the %s's skull!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s crush%s the %s's body!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); } } // End of not-unarmed } // End of critical hit // Bonus attacks! bool shock_them = (has_bionic(bio_shock) && power_level >= 2 && unarmed && one_in(3)); bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 && !is_armed() && z->has_flag(MF_WARM)); if (drain_them) power_level--; drain_them &= one_in(2); // Only works half the time std::vector<special_attack> special_attacks = mutation_attacks(z); if (shock_them) { power_level -= 2; if (can_see) g->add_msg("%s shock%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); int shock = rng(2, 5); dam += shock * rng(1, 3); z->moves -= shock * 180; } if (drain_them) { charge_power(rng(0, 4)); if (can_see) g->add_msg("%s drain%s the %s's body heat!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); dam += rng(4, 10); z->moves -= rng(80, 120); } for (int i = 0; i < special_attacks.size(); i++) { int spec_dam = 0; spec_dam += special_attacks[i].bash; if (special_attacks[i].cut > z->armor_cut()) spec_dam += special_attacks[i].cut - z->armor_cut(); if (special_attacks[i].stab > z->armor_cut() * .8) spec_dam += special_attacks[i].stab - z->armor_cut() * .8; if (!can_poison && one_in(2) && (special_attacks[i].cut > z->armor_cut() || special_attacks[i].stab > z->armor_cut() * .8)) can_poison = true; if (spec_dam > 0) { g->add_msg( special_attacks[i].text.c_str() ); dam += spec_dam; } } if (can_poison && has_trait(PF_POISONOUS)) { if (is_u) g->add_msg("You poison the %s!", z->name().c_str()); z->add_effect(ME_POISONED, 6); } // Make a rather quiet sound, to alert any nearby monsters g->sound(posx, posy, 8, ""); // Glass weapons shatter sometimes if (weapon.made_of(GLASS) && rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { if (can_see) g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); g->sound(posx, posy, 16, ""); // Dump its contents on the ground for (int i = 0; i < weapon.contents.size(); i++) g->m.add_item(posx, posy, weapon.contents[i]); hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage if (weapon.is_two_handed(this))// Hurt left arm too, if it was big hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra remove_weapon(); } if (dam <= 0) { if (is_u) g->add_msg("You hit the %s, but do no damage.", z->name().c_str()); else if (can_see) g->add_msg("%s's %s hits the %s, but does no damage.", You.c_str(), weapon.tname(g).c_str(), z->name().c_str()); practice(sk_melee, rng(2, 5)); if (unarmed) practice(sk_unarmed, 2); if (bashing) practice(sk_bashing, 2); if (cutting) practice(sk_cutting, 2); if (stabbing) practice(sk_stabbing, 2); return 0; } if (is_u) g->add_msg("You hit the %s for %d damage.", z->name().c_str(), dam); else if (can_see) g->add_msg("%s hits the %s with %s %s.", You.c_str(), z->name().c_str(), (male ? "his" : "her"), (weapon.type->id == 0 ? "fists" : weapon.tname(g).c_str())); practice(sk_melee, rng(5, 10)); if (unarmed) practice(sk_unarmed, rng(5, 10)); if (bashing) practice(sk_bashing, rng(5, 10)); if (cutting) practice(sk_cutting, rng(5, 10)); if (stabbing) practice(sk_stabbing, rng(5, 10)); // Penalize the player if their cutting weapon got stuck if (!unarmed && dam < z->hp && cutting_penalty > dice(str_cur * 2, 20)) { if (is_u) g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!", weapon.tname().c_str(), z->type->name.c_str()); z->add_item(remove_weapon()); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .7; else z->speed *= .85; } else { if (dam >= z->hp) { cutting_penalty /= 2; cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2); } if (cutting_penalty > 0) moves -= cutting_penalty; if (cutting_penalty >= 50 && is_u) g->add_msg("Your %s gets stuck in the %s, but you yank it free.", weapon.tname().c_str(), z->type->name.c_str()); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .9; } return dam; }
bool player::install_bionics(game *g, it_bionic* type) { if (type == NULL) { debugmsg("Tried to install NULL bionic"); return false; } std::string bio_name = type->name.substr(5); // Strip off "CBM: " WINDOW* w = newwin(25, 80, 0, 0); int pl_skill = int_cur + skillLevel("electronics").level() * 4 + skillLevel("firstaid").level() * 3 + skillLevel("mechanics").level() * 2; int skint = int(pl_skill / 4); int skdec = int((pl_skill * 10) / 4) % 10; // Header text mvwprintz(w, 0, 0, c_white, "Installing bionics:"); mvwprintz(w, 0, 20, type->color, bio_name.c_str()); // Dividing bars for (int i = 0; i < 80; i++) { mvwputch(w, 1, i, c_ltgray, LINE_OXOX); mvwputch(w, 21, i, c_ltgray, LINE_OXOX); } // Init the list of bionics for (unsigned int i = 1; i < type->options.size(); i++) { bionic_id id = type->options[i]; mvwprintz(w, i + 2, 0, (has_bionic(id) ? c_ltred : c_ltblue), bionics[id].name.c_str()); } // Helper text mvwprintz(w, 2, 40, c_white, "Difficulty of this module: %d", type->difficulty); mvwprintz(w, 3, 40, c_white, "Your installation skill: %d.%d", skint, skdec); mvwprintz(w, 4, 40, c_white, "Installation requires high intelligence,"); mvwprintz(w, 5, 40, c_white, "and skill in electronics, first aid, and"); mvwprintz(w, 6, 40, c_white, "mechanics (in that order of importance)."); int chance_of_success = int((100 * pl_skill) / (pl_skill + 4 * type->difficulty)); mvwprintz(w, 8, 40, c_white, "Chance of success:"); nc_color col_suc; if (chance_of_success >= 95) col_suc = c_green; else if (chance_of_success >= 80) col_suc = c_ltgreen; else if (chance_of_success >= 60) col_suc = c_yellow; else if (chance_of_success >= 35) col_suc = c_ltred; else col_suc = c_red; mvwprintz(w, 8, 59, col_suc, "%d%%%%", chance_of_success); mvwprintz(w, 10, 40, c_white, "Failure may result in crippling damage,"); mvwprintz(w, 11, 40, c_white, "loss of existing bionics, genetic damage"); mvwprintz(w, 12, 40, c_white, "or faulty installation."); wrefresh(w); if (type->id == itm_bionics_battery) { // No selection list; just confirm mvwprintz(w, 2, 0, h_ltblue, "Battery Level +%d", BATTERY_AMOUNT); mvwprintz(w, 22, 0, c_ltblue, "\ Installing this bionic will increase your total battery capacity by %d.\n\ Batteries are necessary for most bionics to function. They also require a\n\ charge mechanism, which must be installed from another CBM.", BATTERY_AMOUNT); char ch; wrefresh(w); do ch = getch(); while (ch != 'q' && ch != '\n' && ch != KEY_ESCAPE); if (ch == '\n') { practice("electronics", (100 - chance_of_success) * 1.5); practice("firstaid", (100 - chance_of_success) * 1.0); practice("mechanics", (100 - chance_of_success) * 0.5); int success = chance_of_success - rng(1, 100); if (success > 0) { g->add_msg("Successfully installed batteries."); max_power_level += BATTERY_AMOUNT; } else bionics_install_failure(g, this, success); werase(w); delwin(w); g->refresh_all(); return true; } werase(w); delwin(w); g->refresh_all(); return false; }
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 ); }