double calculate_missed_by(player &p, int trange, item* weapon) { // No type for gunmods,so use player weapon. it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); // Calculate deviation from intended target (assuming we shoot for the head) double deviation = 0.; // Measured in quarter-degrees // Up to 1.5 degrees for each skill point < 4; up to 1.25 for each point > 4 if (p.skillLevel(firing->skill_used) < 4) deviation += rng(0, 6 * (4 - p.skillLevel(firing->skill_used))); else if (p.skillLevel(firing->skill_used) > 4) deviation -= rng(0, 5 * (p.skillLevel(firing->skill_used) - 4)); if (p.skillLevel("gun") < 3) deviation += rng(0, 3 * (3 - p.skillLevel("gun"))); else deviation -= rng(0, 2 * (p.skillLevel("gun") - 3)); deviation += p.ranged_dex_mod(); deviation += p.ranged_per_mod(); deviation += rng(0, 2 * p.encumb(bp_arms)) + rng(0, 4 * p.encumb(bp_eyes)); deviation += rng(0, weapon->curammo->accuracy); // item::accuracy() doesn't support gunmods. deviation += rng(0, p.weapon.accuracy()); int adj_recoil = p.recoil + p.driving_recoil; deviation += rng(int(adj_recoil / 4), adj_recoil); // .013 * trange is a computationally cheap version of finding the tangent. // (note that .00325 * 4 = .013; .00325 is used because deviation is a number // of quarter-degrees) // It's also generous; missed_by will be rather short. return (.00325 * deviation * trange); }
double calculate_missed_by(player &p, int trange, item* weapon) { // No type for gunmods,so use player weapon. it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); // Calculate deviation from intended target (assuming we shoot for the head) double deviation = 0.; // Measured in quarter-degrees. // Up to 0.75 degrees for each skill point < 8. if (p.skillLevel(firing->skill_used) < 8) { deviation += rng(0, 3 * (8 - p.skillLevel(firing->skill_used))); } // Up to 0.25 deg per each skill point < 9. if (p.skillLevel("gun") < 9) { deviation += rng(0, 9 - p.skillLevel("gun")); } deviation += rng(0, p.ranged_dex_mod()); deviation += rng(0, p.ranged_per_mod()); deviation += rng(0, 2 * p.encumb(bp_arms)) + rng(0, 4 * p.encumb(bp_eyes)); deviation += rng(0, weapon->curammo->dispersion); // item::dispersion() doesn't support gunmods. deviation += rng(0, p.weapon.dispersion()); int adj_recoil = p.recoil + p.driving_recoil; deviation += rng(int(adj_recoil / 4), adj_recoil); if (deviation < 0) { return 0; } // .013 * trange is a computationally cheap version of finding the tangent. // (note that .00325 * 4 = .013; .00325 is used because deviation is a number // of quarter-degrees) // It's also generous; missed_by will be rather short. return (.00325 * deviation * trange); }
int item::reload_time(player &u) { int ret = 0; if (is_gun()) { it_gun* reloading = dynamic_cast<it_gun*>(type); ret = reloading->reload_time; if (charges == 0) { int spare_mag = has_gunmod(itm_spare_mag); if (spare_mag != -1 && contents[spare_mag].charges > 0) ret -= double(ret) * 0.9; } double skill_bonus = double(u.sklevel[reloading->skill_used]) * .075; if (skill_bonus > .75) skill_bonus = .75; ret -= double(ret) * skill_bonus; } else if (is_tool()) ret = 100 + volume() + weight(); if (has_flag(IF_STR_RELOAD)) ret -= u.str_cur * 20; if (ret < 25) ret = 25; ret += u.encumb(bp_hands) * 30; return ret; }
bool trap::detect_trap( const tripoint &pos, const player &p ) const { // Some decisions are based around: // * Starting, and thus average perception, is 8. // * Buried landmines, the silent killer, has a visibility of 10. // * There will always be a distance malus of 1 unless you're on top of the trap. // * ...and an average character should at least have a minor chance of // noticing a buried landmine if standing right next to it. // Effective Perception... ///\EFFECT_PER increases chance of detecting a trap return ( p.per_cur - ( p.encumb( bp_eyes ) / 10 ) ) + // ...small bonus from stimulants... ( p.stim > 10 ? rng( 1, 2 ) : 0 ) + // ...bonus from trap skill... ///\EFFECT_TRAPS increases chance of detecting a trap ( p.get_skill_level( skill_traps ) * 2 ) + // ...luck, might be good, might be bad... rng( -4, 4 ) - // ...malus if we are tired... ( p.has_effect( effect_lack_sleep ) ? rng( 1, 5 ) : 0 ) - // ...malus farther we are from trap... rl_dist( p.pos(), pos ) + // Police are trained to notice Something Wrong. ( p.has_trait( trait_id( "PROF_POLICE" ) ) ? 1 : 0 ) + ( p.has_trait( trait_id( "PROF_PD_DET" ) ) ? 2 : 0 ) > // ...must all be greater than the trap visibility. visibility; }
bool trap::detect_trap(const player &p, int x, int y) const { // Some decisions are based around: // * Starting, and thus average perception, is 8. // * Buried landmines, the silent killer, has a visibility of 10. // * There will always be a distance malus of 1 unless you're on top of the trap. // * ...and an average character should at least have a minor chance of // noticing a buried landmine if standing right next to it. // Effective Perception... return (p.per_cur - p.encumb(bp_eyes)) + // ...small bonus from stimulants... (p.stim > 10 ? rng(1, 2) : 0) + // ...bonus from trap skill... (const_cast<player &>(p).skillLevel("traps") * 2) + // ...luck, might be good, might be bad... rng(-4, 4) - // ...malus if we are tired... (p.has_disease("lack_sleep") ? rng(1, 5) : 0) - // ...malus farther we are from trap... rl_dist(p.posx, p.posy, x, y) + // Police are trained to notice Something Wrong. (p.has_trait("PROF_POLICE") ? 1 : 0) + (p.has_trait("PROF_PD_DET") ? 2 : 0) > // ...must all be greater than the trap visibility. visibility; }
int attack_speed(player &u, bool missed) { int move_cost = u.weapon.attack_time() + 20 * u.encumb(bp_torso); if (u.has_trait(PF_LIGHT_BONES)) move_cost *= .9; if (u.has_trait(PF_HOLLOW_BONES)) move_cost *= .8; move_cost -= u.disease_intensity(DI_SPEED_BOOST); if (move_cost < 25) return 25; return move_cost; }
int item::reload_time(player &u) { int ret = 0; if (is_gun()) { it_gun* reloading = dynamic_cast<it_gun*>(type); ret = reloading->reload_time; double skill_bonus = double(u.sklevel[reloading->skill_used]) * .075; if (skill_bonus > .75) skill_bonus = .75; ret -= double(ret) * skill_bonus; } else if (is_tool()) ret = 100 + volume() + weight(); if (has_flag(IF_STR_RELOAD)) ret -= u.str_cur * 20; if (ret < 25) ret = 25; ret += u.encumb(bp_hands) * 30; return ret; }
void monster::hit_player(game *g, player &p, bool can_grab) { moves -= 100; if (type->melee_dice == 0) // We don't attack, so just return { return; } add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect(ME_RUN, 4); } bool is_npc = p.is_npc(); bool u_see = (!is_npc || g->u_see(p.posx, p.posy)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; technique_id tech = p.pick_defensive_technique(g, this, NULL); p.perform_defensive_technique(tech, g, this, NULL, bphit, side, dam, cut, stab); //110*e^(-.3*[melee skill of monster]) = % chance to miss. *100 to track .01%'s //Returns ~80% at 1, drops quickly to 33% at 4, then slowly to 5% at 10 and 1% at 16 if (rng(0, 10000) < 11000 * exp(-.3 * type->melee_skill)) { g->add_msg("The %s misses.", name().c_str()); } else { //Reduce player's ability to dodge by monster's ability to hit int dodge_ii = p.dodge(g) - rng(0, type->melee_skill); if (dodge_ii < 0) { dodge_ii = 0; } // 100/(1+99*e^(-.6*[dodge() return modified by monster's skill])) = % chance to dodge // *100 to track .01%'s // 1% minimum, scales slowly to 16% at 5, then rapidly to 80% at 10, // then returns less with each additional point, reaching 99% at 16 if (rng(0, 10000) < 10000/(1 + 99 * exp(-.6 * dodge_ii))) { g->add_msg("%s dodge the %s.", You.c_str(), name().c_str()); p.practice(g->turn, "dodge", type->melee_skill * 2); //Better monster = more skill gained } //Successful hit with damage else if (dam > 0) { p.practice(g->turn, "dodge", type->melee_skill); if (u_see && tech != TEC_BLOCK) { g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); } // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) { g->add_msg("You stop reloading."); } else if (g->u.activity.type == ACT_READ) { g->add_msg("You stop reading."); } else if (g->u.activity.type == ACT_CRAFT || g->u.activity.type == ACT_LONGCRAFT) { g->add_msg("You stop crafting."); g->u.activity.type = ACT_NULL; } } if (p.has_active_bionic("bio_ods")) { if (u_see) { g->add_msg("%s offensive defense system shocks it!", Your.c_str()); } if (hurt(rng(10, 40))) die(g); } if (p.encumb(bphit) == 0 &&(p.has_trait(PF_SPINES) || p.has_trait(PF_QUILLS))) { int spine = rng(1, (p.has_trait(PF_QUILLS) ? 20 : 8)); g->add_msg("%s %s puncture it!", Your.c_str(), (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); if (hurt(spine)) die(g); } if (dam + cut <= 0) { return; // Defensive technique canceled damage. } //Hurt the player dam = p.hit(g, bphit, side, dam, cut); //Monster effects if (dam > 0 && has_flag(MF_VENOM)) { if (!is_npc) { g->add_msg("You're poisoned!"); } p.add_disease("poison", 30); } else if (dam > 0 && has_flag(MF_BADVENOM)) { if (!is_npc) { g->add_msg("You feel poison flood your body, wracking you with pain..."); } p.add_disease("badpoison", 40); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { if (!is_npc) { g->add_msg("You're Bleeding!"); } p.add_disease("bleed", 60); } //Same as monster's chance to not miss if (can_grab && has_flag(MF_GRABS) && (rng(0, 10000) > 11000 * exp(-.3 * type->melee_skill))) { if (!is_npc) { g->add_msg("The %s grabs you!", name().c_str()); } if (p.weapon.has_technique(TEC_BREAK, &p) && dice(p.dex_cur + p.skillLevel("melee"), 12) > dice(type->melee_dice, 10)) { if (!is_npc) { g->add_msg("You break the grab!"); } } else hit_player(g, p, false); //We grabed, so hit them again } //Counter-attack? if (tech == TEC_COUNTER && !is_npc) { g->add_msg("Counter-attack!"); // A counterattack is a free action to avoid stunlocking the player. int player_moves = p.moves; hurt( p.hit_mon(g, this) ); p.moves = player_moves; } } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) { g->active_npc.erase(g->active_npc.begin() + index); } plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; for (int i = 0; i < type->anger.size(); i++) { if (type->anger[i] == MTRIG_FRIEND_ATTACKED) { anger_adjust += 15; } } for (int i = 0; i < type->placate.size(); i++) { if (type->placate[i] == MTRIG_FRIEND_ATTACKED) { anger_adjust -= 15; } } for (int i = 0; i < type->fear.size(); i++) { if (type->fear[i] == MTRIG_FRIEND_ATTACKED) { morale_adjust -= 15; } } if (anger_adjust != 0 && morale_adjust != 0) { for (int i = 0; i < g->z.size(); i++) { g->z[i].morale += morale_adjust; g->z[i].anger += anger_adjust; } } }
void monster::hit_player(game *g, player &p, bool can_grab) { moves -= 100; if (type->melee_dice == 0) // We don't attack, so just return { return; } add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect(ME_RUN, 4); } bool is_npc = p.is_npc(); bool u_see = (!is_npc || g->u_see(p.posx, p.posy)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; int side = random_side(bphit); //110*e^(-.3*[melee skill of monster]) = % chance to miss. *100 to track .01%'s //Returns ~80% at 1, drops quickly to 33% at 4, then slowly to 5% at 10 and 1% at 16 if (rng(0, 10000) < 11000 * exp(-.3 * type->melee_skill)) { if (u_see) { g->add_msg(_("The %s misses."), name().c_str()); } } else { if (!g->u.uncanny_dodge()) { //Reduce player's ability to dodge by monster's ability to hit int dodge_ii = p.dodge(g) - rng(0, type->melee_skill); if (dodge_ii < 0) { dodge_ii = 0; } // 100/(1+99*e^(-.6*[dodge() return modified by monster's skill])) = % chance to dodge // *100 to track .01%'s // 1% minimum, scales slowly to 16% at 5, then rapidly to 80% at 10, // then returns less with each additional point, reaching 99% at 16 if (rng(0, 10000) < 10000/(1 + 99 * exp(-.6 * dodge_ii))) { if (is_npc) { if(u_see) { g->add_msg(_("%1$s dodges the %2$s."), p.name.c_str(), name().c_str()); } } else { g->add_msg(_("You dodge the %s."), name().c_str()); } p.practice(g->turn, "dodge", type->melee_skill * 2); //Better monster = more skill gained } //Successful hit with damage else if (dam > 0) { p.practice(g->turn, "dodge", type->melee_skill); if(!p.block_hit(g, this, NULL, bphit, side, dam, cut, stab) && u_see) { if (is_npc) { if( u_see ) { g->add_msg(_("The %1$s hits %2$s's %3$s."), name().c_str(), p.name.c_str(), body_part_name(bphit, side).c_str()); } } else { g->add_msg(_("The %1$s hits your %2$s."), name().c_str(), body_part_name(bphit, side).c_str()); } } // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) { g->add_msg(_("You stop reloading.")); } else if (g->u.activity.type == ACT_READ) { g->add_msg(_("You stop reading.")); } else if (g->u.activity.type == ACT_CRAFT || g->u.activity.type == ACT_LONGCRAFT) { g->add_msg(_("You stop crafting.")); g->u.activity.type = ACT_NULL; } } if (p.has_active_bionic("bio_ods")) { if (!is_npc) { g->add_msg(_("Your offensive defense system shocks it!"), p.name.c_str()); } else if (u_see) { g->add_msg(_("%s's offensive defense system shocks it!"), p.name.c_str()); } hurt(rng(10, 40)); } if (p.encumb(bphit) == 0 &&(p.has_trait("SPINES") || p.has_trait("QUILLS"))) { int spine = rng(1, (p.has_trait("QUILLS") ? 20 : 8)); if (is_npc) { if( u_see ) { g->add_msg(_("%1$s's %2$s puncture it!"), p.name.c_str(), (g->u.has_trait("QUILLS") ? _("quills") : _("spines"))); } } else { g->add_msg(_("Your %s puncture it!"), (g->u.has_trait("QUILLS") ? _("quills") : _("spines"))); } hurt(spine); } if (dam + cut <= 0) { return; // Defensive technique canceled damage. } //Hallucinations don't actually hurt the player, but do produce the message if(is_hallucination()) { //~14% chance of vanishing after hitting the player if(one_in(7)) { die(g); return; } } else { //Hurt the player dam = p.hit(g, bphit, side, dam, cut); //Monster effects if (dam > 0 && has_flag(MF_VENOM)) { g->add_msg_if_player(&p, _("You're poisoned!")); p.add_disease("poison", 30); } else if (dam > 0 && has_flag(MF_BADVENOM)) { g->add_msg_if_player(&p, _("You feel poison flood your body, wracking you with pain...")); p.add_disease("badpoison", 40); } else if (dam > 0 && has_flag(MF_PARALYZE)) { g->add_msg_if_player(&p, _("You feel poison enter your body!")); p.add_disease("paralyzepoison", 100, false, 1, 20, 100); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { g->add_msg_if_player(&p, _("You're Bleeding!")); p.add_disease("bleed", 60, false, 1, 3, 120, 1, bphit, side, true); } //Same as monster's chance to not miss if (can_grab && has_flag(MF_GRABS) && (rng(0, 10000) > 11000 * exp(-.3 * type->melee_skill))) { g->add_msg(_("The %s grabs you!"), name().c_str()); if (p.has_grab_break_tec() && dice(p.dex_cur + p.skillLevel("melee"), 12) > dice(type->melee_dice, 10)) { g->add_msg_if_player(&p, _("You break the grab!")); } else { hit_player(g, p, false); //We grabed, so hit them again } } } // TODO: readd with counter mechanic } } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) { g->active_npc.erase(g->active_npc.begin() + index); } plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_ATTACKED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (int i = 0; i < g->num_zombies(); i++) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } }
void game::throw_item(player &p, int tarx, int tary, item &thrown, std::vector<point> &trajectory) { int deviation = 0; int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary); // Throwing attempts below "Basic Competency" level are extra-bad int skillLevel = p.skillLevel("throw"); if (skillLevel < 3) deviation += rng(0, 8 - skillLevel); if (skillLevel < 8) deviation += rng(0, 8 - skillLevel); else deviation -= skillLevel - 6; deviation += p.throw_dex_mod(); if (p.per_cur < 6) deviation += rng(0, 8 - p.per_cur); else if (p.per_cur > 8) deviation -= p.per_cur - 8; deviation += rng(0, p.encumb(bp_hands) * 2 + p.encumb(bp_eyes) + 1); if (thrown.volume() > 5) deviation += rng(0, 1 + (thrown.volume() - 5) / 4); if (thrown.volume() == 0) deviation += rng(0, 3); deviation += rng(0, 1 + abs(p.str_cur - thrown.weight())); double missed_by = .01 * deviation * trange; bool missed = false; int tart; if (missed_by >= 1) { // We missed D: // Shoot a random nearby space? if (missed_by > 9) missed_by = 9; tarx += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); tary += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, tarx, tary, -1, tart)) trajectory = line_to(p.posx, p.posy, tarx, tary, tart); else trajectory = line_to(p.posx, p.posy, tarx, tary, 0); missed = true; if (!p.is_npc()) add_msg("You miss!"); } else if (missed_by >= .6) { // Hit the space, but not necessarily the monster there missed = true; if (!p.is_npc()) add_msg("You barely miss!"); } std::string message; int dam = (thrown.weight() / 4 + thrown.type->melee_dam / 2 + p.str_cur / 2) / double(2 + double(thrown.volume() / 4)); if (dam > thrown.weight() * 3) dam = thrown.weight() * 3; int i = 0, tx = 0, ty = 0; for (i = 0; i < trajectory.size() && dam > -10; i++) { message = ""; double goodhit = missed_by; tx = trajectory[i].x; ty = trajectory[i].y; // If there's a monster in the path of our item, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it if (mon_at(tx, ty) != -1 && (!missed || one_in(7 - int(z[mon_at(tx, ty)].type->size)))) { if (rng(0, 100) < 20 + skillLevel * 12 && thrown.type->melee_cut > 0) { if (!p.is_npc()) { message += " You cut the "; message += z[mon_at(tx, ty)].name(); message += "!"; } if (thrown.type->melee_cut > z[mon_at(tx, ty)].armor_cut()) dam += (thrown.type->melee_cut - z[mon_at(tx, ty)].armor_cut()); } if (thrown.made_of(GLASS) && !thrown.active && // active = molotov, etc. rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) add_msg("The %s shatters!", thrown.tname().c_str()); for (int i = 0; i < thrown.contents.size(); i++) m.add_item(tx, ty, thrown.contents[i]); sound(tx, ty, 16, "glass breaking!"); int glassdam = rng(0, thrown.volume() * 2); if (glassdam > z[mon_at(tx, ty)].armor_cut()) dam += (glassdam - z[mon_at(tx, ty)].armor_cut()); } else m.add_item(tx, ty, thrown); if (i < trajectory.size() - 1) goodhit = double(double(rand() / RAND_MAX) / 2); if (goodhit < .1 && !z[mon_at(tx, ty)].has_flag(MF_NOHEAD)) { message = "Headshot!"; dam = rng(dam, dam * 3); p.practice(turn, "throw", 5); } else if (goodhit < .2) { message = "Critical!"; dam = rng(dam, dam * 2); p.practice(turn, "throw", 2); } else if (goodhit < .4) dam = rng(int(dam / 2), int(dam * 1.5)); else if (goodhit < .5) { message = "Grazing hit."; dam = rng(0, dam); } if (!p.is_npc()) add_msg("%s You hit the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); else if (u_see(tx, ty)) add_msg("%s hits the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); if (z[mon_at(tx, ty)].hurt(dam)) kill_mon(mon_at(tx, ty), !p.is_npc()); return; } else // No monster hit, but the terrain might be. m.shoot(this, tx, ty, dam, false, 0); if (m.move_cost(tx, ty) == 0) { if (i > 0) { tx = trajectory[i - 1].x; ty = trajectory[i - 1].y; } else { tx = u.posx; ty = u.posy; } i = trajectory.size(); } } if (m.move_cost(tx, ty) == 0) { if (i > 1) { tx = trajectory[i - 2].x; ty = trajectory[i - 2].y; } else { tx = u.posx; ty = u.posy; } } if (thrown.made_of(GLASS) && !thrown.active && // active means molotov, etc rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) add_msg("The %s shatters!", thrown.tname().c_str()); for (int i = 0; i < thrown.contents.size(); i++) m.add_item(tx, ty, thrown.contents[i]); sound(tx, ty, 16, "glass breaking!"); } else { sound(tx, ty, 8, "thud."); m.add_item(tx, ty, thrown); } }
void monster::hit_player(game *g, player &p, bool can_grab) { if (type->melee_dice == 0) // We don't attack, so just return return; add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) add_effect(ME_RUN, 4); bool is_npc = p.is_npc(); int junk; bool u_see = (!is_npc || g->u_see(p.posx, p.posy, junk)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; technique_id tech = p.pick_defensive_technique(g, this, NULL); p.perform_defensive_technique(tech, g, this, NULL, bphit, side, dam, cut, stab); if (dam == 0 && u_see) g->add_msg("The %s misses %s.", name().c_str(), you.c_str()); else if (dam > 0) { if (u_see && tech != TEC_BLOCK) g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) g->add_msg("You stop reloading."); else if (g->u.activity.type == ACT_READ) g->add_msg("You stop reading."); else if (g->u.activity.type == ACT_CRAFT) g->add_msg("You stop crafting."); g->u.activity.type = ACT_NULL; } if (p.has_active_bionic(bio_ods)) { if (u_see) g->add_msg("%s offensive defense system shocks it!", Your.c_str()); hurt(rng(10, 40)); } if (p.encumb(bphit) == 0 && (p.has_trait(PF_SPINES) || p.has_trait(PF_QUILLS))) { int spine = rng(1, (p.has_trait(PF_QUILLS) ? 20 : 8)); g->add_msg("%s %s puncture it!", Your.c_str(), (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); hurt(spine); } if (dam + cut <= 0) return; // Defensive technique canceled damage. p.hit(g, bphit, side, dam, cut); if (has_flag(MF_VENOM)) { if (!is_npc) g->add_msg("You're poisoned!"); p.add_disease(DI_POISON, 30, g); } else if (has_flag(MF_BADVENOM)) { if (!is_npc) g->add_msg("You feel poison flood your body, wracking you with pain..."); p.add_disease(DI_BADPOISON, 40, g); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { if (!is_npc) g->add_msg("You're Bleeding!"); p.add_disease(DI_BLEED, 30, g); } if (can_grab && has_flag(MF_GRABS) && dice(type->melee_dice, 10) > dice(p.dodge(g), 10)) { if (!is_npc) g->add_msg("The %s grabs you!", name().c_str()); if (p.weapon.has_technique(TEC_BREAK, &p) && dice(p.dex_cur + p.skillLevel("melee").level(), 12) > dice(type->melee_dice, 10)){ if (!is_npc) g->add_msg("You break the grab!"); } else hit_player(g, p, false); } if (tech == TEC_COUNTER && !is_npc) { g->add_msg("Counter-attack!"); hurt( p.hit_mon(g, this) ); } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) g->active_npc.erase(g->active_npc.begin() + index); plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; for (unsigned int i = 0; i < type->anger.size(); i++) { if (type->anger[i] == MTRIG_FRIEND_ATTACKED) anger_adjust += 15; } for (unsigned int i = 0; i < type->placate.size(); i++) { if (type->placate[i] == MTRIG_FRIEND_ATTACKED) anger_adjust -= 15; } for (unsigned int i = 0; i < type->fear.size(); i++) { if (type->fear[i] == MTRIG_FRIEND_ATTACKED) morale_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (unsigned int i = 0; i < g->z.size(); i++) { g->z[i].morale += morale_adjust; g->z[i].anger += anger_adjust; } } }
void monster::hit_player(game *g, player &p) { if (type->melee_dice == 0) // We don't attack, so just return return; bool is_npc = p.is_npc(); int junk; bool u_see = (!is_npc || g->u_see(p.posx, p.posy, junk)); std::string you = (is_npc ? p.name : "you"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); int dam = hit(p, bphit); if (dam == 0 && u_see) g->add_msg("The %s misses %s.", name().c_str(), you.c_str()); else if (dam > 0) { if (u_see) g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) g->add_msg("You stop reloading."); else if (g->u.activity.type == ACT_READ) g->add_msg("You stop reading."); else if (g->u.activity.type == ACT_CRAFT) g->add_msg("You stop crafting."); g->u.activity.type = ACT_NULL; } if (p.has_active_bionic(bio_ods)) { if (u_see) g->add_msg("%s offensive defense system shocks it!", Your.c_str()); hurt(rng(10, 40)); } if (p.encumb(bphit) == 0 && (p.has_trait(PF_SPINES) || p.has_trait(PF_QUILLS))) { int spine = rng(1, (p.has_trait(PF_QUILLS) ? 20 : 8)); g->add_msg("%s %s puncture it!", Your.c_str(), (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); hurt(spine); } p.hit(g, bphit, side, dam, type->melee_cut); if (has_flag(MF_VENOM)) { if (!is_npc) g->add_msg("You're poisoned!"); p.add_disease(DI_POISON, 30, g); } else if (has_flag(MF_BADVENOM)) { if (!is_npc) g->add_msg("You feel poison flood your body, wracking you with pain..."); p.add_disease(DI_BADPOISON, 40, g); } } if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); g->active_npc.erase(g->active_npc.begin() + index); plans.clear(); } } }
void map::drawsq(WINDOW* w, player &u, int x, int y, bool invert, bool show_items) { if (!inbounds(x, y)) return; // Out of bounds int k = x + SEEX - u.posx; int j = y + SEEY - u.posy; nc_color tercol; char sym = terlist[ter(x, y)].sym; bool hi = false; if (u.has_disease(DI_BOOMERED)) tercol = c_magenta; else if ((u.is_wearing(itm_goggles_nv) && u.has_active_item(itm_UPS_on)) || u.has_active_bionic(bio_night_vision)) tercol = c_ltgreen; else tercol = terlist[ter(x, y)].color; if (move_cost(x, y) == 0 && has_flag(swimmable, x, y) && !u.underwater) show_items = false; // Can only see underwater items if WE are underwater // If there's a trap here, and we have sufficient perception, draw that instead if (tr_at(x, y) != tr_null && u.per_cur - u.encumb(bp_eyes) >= (*traps)[tr_at(x, y)]->visibility) { tercol = (*traps)[tr_at(x, y)]->color; if ((*traps)[tr_at(x, y)]->sym == '%') { switch(rng(1, 5)) { case 1: sym = '*'; break; case 2: sym = '0'; break; case 3: sym = '8'; break; case 4: sym = '&'; break; case 5: sym = '+'; break; } } else sym = (*traps)[tr_at(x, y)]->sym; } // If there's a field here, draw that instead (unless its symbol is %) if (field_at(x, y).type != fd_null) { tercol = fieldlist[field_at(x, y).type].color[field_at(x, y).density - 1]; if (fieldlist[field_at(x, y).type].sym == '*') { switch (rng(1, 5)) { case 1: sym = '*'; break; case 2: sym = '0'; break; case 3: sym = '8'; break; case 4: sym = '&'; break; case 5: sym = '+'; break; } } else if (fieldlist[field_at(x, y).type].sym != '%') sym = fieldlist[field_at(x, y).type].sym; } // If there's items here, draw those instead if (show_items && i_at(x, y).size() > 0 && field_at(x, y).is_null()) { if ((terlist[ter(x, y)].sym != '.')) hi = true; else { tercol = i_at(x, y)[i_at(x, y).size() - 1].color(); if (i_at(x, y).size() > 1) invert = !invert; sym = i_at(x, y)[i_at(x, y).size() - 1].symbol(); } } if (invert) mvwputch_inv(w, j, k, tercol, sym); else if (hi) mvwputch_hi (w, j, k, tercol, sym); else mvwputch (w, j, k, tercol, sym); }
void game::throw_item(player &p, int tarx, int tary, item &thrown, std::vector<point> &trajectory) { int deviation = 0; int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary); std::set<std::string> no_effects; // Throwing attempts below "Basic Competency" level are extra-bad int skillLevel = p.skillLevel("throw"); if (skillLevel < 3) { deviation += rng(0, 8 - skillLevel); } if (skillLevel < 8) { deviation += rng(0, 8 - skillLevel); } else { deviation -= skillLevel - 6; } deviation += p.throw_dex_mod(); if (p.per_cur < 6) { deviation += rng(0, 8 - p.per_cur); } else if (p.per_cur > 8) { deviation -= p.per_cur - 8; } deviation += rng(0, p.encumb(bp_hands) * 2 + p.encumb(bp_eyes) + 1); if (thrown.volume() > 5) { deviation += rng(0, 1 + (thrown.volume() - 5) / 4); } if (thrown.volume() == 0) { deviation += rng(0, 3); } deviation += rng(0, std::max( 0, p.str_cur - thrown.weight() / 113 ) ); double missed_by = .01 * deviation * trange; bool missed = false; int tart; if (missed_by >= 1) { // We missed D: // Shoot a random nearby space? if (missed_by > 9) { missed_by = 9; } tarx += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); tary += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, tarx, tary, -1, tart)) { trajectory = line_to(p.posx, p.posy, tarx, tary, tart); } else { trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } missed = true; add_msg_if_player(&p,_("You miss!")); } else if (missed_by >= .6) { // Hit the space, but not necessarily the monster there missed = true; add_msg_if_player(&p,_("You barely miss!")); } std::string message; int real_dam = (thrown.weight() / 452 + thrown.type->melee_dam / 2 + p.str_cur / 2) / double(2 + double(thrown.volume() / 4)); if (real_dam > thrown.weight() / 40) { real_dam = thrown.weight() / 40; } if (p.has_active_bionic("bio_railgun") && (thrown.made_of("iron") || thrown.made_of("steel"))) { real_dam *= 2; } int dam = real_dam; int i = 0, tx = 0, ty = 0; for (i = 0; i < trajectory.size() && dam >= 0; i++) { message = ""; double goodhit = missed_by; tx = trajectory[i].x; ty = trajectory[i].y; const int zid = mon_at(tx, ty); // If there's a monster in the path of our item, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it if (zid != -1 && (!missed || one_in(7 - int(zombie(zid).type->size)))) { monster &z = zombie(zid); if (rng(0, 100) < 20 + skillLevel * 12 && thrown.type->melee_cut > 0) { if (!p.is_npc()) { message += string_format(_(" You cut the %s!"), z.name().c_str()); } if (thrown.type->melee_cut > z.get_armor_cut(bp_torso)) { dam += (thrown.type->melee_cut - z.get_armor_cut(bp_torso)); } } if (thrown.made_of("glass") && !thrown.active && // active = molotov, etc. rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) { add_msg(_("The %s shatters!"), thrown.tname().c_str()); } for (int i = 0; i < thrown.contents.size(); i++) { m.add_item_or_charges(tx, ty, thrown.contents[i]); } sound(tx, ty, 16, _("glass breaking!")); int glassdam = rng(0, thrown.volume() * 2); if (glassdam > z.get_armor_cut(bp_torso)) { dam += (glassdam - z.get_armor_cut(bp_torso)); } } else { m.add_item_or_charges(tx, ty, thrown); } if (i < trajectory.size() - 1) { goodhit = double(double(rand() / RAND_MAX) / 2); } if (goodhit < .1 && !z.has_flag(MF_NOHEAD)) { message = _("Headshot!"); dam = rng(dam, dam * 3); p.practice(turn, "throw", 5); p.lifetime_stats()->headshots++; } else if (goodhit < .2) { message = _("Critical!"); dam = rng(dam, dam * 2); p.practice(turn, "throw", 2); } else if (goodhit < .4) { dam = rng(int(dam / 2), int(dam * 1.5)); } else if (goodhit < .5) { message = _("Grazing hit."); dam = rng(0, dam); } if (u_see(tx, ty)) { g->add_msg_player_or_npc(&p, _("%s You hit the %s for %d damage."), _("%s <npcname> hits the %s for %d damage."), message.c_str(), z.name().c_str(), dam); } if (z.hurt(dam, real_dam)) { z.die(&p); } return; } else { // No monster hit, but the terrain might be. m.shoot(tx, ty, dam, false, no_effects); } // Collide with impassable terrain if (m.move_cost(tx, ty) == 0) { if (i > 0) { tx = trajectory[i - 1].x; ty = trajectory[i - 1].y; } else { tx = u.posx; ty = u.posy; } i = trajectory.size(); } if (p.has_active_bionic("bio_railgun") && (thrown.made_of("iron") || thrown.made_of("steel"))) { m.add_field(tx, ty, fd_electricity, rng(2,3)); } } if (thrown.made_of("glass") && !thrown.active && // active means molotov, etc rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) { add_msg(_("The %s shatters!"), thrown.tname().c_str()); } for (int i = 0; i < thrown.contents.size(); i++) { m.add_item_or_charges(tx, ty, thrown.contents[i]); } sound(tx, ty, 16, _("glass breaking!")); } else { if(m.has_flag("LIQUID", tx, ty)) { sound(tx, ty, 10, _("splash!")); } else { sound(tx, ty, 8, _("thud.")); } m.add_item_or_charges(tx, ty, thrown); } }