bool monster::is_fleeing(player &u) { // fleefactor is by default the agressiveness of the animal, minus the // percentage of remaining HP times four. So, aggresiveness of 5 has a // fleefactor of 2 AT MINIMUM. if (type->hp == 0) { debugmsg("%s has type->hp of 0!", type->name.c_str()); return false; } if (friendly != 0) return false; int fleefactor = type->agro - ((4 * (type->hp - hp)) / type->hp); if (u.has_trait(PF_ANIMALEMPATH) && has_flag(MF_ANIMAL)) { if (type->agro > 0) // Agressive animals flee instead fleefactor -= 5; else // Scared animals approach you return false; } if (u.has_trait(PF_TERRIFYING)) fleefactor -= 1; if (fleefactor > 0) return false; return true; }
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; }
virtual bool key(int key, int entnum, uimenu *menu) { if ( key == 't' && p->has_trait( vTraits[ entnum ] ) ) { if ( p->has_base_trait( vTraits[ entnum ] ) ) { p->toggle_trait( vTraits[ entnum ] ); p->toggle_mutation( vTraits[ entnum ] ); } else { p->toggle_mutation( vTraits[ entnum ] ); p->toggle_trait( vTraits[ entnum ] ); } menu->entries[ entnum ].text_color = ( p->has_trait( vTraits[ entnum ] ) ? c_green : menu->text_color ); menu->entries[ entnum ].extratxt.txt= ( p->has_base_trait( vTraits[ entnum ] ) ? "T" : "" ); return true; } return false; }
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; }
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 key( const input_event &event, int entnum, uimenu *menu ) override { if( event.get_first_input() == 't' && p->has_trait( vTraits[ entnum ] ) ) { if( p->has_base_trait( vTraits[ entnum ] ) ) { p->toggle_trait( vTraits[ entnum ] ); p->unset_mutation( vTraits[ entnum ] ); } else { p->set_mutation( vTraits[ entnum ] ); p->toggle_trait( vTraits[ entnum ] ); } menu->entries[ entnum ].text_color = ( p->has_trait( vTraits[ entnum ] ) ? c_green : menu->text_color ); menu->entries[ entnum ].extratxt.txt = ( p->has_base_trait( vTraits[ entnum ] ) ? "T" : "" ); return true; } return false; }
std::string addiction_text(player const& u, addiction const &cur) { int const strpen = 1 + cur.intensity / 7; switch (cur.type) { case ADD_CIG: return _("Intelligence - 1; Occasional cravings"); case ADD_CAFFEINE: return _("Strength - 1; Slight sluggishness; Occasional cravings"); case ADD_ALCOHOL: return _("Perception - 1; Intelligence - 1; Occasional Cravings;\n" "Risk of delirium tremens"); case ADD_SLEEP: return _("You may find it difficult to sleep without medication."); case ADD_PKILLER: { if (u.has_trait("NOPAIN")) { return string_format(_( "Strength - %d; Perception - 1; Dexterity - 1;\n" "Depression. Frequent cravings. Vomiting."), strpen); } else { return string_format(_( "Strength - %d; Perception - 1; Dexterity - 1;\n" "Depression and physical pain to some degree. Frequent cravings. Vomiting."), strpen); } } case ADD_SPEED: return _("Strength - 1; Intelligence - 1;\n" "Movement rate reduction. Depression. Weak immune system. Frequent cravings."); case ADD_COKE: return _("Perception - 1; Intelligence - 1; Frequent cravings."); case ADD_CRACK: return _("Perception - 2; Intelligence - 2; Frequent cravings."); case ADD_MUTAGEN: return _("You've gotten a taste for mutating and the chemicals that cause it. But you can stop, yeah, any time you want."); case ADD_DIAZEPAM: return _("Perception - 1; Intelligence - 1;\n" "Anxiety, nausea, hallucinations, and general malaise."); case ADD_MARLOSS_R: return _("You should try some of those pink berries."); case ADD_MARLOSS_B: return _("You should try some of those cyan seeds."); case ADD_MARLOSS_Y: return _("You should try some of that golden gel."); default: return ""; } }
bool player_can_build( player &p, const inventory &pinv, const construction &con ) { if( p.has_trait( "DEBUG_HS" ) ) { return true; } if( p.get_skill_level( con.skill ) < con.difficulty ) { return false; } return con.requirements->can_make_with_inventory( pinv ); }
int stumble(player &u) { int stumble_pen = 2 * u.weapon.volume() + u.weapon.weight(); if (u.has_trait(PF_DEFT)) stumble_pen = int(stumble_pen * .3) - 10; if (stumble_pen < 0) stumble_pen = 0; // TODO: Reflect high strength bonus in newcharacter.cpp if (stumble_pen > 0 && (u.str_cur >= 15 || u.dex_cur >= 21 || one_in(16 - u.str_cur) || one_in(22 - u.dex_cur))) stumble_pen = rng(0, stumble_pen); return stumble_pen; }
void addict_effect( player &u, addiction &add ) { const int in = std::min( 20, add.intensity ); switch( add.type ) { case ADD_CIG: if( !one_in( 2000 - 20 * in ) ) { break; } u.add_msg_if_player( rng( 0, 6 ) < in ? _( "You need some nicotine." ) : _( "You could use some nicotine." ) ); u.add_morale( MORALE_CRAVING_NICOTINE, -15, -3 * in ); if( one_in( 800 - 50 * in ) ) { u.mod_fatigue( 1 ); } if( u.stim > -5 * in && one_in( 400 - 20 * in ) ) { u.stim--; } break; case ADD_CAFFEINE: if( !one_in( 2000 - 20 * in ) ) { break; } u.add_msg_if_player( m_warning, _( "You want some caffeine." ) ); u.add_morale( MORALE_CRAVING_CAFFEINE, -5, -30 ); if( u.stim > -10 * in && rng( 0, 10 ) < in ) { u.stim--; } if( rng( 8, 400 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need it bad!" ) ); u.add_effect( effect_shakes, 20 ); } break; case ADD_ALCOHOL: case ADD_DIAZEPAM: { static const std::string alc_1 = _( "You could use a drink. " ); static const std::string alc_2 = _( "Your hands start shaking... you need a drink bad!" ) ; static const std::string dia_1 = _( "You could use some diazepam." ); static const std::string dia_2 = _( "You're shaking... you need some diazepam!" ); const std::string &msg_1 = add.type == ADD_ALCOHOL ? alc_1 : dia_1; const std::string &msg_2 = add.type == ADD_ALCOHOL ? alc_2 : dia_2; const auto morale_type = add.type == ADD_ALCOHOL ? MORALE_CRAVING_ALCOHOL : MORALE_CRAVING_DIAZEPAM; u.mod_per_bonus( -1 ); u.mod_int_bonus( -1 ); if( x_in_y( in, HOURS( 2 ) ) ) { u.mod_healthy_mod( -1, -in * 10 ); } if( one_in( 20 ) && rng( 0, 20 ) < in ) { u.add_msg_if_player( m_warning, msg_1.c_str() ); u.add_morale( morale_type, -35, -10 * in ); } else if( rng( 8, 300 ) < in ) { u.add_msg_if_player( m_bad, msg_2.c_str() ); u.add_morale( morale_type, -35, -10 * in ); u.add_effect( effect_shakes, 50 ); } else if( !u.has_effect( effect_hallu ) && rng( 10, 1600 ) < in ) { u.add_effect( effect_hallu, 3600 ); } break; } case ADD_SLEEP: // No effects here--just in player::can_sleep() // EXCEPT! Prolong this addiction longer than usual. if( one_in( 2 ) && add.sated < 0 ) { add.sated++; } break; case ADD_PKILLER: if( calendar::once_every( 100 - in * 4 ) && u.get_painkiller() > 20 - in ) { u.mod_painkiller( -1 ); // Tolerance increases! } if( u.get_painkiller() >= 35 ) { // No further effects if we're doped up. add.sated = 0; break; } u.mod_str_bonus( -1 ); u.mod_per_bonus( -1 ); u.mod_dex_bonus( -1 ); if( u.get_pain() < in * 2 ) { u.mod_pain( 1 ); } if( one_in( 1200 - 30 * in ) ) { u.mod_healthy_mod( -1, -in * 30 ); } if( one_in( 20 ) && dice( 2, 20 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need some painkillers." ) ); u.add_morale( MORALE_CRAVING_OPIATE, -40, -10 * in ); u.add_effect( effect_shakes, 20 + in * 5 ); } else if( one_in( 20 ) && dice( 2, 30 ) < in ) { u.add_msg_if_player( m_bad, _( "You feel anxious. You need your painkillers!" ) ); u.add_morale( MORALE_CRAVING_OPIATE, -30, -10 * in ); } else if( one_in( 50 ) && dice( 3, 50 ) < in ) { u.vomit(); } break; case ADD_SPEED: { u.mod_int_bonus( -1 ); u.mod_str_bonus( -1 ); if( u.stim > -100 && x_in_y( in, 20 ) ) { u.stim--; } if( rng( 0, 150 ) <= in ) { u.mod_healthy_mod( -1, -in ); } if( dice( 2, 100 ) < in ) { u.add_msg_if_player( m_warning, _( "You feel depressed. Speed would help." ) ); u.add_morale( MORALE_CRAVING_SPEED, -25, -20 * in ); } else if( one_in( 10 ) && dice( 2, 80 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need a pick-me-up." ) ); u.add_morale( MORALE_CRAVING_SPEED, -25, -20 * in ); u.add_effect( effect_shakes, in * 20 ); } else if( one_in( 50 ) && dice( 2, 100 ) < in ) { u.add_msg_if_player( m_bad, _( "You stop suddenly, feeling bewildered." ) ); u.moves -= 300; } else if( !u.has_effect( effect_hallu ) && one_in( 20 ) && 8 + dice( 2, 80 ) < in ) { u.add_effect( effect_hallu, 3600 ); } } break; case ADD_COKE: case ADD_CRACK: { static const std::string coke_msg = _( "You feel like you need a bump." ); static const std::string crack_msg = _( "You're shivering, you need some crack." ); const std::string &cur_msg = add.type == ADD_COKE ? coke_msg : crack_msg; const auto morale_type = add.type == ADD_COKE ? MORALE_CRAVING_COCAINE : MORALE_CRAVING_CRACK; u.mod_int_bonus( -1 ); u.mod_per_bonus( -1 ); if( one_in( 900 - 30 * in ) ) { u.add_msg_if_player( m_warning, cur_msg.c_str() ); u.add_morale( morale_type, -20, -15 * in ); } if( dice( 2, 80 ) <= in ) { u.add_msg_if_player( m_warning, cur_msg.c_str() ); u.add_morale( morale_type, -20, -15 * in ); if( u.stim > -150 ) { u.stim -= 3; } } break; } case ADD_MUTAGEN: if( u.has_trait( "MUT_JUNKIE" ) ) { if( one_in( 600 - 50 * in ) ) { u.add_msg_if_player( m_warning, rng( 0, 6 ) < in ? _( "You so miss the exquisite rainbow of post-humanity." ) : _( "Your body is SOO booorrrring. Just a little sip to liven things up?" ) ); u.add_morale( MORALE_CRAVING_MUTAGEN, -20, -200 ); } if( u.focus_pool > 40 && one_in( 800 - 20 * in ) ) { u.focus_pool -= ( in ); u.add_msg_if_player( m_warning, _( "You daydream what it'd be like if you were *different*. Different is good." ) ); } } else if( in > 5 || one_in( ( 500 - 15 * in ) ) ) { u.add_msg_if_player( m_warning, rng( 0, 6 ) < in ? _( "You haven't had any mutagen lately." ) : _( "You could use some new parts..." ) ); u.add_morale( MORALE_CRAVING_MUTAGEN, -5, -50 ); } break; case ADD_MARLOSS_R: marloss_add( u, in, _( "You daydream about luscious pink berries as big as your fist." ) ); break; case ADD_MARLOSS_B: marloss_add( u, in, _( "You daydream about nutty cyan seeds as big as your hand." ) ); break; case ADD_MARLOSS_Y: marloss_add( u, in, _( "You daydream about succulent, pale golden gel, sweet but light." ) ); break; case ADD_NULL: break; } }
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::fire(player &p, int tarx, int tary, std::vector<point> &trajectory, bool burst) { item ammotmp; item* gunmod = p.weapon.active_gunmod(); it_ammo *curammo = NULL; item *weapon = NULL; if (p.weapon.has_flag(IF_CHARGE)) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = p.weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); tmpammo->range = 5 + charges * 5; if (charges <= 4) tmpammo->accuracy = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->accuracy = charges * (charges - 4); tmpammo->recoil = tmpammo->accuracy * .8; tmpammo->ammo_effects = 0; if (charges == 8) tmpammo->ammo_effects |= mfb(AMMO_EXPLOSIVE_BIG); else if (charges >= 6) tmpammo->ammo_effects |= mfb(AMMO_EXPLOSIVE); if (charges >= 5) tmpammo->ammo_effects |= mfb(AMMO_FLAME); else if (charges >= 4) tmpammo->ammo_effects |= mfb(AMMO_INCENDIARY); if (gunmod != NULL) { weapon = gunmod; } else { weapon = &p.weapon; } curammo = tmpammo; weapon->curammo = tmpammo; weapon->active = false; weapon->charges = 0; } else if (gunmod != NULL) { weapon = gunmod; curammo = weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = p.weapon.curammo; weapon = &p.weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!weapon->is_gun() && !weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", p.name.c_str(), weapon->tname().c_str()); return; } bool is_bolt = false; unsigned int effects = curammo->ammo_effects; // Bolts and arrows are silent if (curammo->type == AT_BOLT || curammo->type == AT_ARROW) is_bolt = true; int x = p.posx, y = p.posy; // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); if (p.has_trait(PF_TRIGGERHAPPY) && one_in(30)) burst = true; if (burst && weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto bool u_see_shooter = u_see(p.posx, p.posy); // Use different amounts of time depending on the type of gun and our skill p.moves -= time_to_fire(p, firing); // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = weapon->burst_size(); if (num_shots > weapon->num_charges() && !weapon->has_flag(IF_CHARGE)) num_shots = weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); // Set up a timespec for use in the nanosleep function below timespec ts; ts.tv_sec = 0; ts.tv_nsec = BULLET_SPEED; // Use up some ammunition int trange = trig_dist(p.posx, p.posy, tarx, tary); if (trange < int(firing->volume / 3) && firing->ammo != AT_SHOT) trange = int(firing->volume / 3); else if (p.has_bionic("bio_targeting")) { if (trange > LONG_RANGE) trange = int(trange * .65); else trange = int(trange * .8); } if (firing->skill_used == Skill::skill("rifle") && trange > LONG_RANGE) trange = LONG_RANGE + .6 * (trange - LONG_RANGE); std::string message = ""; bool missed = false; int tart; for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first if (curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0)) { std::vector<point> new_targets; int mondex; for (int radius = 1; radius <= 2 + p.skillLevel("gun") && new_targets.empty(); radius++) { for (int diff = 0 - radius; diff <= radius; diff++) { mondex = mon_at(tarx + diff, tary - radius); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary - radius) ); mondex = mon_at(tarx + diff, tary + radius); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary + radius) ); if (diff != 0 - radius && diff != radius) { // Corners were already checked mondex = mon_at(tarx - radius, tary + diff); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx - radius, tary + diff) ); mondex = mon_at(tarx + radius, tary + diff); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + radius, tary + diff) ); } } } if (!new_targets.empty()) { int target_picked = rng(0, new_targets.size() - 1); tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) trajectory = line_to(p.posx, p.posy, tarx, tary, tart); else trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } else if ((!p.has_trait(PF_TRIGGERHAPPY) || one_in(3)) && (p.skillLevel("gun") >= 7 || one_in(7 - p.skillLevel("gun")))) return; // No targets, so return } // Drop a shell casing if appropriate. itype_id casing_type = "null"; switch(curammo->type) { case AT_SHOT: casing_type = "shot_hull"; break; case AT_9MM: casing_type = "9mm_casing"; break; case AT_22: casing_type = "22_casing"; break; case AT_38: casing_type = "38_casing"; break; case AT_40: casing_type = "40_casing"; break; case AT_44: casing_type = "44_casing"; break; case AT_45: casing_type = "45_casing"; break; case AT_57: casing_type = "57mm_casing"; break; case AT_46: casing_type = "46mm_casing"; break; case AT_762: casing_type = "762_casing"; break; case AT_223: casing_type = "223_casing"; break; case AT_3006: casing_type = "3006_casing"; break; case AT_308: casing_type = "308_casing"; break; case AT_40MM: casing_type = "40mm_casing"; break; default: /*No casing for other ammo types.*/ break; } if (casing_type != "null") { int x = p.posx - 1 + rng(0, 2); int y = p.posy - 1 + rng(0, 2); std::vector<item>& items = m.i_at(x, y); int i; for (i = 0; i < items.size(); i++) if (items[i].typeId() == casing_type && items[i].charges < (dynamic_cast<it_ammo*>(items[i].type))->count) { items[i].charges++; break; } if (i == items.size()) { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; m.add_item(x, y, casing); } } // Use up a round (or 100) if (weapon->has_flag(IF_FIRE_100)) weapon->charges -= 100; else weapon->charges--; // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (one_in(2 << firing->durability)) { add_msg("Your weapon misfired!"); return; } make_gun_sound_effect(this, p, burst, weapon); int trange = calculate_range(p, tarx, tary); double missed_by = calculate_missed_by(p, trange, weapon); // Calculate a penalty based on the monster's speed double monster_speed_penalty = 1.; int target_index = mon_at(tarx, tary); if (target_index != -1) { monster_speed_penalty = double(z[target_index].speed) / 80.; if (monster_speed_penalty < 1.) monster_speed_penalty = 1.; } if (curshot > 0) { if (recoil_add(p) % 2 == 1) p.recoil++; p.recoil += recoil_add(p) / 2; } else p.recoil += recoil_add(p); if (missed_by >= 1.) { // We missed D: // Shoot a random nearby space? 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, x, y, -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 (!burst) { if (&p == &u) add_msg("You miss!"); else if (u_see_shooter) add_msg("%s misses!", p.name.c_str()); } } else if (missed_by >= .7 / monster_speed_penalty) { // Hit the space, but not necessarily the monster there missed = true; if (!burst) { if (&p == &u) add_msg("You barely miss!"); else if (u_see_shooter) add_msg("%s barely misses!", p.name.c_str()); } } int dam = weapon->gun_damage(); for (int i = 0; i < trajectory.size() && (dam > 0 || (effects & AMMO_FLAME)); i++) { if (i > 0) m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true); // Drawing the bullet uses player u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if (u_see(trajectory[i].x, trajectory[i].y)) { char bullet = '*'; if (effects & mfb(AMMO_FLAME)) bullet = '#'; mvwputch(w_terrain, trajectory[i].y + VIEWY - u.posy, trajectory[i].x + VIEWX - u.posx, c_red, bullet); wrefresh(w_terrain); if (&p == &u) nanosleep(&ts, NULL); } if (dam <= 0) { // Ran out of momentum. ammo_effects(this, trajectory[i].x, trajectory[i].y, effects); if (is_bolt && ((curammo->m1 == WOOD && !one_in(4)) || (curammo->m1 != WOOD && !one_in(15)))) m.add_item(trajectory[i].x, trajectory[i].y, ammotmp); if (weapon->num_charges() == 0) weapon->curammo = NULL; return; } int tx = trajectory[i].x, ty = trajectory[i].y; // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it int mondex = mon_at(tx, ty); // If we shot us a monster... if (mondex != -1 && (!z[mondex].has_flag(MF_DIGS) || rl_dist(p.posx, p.posy, z[mondex].posx, z[mondex].posy) <= 1) && ((!missed && i == trajectory.size() - 1) || one_in((5 - int(z[mondex].type->size))))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; // Penalize for the monster's speed if (z[mondex].speed > 80) goodhit *= double( double(z[mondex].speed) / 80.); std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam, &z[mondex]); shoot_monster(this, p, z[mondex], dam, goodhit, weapon); } else if ((!missed || one_in(3)) && (npc_at(tx, ty) != -1 || (u.posx == tx && u.posy == ty))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; player *h; if (u.posx == tx && u.posy == ty) h = &u; else h = active_npc[npc_at(tx, ty)]; std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam); shoot_player(this, p, h, dam, goodhit); } else m.shoot(this, tx, ty, dam, i == trajectory.size() - 1, effects); } // Done with the trajectory! int lastx = trajectory[trajectory.size() - 1].x; int lasty = trajectory[trajectory.size() - 1].y; ammo_effects(this, lastx, lasty, effects); if (m.move_cost(lastx, lasty) == 0) { lastx = trajectory[trajectory.size() - 2].x; lasty = trajectory[trajectory.size() - 2].y; } if (is_bolt && ((curammo->m1 == WOOD && !one_in(5)) || (curammo->m1 != WOOD && !one_in(15)) )) m.add_item(lastx, lasty, ammotmp); } if (weapon->num_charges() == 0) weapon->curammo = NULL; }
virtual void select(int entnum, uimenu *menu) { if ( ! started ) { started = true; padding = std::string(menu->pad_right - 1, ' '); for (std::map<std::string, trait>::iterator iter = traits.begin(); iter != traits.end(); ++iter) { vTraits.push_back(iter->first); pTraits[iter->first] = ( p->has_trait( iter->first ) ); } } int startx = menu->w_width - menu->pad_right; for ( int i = 1; i < lastlen; i++ ) { mvwprintw(menu->window, i, startx, "%s", padding.c_str() ); } mvwprintw(menu->window, 1, startx, mutation_data[vTraits[ entnum ]].valid ? _("Valid") : _("Nonvalid")); int line2 = 2; if ( !mutation_data[vTraits[entnum]].prereqs.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Prereqs:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].prereqs.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].prereqs[j]; mvwprintz(menu->window, line2, startx + 11, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].prereqs2.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Prereqs, 2d:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].prereqs2.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].prereqs2[j]; mvwprintz(menu->window, line2, startx + 15, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].threshreq.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Thresholds required:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].threshreq.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].threshreq[j]; mvwprintz(menu->window, line2, startx + 21, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].cancels.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Cancels:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].cancels.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].cancels[j]; mvwprintz(menu->window, line2, startx + 11, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].replacements.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Becomes:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].replacements.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].replacements[j]; mvwprintz(menu->window, line2, startx + 11, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].additions.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Add-ons:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].additions.size(); j++) { std::string mstr = mutation_data[vTraits[ entnum ]].additions[j]; mvwprintz(menu->window, line2, startx + 11, mcolor(mstr), "%s", traits[ mstr ].name.c_str()); line2++; } } if ( !mutation_data[vTraits[entnum]].category.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Category:")); for (int j = 0; j < mutation_data[vTraits[ entnum ]].category.size(); j++) { mvwprintw(menu->window, line2, startx + 11, "%s", mutation_data[vTraits[ entnum ]].category[j].c_str()); line2++; } } line2 += 2; mvwprintz(menu->window, line2, startx, c_ltgray, "pts: %d vis: %d ugly: %d", traits[vTraits[entnum]].points, traits[vTraits[entnum]].visibility, traits[vTraits[entnum]].ugliness ); line2 += 2; std::vector<std::string> desc = foldstring( traits[vTraits[ entnum ]].description, menu->pad_right - 1 ); for( size_t j = 0; j < desc.size(); ++j ) { mvwprintz(menu->window, line2, startx, c_ltgray, "%s", desc[j].c_str() ); line2++; } lastlen = line2 + 1; mvwprintz(menu->window, menu->w_height - 3, startx, c_green, "%s", msg.c_str()); msg = padding; mvwprintw(menu->window, menu->w_height - 2, startx, _("[/] find, [q]uit")); };
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 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 select( int entnum, uimenu *menu ) override { if( ! started ) { started = true; padding = std::string( menu->pad_right - 1, ' ' ); for( auto &traits_iter : mutation_branch::get_all() ) { vTraits.push_back( traits_iter.first ); pTraits[traits_iter.first] = ( p->has_trait( traits_iter.first ) ); } } const mutation_branch &mdata = vTraits[entnum].obj(); int startx = menu->w_width - menu->pad_right; for( int i = 2; i < lastlen; i++ ) { mvwprintw( menu->window, i, startx, padding ); } mvwprintw( menu->window, 3, startx, mdata.valid ? _( "Valid" ) : _( "Nonvalid" ) ); int line2 = 4; if( !mdata.prereqs.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Prereqs:" ) ); for( auto &j : mdata.prereqs ) { mvwprintz( menu->window, line2, startx + 11, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.prereqs2.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Prereqs, 2d:" ) ); for( auto &j : mdata.prereqs2 ) { mvwprintz( menu->window, line2, startx + 15, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.threshreq.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Thresholds required:" ) ); for( auto &j : mdata.threshreq ) { mvwprintz( menu->window, line2, startx + 21, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.cancels.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Cancels:" ) ); for( auto &j : mdata.cancels ) { mvwprintz( menu->window, line2, startx + 11, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.replacements.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Becomes:" ) ); for( auto &j : mdata.replacements ) { mvwprintz( menu->window, line2, startx + 11, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.additions.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Add-ons:" ) ); for( auto &j : mdata.additions ) { mvwprintz( menu->window, line2, startx + 11, mcolor( j ), mutation_branch::get_name( j ) ); line2++; } } if( !mdata.types.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Type:" ) ); for( auto &j : mdata.types ) { mvwprintw( menu->window, line2, startx + 11, j ); line2++; } } if( !mdata.category.empty() ) { line2++; mvwprintz( menu->window, line2, startx, c_light_gray, _( "Category:" ) ); for( auto &j : mdata.category ) { mvwprintw( menu->window, line2, startx + 11, j ); line2++; } } line2 += 2; mvwprintz( menu->window, line2, startx, c_light_gray, "pts: %d vis: %d ugly: %d", mdata.points, mdata.visibility, mdata.ugliness ); line2 += 2; std::vector<std::string> desc = foldstring( mdata.description, menu->pad_right - 1 ); for( auto &elem : desc ) { mvwprintz( menu->window, line2, startx, c_light_gray, elem ); line2++; } lastlen = line2 + 1; mvwprintz( menu->window, menu->w_height - 3, startx, c_green, msg ); msg = padding; input_context ctxt( "UIMENU" ); mvwprintw( menu->window, menu->w_height - 2, startx, _( "[%s] find, [%s] quit, [t] toggle base trait" ), ctxt.get_desc( "FILTER" ).c_str(), ctxt.get_desc( "QUIT" ).c_str() ); };
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 addict_effect(player &u, addiction &add, std::function<void (char const*)> const &cancel_activity) { int const in = add.intensity; switch (add.type) { case ADD_CIG: if (in > 20 || one_in((500 - 20 * in))) { u.add_msg_if_player(rng(0, 6) < in ? _("You need some nicotine.") : _("You could use some nicotine.")); u.add_morale(MORALE_CRAVING_NICOTINE, -15, -50); if (one_in(800 - 50 * in)) { u.fatigue++; } if (u.stim > -50 && one_in(400 - 20 * in)) { u.stim--; } } break; case ADD_CAFFEINE: u.moves -= 2; if (in > 20 || one_in((500 - 20 * in))) { u.add_msg_if_player(m_warning, _("You want some caffeine.")); u.add_morale(MORALE_CRAVING_CAFFEINE, -5, -30); if (u.stim > -150 && rng(0, 10) < in) { u.stim--; } if (rng(8, 400) < in) { u.add_msg_if_player(m_bad, _("Your hands start shaking... you need it bad!")); u.add_effect("shakes", 20); } } break; case ADD_ALCOHOL: u.mod_per_bonus(-1); u.mod_int_bonus(-1); if (rng(40, 1200) <= in * 10) { u.mod_healthy_mod(-1, -in * 10); } if (one_in(20) && rng(0, 20) < in) { u.add_msg_if_player(m_warning, _("You could use a drink.")); u.add_morale(MORALE_CRAVING_ALCOHOL, -35, -120); } else if (rng(8, 300) < in) { u.add_msg_if_player(m_bad, _("Your hands start shaking... you need a drink bad!")); u.add_morale(MORALE_CRAVING_ALCOHOL, -35, -120); u.add_effect("shakes", 50); } else if (!u.has_effect("hallu") && rng(10, 1600) < in) { u.add_effect("hallu", 3600); } break; case ADD_SLEEP: // No effects here--just in player::can_sleep() // EXCEPT! Prolong this addiction longer than usual. if (one_in(2) && add.sated < 0) { add.sated++; } break; case ADD_PKILLER: if ((in >= 25 || int(calendar::turn) % (100 - in * 4) == 0) && u.pkill > 0) { u.pkill--; // Tolerance increases! } if (u.pkill >= 35) { // No further effects if we're doped up. add.sated = 0; } else { u.mod_str_bonus(-(1 + int(in / 7))); u.mod_per_bonus(-1); u.mod_dex_bonus(-1); if (u.pain < in * 3) { u.mod_pain(1); } if (in >= 40 || one_in(1200 - 30 * in)) { u.mod_healthy_mod(-1, -in * 30); } if (one_in(20) && dice(2, 20) < in) { u.add_msg_if_player(m_bad, _("Your hands start shaking... you need some painkillers.")); u.add_morale(MORALE_CRAVING_OPIATE, -40, -200); u.add_effect("shakes", 20 + in * 5); } else if (one_in(20) && dice(2, 30) < in) { u.add_msg_if_player(m_bad, _("You feel anxious. You need your painkillers!")); u.add_morale(MORALE_CRAVING_OPIATE, -30, -200); } else if (one_in(50) && dice(3, 50) < in) { u.add_msg_if_player(m_bad, _("You throw up heavily!")); cancel_activity(_("Throwing up.")); u.vomit(); } } break; case ADD_SPEED: { // Minimal speed of PC is 0.25 * base speed, that is // usually 25 moves, this ensures that even at minimal speed // the PC gets 5 moves per turn. int move_pen = std::min(in * 5, 20); u.moves -= move_pen; u.mod_int_bonus(-1); u.mod_str_bonus(-1); if (u.stim > -100 && (in >= 20 || int(calendar::turn) % (100 - in * 5) == 0)) { u.stim--; } if (rng(0, 150) <= in) { u.mod_healthy_mod(-1, -in); } if (dice(2, 100) < in) { u.add_msg_if_player(m_warning, _("You feel depressed. Speed would help.")); u.add_morale(MORALE_CRAVING_SPEED, -25, -200); } else if (one_in(10) && dice(2, 80) < in) { u.add_msg_if_player(m_bad, _("Your hands start shaking... you need a pick-me-up.")); u.add_morale(MORALE_CRAVING_SPEED, -25, -200); u.add_effect("shakes", in * 20); } else if (one_in(50) && dice(2, 100) < in) { u.add_msg_if_player(m_bad, _("You stop suddenly, feeling bewildered.")); cancel_activity(nullptr); u.moves -= 300; } else if (!u.has_effect("hallu") && one_in(20) && 8 + dice(2, 80) < in) { u.add_effect("hallu", 3600); } } break; case ADD_COKE: u.mod_int_bonus(-1); u.mod_per_bonus(-1); if (in >= 30 || one_in((900 - 30 * in))) { u.add_msg_if_player(m_warning, _("You feel like you need a bump.")); u.add_morale(MORALE_CRAVING_COCAINE, -20, -250); } if (dice(2, 80) <= in) { u.add_msg_if_player(m_warning, _("You feel like you need a bump.")); u.add_morale(MORALE_CRAVING_COCAINE, -20, -250); if (u.stim > -150) { u.stim -= 3; } } break; case ADD_CRACK: u.mod_int_bonus(-1); u.mod_per_bonus(-1); if (in >= 30 || one_in((900 - 30 * in))) { u.add_msg_if_player(m_bad, _("You're shivering, you need some crack.")); u.add_morale(MORALE_CRAVING_CRACK, -80, -250); } if (dice(2, 80) <= in) { u.add_msg_if_player(m_bad, _("You're shivering, you need some crack.")); u.add_morale(MORALE_CRAVING_CRACK, -80, -250); if (u.stim > -150) { u.stim -= 3; } } break; case ADD_MUTAGEN: if (u.has_trait("MUT_JUNKIE")) { if (one_in(600 - 50 * in)) { u.add_msg_if_player(m_warning, rng(0, 6) < in ? _("You so miss the exquisite rainbow of post-humanity.") : _("Your body is SOO booorrrring. Just a little sip to liven things up?")); u.add_morale(MORALE_CRAVING_MUTAGEN, -20, -200); } if (u.focus_pool > 40 && one_in(800 - 20 * in)) { u.focus_pool -= (in); u.add_msg_if_player(m_warning, _("You daydream what it'd be like if you were *different*. Different is good.")); } } else if (in > 5 || one_in((500 - 15 * in))) { u.add_msg_if_player(m_warning, rng(0, 6) < in ? _("You haven't had any mutagen lately.") : _("You could use some new parts...")); u.add_morale(MORALE_CRAVING_MUTAGEN, -5, -50); } break; case ADD_DIAZEPAM: u.mod_per_bonus(-1); u.mod_int_bonus(-1); if (rng(40, 1200) <= in * 10) { u.mod_healthy_mod(-1, -in * 10); } if (one_in(20) && rng(0, 20) < in) { u.add_msg_if_player(m_warning, _("You could use some diazepam.")); u.add_morale(MORALE_CRAVING_DIAZEPAM, -35, -120); } else if (rng(8, 200) < in) { u.add_msg_if_player(m_bad, _("You're shaking... you need some diazepam!")); u.add_morale(MORALE_CRAVING_DIAZEPAM, -35, -120); u.add_effect("shakes", 50); } else if (!u.has_effect("hallu") && rng(10, 3200) < in) { u.add_effect("hallu", 3600); } else if (one_in(50) && dice(3, 50) < in) { u.add_msg_if_player(m_bad, _("You throw up heavily!")); cancel_activity(_("Throwing up.")); u.vomit(); } break; case ADD_MARLOSS_R: if (one_in(800 - 20 * in)) { u.add_morale(MORALE_CRAVING_MARLOSS, -5, -25); u.add_msg_if_player(m_info, _("You daydream about luscious pink berries as big as your fist.")); if (u.focus_pool > 40) { u.focus_pool -= (in); } } break; case ADD_MARLOSS_B: if (one_in(800 - 20 * in)) { u.add_morale(MORALE_CRAVING_MARLOSS, -5, -25); u.add_msg_if_player(m_info, _("You daydream about nutty cyan seeds as big as your hand.")); if (u.focus_pool > 40) { u.focus_pool -= (in); } } break; case ADD_MARLOSS_Y: if (one_in(800 - 20 * in)) { u.add_morale(MORALE_CRAVING_MARLOSS, -5, -25); u.add_msg_if_player(m_info, _("You daydream about succulent, pale golden gel, sweet but light.")); if (u.focus_pool > 40) { u.focus_pool -= (in); } } break; //for any other unhandled cases default: break; } }
virtual void select(int entnum, uimenu *menu) override { if ( ! started ) { started = true; padding = std::string(menu->pad_right - 1, ' '); for( auto &traits_iter : mutation_branch::get_all() ) { vTraits.push_back( traits_iter.first ); pTraits[traits_iter.first] = ( p->has_trait( traits_iter.first ) ); } } auto &mdata = mutation_branch::get( vTraits[entnum] ); int startx = menu->w_width - menu->pad_right; for ( int i = 2; i < lastlen; i++ ) { mvwprintw(menu->window, i, startx, "%s", padding.c_str() ); } mvwprintw(menu->window, 3, startx, mdata.valid ? _("Valid") : _("Nonvalid")); int line2 = 4; if ( !mdata.prereqs.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Prereqs:")); for (auto &j : mdata.prereqs) { mvwprintz(menu->window, line2, startx + 11, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.prereqs2.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Prereqs, 2d:")); for (auto &j : mdata.prereqs2) { mvwprintz(menu->window, line2, startx + 15, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.threshreq.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Thresholds required:")); for (auto &j : mdata.threshreq) { mvwprintz(menu->window, line2, startx + 21, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.cancels.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Cancels:")); for (auto &j : mdata.cancels) { mvwprintz(menu->window, line2, startx + 11, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.replacements.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Becomes:")); for (auto &j : mdata.replacements) { mvwprintz(menu->window, line2, startx + 11, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.additions.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Add-ons:")); for (auto &j : mdata.additions) { mvwprintz(menu->window, line2, startx + 11, mcolor(j), "%s", mutation_branch::get_name( j ).c_str()); line2++; } } if ( !mdata.category.empty() ) { line2++; mvwprintz(menu->window, line2, startx, c_ltgray, _("Category:")); for (auto &j : mdata.category) { mvwprintw(menu->window, line2, startx + 11, "%s", j.c_str()); line2++; } } line2 += 2; mvwprintz(menu->window, line2, startx, c_ltgray, "pts: %d vis: %d ugly: %d", mdata.points, mdata.visibility, mdata.ugliness ); line2 += 2; std::vector<std::string> desc = foldstring( mdata.description, menu->pad_right - 1 ); for( auto &elem : desc ) { mvwprintz( menu->window, line2, startx, c_ltgray, "%s", elem.c_str() ); line2++; } lastlen = line2 + 1; mvwprintz(menu->window, menu->w_height - 3, startx, c_green, "%s", msg.c_str()); msg = padding; mvwprintw(menu->window, menu->w_height - 2, startx, _("[/] find, [q]uit")); };
void game::fire(player &p, int tarx, int tary, std::vector<point> &trajectory, bool burst) { item ammotmp; item* gunmod = p.weapon.active_gunmod(); it_ammo *curammo = NULL; item *weapon = NULL; if (p.weapon.has_flag("CHARGE")) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = p.weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); tmpammo->range = 5 + charges * 5; if (charges <= 4) tmpammo->dispersion = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->dispersion = charges * (charges - 4); tmpammo->recoil = tmpammo->dispersion * .8; if (charges == 8) { tmpammo->ammo_effects.insert("EXPLOSIVE_BIG"); } else if (charges >= 6) { tmpammo->ammo_effects.insert("EXPLOSIVE"); } if (charges >= 5){ tmpammo->ammo_effects.insert("FLAME"); } else if (charges >= 4) { tmpammo->ammo_effects.insert("INCENDIARY"); } if (gunmod != NULL) { weapon = gunmod; } else { weapon = &p.weapon; } curammo = tmpammo; weapon->curammo = tmpammo; weapon->active = false; weapon->charges = 0; } else if (gunmod != NULL) { weapon = gunmod; curammo = weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = p.weapon.curammo; weapon = &p.weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!weapon->is_gun() && !weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", p.name.c_str(), weapon->tname().c_str()); return; } bool is_bolt = false; std::set<std::string> *effects = &curammo->ammo_effects; // Bolts and arrows are silent if (curammo->type == "bolt" || curammo->type == "arrow") is_bolt = true; int x = p.posx, y = p.posy; // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); if (p.has_trait(PF_TRIGGERHAPPY) && one_in(30)) burst = true; if (burst && weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto // Use different amounts of time depending on the type of gun and our skill if (!effects->count("BOUNCE")) { p.moves -= time_to_fire(p, firing); } // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = weapon->burst_size(); if (num_shots > weapon->num_charges() && !weapon->has_flag("CHARGE")) num_shots = weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); // Set up a timespec for use in the nanosleep function below timespec ts; ts.tv_sec = 0; ts.tv_nsec = BULLET_SPEED; // Use up some ammunition int trange = rl_dist(p.posx, p.posy, tarx, tary); if (trange < int(firing->volume / 3) && firing->ammo != "shot") trange = int(firing->volume / 3); else if (p.has_bionic("bio_targeting")) { if (trange > LONG_RANGE) trange = int(trange * .65); else trange = int(trange * .8); } if (firing->skill_used == Skill::skill("rifle") && trange > LONG_RANGE) trange = LONG_RANGE + .6 * (trange - LONG_RANGE); std::string message = ""; bool missed = false; int tart; const bool debug_retarget = false; // this will inevitably be needed const bool wildly_spraying = false; // stub for now. later, rng based on stress/skill/etc at the start, int weaponrange = p.weapon.range(); // this is expensive, let's cache. todo: figure out if we need p.weapon.range(&p); for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first if ( curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0) ) { std::vector<point> new_targets; new_targets.clear(); if ( debug_retarget == true ) { mvprintz(curshot,5,c_red,"[%d] %s: retarget: mon_at(%d,%d)",curshot,p.name.c_str(),tarx,tary); if(mon_at(tarx, tary) == -1) { printz(c_red, " = -1"); } else { printz(c_red, ".hp=%d", z[mon_at(tarx, tary)].hp ); } } for ( int radius = 0; /* range from last target, not shooter! */ radius <= 2 + p.skillLevel("gun") && /* more skill: wider burst area? */ radius <= weaponrange && /* this seems redundant */ ( new_targets.empty() || /* got target? stop looking. However this breaks random selection, aka, wildly spraying, so: */ wildly_spraying == true ); /* lets set this based on rng && stress or whatever elsewhere */ radius++ ) { /* iterate from last target's position: makes sense for burst fire.*/ for (std::vector<monster>::iterator it = z.begin(); it != z.end(); it++) { int nt_range_to_me = rl_dist(p.posx, p.posy, it->posx, it->posy); int dummy; if (nt_range_to_me == 0 || nt_range_to_me > weaponrange || !pl_sees(&p, &(*it), dummy)) { /* reject out of range and unseen targets as well as MY FACE */ continue; } int nt_range_to_lt = rl_dist(tarx,tary,it->posx,it->posy); /* debug*/ if ( debug_retarget && nt_range_to_lt <= 5 ) printz(c_red, " r:%d/l:%d/m:%d ..", radius, nt_range_to_lt, nt_range_to_me ); if (nt_range_to_lt != radius) { continue; /* we're spiralling outward, catch you next iteration (maybe) */ } if (it->hp >0 && it->friendly == 0) { new_targets.push_back(point(it->posx, it->posy)); /* oh you're not dead and I don't like you. Hello! */ } } } if ( new_targets.empty() == false ) { /* new victim! or last victim moved */ int target_picked = rng(0, new_targets.size() - 1); /* 1 victim list unless wildly spraying */ tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) { trajectory = line_to(p.posx, p.posy, tarx, tary, tart); } else { trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } /* debug */ if (debug_retarget) printz(c_ltgreen, " NEW:(%d:%d,%d) %d,%d (%s)[%d] hp: %d", target_picked, new_targets[target_picked].x, new_targets[target_picked].y, tarx, tary, z[mon_at(tarx, tary)].name().c_str(), mon_at(tarx, tary), z[mon_at(tarx, tary)].hp); } else if ( ( !p.has_trait(PF_TRIGGERHAPPY) || /* double tap. TRIPLE TAP! wait, no... */ one_in(3) /* on second though...everyone double-taps at times. */ ) && ( p.skillLevel("gun") >= 7 || /* unless trained */ one_in(7 - p.skillLevel("gun")) /* ...sometimes */ ) ) { return; // No targets, so return } else if (debug_retarget) { printz(c_red, " new targets.empty()!"); } } else if (debug_retarget) { mvprintz(curshot,5,c_red,"[%d] %s: target == mon_at(%d,%d)[%d] %s hp %d",curshot, p.name.c_str(), tarx ,tary, mon_at(tarx, tary), z[mon_at(tarx, tary)].name().c_str(), z[mon_at(tarx, tary)].hp); } // Drop a shell casing if appropriate. itype_id casing_type = "null"; if( curammo->type == "shot" ) casing_type = "shot_hull"; else if( curammo->type == "9mm" ) casing_type = "9mm_casing"; else if( curammo->type == "22" ) casing_type = "22_casing"; else if( curammo->type == "38" ) casing_type = "38_casing"; else if( curammo->type == "40" ) casing_type = "40_casing"; else if( curammo->type == "44" ) casing_type = "44_casing"; else if( curammo->type == "45" ) casing_type = "45_casing"; else if( curammo->type == "454" ) casing_type = "454_casing"; else if( curammo->type == "500" ) casing_type = "500_casing"; else if( curammo->type == "57" ) casing_type = "57mm_casing"; else if( curammo->type == "46" ) casing_type = "46mm_casing"; else if( curammo->type == "762" ) casing_type = "762_casing"; else if( curammo->type == "223" ) casing_type = "223_casing"; else if( curammo->type == "3006" ) casing_type = "3006_casing"; else if( curammo->type == "308" ) casing_type = "308_casing"; else if( curammo->type == "40mm" ) casing_type = "40mm_casing"; if (casing_type != "null") { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; if( weapon->has_gunmod("brass_catcher") != -1 ) { p.i_add( casing ); } else { int x = p.posx - 1 + rng(0, 2); int y = p.posy - 1 + rng(0, 2); m.add_item_or_charges(x, y, casing); } } // Use up a round (or 100) if (weapon->has_flag("FIRE_100")) weapon->charges -= 100; else weapon->charges--; if (firing->skill_used != Skill::skill("archery") && firing->skill_used != Skill::skill("throw")) { // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (one_in(2 << firing->durability)) { add_msg_player_or_npc( &p, _("Your weapon misfires!"), _("<npcname>'s weapon misfires!") ); return; } } make_gun_sound_effect(this, p, burst, weapon); int trange = calculate_range(p, tarx, tary); double missed_by = calculate_missed_by(p, trange, weapon); // Calculate a penalty based on the monster's speed double monster_speed_penalty = 1.; int target_index = mon_at(tarx, tary); if (target_index != -1) { monster_speed_penalty = double(z[target_index].speed) / 80.; if (monster_speed_penalty < 1.) monster_speed_penalty = 1.; } if (curshot > 0) { if (recoil_add(p) % 2 == 1) p.recoil++; p.recoil += recoil_add(p) / 2; } else p.recoil += recoil_add(p); if (missed_by >= 1.) { // We missed D: // Shoot a random nearby space? int mtarx = tarx + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); int mtary = tary + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, x, y, -1, tart)) trajectory = line_to(p.posx, p.posy, mtarx, mtary, tart); else trajectory = line_to(p.posx, p.posy, mtarx, mtary, 0); missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You miss!"), _("<npcname> misses!") ); } } else if (missed_by >= .8 / monster_speed_penalty) { // Hit the space, but not necessarily the monster there missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You barely miss!"), _("<npcname> barely misses!") ); } } int dam = weapon->gun_damage(); int tx = trajectory[0].x; int ty = trajectory[0].y; int px = trajectory[0].x; int py = trajectory[0].y; for (int i = 0; i < trajectory.size() && (dam > 0 || (effects->count("FLAME"))); i++) { px = tx; py = ty; tx = trajectory[i].x; ty = trajectory[i].y; // Drawing the bullet uses player u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if (u_see(tx, ty)) { if (i > 0) { m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true, u.posx + u.view_offset_x, u.posy + u.view_offset_y); } char bullet = '*'; if (effects->count("FLAME")) bullet = '#'; mvwputch(w_terrain, ty + VIEWY - u.posy - u.view_offset_y, tx + VIEWX - u.posx - u.view_offset_x, c_red, bullet); wrefresh(w_terrain); if (&p == &u) nanosleep(&ts, NULL); } if (dam <= 0 && !(effects->count("FLAME"))) { // Ran out of momentum. ammo_effects(this, tx, ty, *effects); if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(4)) || (curammo->m1 != "wood" && !one_in(15)))) m.add_item_or_charges(tx, ty, ammotmp); if (weapon->num_charges() == 0) weapon->curammo = NULL; return; } // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it int mondex = mon_at(tx, ty); // If we shot us a monster... if (mondex != -1 && (!z[mondex].has_flag(MF_DIGS) || rl_dist(p.posx, p.posy, z[mondex].posx, z[mondex].posy) <= 1) && ((!missed && i == trajectory.size() - 1) || one_in((5 - int(z[mondex].type->size))))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; // Penalize for the monster's speed if (z[mondex].speed > 80) goodhit *= double( double(z[mondex].speed) / 80.); std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam, &z[mondex]); shoot_monster(this, p, z[mondex], dam, goodhit, weapon); } else if ((!missed || one_in(3)) && (npc_at(tx, ty) != -1 || (u.posx == tx && u.posy == ty))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; player *h; if (u.posx == tx && u.posy == ty) h = &u; else h = active_npc[npc_at(tx, ty)]; if (h->power_level >= 10 && h->uncanny_dodge()) { h->power_level -= 7; // dodging bullets costs extra } else { std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam); shoot_player(this, p, h, dam, goodhit); } } else m.shoot(this, tx, ty, dam, i == trajectory.size() - 1, *effects); } // Done with the trajectory! ammo_effects(this, tx, ty, *effects); if (effects->count("BOUNCE")) { for (int i = 0; i < z.size(); i++) { // search for monsters in radius 4 around impact site if (rl_dist(z[i].posx, z[i].posy, tx, ty) <= 4) { // don't hit targets that have already been hit if (!z[i].has_effect(ME_BOUNCED) && !z[i].dead) { add_msg(_("The attack bounced to %s!"), z[i].name().c_str()); trajectory = line_to(tx, ty, z[i].posx, z[i].posy, 0); if (weapon->charges > 0) fire(p, z[i].posx, z[i].posy, trajectory, false); break; } } } } if (m.move_cost(tx, ty) == 0) { tx = px; ty = py; } if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(5)) || (curammo->m1 != "wood" && !one_in(15)) )) m.add_item_or_charges(tx, ty, ammotmp); } if (weapon->num_charges() == 0) weapon->curammo = NULL; }