/** * 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; }
/** * Attempts to harm a creature with a projectile. * * @param source Pointer to the creature who shot the projectile. * @param missed_by Deviation of the projectile. * @param proj Reference to the projectile hitting the creature. * @param dealt_dam A reference storing the damage dealt. * @return 0 signals that the projectile should stop, * 1 signals that the projectile should not stop (i.e. dodged, passed through). */ int Creature::deal_projectile_attack(Creature *source, double missed_by, const projectile &proj, dealt_damage_instance &dealt_dam) { bool u_see_this = g->u.sees(*this); body_part bp_hit; // do 10,speed because speed could potentially be > 10000 if (dodge_roll() >= dice(10, proj.speed)) { if (is_player()) add_msg(_("You dodge %s projectile!"), source->disp_name(true).c_str()); else if (u_see_this) add_msg(_("%s dodges %s projectile."), disp_name().c_str(), source->disp_name(true).c_str()); return 1; } // Bounce applies whether it does damage or not. if (proj.proj_effects.count("BOUNCE")) { add_effect("bounced", 1); } 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 monster_speed_penalty = std::max(double(get_speed()) / 80., 1.0); double goodhit = missed_by / monster_speed_penalty; double damage_mult = 1.0; std::string message = ""; game_message_type gmtSCTcolor = m_neutral; if (goodhit <= .1) { message = _("Headshot!"); source->add_msg_if_player(m_good, message.c_str()); gmtSCTcolor = m_headshot; damage_mult *= rng_float(2.45, 3.35); bp_hit = bp_head; // headshot hits the head, of course } else if (goodhit <= .2) { message = _("Critical!"); source->add_msg_if_player(m_good, message.c_str()); gmtSCTcolor = m_critical; damage_mult *= rng_float(1.75, 2.3); } else if (goodhit <= .4) { message = _("Good hit!"); source->add_msg_if_player(m_good, message.c_str()); gmtSCTcolor = m_good; damage_mult *= rng_float(1, 1.5); } else if (goodhit <= .6) { damage_mult *= rng_float(0.5, 1); } else if (goodhit <= .8) { message = _("Grazing hit."); source->add_msg_if_player(m_good, message.c_str()); gmtSCTcolor = m_grazing; damage_mult *= rng_float(0, .25); } else { damage_mult *= 0; } // copy it, since we're mutating damage_instance impact = proj.impact; if( item(proj.ammo->id, 0).has_flag("NOGIB") ) { impact.add_effect("NOGIB"); } impact.mult_damage(damage_mult); dealt_dam = deal_damage(source, bp_hit, impact); dealt_dam.bp_hit = bp_hit; // Apply ammo effects to target. const std::string target_material = get_material(); if (proj.proj_effects.count("FLAME")) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood" ) ) { add_effect("onfire", rng(8, 20)); } else if (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) { add_effect("onfire", rng(5, 10)); } } else if (proj.proj_effects.count("INCENDIARY") ) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood") ) { add_effect("onfire", rng(2, 6)); } else if ( (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) && one_in(4) ) { add_effect("onfire", rng(1, 4)); } } else if (proj.proj_effects.count("IGNITE")) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood") ) { add_effect("onfire", rng(6, 6)); } else if (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) { add_effect("onfire", rng(10, 10)); } } int stun_strength = 0; if (proj.proj_effects.count("BEANBAG")) { stun_strength = 4; } if(proj.proj_effects.count("WHIP")) { stun_strength = rng(4, 10); } 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( "stunned", rng(stun_strength / 2, stun_strength) ); } if(u_see_this) { if (damage_mult == 0) { if(source != NULL) { add_msg(source->is_player() ? _("You miss!") : _("The shot misses!")); } } else if (dealt_dam.total_damage() == 0) { add_msg(_("The shot reflects off %s %s!"), disp_name(true).c_str(), skin_name().c_str()); } else if (source != NULL) { 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(this->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( u_see_this ) { add_msg(_("%s shoots %s."), source->disp_name().c_str(), disp_name().c_str()); } } } check_dead_state(); return 0; }
hp_part Character::body_window( const std::string &menu_header, bool show_all, bool precise, int normal_bonus, int head_bonus, int torso_bonus, bool bleed, bool bite, bool infect ) const { WINDOW *hp_window = newwin(10, 31, (TERMY - 10) / 2, (TERMX - 31) / 2); draw_border(hp_window); trim_and_print( hp_window, 1, 1, getmaxx(hp_window) - 2, c_ltred, menu_header.c_str() ); const int y_off = 2; // 1 for border, 1 for header /* This struct estabiles some kind of connection between the hp_part (which can be healed and * have HP) and the body_part. Note that there are more body_parts than hp_parts. For example: * Damage to bp_head, bp_eyes and bp_mouth is all applied on the HP of hp_head. */ struct healable_bp { mutable bool allowed; body_part bp; hp_part hp; std::string name; // Translated name as it appears in the menu. int bonus; }; /* The array of the menu entries show to the player. The entries are displayed in this order, * it may be changed here. */ std::array<healable_bp, num_hp_parts> parts = { { { false, bp_head, hp_head, _("Head"), head_bonus }, { false, bp_torso, hp_torso, _("Torso"), torso_bonus }, { false, bp_arm_l, hp_arm_l, _("Left Arm"), normal_bonus }, { false, bp_arm_r, hp_arm_r, _("Right Arm"), normal_bonus }, { false, bp_leg_l, hp_leg_l, _("Left Leg"), normal_bonus }, { false, bp_leg_r, hp_leg_r, _("Right Leg"), normal_bonus }, } }; for( size_t i = 0; i < parts.size(); i++ ) { const auto &e = parts[i]; const body_part bp = e.bp; const hp_part hp = e.hp; const int maximal_hp = hp_max[hp]; const int current_hp = hp_cur[hp]; const int bonus = e.bonus; // This will c_ltgray if the part does not have any effects cured by the item // (e.g. it cures only bites, but the part does not have a bite effect) const nc_color state_col = limb_color( bp, bleed, bite, infect ); const bool has_curable_effect = state_col != c_ltgray; // The same as in the main UI sidebar. Independent of the capability of the healing item! const nc_color all_state_col = limb_color( bp, true, true, true ); const bool has_any_effect = all_state_col != c_ltgray; // Broken means no HP can be restored, it requires surgical attention. const bool limb_is_broken = current_hp == 0; // This considers only the effects that can *not* be removed. const nc_color new_state_col = limb_color( bp, !bleed, !bite, !infect ); if( show_all ) { e.allowed = true; } else if( has_curable_effect ) { e.allowed = true; } else if( limb_is_broken ) { continue; } else if( current_hp < maximal_hp && e.bonus != 0 ) { e.allowed = true; } else { continue; } const int line = i + y_off; const nc_color color = show_all ? c_green : state_col; mvwprintz( hp_window, line, 1, color, "%d: %s", i + 1, e.name.c_str() ); const auto print_hp = [&]( const int x, const nc_color col, const int hp ) { const auto bar = get_hp_bar( hp, maximal_hp, false ); if( hp == 0 ) { mvwprintz( hp_window, line, x, col, "-----" ); } else if( precise ) { mvwprintz( hp_window, line, x, col, "%5d", hp ); } else { mvwprintz( hp_window, line, x, col, bar.first.c_str() ); } }; if( !limb_is_broken ) { // Drop the bar color, use the state color instead const nc_color color = has_any_effect ? all_state_col : c_green; print_hp( 15, color, current_hp ); } else { // But still could be infected or bleeding const nc_color color = has_any_effect ? all_state_col : c_dkgray; print_hp( 15, color, 0 ); } if( !limb_is_broken ) { const int new_hp = std::max( 0, std::min( maximal_hp, current_hp + bonus ) ); if( new_hp == current_hp && !has_curable_effect ) { // Nothing would change continue; } mvwprintz( hp_window, line, 20, c_dkgray, " -> " ); const nc_color color = has_any_effect ? new_state_col : c_green; print_hp( 24, color, new_hp ); } else { const nc_color color = has_any_effect ? new_state_col : c_dkgray; mvwprintz( hp_window, line, 20, c_dkgray, " -> " ); print_hp( 24, color, 0 ); } } mvwprintz( hp_window, parts.size() + y_off, 1, c_ltgray, _("%d: Exit"), parts.size() + 1 ); wrefresh(hp_window); char ch; hp_part healed_part = num_hp_parts; do { ch = getch(); const size_t index = ch - '1'; if( index < parts.size() && parts[index].allowed ) { healed_part = parts[index].hp; break; } else if( index == parts.size() || ch == KEY_ESCAPE) { healed_part = num_hp_parts; break; } } while (ch < '1' || ch > '7'); werase(hp_window); wrefresh(hp_window); delwin(hp_window); refresh(); return healed_part; }
hp_part Character::body_window( const std::string &menu_header, bool show_all, bool precise, int normal_bonus, int head_bonus, int torso_bonus, int bleed, int bite, int infect ) const { WINDOW *hp_window = newwin(10, 31, (TERMY - 10) / 2, (TERMX - 31) / 2); draw_border(hp_window); trim_and_print( hp_window, 1, 1, getmaxx(hp_window) - 2, c_ltred, menu_header.c_str() ); nc_color color = c_ltgray; bool allowed_result[num_hp_parts] = { false }; const auto check_part = [&]( hp_part part, std::string part_name, int heal_val, int line_num ) { body_part bp = player::hp_to_bp( part ); if( show_all || hp_cur[part] < hp_max[part] || has_effect("infected", bp) || has_effect("bite", bp) || has_effect("bleed", bp) ) { nc_color color = show_all ? c_green : limb_color( bp, bleed, bite, infect ); if( color != c_ltgray || heal_val != 0 ) { mvwprintz( hp_window, line_num, 1, color, part_name.c_str() ); allowed_result[part] = true; } } }; check_part( hp_head, _("1: Head"), head_bonus, 2 ); check_part( hp_torso, _("2: Torso"), torso_bonus, 3 ); check_part( hp_arm_l, _("3: Left Arm"), normal_bonus, 4 ); check_part( hp_arm_r, _("4: Right Arm"), normal_bonus, 5 ); check_part( hp_leg_l, _("5: Left Leg"), normal_bonus, 6 ); check_part( hp_leg_r, _("6: Right Leg"), normal_bonus, 7 ); mvwprintz( hp_window, 8, 1, c_ltgray, _("7: Exit") ); std::string health_bar; for( int i = 0; i < num_hp_parts; i++ ) { if( !allowed_result[i] ) { continue; } body_part bp = body_part( i ); // Have printed the name of the body part, can select it int current_hp = hp_cur[i]; if( current_hp != 0 ) { std::tie( health_bar, color ) = get_hp_bar(current_hp, hp_max[i], false); // Drop the bar color, use the state color instead const nc_color state_col = limb_color( bp, true, true, true ); color = state_col != c_ltgray ? state_col : c_green; if( precise ) { mvwprintz(hp_window, i + 2, 15, color, "%5d", current_hp); } else { mvwprintz(hp_window, i + 2, 15, color, health_bar.c_str()); } } else { // curhp is 0; requires surgical attention // But still could be infected or bleeding const nc_color state_col = limb_color( bp, true, true, true ); color = state_col != c_ltgray ? state_col : c_dkgray; mvwprintz(hp_window, i + 2, 15, color, "-----"); } if( current_hp != 0 ) { switch( hp_part( i ) ) { case hp_head: current_hp += head_bonus; break; case hp_torso: current_hp += torso_bonus; break; default: current_hp += normal_bonus; break; } if( current_hp > hp_max[i] ) { current_hp = hp_max[i]; } else if (current_hp < 0) { current_hp = 0; } if( current_hp == hp_cur[i] && ( infect <= 0 || !has_effect( "infected", bp ) ) && ( bite <= 0 || !has_effect( "bite", bp ) ) && ( bleed <= 0 || !has_effect( "bleed", bp ) ) ) { // Nothing would change continue; } mvwprintz( hp_window, i + 2, 20, c_dkgray, " -> " ); std::tie( health_bar, color ) = get_hp_bar( current_hp, hp_max[i], false ); const nc_color state_col = limb_color( bp, bleed > 0, bite > 0, infect > 0 ); color = state_col != c_ltgray ? state_col : c_green; if( precise ) { mvwprintz( hp_window, i + 2, 24, color, "%5d", current_hp ); } else { mvwprintz( hp_window, i + 2, 24, color, health_bar.c_str() ); } } else { // curhp is 0; requires surgical attention const nc_color state_col = limb_color( bp, bleed > 0, bite > 0, infect > 0 ); color = state_col != c_ltgray ? state_col : c_dkgray; mvwprintz(hp_window, i + 2, 24, color, "-----"); } } wrefresh(hp_window); char ch; hp_part healed_part = num_hp_parts; do { ch = getch(); if (ch == '1') { healed_part = hp_head; } else if (ch == '2') { healed_part = hp_torso; } else if (ch == '3') { healed_part = hp_arm_l; } else if (ch == '4') { healed_part = hp_arm_r; } else if (ch == '5') { healed_part = hp_leg_l; } else if (ch == '6') { healed_part = hp_leg_r; } else if (ch == '7' || ch == KEY_ESCAPE) { healed_part = num_hp_parts; break; } } while (ch < '1' || ch > '7'); werase(hp_window); wrefresh(hp_window); delwin(hp_window); refresh(); return healed_part; }