bool map::process_fields(game *g) { bool found_field = false; field *cur; field_id curtype; for (int x = 0; x < SEEX * 3; x++) { for (int y = 0; y < SEEY * 3; y++) { cur = &field_at(x, y); curtype = cur->type; if (!found_field && curtype != fd_null) found_field = true; if (cur->density > 3) debugmsg("Whoooooa density of %d", cur->density); if (cur->age == 0) // Don't process "newborn" fields curtype = fd_null; switch (curtype) { case fd_null: break; // Do nothing, obviously. OBVIOUSLY. case fd_blood: case fd_bile: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 250; break; case fd_acid: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 20; for (int i = 0; i < i_at(x, y).size(); i++) { item *melting = &(i_at(x, y)[i]); if (melting->made_of(LIQUID) || melting->made_of(VEGGY) || melting->made_of(FLESH) || melting->made_of(POWDER) || melting->made_of(COTTON) || melting->made_of(WOOL) || melting->made_of(PAPER) || melting->made_of(PLASTIC) || (melting->made_of(GLASS) && !one_in(3)) || one_in(4)) { // Acid destructable objects here melting->damage++; if (melting->damage >= 5 || (melting->made_of(PAPER) && melting->damage >= 3)) { cur->age += melting->volume(); for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } } break; case fd_fire: // Consume items as fuel to help us grow/last longer. bool destroyed; int vol; for (int i = 0; i < i_at(x, y).size(); i++) { destroyed = false; vol = i_at(x, y)[i].volume(); if (i_at(x, y)[i].is_ammo()) { cur->age /= 2; cur->age -= 300; destroyed = true; } else if (i_at(x, y)[i].made_of(PAPER)) { cur->age -= vol * 10; destroyed = true; } else if ((i_at(x, y)[i].made_of(WOOD) || i_at(x, y)[i].made_of(VEGGY)) && (vol <= cur->density*10-(cur->age>0 ? rng(0,cur->age/10) : 0) || cur->density == 3)) { cur->age -= vol * 10; destroyed = true; } else if ((i_at(x, y)[i].made_of(COTTON) || i_at(x, y)[i].made_of(FLESH)|| i_at(x, y)[i].made_of(WOOL)) && (vol <= cur->density*2 || (cur->density == 3 && one_in(vol)))) { cur->age -= vol * 5; destroyed = true; } else if (i_at(x, y)[i].made_of(LIQUID) || i_at(x, y)[i].made_of(POWDER)|| i_at(x, y)[i].made_of(PLASTIC)|| (cur->density >= 2 && i_at(x, y)[i].made_of(GLASS)) || (cur->density == 3 && i_at(x, y)[i].made_of(IRON))) { switch (i_at(x, y)[i].type->id) { // TODO: Make this be not a hack. case itm_whiskey: case itm_vodka: case itm_rum: case itm_tequila: cur->age -= 220; break; } destroyed = true; } if (destroyed) { for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } // Consume the terrain we're on if (terlist[ter(x, y)].flags & mfb(flammable) && one_in(8 - cur->density)) { cur->age -= cur->density * cur->density * 40; if (cur->density == 3) ter(x, y) = t_rubble; } else if (terlist[ter(x, y)].flags & mfb(explodes)) { ter(x, y) = ter_id(int(ter(x, y)) + 1); cur->age = 0; cur->density = 3; g->explosion(x, y, 40, 0, true); } else if (terlist[ter(x, y)].flags & mfb(swimmable)) cur->age += 800; // Flames die quickly on water // If we consumed a lot, the flames grow higher while (cur->density < 3 && cur->age < 0) { cur->age += 300; cur->density++; } // If the flames are REALLY big, they contribute to adjacent flames if (cur->density == 3 && cur->age < 0) { // If the flames are in a pit, it can't spread to non-pit bool in_pit = (ter(x, y) == t_pit); // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3 && cur->age < 0; i++) { for (int j = 0; j < 3 && cur->age < 0; j++) { if (field_at(x+((i+starti)%3), y+((j+startj)%3)).type == fd_fire && field_at(x+((i+starti)%3), y+((j+startj)%3)).density < 3 && (!in_pit || ter(x+((i+starti)%3), y+((j+startj)%3)) == t_pit)) { field_at(x+((i+starti)%3), y+((j+startj)%3)).density++; field_at(x+((i+starti)%3), y+((j+startj)%3)).age = 0; cur->age = 0; } } } } // Consume adjacent fuel / terrain to spread. for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (x+i >= 0 && y+j >= 0 && x+i < SEEX * 3 && y+j <= SEEY * 3) { if (has_flag(explodes, x + i, y + j) && one_in(8 - cur->density)) { ter(x + i, y + i) = ter_id(int(ter(x + i, y + i)) + 1); g->explosion(x+i, y+j, 40, 0, true); } else if ((i != 0 || j != 0) && (i_at(x+i, y+j).size() > 0 || rng(15, 120) < cur->density * 10)) { if (field_at(x+i, y+j).type == fd_smoke) field_at(x+i, y+j) = field(fd_fire, 1, 0); // Fire in pits can only spread to adjacent pits else if (ter(x, y) != t_pit || ter(x + i, y + j) == t_pit) add_field(g, x+i, y+j, fd_fire, 1); // If we're not spreading, maybe we'll stick out some smoke, huh? } else if (move_cost(x+i, y+j) > 0 && rng(7, 40) < cur->density * 10 && cur->age < 1000) { add_field(g, x+i, y+j, fd_smoke, rng(1, cur->density)); } } } } break; case fd_smoke: for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 50; if (one_in(2)) { std::vector <point> spread; for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if ((field_at(x+a, y+b).type == fd_smoke && field_at(x+a, y+b).density < 3) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; if (field_at(p.x, p.y).type == fd_smoke && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_smoke, 1)){ cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_tear_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 30; // One in three chance that it spreads (less than smoke!) if (one_in(3)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby teargas grows thicker if (field_at(p.x, p.y).type == fd_tear_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke is converted into teargas } else if (field_at(p.x, p.y).type == fd_smoke) { field_at(p.x, p.y).type = fd_tear_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_tear_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_nuke_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; // Increase long-term radiation in the land underneath radiation(x, y) += rng(0, cur->density); if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby nukegas grows thicker if (field_at(p.x, p.y).type == fd_nuke_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke & teargas is converted into nukegas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_nuke_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_nuke_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_electricity: if (!one_in(5)) { // 4 in 5 chance to spread std::vector<point> valid; if (move_cost(x, y) == 0 && cur->density > 1) { // We're grounded int tries = 0; while (tries < 10 && cur->age < 50) { int cx = x + rng(-1, 1), cy = y + rng(-1, 1); if (move_cost(cx, cy) != 0 && field_at(cx, cy).is_null()) { add_field(g, cx, cy, fd_electricity, 1); cur->density--; tries = 0; } else tries++; } } else { // We're not grounded; attempt to ground for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (move_cost(x + a, y + b) == 0 && // Grounded tiles first field_at(x + a, y + b).is_null()) valid.push_back(point(x + a, y + b)); } } if (valid.size() == 0) { // Spread to adjacent space, then int px = x + rng(-1, 1), py = y + rng(-1, 1); if (move_cost(px, py) > 0 && field_at(px, py).type == fd_electricity && field_at(px, py).density < 3) field_at(px, py).density++; else if (move_cost(px, py) > 0) add_field(g, px, py, fd_electricity, 1); cur->density--; } while (valid.size() > 0 && cur->density > 0) { int index = rng(0, valid.size() - 1); add_field(g, valid[index].x, valid[index].y, fd_electricity, 1); cur->density--; valid.erase(valid.begin() + index); } } } break; case fd_fatigue: if (cur->density < 3 && g->turn % 3600 == 0 && one_in(10)) cur->density++; else if (cur->density == 3 && one_in(3600)) { // Spawn nether creature! mon_id type = mon_id(rng(mon_flying_polyp, mon_blank)); monster creature(g->mtypes[type]); creature.spawn(x + rng(-3, 3), y + rng(-3, 3)); g->z.push_back(creature); } break; } if (fieldlist[cur->type].halflife > 0) { cur->age++; if (cur->age > 0 && dice(3, cur->age) > dice(3, fieldlist[cur->type].halflife)) { cur->age = 0; cur->density--; } if (cur->density <= 0) // Totally dissapated. field_at(x, y) = field(); } } } return found_field; }
void monster::plan( const mfactions &factions ) { // Bots are more intelligent than most living stuff bool electronic = has_flag( MF_ELECTRONIC ); Creature *target = nullptr; // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33 float dist = !electronic ? 1000 : 8.6f; bool fleeing = false; bool docile = has_flag( MF_VERMIN ) || ( friendly != 0 && has_effect( "docile" ) ); bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end(); int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0; int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0; bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale; bool swarms = has_flag( MF_SWARMS ); auto mood = attitude(); // If we can see the player, move toward them or flee. if( friendly == 0 && sees( g->u ) ) { dist = rate_target( g->u, dist, electronic ); fleeing = fleeing || is_fleeing( g->u ); target = &g->u; if( dist <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } else if( friendly != 0 && !docile ) { // Target unfriendly monsters, only if we aren't interacting with the player. for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) { monster &tmp = g->zombie( i ); if( tmp.friendly == 0 ) { float rating = rate_target( tmp, dist, electronic ); if( rating < dist ) { target = &tmp; dist = rating; } } } } if( docile ) { if( friendly != 0 && target != nullptr ) { set_dest( target->pos() ); } return; } for( size_t i = 0; i < g->active_npc.size(); i++ ) { npc *me = g->active_npc[i]; float rating = rate_target( *me, dist, electronic ); bool fleeing_from = is_fleeing( *me ); // Switch targets if closer and hostile or scarier than current target if( ( rating < dist && fleeing ) || ( rating < dist && attitude( me ) == MATT_ATTACK ) || ( !fleeing && fleeing_from ) ) { target = me; dist = rating; } fleeing = fleeing || fleeing_from; if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } fleeing = fleeing || ( mood == MATT_FLEE ); if( friendly == 0 ) { for( const auto &fac : factions ) { auto faction_att = faction.obj().attitude( fac.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } for( int i : fac.second ) { // mon indices monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, electronic ); if( rating < dist ) { target = &mon; dist = rating; } if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } } // Friendly monsters here // Avoid for hordes of same-faction stuff or it could get expensive const auto actual_faction = friendly == 0 ? faction : mfaction_str_id( "player" ); auto const &myfaction_iter = factions.find( actual_faction ); if( myfaction_iter == factions.end() ) { DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction " << actual_faction.id().str() << " which wasn't loaded in game::monmove"; swarms = false; group_morale = false; } swarms = swarms && target == nullptr; // Only swarm if we have no target if( group_morale || swarms ) { for( const int i : myfaction_iter->second ) { monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, electronic ); if( group_morale && rating <= 10 ) { morale += 10 - rating; } if( swarms ) { if( rating < 5 ) { // Too crowded here wander_pos.x = posx() * rng( 1, 3 ) - mon.posx(); wander_pos.y = posy() * rng( 1, 3 ) - mon.posy(); wandf = 2; target = nullptr; // Swarm to the furthest ally you can see } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) { target = &mon; dist = rating; } } } } if( target != nullptr ) { tripoint dest = target->pos(); auto att_to_target = attitude_to( *target ); if( att_to_target == Attitude::A_HOSTILE && !fleeing ) { set_dest( dest ); } else if( fleeing ) { set_dest( tripoint( posx() * 2 - dest.x, posy() * 2 - dest.y, posz() ) ); } if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) { int hp_per = target->hp_percentage(); if( hp_per <= 70 ) { anger += 10 - int( hp_per / 10 ); } } } else if( friendly > 0 && one_in( 3 ) ) { // Grow restless with no targets friendly--; } else if( friendly < 0 && sees( g->u ) ) { if( rl_dist( pos3(), g->u.pos3() ) > 2 ) { set_dest( g->u.pos3() ); } else { unset_dest(); } } }
bool monster::move_to( const tripoint &p, bool force, float slope ) { const bool digs = digging(); const bool flies = has_flag( MF_FLIES ); const bool on_ground = !digs && !flies; const bool climbs = has_flag( MF_CLIMBS ) && g->m.has_flag( TFLAG_NO_FLOOR, p ); // Allows climbing monsters to move on terrain with movecost <= 0 Creature *critter = g->critter_at( p, is_hallucination() ); if( g->m.has_flag( "CLIMBABLE", p ) ) { if( g->m.move_cost( p ) == 0 && critter == nullptr ) { if( flies ) { moves -= 100; force = true; if (g->u.sees( *this )){ add_msg(_("The %1$s flies over the %2$s."), name().c_str(), g->m.has_flag_furn("CLIMBABLE", p) ? g->m.furnname(p).c_str() : g->m.tername(p).c_str()); } } else if (has_flag(MF_CLIMBS)) { moves -= 150; force = true; if (g->u.sees( *this )){ add_msg(_("The %1$s climbs over the %2$s."), name().c_str(), g->m.has_flag_furn("CLIMBABLE", p) ? g->m.furnname(p).c_str() : g->m.tername(p).c_str()); } } } } if( critter != nullptr && !force ) { return false; } // Make sure that we can move there, unless force is true. if( !force && !can_move_to( p ) ) { return false; } if( !force ) { // This adjustment is to make it so that monster movement speed relative to the player // is consistent even if the monster stumbles, // and the same regardless of the distance measurement mode. const float stumble_multiplier = has_flag(MF_STUMBLES) ? (trigdist ? 0.83 : 1.0 - (0.25 * slope)) : 1.0; const int cost = stumble_multiplier * (float)(climbs ? calc_climb_cost( pos(), p ) : calc_movecost( pos(), p )); if( cost > 0 ) { moves -= cost; } else { return false; } } //Check for moving into/out of water bool was_water = g->m.is_divable( pos3() ); bool will_be_water = on_ground && can_submerge() && g->m.is_divable( p ); if( was_water && !will_be_water && g->u.sees( p ) ) { //Use more dramatic messages for swimming monsters add_msg( m_warning, _( "A %1$s %2$s from the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "leaps" ) : _( "emerges" ), g->m.tername( pos() ).c_str() ); } else if( !was_water && will_be_water && g->u.sees( p ) ) { add_msg( m_warning, _( "A %1$s %2$s into the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "dives" ) : _( "sinks" ), g->m.tername( p ).c_str() ); } setpos( p ); footsteps( p ); underwater = will_be_water; if( is_hallucination() ) { //Hallucinations don't do any of the stuff after this point return true; } // TODO: Make tanks stop taking damage from rubble, because it's just silly if( type->size != MS_TINY && on_ground ) { if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 10 ) ); } if( g->m.has_flag( "ROUGH", pos() ) && one_in( 6 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 2 ) ); } } if( g->m.has_flag( "UNSTABLE", p ) && on_ground ) { add_effect( "bouldering", 1, num_bp, true ); } else if( has_effect( "bouldering" ) ) { remove_effect( "bouldering" ); } g->m.creature_on_trap( *this ); if( !will_be_water && ( has_flag( MF_DIGS ) || has_flag( MF_CAN_DIG ) ) ) { underwater = g->m.has_flag( "DIGGABLE", pos() ); } // Diggers turn the dirt into dirtmound if( digging() ) { int factor = 0; switch( type->size ) { case MS_TINY: factor = 100; break; case MS_SMALL: factor = 30; break; case MS_MEDIUM: factor = 6; break; case MS_LARGE: factor = 3; break; case MS_HUGE: factor = 1; break; } if( has_flag( MF_VERMIN ) ) { factor *= 100; } if( one_in( factor ) ) { g->m.ter_set( pos(), t_dirtmound ); } } // Acid trail monsters leave... a trail of acid if( has_flag( MF_ACIDTRAIL ) ) { g->m.add_field( pos(), fd_acid, 3, 0 ); } if( has_flag( MF_SLUDGETRAIL ) ) { for( const tripoint &sludge_p : g->m.points_in_radius( pos(), 1 ) ) { const int fstr = 3 - ( abs( sludge_p.x - posx() ) + abs( sludge_p.y - posy() ) ); if( fstr >= 2 ) { g->m.add_field( sludge_p, fd_sludge, fstr, 0 ); } } } if( has_flag( MF_LEAKSGAS ) ) { if( one_in( 6 ) ) { tripoint dest( posx() + rng( -1, 1 ), posy() + rng( -1, 1 ), posz() ); g->m.add_field( dest, fd_toxic_gas, 3, 0 ); } } return true; }
void activity_handlers::make_zlave_finish( player_activity *act, player *p ) { static const int full_pulp_threshold = 4; auto items = g->m.i_at(p->pos()); std::string corpse_name = act->str_values[0]; item *body = NULL; for( auto it = items.begin(); it != items.end(); ++it ) { if( it->display_name() == corpse_name ) { body = &*it; } } if( body == NULL ) { add_msg(m_info, _("There's no corpse to make into a zombie slave!")); return; } int success = act->values[0]; if( success > 0 ) { p->practice("firstaid", rng(2, 5)); p->practice("survival", rng(2, 5)); p->add_msg_if_player(m_good, _("You slice muscles and tendons, and remove body parts until you're confident the zombie won't be able to attack you when it reainmates.")); body->set_var( "zlave", "zlave" ); //take into account the chance that the body yet can regenerate not as we need. if( one_in(10) ) { body->set_var( "zlave", "mutilated" ); } } else { if( success > -20 ) { p->practice("firstaid", rng(3, 6)); p->practice("survival", rng(3, 6)); p->add_msg_if_player(m_warning, _("You hack into the corpse and chop off some body parts. You think the zombie won't be able to attack when it reanimates.")); success += rng(1, 20); if( success > 0 && !one_in(5) ) { body->set_var( "zlave", "zlave" ); } else { body->set_var( "zlave", "mutilated" ); } } else { p->practice("firstaid", rng(1, 8)); p->practice("survival", rng(1, 8)); int pulp = rng(1, full_pulp_threshold); body->damage += pulp; if( body->damage >= full_pulp_threshold ) { body->damage = full_pulp_threshold; body->active = false; p->add_msg_if_player(m_warning, _("You cut up the corpse too much, it is thoroughly pulped.")); } else { p->add_msg_if_player(m_warning, _("You cut into the corpse trying to make it unable to attack, but you don't think you have it right.")); } } } }
void activity_handlers::butcher_finish( player_activity *act, player *p ) { // corpses can disappear (rezzing!), so check for that if( static_cast<int>(g->m.i_at(p->pos()).size()) <= act->index || !(g->m.i_at(p->pos())[act->index].is_corpse() ) ) { add_msg(m_info, _("There's no corpse to butcher!")); return; } const mtype *corpse = g->m.i_at(p->pos())[act->index].get_mtype(); std::vector<item> contents = g->m.i_at(p->pos())[act->index].contents; int age = g->m.i_at(p->pos())[act->index].bday; g->m.i_rem(p->pos(), act->index); int factor = p->butcher_factor(); int pieces = 0, skins = 0, bones = 0, fats = 0, sinews = 0, feathers = 0; bool stomach = false; switch (corpse->size) { case MS_TINY: pieces = 1; skins = 1; bones = 1; fats = 1; sinews = 1; feathers = 2; break; case MS_SMALL: pieces = 2; skins = 2; bones = 4; fats = 2; sinews = 4; feathers = 6; break; case MS_MEDIUM: pieces = 4; skins = 4; bones = 9; fats = 4; sinews = 9; feathers = 11; break; case MS_LARGE: pieces = 8; skins = 8; bones = 14; fats = 8; sinews = 14; feathers = 17; break; case MS_HUGE: pieces = 16; skins = 16; bones = 21; fats = 16; sinews = 21; feathers = 24; break; } int sSkillLevel = p->skillLevel("survival"); auto roll_butchery = [&] () { double skill_shift = 0.; skill_shift += rng_float( 0, sSkillLevel - 3 ); skill_shift += rng_float( 0, p->dex_cur - 8 ) / 4.0; if( p->str_cur < 4 ) { skill_shift -= rng_float( 0, 5 * ( 4 - p->str_cur ) ) / 4.0; } if( factor < 0 ) { skill_shift -= rng_float( 0, -factor / 5.0 ); } return static_cast<int>( skill_shift ); }; int practice = std::max( 0, 4 + pieces + roll_butchery()); p->practice("survival", practice); // Lose some meat, skins, etc if the rolls are low pieces += std::min( 0, roll_butchery() ); skins += std::min( 0, roll_butchery() - 4 ); bones += std::min( 0, roll_butchery() - 2 ); fats += std::min( 0, roll_butchery() - 4 ); sinews += std::min( 0, roll_butchery() - 8 ); feathers += std::min( 0, roll_butchery() - 1 ); stomach = (roll_butchery() >= 0); if( bones > 0 ) { if( corpse->has_material("veggy") ) { g->m.spawn_item(p->pos(), "plant_sac", bones, 0, age); add_msg(m_good, _("You harvest some fluid bladders!")); } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "bone_tainted", bones / 2, 0, age); add_msg(m_good, _("You harvest some salvageable bones!")); } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_HUMAN) ) { g->m.spawn_item(p->pos(), "bone_human", bones, 0, age); add_msg(m_good, _("You harvest some salvageable bones!")); } else if( corpse->has_flag(MF_BONES) ) { g->m.spawn_item(p->pos(), "bone", bones, 0, age); add_msg(m_good, _("You harvest some usable bones!")); } } if( sinews > 0 ) { if( corpse->has_flag(MF_BONES) && !corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "sinew", sinews, 0, age); add_msg(m_good, _("You harvest some usable sinews!")); } else if( corpse->has_material("veggy") ) { g->m.spawn_item(p->pos(), "plant_fibre", sinews, 0, age); add_msg(m_good, _("You harvest some plant fibers!")); } } if( stomach ) { const itype_id meat = corpse->get_meat_itype(); if( meat == "meat" ) { if( corpse->size == MS_SMALL || corpse->size == MS_MEDIUM ) { g->m.spawn_item(p->pos(), "stomach", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } else if( corpse->size == MS_LARGE || corpse->size == MS_HUGE ) { g->m.spawn_item(p->pos(), "stomach_large", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } } } if( (corpse->has_flag(MF_FUR) || corpse->has_flag(MF_LEATHER) || corpse->has_flag(MF_CHITIN)) && skins > 0 ) { add_msg(m_good, _("You manage to skin the %s!"), corpse->nname().c_str()); int fur = 0; int leather = 0; int chitin = 0; while (skins > 0 ) { if( corpse->has_flag(MF_CHITIN) ) { chitin = rng(0, skins); skins -= chitin; skins = std::max(skins, 0); } if( corpse->has_flag(MF_FUR) ) { fur = rng(0, skins); skins -= fur; skins = std::max(skins, 0); } if( corpse->has_flag(MF_LEATHER) ) { leather = rng(0, skins); skins -= leather; skins = std::max(skins, 0); } } if( chitin ) { g->m.spawn_item(p->pos(), "chitin_piece", chitin, 0, age); } if( fur ) { g->m.spawn_item(p->pos(), "raw_fur", fur, 0, age); } if( leather ) { g->m.spawn_item(p->pos(), "raw_leather", leather, 0, age); } } if( feathers > 0 ) { if( corpse->has_flag(MF_FEATHER) ) { g->m.spawn_item(p->pos(), "feather", feathers, 0, age); add_msg(m_good, _("You harvest some feathers!")); } } if( fats > 0 ) { if( corpse->has_flag(MF_FAT) && corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "fat_tainted", fats, 0, age); add_msg(m_good, _("You harvest some gooey fat!")); } else if( corpse->has_flag(MF_FAT) ) { g->m.spawn_item(p->pos(), "fat", fats, 0, age); add_msg(m_good, _("You harvest some fat!")); } } //Add a chance of CBM recovery. For shocker and cyborg corpses. //As long as the factor is above -4 (the sinew cutoff), you will be able to extract cbms bool any_cbm = false; bool cbm = false; if( corpse->has_flag(MF_CBM_CIV) ) { if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_common", p->pos3(), age ) || cbm; } } // Zombie scientist bionics if( corpse->has_flag(MF_CBM_SCI) ) { if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_sci", p->pos3(), age ) || cbm; } } // Zombie technician bionics if( corpse->has_flag(MF_CBM_TECH) ) { if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_tech", p->pos3(), age ) || cbm; } } // Substation mini-boss bionics if( corpse->has_flag(MF_CBM_SUBS) ) { if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_subs", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_subs", p->pos3(), age ) || cbm; } } // Payoff for butchering the zombie bio-op if( corpse->has_flag(MF_CBM_OP) ) { if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_item( "bio_power_storage_mkII", p->pos3(), age ) || cbm; } if( roll_butchery() >= 0 ) { any_cbm = true; cbm = butcher_cbm_group( "bionics_op", p->pos3(), age ) || cbm; } } if( cbm ) { add_msg( m_good, _("You discover a CBM in the %s!"), corpse->nname().c_str() ); } else if( any_cbm ) { add_msg( m_good, _("You discover a fused lump of bio-circuitry in the %s!"), corpse->nname().c_str() ); } //Add a chance of CBM power storage recovery. if( corpse->has_flag(MF_CBM_POWER) ) { //As long as the factor is above -4 (the sinew cutoff), you will be able to extract cbms if( roll_butchery() >= 0 ) { //To see if it spawns a battery if( one_in(3) ) { //The battery works 33% of the time. add_msg(m_good, _("You discover a power storage in the %s!"), corpse->nname().c_str()); g->m.spawn_item( p->pos3(), "bio_power_storage", 1, 0, age); } else { //There is a burnt out CBM add_msg(m_good, _("You discover a fused lump of bio-circuitry in the %s!"), corpse->nname().c_str()); g->m.spawn_item( p->pos3(), "burnt_out_bionic", 1, 0, age); } } } // Recover hidden items for( auto &content : contents ) { if( ( roll_butchery() + 10 ) * 5 > rng( 0, 100 ) ) { add_msg( m_good, _( "You discover a %s in the %s!" ), content.tname().c_str(), corpse->nname().c_str() ); g->m.add_item_or_charges( p->pos(), content ); } else if( content.is_bionic() ) { g->m.spawn_item(p->pos(), "burnt_out_bionic", 1, 0, age); } } if( pieces <= 0 ) { add_msg(m_bad, _("Your clumsy butchering destroys the meat!")); } else { add_msg(m_good, _("You butcher the corpse.")); const itype_id meat = corpse->get_meat_itype(); if( meat == "null" ) { return; } item tmpitem(meat, age); tmpitem.set_mtype( corpse ); while ( pieces > 0 ) { pieces--; g->m.add_item_or_charges(p->pos(), tmpitem); } } }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( "SEALED", pos() ) ) { if( !g->m.i_at( pos3() ).empty() ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); for( auto &elem : g->m.i_at( pos3() ) ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear( pos3() ); } } // First, use the special attack, if we can! for( size_t i = 0; i < sp_timeout.size(); ++i ) { if( sp_timeout[i] > 0 ) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && !has_effect( "pacified" ) && !is_hallucination() ) { type->sp_attack[i]( this, i ); } } // The monster can sometimes hang in air due to last fall being blocked if( has_flag( MF_FLIES ) && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } bool attacking = false; if( !move_effects(attacking) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( "stunned" ) ) { stumble( false ); moves = 0; return; } if( friendly != 0 ) { if( friendly > 0 ) { friendly--; } friendly_move(); return; } bool moved = false; tripoint next; // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !plans.empty() ) { if( plans.back() == g->u.pos3() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( plans.back() == i->pos3() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble( false ); return; } // Fix possibly invalid plans // Also make sure the monster won't act across z-levels when it shouldn't. // Don't do it in plan(), because the mon can still use ranged special attacks using // the plans that are not valid for travel/melee. const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); const bool can_fly = has_flag( MF_FLIES ); if( !plans.empty() && !g->m.valid_move( pos(), plans[0], can_bash, can_fly ) ) { plans.clear(); } int mondex = !plans.empty() ? g->mon_at( plans[0] ) : -1; auto mon_att = mondex != -1 ? attitude_to( g->zombie( mondex ) ) : A_HOSTILE; if( !plans.empty() && ( mon_att == A_HOSTILE || has_flag( MF_ATTACKMON ) || has_flag( MF_PUSH_MON ) ) && ( can_move_to( plans[0] ) || ( plans[0] == g->u.pos3() ) || ( ( has_flag( MF_BASHES ) || has_flag( MF_BORES ) ) && g->m.bash_rating( bash_estimate(), plans[0] ) >= 0 ) ) ) { // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if( has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. plans.clear(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { next = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); tripoint tmp = wander_next(); if( tmp != pos() ) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen // Note: The below works because C++ in A() || B() won't call B() if A() is true bool did_something = attack_at( next ) || bash_at( next ) || push_to( next, 0, 0 ) || move_to( next ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; } // If we're close to our target, we get focused and don't stumble if( ( has_flag( MF_STUMBLES ) && ( plans.size() > 3 || plans.empty() ) ) || !moved ) { stumble( moved ); } }
void mdeath::normal(monster *z) { if( z->no_corpse_quiet ) { return; } if( z->type->in_species( ZOMBIE ) ) { sfx::play_variant_sound( "mon_death", "zombie_death", sfx::get_heard_volume( z->pos() ) ); } if( g->u.sees( *z ) ) { //Currently it is possible to get multiple messages that a monster died. add_msg( m_good, _( "The %s dies!" ), z->name().c_str() ); } const int max_hp = std::max( z->get_hp_max(), 1 ); const float overflow_damage = std::max( -z->get_hp(), 0 ); const float corpse_damage = 2.5 * overflow_damage / max_hp; const bool pulverized = corpse_damage > 5 && overflow_damage > z->get_hp_max(); z->bleed(); // leave some blood if we have to if( !pulverized ) { make_mon_corpse( z, int( std::floor( corpse_damage ) ) ); } // Limit chunking to flesh, veggy and insect creatures until other kinds are supported. const std::vector<material_id> gib_mats = {{ material_id( "flesh" ), material_id( "hflesh" ), material_id( "veggy" ), material_id( "iflesh" ), }}; const bool gibbable = !z->type->has_flag( MF_NOGIB ) && std::any_of( gib_mats.begin(), gib_mats.end(), [&z]( const material_id &gm ) { return z->made_of( gm ); } ); const field_id type_blood = z->bloodType(); const field_id type_gib = z->gibType(); if( gibbable ) { const auto area = g->m.points_in_radius( z->pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); if( pulverized && z->type->size >= MS_MEDIUM ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z->pos() ) ); } for( int i = 0; i < number_of_gibs; ++i ) { g->m.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) ); g->m.add_splatter( type_blood, random_entry( area ) ); } } const int num_chunks = z->type->get_meat_chunks_count(); const itype_id meat = z->type->get_meat_itype(); if( pulverized && gibbable ) { const item chunk( meat ); for( int i = 0; i < num_chunks; i++ ) { tripoint tarp( z->pos() + point( rng( -3, 3 ), rng( -3, 3 ) ) ); const auto traj = line_to( z->pos(), tarp ); for( size_t j = 0; j < traj.size(); j++ ) { tarp = traj[j]; if( one_in( 2 ) && type_blood != fd_null ) { g->m.add_splatter( type_blood, tarp ); } else { g->m.add_splatter( type_gib, tarp, rng( 1, j + 1 ) ); } if( g->m.impassable( tarp ) ) { g->m.bash( tarp, 3 ); if( g->m.impassable( tarp ) ) { // Target is obstacle, not destroyed by bashing, // stop trajectory in front of it, if this is the first // point (e.g. wall adjacent to monster) , make it invalid. if( j > 0 ) { tarp = traj[j - 1]; } else { tarp = tripoint_min; } break; } } } if( tarp != tripoint_min ) { g->m.add_item_or_charges( tarp, chunk ); } } } }
bool player::hit_player(player &p, body_part &bp, int &hitdam, int &hitcut) { // TODO: Add bionics and other bonus (e.g. heat drain, shock, etc) if (!is_npc() && p.is_npc()) { npc *foe = dynamic_cast<npc*>(&p); foe->make_angry(); } bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), cutting = weapon.is_cutting_weapon(); int hitit = hit_roll() - p.dodge_roll(); if (hitit < 0) { // They dodged practice(sk_melee, rng(2, 4)); if (unarmed) practice(sk_unarmed, 3); if (bashing) practice(sk_bashing, 1); if (cutting) practice(sk_cutting, 2); return false; } if (hitit >= 15) bp = bp_eyes; else if (hitit >= 12) bp = bp_mouth; else if (hitit >= 10) bp = bp_head; else if (hitit >= 6) bp = bp_torso; else if (hitit >= 2) bp = bp_arms; else bp = bp_legs; hitdam = base_damage(); if (unarmed) {// Unarmed bonuses hitdam += rng(0, sklevel[sk_unarmed]); if (sklevel[sk_unarmed] >= 5) hitdam += rng(sklevel[sk_unarmed], 3 * sklevel[sk_unarmed]); if (has_trait(PF_TALONS)) hitcut += 10; if (sklevel[sk_unarmed] >= 8 && (one_in(3) || rng(5, 20) < sklevel[sk_unarmed])) hitdam *= rng(2, 3); } // Weapon adds (melee_dam / 4) to (melee_dam) hitdam += rng(weapon.damage_bash() / 4, weapon.damage_bash()); if (bashing) hitdam += rng(0, sklevel[sk_bashing]) * sqrt(double(str_cur)); hitdam += int(pow(1.5, double(sklevel[sk_melee]))); hitcut = weapon.damage_cut(); if (hitcut > 0) hitcut += int(sklevel[sk_cutting] / 3); if (hitdam < 0) hitdam = 0; if (hitdam > 0 || hitcut > 0) { // Practicing practice(sk_melee, rng(5, 10)); if (unarmed) practice(sk_unarmed, rng(5, 10)); if (bashing) practice(sk_bashing, rng(5, 10)); if (cutting) practice(sk_cutting, rng(5, 10)); } else { // Less practice if we missed practice(sk_melee, rng(2, 5)); if (unarmed) practice(sk_unarmed, 2); if (bashing) practice(sk_bashing, 2); if (cutting) practice(sk_cutting, 3); } return true; }
bool player::scored_crit(int target_dodge) { bool to_hit_crit = false, dex_crit = false, skill_crit = false; int num_crits = 0; int chance = 25; if (weapon.type->m_to_hit > 0) { for (int i = 1; i <= weapon.type->m_to_hit; i++) chance += (50 / (2 + i)); } else if (chance < 0) { for (int i = 0; i > weapon.type->m_to_hit; i--) chance /= 2; } if (rng(0, 99) < chance) num_crits++; chance = 25; if (dex_cur > 8) { for (int i = 9; i <= dex_cur; i++) chance += (21 - i); // 12, 11, 10... } else { int decrease = 5; for (int i = 7; i >= dex_cur; i--) { chance -= decrease; if (i % 2 == 0) decrease--; } } if (rng(0, 99) < chance) num_crits++; int best_skill = 0; if (weapon.is_bashing_weapon() && sklevel[sk_bashing] > best_skill) best_skill = sklevel[sk_bashing]; if (weapon.is_cutting_weapon() && sklevel[sk_cutting] > best_skill) best_skill = sklevel[sk_cutting]; if ((weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) && sklevel[sk_stabbing] > best_skill) best_skill = sklevel[sk_stabbing]; if (unarmed_attack() && sklevel[sk_unarmed] > best_skill) best_skill = sklevel[sk_unarmed]; best_skill += int(sklevel[sk_melee] / 2.5); chance = 25; if (best_skill > 3) { for (int i = 3; i < best_skill; i++) chance += (50 / (2 + i)); } else if (chance < 3) { for (int i = 3; i > best_skill; i--) chance /= 2; } if (rng(0, 99) < chance) num_crits++; if (num_crits == 3) return true; else if (num_crits == 2) return (hit_roll() >= target_dodge * 1.5 && !one_in(4)); return false; }
ter_id grass_or_dirt() { if (one_in(4)) return t_grass; return t_dirt; }
ter_id dirt_or_pile() { if (one_in(4)) return t_dirtmound; return t_dirt; }
void mapgen_forest_general(map *m, oter_id terrain_type, mapgendata dat, int turn) { switch (terrain_type) { case ot_forest_thick: dat.fill(8); break; case ot_forest_water: dat.fill(4); break; case ot_forest: dat.fill(0); break; } for (int i = 0; i < 4; i++) { if (dat.t_nesw[i] == ot_forest || dat.t_nesw[i] == ot_forest_water) { dat.dir(i) += 14; } else if (dat.t_nesw[i] == ot_forest_thick) { dat.dir(i) += 18; } } for (int i = 0; i < SEEX * 2; i++) { for (int j = 0; j < SEEY * 2; j++) { int forest_chance = 0, num = 0; if (j < dat.n_fac) { forest_chance += dat.n_fac - j; num++; } if (SEEX * 2 - 1 - i < dat.e_fac) { forest_chance += dat.e_fac - (SEEX * 2 - 1 - i); num++; } if (SEEY * 2 - 1 - j < dat.s_fac) { forest_chance += dat.s_fac - (SEEY * 2 - 1 - j); num++; } if (i < dat.w_fac) { forest_chance += dat.w_fac - i; num++; } if (num > 0) { forest_chance /= num; } int rn = rng(0, forest_chance); if ((forest_chance > 0 && rn > 13) || one_in(100 - forest_chance)) { if (one_in(250)) { m->ter_set(i, j, t_tree_apple); m->spawn_item(i, j, "apple", turn); } else { m->ter_set(i, j, t_tree); } } else if ((forest_chance > 0 && rn > 10) || one_in(100 - forest_chance)) { m->ter_set(i, j, t_tree_young); } else if ((forest_chance > 0 && rn > 9) || one_in(100 - forest_chance)) { if (one_in(250)) { m->ter_set(i, j, t_shrub_blueberry); } else { m->ter_set(i, j, t_underbrush); } } else { m->ter_set(i, j, grass_or_dirt()); } } } m->place_items("forest", 60, 0, 0, SEEX * 2 - 1, SEEY * 2 - 1, true, turn); if (terrain_type == ot_forest_water) { // Reset *_fac to handle where to place water for (int i = 0; i < 4; i++) { if (dat.t_nesw[i] == ot_forest_water) { dat.set_dir(i, 2); } else if (dat.t_nesw[i] >= ot_river_center && dat.t_nesw[i] <= ot_river_nw) { dat.set_dir(i, 3); } else if (dat.t_nesw[i] == ot_forest || dat.t_nesw[i] == ot_forest_thick) { dat.set_dir(i, 1); } else { dat.set_dir(i, 0); } } int x = SEEX / 2 + rng(0, SEEX), y = SEEY / 2 + rng(0, SEEY); for (int i = 0; i < 20; i++) { if (x >= 0 && x < SEEX * 2 && y >= 0 && y < SEEY * 2) { if (m->ter(x, y) == t_water_sh) { m->ter_set(x, y, t_water_dp); } else if (m->ter(x, y) == t_dirt || m->ter(x, y) == t_grass || m->ter(x, y) == t_underbrush) { m->ter_set(x, y, t_water_sh); } } else { i = 20; } x += rng(-2, 2); y += rng(-2, 2); if (x < 0 || x >= SEEX * 2) { x = SEEX / 2 + rng(0, SEEX); } if (y < 0 || y >= SEEY * 2) { y = SEEY / 2 + rng(0, SEEY); } for (int j = 0; j < dat.n_fac; j++) { int wx = rng(0, SEEX * 2 -1), wy = rng(0, SEEY - 1); if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass || m->ter(wx, wy) == t_underbrush) { m->ter_set(wx, wy, t_water_sh); } } for (int j = 0; j < dat.e_fac; j++) { int wx = rng(SEEX, SEEX * 2 - 1), wy = rng(0, SEEY * 2 - 1); if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass || m->ter(wx, wy) == t_underbrush) { m->ter_set(wx, wy, t_water_sh); } } for (int j = 0; j < dat.s_fac; j++) { int wx = rng(0, SEEX * 2 - 1), wy = rng(SEEY, SEEY * 2 - 1); if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass || m->ter(wx, wy) == t_underbrush) { m->ter_set(wx, wy, t_water_sh); } } for (int j = 0; j < dat.w_fac; j++) { int wx = rng(0, SEEX - 1), wy = rng(0, SEEY * 2 - 1); if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass || m->ter(wx, wy) == t_underbrush) { m->ter_set(wx, wy, t_water_sh); } } } int rn = rng(0, 2) * rng(0, 1) * (rng(0, 1) + rng(0, 1));// Good chance of 0 for (int i = 0; i < rn; i++) { x = rng(0, SEEX * 2 - 1); y = rng(0, SEEY * 2 - 1); m->add_trap(x, y, tr_sinkhole); if (m->ter(x, y) != t_water_sh) { m->ter_set(x, y, grass_or_dirt()); } } } if (one_in(10000)) { //1-2 per overmap, very bad day for low level characters m->add_spawn("mon_jabberwock", 1, SEEX, SEEY); } if (one_in(1000000)) { //Very rare easter egg, ~1 per 10 overmaps m->add_spawn("mon_shia", 1, SEEX, SEEY); } if (one_in(100)) // One in 100 forests has a spider living in it :o { for (int i = 0; i < SEEX * 2; i++) { for (int j = 0; j < SEEX * 2; j++) { if ((m->ter(i, j) == t_dirt || m->ter(i, j) == t_grass || m->ter(i, j) == t_underbrush) && !one_in(3)) { m->add_field(NULL, i, j, fd_web, rng(1, 3)); } } } m->add_spawn("mon_spider_web", rng(1, 2), SEEX, SEEY); } }
void player::mutate(game *g) { bool force_bad = one_in(3); bool force_good = false; if (has_trait(PF_ROBUST) && force_bad) { // Robust Genetics gives you a 33% chance for a good mutation, // instead of the 33% chance of a bad one. force_bad = false; force_good = true; } // Determine the mutation categorie mutation_category cat = MUTCAT_NULL; // Count up the players number of mutations in categories and find // the category with the highest single count. int total = 0, highest = 0; for (int i = 0; i < NUM_MUTATION_CATEGORIES; i++) { total += mutation_category_level[i]; if (mutation_category_level[i] > highest) { cat = mutation_category(i); highest = mutation_category_level[i]; } } // See if we should ugrade/extend an existing mutation... std::vector<pl_flag> upgrades; // ... or remove one that is not in our highest category std::vector<pl_flag> downgrades; // For each mutation... for (int base_mutation_index = 1; base_mutation_index < PF_MAX2; base_mutation_index++) { pl_flag base_mutation = (pl_flag) base_mutation_index; // ...that we have... if (has_trait(base_mutation)) { // ...consider the mutations that replace it. for (int i = 0; i < g->mutation_data[base_mutation].replacements.size(); i++) { pl_flag mutation = g->mutation_data[base_mutation].replacements[i]; if (mutation_ok(g, mutation, force_good, force_bad)) { upgrades.push_back(mutation); } } // ...consider the mutations that add to it. for (int i = 0; i < g->mutation_data[base_mutation].additions.size(); i++) { pl_flag mutation = g->mutation_data[base_mutation].additions[i]; if (mutation_ok(g, mutation, force_good, force_bad)) { upgrades.push_back(mutation); } } // ...consider whether its in our highest category if( has_trait(base_mutation) && !has_base_trait(base_mutation) ){ // Starting traits don't count toward categories std::vector<pl_flag> group = mutations_from_category(cat); bool in_cat = false; for (int j = 0; j < group.size(); j++) { if (group[j] == base_mutation) { in_cat = true; break; } } // mark for removal if(!in_cat) downgrades.push_back(base_mutation); } } } // Preliminary round to either upgrade or remove existing mutations if(one_in(2)){ if (upgrades.size() > 0) { // (upgrade count) chances to pick an upgrade, 4 chances to pick something else. int roll = rng(0, upgrades.size() + 4); if (roll < upgrades.size()) { // We got a valid upgrade index, so use it and return. mutate_towards(g, upgrades[roll]); return; } } } else { // Remove existing mutations that don't fit into our category if (downgrades.size() > 0 && cat != MUTCAT_NULL) { int roll = rng(0, downgrades.size() + 4); if (roll < downgrades.size()) { remove_mutation(g, downgrades[roll]); return; } } } std::vector<pl_flag> valid; // Valid mutations bool first_pass = true; do { // If we tried once with a non-NULL category, and couldn't find anything valid // there, try again with MUTCAT_NULL if (!first_pass) { cat = MUTCAT_NULL; } if (cat == MUTCAT_NULL) { // Pull the full list for (int i = 1; i < PF_MAX2; i++) { if (g->mutation_data[i].valid) { valid.push_back( pl_flag(i) ); } } } else { // Pull the category's list valid = mutations_from_category(cat); } // Remove anything we already have, that we have a child of, or that // goes against our intention of a good/bad mutation for (int i = 0; i < valid.size(); i++) { if (!mutation_ok(g, valid[i], force_good, force_bad)) { valid.erase(valid.begin() + i); i--; } } if (valid.empty()) { // So we won't repeat endlessly first_pass = false; } } while (valid.empty() && cat != MUTCAT_NULL); if (valid.empty()) { // Couldn't find anything at all! return; } pl_flag selection = valid[ rng(0, valid.size() - 1) ]; // Pick one! mutate_towards(g, selection); }
void player::mutate_towards(game *g, pl_flag mut) { if (has_child_flag(g, mut)) { remove_child_flag(g, mut); return; } bool has_prereqs = false; pl_flag canceltrait = PF_NULL; std::vector<pl_flag> prereq = g->mutation_data[mut].prereqs; std::vector<pl_flag> cancel = g->mutation_data[mut].cancels; for (int i = 0; i < cancel.size(); i++) { if (!has_trait( cancel[i] )) { cancel.erase(cancel.begin() + i); i--; } else { //If we have the trait, but it's a base trait, don't allow it to be removed normally if (has_base_trait( cancel[i] )) { canceltrait = cancel[i]; cancel.erase(cancel.begin() + i); i--; } } } if (!cancel.empty()) { pl_flag removed = cancel[ rng(0, cancel.size() - 1) ]; remove_mutation(g, removed); return; } for (int i = 0; !has_prereqs && i < prereq.size(); i++) { if (has_trait(prereq[i])) has_prereqs = true; } if (!has_prereqs && !prereq.empty()) { pl_flag devel = prereq[ rng(0, prereq.size() - 1) ]; mutate_towards(g, devel); return; } // Check if one of the prereqs that we have TURNS INTO this one pl_flag replacing = PF_NULL; prereq = g->mutation_data[mut].prereqs; // Reset it for (int i = 0; i < prereq.size(); i++) { if (has_trait(prereq[i])) { pl_flag pre = prereq[i]; for (int j = 0; replacing == PF_NULL && j < g->mutation_data[pre].replacements.size(); j++) { if (g->mutation_data[pre].replacements[j] == mut) replacing = pre; } } } toggle_mutation(mut); if (replacing != PF_NULL) { g->add_msg(_("Your %1$s mutation turns into %2$s!"), traits[replacing].name.c_str(), traits[mut].name.c_str()); toggle_mutation(replacing); mutation_loss_effect(g, *this, replacing); mutation_effect(g, *this, mut); } else // If this new mutation cancels a base trait, remove it and add the mutation at the same time if (canceltrait != PF_NULL) { g->add_msg(_("Your innate %1$s trait turns into %2$s!"), traits[canceltrait].name.c_str(), traits[mut].name.c_str()); toggle_mutation(canceltrait); mutation_loss_effect(g, *this, canceltrait); mutation_effect(g, *this, mut); } else { g->add_msg(_("You gain a mutation called %s!"), traits[mut].name.c_str()); mutation_effect(g, *this, mut); } // Weight us towards any categories that include this mutation for (int i = 0; i < NUM_MUTATION_CATEGORIES; i++) { std::vector<pl_flag> group = mutations_from_category(mutation_category(i)); bool found = false; for (int j = 0; !found && j < group.size(); j++) { if (group[j] == mut) found = true; } if (found) mutation_category_level[i] += 8; else if (mutation_category_level[i] > 0 && !one_in(mutation_category_level[i])) mutation_category_level[i]--; } }
void monster::explode() { if( is_hallucination() ) { //Can't gib hallucinations return; } // Send body parts and blood all over! const itype_id meat = type->get_meat_itype(); const field_id type_blood = bloodType(); const field_id type_gib = gibType(); if( meat != "null" || type_blood != fd_null || type_gib != fd_null ) { // Only create chunks if we know what kind to make. int num_chunks = 0; switch( type->size ) { case MS_TINY: num_chunks = 1; break; case MS_SMALL: num_chunks = 2; break; case MS_MEDIUM: num_chunks = 4; break; case MS_LARGE: num_chunks = 8; break; case MS_HUGE: num_chunks = 16; break; } for( int i = 0; i < num_chunks; i++ ) { int tarx = _posx + rng( -3, 3 ), tary = _posy + rng( -3, 3 ); std::vector<point> traj = line_to( _posx, _posy, tarx, tary, 0 ); for( size_t j = 0; j < traj.size(); j++ ) { tarx = traj[j].x; tary = traj[j].y; if( one_in( 2 ) && type_blood != fd_null ) { g->m.add_field( tarx, tary, type_blood, 1 ); } else if( type_gib != fd_null ) { g->m.add_field( tarx, tary, type_gib, rng( 1, j + 1 ) ); } if( g->m.move_cost( tarx, tary ) == 0 ) { if( !g->m.bash( tarx, tary, 3 ).second ) { // Target is obstacle, not destroyed by bashing, // stop trajectory in front of it, if this is the first // point (e.g. wall adjacent to monster) , make it invalid. if( j > 0 ) { tarx = traj[j - 1].x; tary = traj[j - 1].y; } else { tarx = -1; } break; } } } if( meat != "null" && tarx != -1 ) { g->m.spawn_item( tarx, tary, meat, 1, 0, calendar::turn ); } } } }
int player::hit_mon(game *g, monster *z) { bool is_u = (this == &(g->u)); // Affects how we'll display messages int j; bool can_see = (is_u || g->u_see(posx, posy, j)); std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); // Types of combat (may overlap!) bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), cutting = weapon.is_cutting_weapon(), stabbing = (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB) ); // Recoil penalty if (recoil <= 30) recoil += 6; // Movement cost moves -= weapon.attack_time() + 20 * encumb(bp_torso); // Different sizes affect your chance to hit if (hit_roll() < z->dodge_roll() || one_in(2 + dex_cur)) {// A miss! stumble(g); return 0; } // For very high hit rolls, we crit! bool critical_hit = scored_crit(z->dodge_roll()); int dam = base_damage(true); int cutting_penalty = 0; // Moves lost from getting a cutting weapon stuck // Drunken Master damage bonuses if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) { // Remember, a single drink gives 600 levels of DI_DRUNK int mindrunk, maxdrunk; if (unarmed) { mindrunk = disease_level(DI_DRUNK) / 600; maxdrunk = disease_level(DI_DRUNK) / 250; } else { mindrunk = disease_level(DI_DRUNK) / 900; maxdrunk = disease_level(DI_DRUNK) / 400; } dam += rng(mindrunk, maxdrunk); } if (unarmed) { // Unarmed bonuses dam += rng(0, sklevel[sk_unarmed]); if (has_trait(PF_TALONS) && z->type->armor - sklevel[sk_unarmed] < 10) { int z_armor = (z->type->armor - sklevel[sk_unarmed]); if (z_armor < 0) z_armor = 0; dam += 10 - z_armor; } } if (rng(1, 45 - dex_cur) < 2 * sklevel[sk_unarmed] && rng(1, 65 - dex_cur) < 2 * sklevel[sk_unarmed] ) { // Bonus unarmed attack! if (is_u || can_see) { switch (rng(1, 4)) { case 1: g->add_msg("%s kick%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 2: g->add_msg("%s headbutt%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 3: g->add_msg("%s elbow%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; case 4: g->add_msg("%s knee%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); break; } } if (sklevel[sk_unarmed] >= 4) dam += rng(1, sklevel[sk_unarmed] / 2); else dam++; practice(sk_unarmed, 2); } // Melee skill bonus dam += rng(0, sklevel[sk_melee]); // Bashing damage bonus int bash_dam = weapon.damage_bash(), bash_cap = 5 + str_cur + sklevel[sk_bashing]; if (bash_dam > bash_cap)// Cap for weak characters bash_dam = (bash_cap * 3 + bash_dam) / 4; if (bashing) bash_dam += rng(0, sklevel[sk_bashing] + sqrt(double(str_cur))); if (z->has_flag(MF_PLASTIC)) bash_dam /= rng(2, 4); int bash_min = bash_dam / 4; if (bash_min < sklevel[sk_bashing] ) bash_min = sklevel[sk_bashing]; dam += rng(bash_min, bash_dam); // Take some moves away from the target; at this point it's skill & bash damage z->moves -= rng(0, dam * 2); // Spears treat cutting damage specially. if (weapon.has_flag(IF_SPEAR) && weapon.damage_cut() > z->type->armor - int(sklevel[sk_stabbing])) { int z_armor = z->type->armor - int(sklevel[sk_stabbing]); dam += int(weapon.damage_cut() / 5); int minstab = sklevel[sk_stabbing] * 5 + weapon.volume() * 2, maxstab = sklevel[sk_stabbing] * 15 + weapon.volume() * 4; int monster_penalty = rng(minstab, maxstab); if (monster_penalty >= 150) g->add_msg("You force the %s to the ground!", z->name().c_str()); else if (monster_penalty >= 50) g->add_msg("The %s is skewered and flinches!", z->name().c_str()); z->moves -= monster_penalty; cutting_penalty = weapon.damage_cut() * 4 + z_armor * 8 - dice(sklevel[sk_stabbing], 10); practice(sk_stabbing, 2); // Cutting damage bonus } else if (weapon.damage_cut() > z->type->armor - int(sklevel[sk_cutting] / 2)) { int z_armor = z->type->armor - int(sklevel[sk_cutting] / 2); if (z_armor < 0) z_armor = 0; dam += weapon.damage_cut() - z_armor; cutting_penalty = weapon.damage_cut() * 3 + z_armor * 8 - dice(sklevel[sk_cutting], 10); } if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws cutting_penalty /= 6; // Harder to get stuck for (int x = z->posx - 1; x <= z->posx + 1; x++) { for (int y = z->posy - 1; y <= z->posy + 1; y++) { if (!one_in(3)) { if (g->m.field_at(x, y).type == fd_blood && g->m.field_at(x, y).density < 3) g->m.field_at(x, y).density++; else g->m.add_field(g, x, y, fd_blood, 1); } } } } // Bonus attacks! bool shock_them = (has_bionic(bio_shock) && power_level >= 2 && unarmed && one_in(3)); bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 && !is_armed() && z->has_flag(MF_WARM)); bool bite_them = (has_trait(PF_FANGS) && z->armor() < 18 && one_in(20 - dex_cur - sklevel[sk_unarmed])); bool peck_them = (has_trait(PF_BEAK) && z->armor() < 16 && one_in(15 - dex_cur - sklevel[sk_unarmed])); if (drain_them) power_level--; drain_them &= one_in(2); // Only works half the time // Critical hit effects if (critical_hit) { bool headshot = (!z->has_flag(MF_NOHEAD) && !one_in(3)); // Second chance for shock_them, drain_them, bite_them and peck_them shock_them = (shock_them || (has_bionic(bio_shock)&& power_level >= 2 && unarmed && !one_in(3))); drain_them = (drain_them || (has_bionic(bio_heat_absorb) && !is_armed() && power_level >= 1 && z->has_flag(MF_WARM) && !one_in(3))); bite_them = ( bite_them || (has_trait(PF_FANGS) && z->armor() < 18 && one_in(5))); peck_them = ( peck_them || (has_trait(PF_BEAK) && z->armor() < 16 && one_in(4))); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) { dam += weapon.damage_cut(); dam += weapon.damage_cut() * double(sklevel[sk_stabbing] / 10); practice(sk_stabbing, 5); } if (unarmed) { dam += rng(1, 4) * sklevel[sk_unarmed]; if (sklevel[sk_unarmed] > 5) dam += 4 * (sklevel[sk_unarmed] - 5); z->moves -= dam; // Stunning blow if (weapon.type->id == itm_bio_claws) { if (sklevel[sk_cutting] >= 3) dam += 5; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s claws pierce the %s's skull!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s claws stab straight through the %s!", Your.c_str(), z->name().c_str()); } else if (has_trait(PF_TALONS)) { dam += 2; headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s talons tear the %s's head open!", Your.c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s bur%s %s talons into the %s!", You.c_str(),(is_u?"y":"ies"), your.c_str(), z->name().c_str()); } else { headshot &= z->hp < dam && one_in(2); if (headshot && can_see) g->add_msg("%s crush%s the %s's skull in a single blow!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s deliver%s a crushing punch!",You.c_str(),(is_u ? "" : "s")); } if (z->hp > 0 && rng(1, 5) < sklevel[sk_unarmed]) z->add_effect(ME_STUNNED, 1 + sklevel[sk_unarmed]); } else { // Not unarmed if (bashing) { dam += 8 + (str_cur / 2); int turns_stunned = int(dam / 20) + int(sklevel[sk_bashing] / 2); if (turns_stunned > 6) turns_stunned = 6; z->add_effect(ME_STUNNED, turns_stunned); } if (cutting || stabbing) { double cut_multiplier = double(sklevel[sk_cutting] / 12); if (cut_multiplier > 1.5) cut_multiplier = 1.5; dam += cut_multiplier * weapon.damage_cut(); headshot &= z->hp < dam; if (stabbing) { if (headshot && can_see) g->add_msg("%s %s stabs through the %s's skull!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else if (can_see) g->add_msg("%s stab %s %s through the %s!", You.c_str(), your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } else { if (headshot && can_see) g->add_msg("%s %s slices the %s's head off!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); else g->add_msg("%s %s cuts the %s deeply!", Your.c_str(), weapon.tname(g).c_str(), z->name().c_str()); } } else if (bashing) { headshot &= z->hp < dam; if (headshot && can_see) g->add_msg("%s crush%s the %s's skull!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); else if (can_see) g->add_msg("%s crush%s the %s's body!", You.c_str(), (is_u ? "" : "es"), z->name().c_str()); } } // End of not-unarmed } // End of critical hit if (shock_them) { power_level -= 2; if (can_see) g->add_msg("%s shock%s the %s!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); int shock = rng(2, 5); dam += shock * rng(1, 3); z->moves -= shock * 180; } if (drain_them) { charge_power(rng(0, 4)); if (can_see) g->add_msg("%s drain%s the %s's body heat!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); dam += rng(4, 10); z->moves -= rng(80, 120); } if (bite_them) { if (can_see) g->add_msg("%s sink %s fangs into the %s!", You.c_str(), your.c_str(), z->name().c_str()); dam += 18 - z->armor(); } if (peck_them) { if (can_see) g->add_msg("%s peck%s the %s viciously!", You.c_str(), (is_u ? "" : "s"), z->name().c_str()); dam += 16 - z->armor(); } // Make a rather quiet sound, to alert any nearby monsters g->sound(posx, posy, 8, ""); // Glass weapons shatter sometimes if (weapon.made_of(GLASS) && rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { if (can_see) g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); g->sound(posx, posy, 16, ""); // Dump its contents on the ground for (int i = 0; i < weapon.contents.size(); i++) g->m.add_item(posx, posy, weapon.contents[i]); hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage if (weapon.is_two_handed(this))// Hurt left arm too, if it was big hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra remove_weapon(); } if (dam <= 0) { if (is_u) g->add_msg("You hit the %s, but do no damage.", z->name().c_str()); else if (can_see) g->add_msg("%s's %s hits the %s, but does no damage.", You.c_str(), weapon.tname(g).c_str(), z->name().c_str()); practice(sk_melee, rng(2, 5)); if (unarmed) practice(sk_unarmed, 2); if (bashing) practice(sk_bashing, 2); if (cutting) practice(sk_cutting, 2); if (stabbing) practice(sk_stabbing, 2); return 0; } if (is_u) g->add_msg("You hit the %s for %d damage.", z->name().c_str(), dam); else if (can_see) g->add_msg("%s hits the %s with %s %s.", You.c_str(), z->name().c_str(), (male ? "his" : "her"), (weapon.type->id == 0 ? "fists" : weapon.tname(g).c_str())); practice(sk_melee, rng(5, 10)); if (unarmed) practice(sk_unarmed, rng(5, 10)); if (bashing) practice(sk_bashing, rng(5, 10)); if (cutting) practice(sk_cutting, rng(5, 10)); if (stabbing) practice(sk_stabbing, rng(5, 10)); // Penalize the player if their cutting weapon got stuck if (!unarmed && dam < z->hp && cutting_penalty > dice(str_cur * 2, 20)) { if (is_u) g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!", weapon.tname().c_str(), z->type->name.c_str()); z->add_item(remove_weapon()); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .7; else z->speed *= .85; } else { if (dam >= z->hp) { cutting_penalty /= 2; cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2); } if (cutting_penalty > 0) moves -= cutting_penalty; if (cutting_penalty >= 50 && is_u) g->add_msg("Your %s gets stuck in the %s, but you yank it free.", weapon.tname().c_str(), z->type->name.c_str()); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .9; } return dam; }
void trapfuncm::snake(game *g, monster *z, int x, int y) { g->sound(x, y, 10, "ssssssss"); if (one_in(6)) g->m.tr_at(x, y) = tr_null; }
/** * Attempts to harm a creature with a projectile. * * @param source Pointer to the creature who shot the projectile. * @param attack A structure describing the attack and its results. */ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) { const double missed_by = attack.missed_by; if( missed_by >= 1.0 ) { // Total miss return; } const projectile &proj = attack.proj; dealt_damage_instance &dealt_dam = attack.dealt_dam; const auto &proj_effects = proj.proj_effects; const bool u_see_this = g->u.sees(*this); const int avoid_roll = dodge_roll(); // Do dice(10, speed) instead of dice(speed, 10) because speed could potentially be > 10000 const int diff_roll = dice( 10, proj.speed ); // Partial dodge, capped at [0.0, 1.0], added to missed_by const double dodge_rescaled = avoid_roll / static_cast<double>( diff_roll ); const double goodhit = missed_by + std::max( 0.0, std::min( 1.0, dodge_rescaled ) ) ; if( goodhit >= 1.0 ) { // "Avoid" rather than "dodge", because it includes removing self from the line of fire // rather than just Matrix-style bullet dodging if( source != nullptr && g->u.sees( *source ) ) { add_msg_player_or_npc( m_warning, _("You avoid %s projectile!"), _("<npcname> avoids %s projectile."), source->disp_name(true).c_str() ); } else { add_msg_player_or_npc( m_warning, _("You avoid an incoming projectile!"), _("<npcname> avoids an incoming projectile.") ); } attack.missed_by = 1.0; // Arbitrary value return; } // Bounce applies whether it does damage or not. if( proj.proj_effects.count( "BOUNCE" ) ) { add_effect( effect_bounced, 1_turns ); } body_part bp_hit; double hit_value = missed_by + rng_float(-0.5, 0.5); // Headshots considered elsewhere if( hit_value <= 0.4 ) { bp_hit = bp_torso; } else if (one_in(4)) { if( one_in(2)) { bp_hit = bp_leg_l; } else { bp_hit = bp_leg_r; } } else { if( one_in(2)) { bp_hit = bp_arm_l; } else { bp_hit = bp_arm_r; } } double damage_mult = 1.0; std::string message = ""; game_message_type gmtSCTcolor = m_neutral; if( goodhit < accuracy_headshot ) { message = _("Headshot!"); gmtSCTcolor = m_headshot; damage_mult *= rng_float(1.95, 2.05); bp_hit = bp_head; // headshot hits the head, of course } else if( goodhit < accuracy_critical ) { message = _("Critical!"); gmtSCTcolor = m_critical; damage_mult *= rng_float(1.5, 2.0); } else if( goodhit < accuracy_goodhit ) { message = _("Good hit!"); gmtSCTcolor = m_good; damage_mult *= rng_float(1, 1.5); } else if( goodhit < accuracy_standard ) { damage_mult *= rng_float(0.5, 1); } else if( goodhit < accuracy_grazing ) { message = _("Grazing hit."); gmtSCTcolor = m_grazing; damage_mult *= rng_float(0, .25); } if( source != nullptr && !message.empty() ) { source->add_msg_if_player(m_good, message.c_str()); } attack.missed_by = goodhit; // copy it, since we're mutating damage_instance impact = proj.impact; if( damage_mult > 0.0f && proj_effects.count( "NO_DAMAGE_SCALING" ) ) { damage_mult = 1.0f; } impact.mult_damage(damage_mult); if( proj_effects.count( "NOGIB" ) > 0 ) { float dmg_ratio = (float)impact.total_damage() / get_hp_max( player::bp_to_hp( bp_hit ) ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } dealt_dam = deal_damage(source, bp_hit, impact); dealt_dam.bp_hit = bp_hit; // Apply ammo effects to target. if (proj.proj_effects.count("FLAME")) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, rng( 8_turns, 20_turns ), bp_hit ); } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) { add_effect( effect_onfire, rng( 5_turns, 10_turns ), bp_hit ); } } else if (proj.proj_effects.count("INCENDIARY") ) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, rng( 2_turns, 6_turns ), bp_hit ); } else if ( (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) && one_in(4) ) { add_effect( effect_onfire, rng( 1_turns, 4_turns ), bp_hit ); } } else if (proj.proj_effects.count("IGNITE")) { if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) || made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) || made_of( material_id( "wood" ) ) ) { add_effect( effect_onfire, 6_turns, bp_hit ); } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) { add_effect( effect_onfire, 10_turns, bp_hit ); } } if( bp_hit == bp_head && proj_effects.count( "BLINDS_EYES" ) ) { // TODO: Change this to require bp_eyes add_env_effect( effect_blind, bp_eyes, 5, rng( 3_turns, 10_turns ) ); } if( proj_effects.count( "APPLY_SAP" ) ) { add_effect( effect_sap, 1_turns * dealt_dam.total_damage() ); } int stun_strength = 0; if (proj.proj_effects.count("BEANBAG")) { stun_strength = 4; } if (proj.proj_effects.count("LARGE_BEANBAG")) { stun_strength = 16; } if( stun_strength > 0 ) { switch( get_size() ) { case MS_TINY: stun_strength *= 4; break; case MS_SMALL: stun_strength *= 2; break; case MS_MEDIUM: default: break; case MS_LARGE: stun_strength /= 2; break; case MS_HUGE: stun_strength /= 4; break; } add_effect( effect_stunned, 1_turns * rng( stun_strength / 2, stun_strength ) ); } if(u_see_this) { if( damage_mult == 0 ) { if( source != nullptr ) { add_msg( source->is_player() ? _("You miss!") : _("The shot misses!") ); } } else if( dealt_dam.total_damage() == 0 ) { //~ 1$ - monster name, 2$ - character's bodypart or monster's skin/armor add_msg( _("The shot reflects off %1$s %2$s!"), disp_name(true).c_str(), is_monster() ? skin_name().c_str() : body_part_name_accusative(bp_hit).c_str() ); } else if( is_player() ) { //monster hits player ranged //~ Hit message. 1$s is bodypart name in accusative. 2$d is damage value. add_msg_if_player(m_bad, _( "You were hit in the %1$s for %2$d damage." ), body_part_name_accusative(bp_hit).c_str(), dealt_dam.total_damage()); } else if( source != nullptr ) { if( source->is_player() ) { //player hits monster ranged SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(dealt_dam.total_damage(), get_hp_max(), true).first, m_good, message, gmtSCTcolor); if (get_hp() > 0) { SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(get_hp(), get_hp_max(), true).first, m_good, //~ "hit points", used in scrolling combat text _("hp"), m_neutral, "hp"); } else { SCT.removeCreatureHP(); } add_msg(m_good, _("You hit %s for %d damage."), disp_name().c_str(), dealt_dam.total_damage()); } else if( u_see_this ) { //~ 1$ - shooter, 2$ - target add_msg(_("%1$s shoots %2$s."), source->disp_name().c_str(), disp_name().c_str()); } } } check_dead_state(); attack.hit_critter = this; attack.missed_by = goodhit; }
void apply_ammo_effects( const tripoint &p, const std::set<std::string> &effects ) { if( effects.count( "EXPLOSIVE_SMALL" ) > 0 ) { g->explosion( p, 24, 0.4 ); } if( effects.count( "EXPLOSIVE" ) > 0 ) { g->explosion( p, 24 ); } if( effects.count( "FRAG" ) > 0 ) { explosion_data frag; frag.power = 1.0f; frag.shrapnel.count = 50; frag.shrapnel.mass = 5; frag.shrapnel.recovery = 100; frag.shrapnel.drop = "shrapnel"; g->explosion( p, frag ); } if( effects.count( "NAPALM" ) > 0 ) { g->explosion( p, 4, 0.7, true ); // More intense fire near the center for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_fire, 1, 0 ); } } if( effects.count( "NAPALM_BIG" ) > 0 ) { g->explosion( p, 24, 0.8, true ); // More intense fire near the center for( auto &pt : g->m.points_in_radius( p, 3, 0 ) ) { g->m.add_field( pt, fd_fire, 1, 0 ); } } if( effects.count( "MININUKE_MOD" ) > 0 ) { g->explosion( p, 450 ); for( auto &pt : g->m.points_in_radius( p, 6, 0 ) ) { if( g->m.sees( p, pt, 3 ) && g->m.passable( pt ) ) { g->m.add_field( pt, fd_nuke_gas, 3, 0 ); } } } if( effects.count( "ACIDBOMB" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_acid, 3, 0 ); } } if( effects.count( "EXPLOSIVE_BIG" ) > 0 ) { g->explosion( p, 40 ); } if( effects.count( "EXPLOSIVE_HUGE" ) > 0 ) { g->explosion( p, 80 ); } if( effects.count( "TOXICGAS" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_toxic_gas, 3, 0 ); } } if( effects.count( "GAS_FUNGICIDAL" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_fungicidal_gas, 3, 0 ); } } if( effects.count( "SMOKE" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_smoke, MAX_FIELD_DENSITY ); } } if( effects.count( "SMOKE_BIG" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 6, 0 ) ) { g->m.add_field( pt, fd_smoke, MAX_FIELD_DENSITY ); } } if( effects.count( "FLASHBANG" ) ) { g->flashbang( p ); } if( effects.count( "EMP" ) ) { g->emp_blast( p ); } if( effects.count( "NO_BOOM" ) == 0 && effects.count( "FLAME" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_fire, 1, 0 ); } } if( effects.count( "FLARE" ) > 0 ) { g->m.add_field( p, fd_fire, 1, 0 ); } if( effects.count( "LIGHTNING" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { g->m.add_field( pt, fd_electricity, 3, 0 ); } } if( effects.count( "PLASMA" ) > 0 ) { for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) { if( one_in( 2 ) ) { g->m.add_field( pt, fd_plasma, rng( 2, 3 ), 0 ); } } } }
int monster::trigger_sum(game *g, std::vector<monster_trigger> *triggers) { int ret = 0; bool check_terrain = false, check_meat = false, check_fire = false; for (int i = 0; i < triggers->size(); i++) { switch ((*triggers)[i]) { case MTRIG_TIME: if (one_in(20)) ret++; break; case MTRIG_MEAT: check_terrain = true; check_meat = true; break; case MTRIG_PLAYER_CLOSE: if (rl_dist(posx, posy, g->u.posx, g->u.posy) <= 5) ret += 5; for (int i = 0; i < g->active_npc.size(); i++) { if (rl_dist(posx, posy, g->active_npc[i].posx, g->active_npc[i].posy) <= 5) ret += 5; } break; case MTRIG_FIRE: check_terrain = true; check_fire = true; break; case MTRIG_PLAYER_WEAK: if (g->u.hp_percentage() <= 70) ret += 10 - int(g->u.hp_percentage() / 10); break; default: break; // The rest are handled when the impetus occurs } } if (check_terrain) { for (int x = posx - 3; x <= posx + 3; x++) { for (int y = posy - 3; y <= posy + 3; y++) { if (check_meat) { std::vector<item> *items = &(g->m.i_at(x, y)); for (int n = 0; n < items->size(); n++) { if ((*items)[n].type->id == "corpse" || (*items)[n].type->id == "meat" || (*items)[n].type->id == "meat_cooked" || (*items)[n].type->id == "human_flesh") { ret += 3; check_meat = false; } } } if (check_fire) { if (g->m.field_at(x, y).type == fd_fire) ret += 5 * g->m.field_at(x, y).density; } } } if (check_fire) { if (g->u.has_amount("torch_lit", 1)) ret += 49; } } return ret; }
void activity_handlers::forage_finish( player_activity *act, player *p ) { int veggy_chance = rng(1, 100); bool found_something = false; if( one_in(12) ) { add_msg(m_good, _("You found some trash!")); g->m.put_items_from_loc( "trash_forest", p->pos3(), calendar::turn ); found_something = true; } items_location loc; ter_id next_ter = t_null; //Just to have a default for compilers' sake switch (calendar::turn.get_season() ) { case SPRING: loc = "forage_spring"; next_ter = terfind("t_underbrush_harvested_spring"); break; case SUMMER: loc = "forage_summer"; next_ter = terfind("t_underbrush_harvested_summer"); break; case AUTUMN: loc = "forage_autumn"; next_ter = terfind("t_underbrush_harvested_autumn"); break; case WINTER: loc = "forage_winter"; next_ter = terfind("t_underbrush_harvested_winter"); break; } // Compromise: Survival gives a bigger boost, and Peception is leveled a bit. if( veggy_chance < ((p->skillLevel("survival") * 1.5) + ((p->per_cur / 2 - 4) + 3)) ) { // Returns zero if location has no defined items. int cnt = g->m.put_items_from_loc( loc, p->pos3(), calendar::turn ); if( cnt > 0 ) { add_msg(m_good, _("You found something!")); g->m.ter_set( act->placement, next_ter ); found_something = true; } } else { if( one_in(2) ) { g->m.ter_set( act->placement, next_ter ); } } if( !found_something ) { add_msg(_("You didn't find anything.")); } //Determinate maximum level of skill attained by foraging using ones intelligence score int max_forage_skill = 0; if( p->int_cur < 4 ) { max_forage_skill = 1; } else if( p->int_cur < 6 ) { max_forage_skill = 2; } else if( p->int_cur < 8 ) { max_forage_skill = 3; } else if( p->int_cur < 11 ) { max_forage_skill = 4; } else if( p->int_cur < 15 ) { max_forage_skill = 5; } else if( p->int_cur < 20 ) { max_forage_skill = 6; } else if( p->int_cur < 26 ) { max_forage_skill = 7; } else if( p->int_cur > 25 ) { max_forage_skill = 8; } const int max_exp {(max_forage_skill * 2) - (p->skillLevel("survival") * 2)}; //Award experience for foraging attempt regardless of success p->practice("survival", rng(1, max_exp), max_forage_skill); }
void game_loop() { int regen_timer = 0; _draw(); while (io::window_is_open() && _game_running) { if (!io::window_is_open()) break; bool do_update_action_list = false; int state = statestack_top(); if (get_action_list_size() == 0) { append_action_list(&player); for (size_t i = 0; i < current_dungeon->creatures.size(); i++) { append_action_list(current_dungeon->creatures.at(i)); } _regenerate_creatures(regen_timer); // Tick effects every turn. tick_effects(); // Update gas clouds and gas machines. update_gas_machines(current_dungeon); // Update stealth check_stealth(); // Respawn creatures if few enough. if (_count_enemies_in_current_dungeon() <= 3 && one_in(25)) { spawn_monsters(current_branch->name, current_dungeon, current_dungeon->level, random(0, 3), true); } // Increase turn count. turn_count++; } if (current_actor()->identity == IDENT_PLAYER) { int old_player_ap = player.ap; // Do this before taking action -> // Draws everything from the previous turn and also // shows all the previous turns messages. _draw(); if (state == STATE_READ_COMMAND) { _read_command(); } else if (state == STATE_EXAMINE || state == STATE_CAST_SPELL || state == STATE_ZAP_WAND) { _move_targeting_cursor(); } else if (state == STATE_CLOSE_DOOR || state == STATE_CHAT) { _select_direction(); } else if (state == STATE_TRAVEL) { _travel(); } do_update_action_list = old_player_ap != player.ap; if (do_update_action_list) { check_cloud_collisions(current_dungeon); // Check if player has gained a level. if (can_gain_level()) { gain_level(); } } } else { creature_t* actor = current_actor(); actor->update(); do_update_action_list = true; } if (do_update_action_list) { if (current_actor() && current_actor()->ap <= 0) { if (current_actor()->identity == IDENT_PLAYER) { tick_food_clock(); } // TODO: Regenerate creature here. current_actor()->ap += 100; remove_from_action_list(current_actor()); } sort_action_list(); } } }
void activity_handlers::pulp_do_turn( player_activity *act, player *p ) { const tripoint &pos = act->placement; static const int full_pulp_threshold = 4; const int move_cost = int(p->weapon.is_null() ? 80 : p->weapon.attack_time() * 0.8); // numbers logic: a str 8 character with a butcher knife (4 bash, 18 cut) // should have at least a 50% chance of damaging an intact zombie corpse (75 volume). // a str 8 character with a baseball bat (28 bash, 0 cut) should have around a 25% chance. int cut_power = p->weapon.type->melee_cut; // stabbing weapons are a lot less effective at pulping if( p->weapon.has_flag("STAB") || p->weapon.has_flag("SPEAR") ) { cut_power /= 2; } double pulp_power = sqrt((double)(p->str_cur + p->weapon.type->melee_dam)) * std::min(1.0, sqrt((double)(cut_power + 1))); pulp_power = std::min(pulp_power, (double)p->str_cur); pulp_power *= 20; // constant multiplier to get the chance right int moves = 0; int &num_corpses = act->index; // use this to collect how many corpse are pulped auto corpse_pile = g->m.i_at(pos); for( auto corpse = corpse_pile.begin(); corpse != corpse_pile.end(); ++corpse ) { if( !(corpse->is_corpse() && corpse->damage < full_pulp_threshold) ) { continue; // no corpse or already pulped } int damage = pulp_power / corpse->volume(); //Determine corpse's blood type. field_id type_blood = corpse->get_mtype()->bloodType(); do { moves += move_cost; const int mod_sta = ( (p->weapon.weight() / 100 ) + 20) * -1; p->mod_stat("stamina", mod_sta); // Increase damage as we keep smashing, // to insure that we eventually smash the target. if( x_in_y(pulp_power, corpse->volume()) ) { corpse->damage++; p->handle_melee_wear(); } // Splatter some blood around tripoint tmp = pos; if( type_blood != fd_null ) { for( tmp.x = pos.x - 1; tmp.x <= pos.x + 1; tmp.x++ ) { for( tmp.y = pos.y - 1; tmp.y <= pos.y + 1; tmp.y++ ) { if( !one_in(damage + 1) && type_blood != fd_null ) { g->m.add_field( tmp, type_blood, 1, 0 ); } } } } if( corpse->damage >= full_pulp_threshold ) { corpse->damage = full_pulp_threshold; corpse->active = false; num_corpses++; } if( moves >= p->moves ) { // enough for this turn; p->moves -= moves; return; } } while( corpse->damage < full_pulp_threshold ); } // If we reach this, all corpses have been pulped, finish the activity act->moves_left = 0; // TODO: Factor in how long it took to do the smashing. add_msg(ngettext("The corpse is thoroughly pulped.", "The corpses are thoroughly pulped.", num_corpses)); }
dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tripoint &source, const tripoint &target_arg, dispersion_sources dispersion, Creature *origin, const vehicle *in_veh ) { const bool do_animation = get_option<bool>( "ANIMATIONS" ); double range = rl_dist( source, target_arg ); Creature *target_critter = g->critter_at( target_arg ); double target_size = target_critter != nullptr ? target_critter->ranged_target_size() : g->m.ranged_target_size( target_arg ); projectile_attack_aim aim = projectile_attack_roll( dispersion, range, target_size ); // TODO: move to-hit roll back in here dealt_projectile_attack attack { proj_arg, nullptr, dealt_damage_instance(), source, aim.missed_by }; if( source == target_arg ) { // No suicidal shots debugmsg( "Projectile_attack targeted own square." ); return attack; } projectile &proj = attack.proj; const auto &proj_effects = proj.proj_effects; const bool stream = proj_effects.count( "STREAM" ) > 0 || proj_effects.count( "STREAM_BIG" ) > 0 || proj_effects.count( "JET" ) > 0; const char bullet = stream ? '#' : '*'; const bool no_item_damage = proj_effects.count( "NO_ITEM_DAMAGE" ) > 0; const bool do_draw_line = proj_effects.count( "DRAW_AS_LINE" ) > 0; const bool null_source = proj_effects.count( "NULL_SOURCE" ) > 0; // Determines whether it can penetrate obstacles const bool is_bullet = proj_arg.speed >= 200 && std::any_of( proj_arg.impact.damage_units.begin(), proj_arg.impact.damage_units.end(), []( const damage_unit & dam ) { return dam.type == DT_CUT; } ); // If we were targetting a tile rather than a monster, don't overshoot // Unless the target was a wall, then we are aiming high enough to overshoot const bool no_overshoot = proj_effects.count( "NO_OVERSHOOT" ) || ( g->critter_at( target_arg ) == nullptr && g->m.passable( target_arg ) ); double extend_to_range = no_overshoot ? range : proj_arg.range; tripoint target = target_arg; std::vector<tripoint> trajectory; if( aim.missed_by_tiles >= 1.0 ) { // We missed enough to target a different tile double dx = target_arg.x - source.x; double dy = target_arg.y - source.y; double rad = atan2( dy, dx ); // cap wild misses at +/- 30 degrees rad += ( one_in( 2 ) ? 1 : -1 ) * std::min( ARCMIN( aim.dispersion ), DEGREES( 30 ) ); // @todo: This should also represent the miss on z axis const int offset = std::min<int>( range, sqrtf( aim.missed_by_tiles ) ); int new_range = no_overshoot ? range + rng( -offset, offset ) : rng( range - offset, proj_arg.range ); new_range = std::max( new_range, 1 ); target.x = source.x + roll_remainder( new_range * cos( rad ) ); target.y = source.y + roll_remainder( new_range * sin( rad ) ); if( target == source ) { target.x = source.x + sgn( dx ); target.y = source.y + sgn( dy ); } // Don't extend range further, miss here can mean hitting the ground near the target range = rl_dist( source, target ); extend_to_range = range; sfx::play_variant_sound( "bullet_hit", "hit_wall", sfx::get_heard_volume( target ), sfx::get_heard_angle( target ) ); // TODO: Z dispersion // If we missed, just draw a straight line. trajectory = line_to( source, target ); } else { // Go around obstacles a little if we're on target. trajectory = g->m.find_clear_path( source, target ); } add_msg( m_debug, "missed_by_tiles: %.2f; missed_by: %.2f; target (orig/hit): %d,%d,%d/%d,%d,%d", aim.missed_by_tiles, aim.missed_by, target_arg.x, target_arg.y, target_arg.z, target.x, target.y, target.z ); // Trace the trajectory, doing damage in order tripoint &tp = attack.end_point; tripoint prev_point = source; trajectory.insert( trajectory.begin(), source ); // Add the first point to the trajectory static emit_id muzzle_smoke( "emit_smoke_plume" ); if( proj_effects.count( "MUZZLE_SMOKE" ) ) { g->m.emit_field( trajectory.front(), muzzle_smoke ); } if( !no_overshoot && range < extend_to_range ) { // Continue line is very "stiff" when the original range is short // @todo: Make it use a more distant point for more realistic extended lines std::vector<tripoint> trajectory_extension = continue_line( trajectory, extend_to_range - range ); trajectory.reserve( trajectory.size() + trajectory_extension.size() ); trajectory.insert( trajectory.end(), trajectory_extension.begin(), trajectory_extension.end() ); } // Range can be 0 size_t traj_len = trajectory.size(); while( traj_len > 0 && rl_dist( source, trajectory[traj_len - 1] ) > proj_arg.range ) { --traj_len; } const float projectile_skip_multiplier = 0.1; // Randomize the skip so that bursts look nicer int projectile_skip_calculation = range * projectile_skip_multiplier; int projectile_skip_current_frame = rng( 0, projectile_skip_calculation ); bool has_momentum = true; for( size_t i = 1; i < traj_len && ( has_momentum || stream ); ++i ) { prev_point = tp; tp = trajectory[i]; if( ( tp.z > prev_point.z && g->m.has_floor( tp ) ) || ( tp.z < prev_point.z && g->m.has_floor( prev_point ) ) ) { // Currently strictly no shooting through floor // TODO: Bash the floor tp = prev_point; traj_len = --i; break; } // Drawing the bullet uses player g->u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if( do_animation && !do_draw_line ) { // TODO: Make this draw thrown item/launched grenade/arrow if( projectile_skip_current_frame >= projectile_skip_calculation ) { g->draw_bullet( tp, ( int )i, trajectory, bullet ); projectile_skip_current_frame = 0; // If we missed recalculate the skip factor so they spread out. projectile_skip_calculation = std::max( ( size_t )range, i ) * projectile_skip_multiplier; } else { projectile_skip_current_frame++; } } if( in_veh != nullptr ) { int part; vehicle *other = g->m.veh_at( tp, part ); if( in_veh == other && other->is_inside( part ) ) { continue; // Turret is on the roof and can't hit anything inside } } Creature *critter = g->critter_at( tp ); if( origin == critter ) { // No hitting self with "weird" attacks. critter = nullptr; } monster *mon = dynamic_cast<monster *>( critter ); // ignore non-point-blank digging targets (since they are underground) if( mon != nullptr && mon->digging() && rl_dist( source, tp ) > 1 ) { critter = nullptr; mon = nullptr; } // Reset hit critter from the last iteration attack.hit_critter = nullptr; // If we shot us a monster... // TODO: add size effects to accuracy // 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 double cur_missed_by = aim.missed_by; // unintentional hit on something other than our actual target // don't re-roll for the actual target, we already decided on a missed_by value for that // at the start, misses should stay as misses if( critter != nullptr && tp != target_arg ) { // Unintentional hit cur_missed_by = std::max( rng_float( 0.1, 1.5 - aim.missed_by ) / critter->ranged_target_size(), 0.4 ); } if( critter != nullptr && cur_missed_by < 1.0 ) { if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh && critter->is_player() ) { // Turret either was aimed by the player (who is now ducking) and shoots from above // Or was just IFFing, giving lots of warnings and time to get out of the line of fire continue; } attack.missed_by = cur_missed_by; critter->deal_projectile_attack( null_source ? nullptr : origin, attack ); // Critter can still dodge the projectile // In this case hit_critter won't be set if( attack.hit_critter != nullptr ) { const size_t bt_len = blood_trail_len( attack.dealt_dam.total_damage() ); if( bt_len > 0 ) { const tripoint &dest = move_along_line( tp, trajectory, bt_len ); g->m.add_splatter_trail( critter->bloodType(), tp, dest ); } sfx::do_projectile_hit( *attack.hit_critter ); has_momentum = false; } else { attack.missed_by = aim.missed_by; } } else if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh ) { // Don't do anything, especially don't call map::shoot as this would damage the vehicle } else { g->m.shoot( tp, proj, !no_item_damage && tp == target ); has_momentum = proj.impact.total_damage() > 0; } if( ( !has_momentum || !is_bullet ) && g->m.impassable( tp ) ) { // Don't let flamethrowers go through walls // TODO: Let them go through bars traj_len = i; break; } } // Done with the trajectory! if( do_animation && do_draw_line && traj_len > 2 ) { trajectory.erase( trajectory.begin() ); trajectory.resize( traj_len-- ); g->draw_line( tp, trajectory ); g->draw_bullet( tp, int( traj_len-- ), trajectory, bullet ); } if( g->m.impassable( tp ) ) { tp = prev_point; } drop_or_embed_projectile( attack ); apply_ammo_effects( tp, proj.proj_effects ); const auto &expl = proj.get_custom_explosion(); if( expl.power > 0.0f ) { g->explosion( tp, proj.get_custom_explosion() ); } // TODO: Move this outside now that we have hit point in return values? if( proj.proj_effects.count( "BOUNCE" ) ) { // Add effect so the shooter is not targeted itself. if( origin && !origin->has_effect( effect_bounced ) ) { origin->add_effect( effect_bounced, 1 ); } Creature *mon_ptr = g->get_creature_if( [&]( const Creature & z ) { // search for creatures in radius 4 around impact site if( rl_dist( z.pos(), tp ) <= 4 && g->m.sees( z.pos(), tp, -1 ) ) { // don't hit targets that have already been hit if( !z.has_effect( effect_bounced ) ) { return true; } } return false; } ); if( mon_ptr ) { Creature &z = *mon_ptr; add_msg( _( "The attack bounced to %s!" ), z.get_name().c_str() ); z.add_effect( effect_bounced, 1 ); projectile_attack( proj, tp, z.pos(), dispersion, origin, in_veh ); sfx::play_variant_sound( "fire_gun", "bio_lightning_tail", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); } } return attack; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && ( has_flag( MF_ABSORBS ) || has_flag( MF_ABSORBS_SPLITS ) ) && !g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) { if( g->u.sees( *this ) ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); } static const auto volume_per_hp = units::from_milliliter( 250 ); for( auto &elem : g->m.i_at( pos() ) ) { hp += elem.volume() / volume_per_hp; // Yeah this means it can get more HP than normal. if( has_flag( MF_ABSORBS_SPLITS ) && hp * 2 > type->hp ) { for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) && hp * 2 > type->hp ) { if( monster *const spawn = g->summon_mon( type->id, dest ) ) { hp -= type->hp; //this is a new copy of the monster. Ideally we should copy the stats/effects that affect the parent spawn->make_ally( *this ); if( g->u.sees( *this ) ) { add_msg( _( "The %s splits in two!" ), name().c_str() ); } } } } } } g->m.i_clear( pos() ); } const bool pacified = has_effect( effect_pacified ); // First, use the special attack, if we can! // The attack may change `monster::special_attacks` (e.g. by transforming // this into another monster type). Therefore we can not iterate over it // directly and instead iterate over the map from the monster type // (properties of monster types should never change). for( const auto &sp_type : type->special_attacks ) { const std::string &special_name = sp_type.first; const auto local_iter = special_attacks.find( special_name ); if( local_iter == special_attacks.end() ) { continue; } mon_special_attack &local_attack_data = local_iter->second; if( !local_attack_data.enabled ) { continue; } // Cooldowns are decremented in monster::process_turn if( local_attack_data.cooldown == 0 && !pacified && !is_hallucination() ) { if( !sp_type.second->call( *this ) ) { continue; } // `special_attacks` might have changed at this point. Sadly `reset_special` // doesn't check the attack name, so we need to do it here. if( special_attacks.count( special_name ) == 0 ) { continue; } reset_special( special_name ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects( attacking ) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( effect_stunned ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos() ) { current_attitude = attitude( &( g->u ) ); } else { for( const npc &guy : g->all_npcs() ) { if( goal == guy.pos() ) { current_attitude = attitude( &guy ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist( pos(), goal ) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // If true, don't try to greedily avoid locally bad paths bool pathed = false; if( !wander() ) { while( !path.empty() && path.front() == pos() ) { path.erase( path.begin() ); } const auto &pf_settings = get_pathfinding_settings(); if( pf_settings.max_dist >= rl_dist( pos(), goal ) && ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != goal ) ) { // We need a new path path = g->m.route( pos(), goal, pf_settings, get_path_avoid() ); } // Try to respect old paths, even if we can't pathfind at the moment if( !path.empty() && path.back() == goal ) { destination = path.front(); moved = true; pathed = true; } else { // Straight line forward, probably because we can't pathfind (well enough) destination = goal; moved = true; } } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved && friendly == 0 ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); if( wander_pos != pos() ) { destination = wander_pos; moved = true; } } if( !g->m.has_zlevels() ) { // Otherwise weird things happen destination.z = posz(); } tripoint next_step; const bool staggers = has_flag( MF_STUMBLES ); const bool can_climb = has_flag( MF_CLIMBS ); if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = bash_skill() > 0; // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { if( candidate.z != posz() ) { bool can_z_move = true; if( !g->m.valid_move( pos(), candidate, false, true ) ) { // Can't phase through floor can_z_move = false; } // If we're trying to go up but can't fly, check if we can climb. If we can't, then don't // This prevents non-climb/fly enemies running up walls if( candidate.z > posz() && !can_fly ) { if( !can_climb || !g->m.has_floor_or_support( candidate ) ) { // Can't "jump" up a whole z-level can_z_move = false; } } // Last chance - we can still do the z-level stair teleport bullshit that isn't removed yet // @todo: Remove z-level stair bullshit teleport after aligning all stairs if( !can_z_move && posx() / ( SEEX * 2 ) == candidate.x / ( SEEX * 2 ) && posy() / ( SEEY * 2 ) == candidate.y / ( SEEY * 2 ) ) { const tripoint &upper = candidate.z > posz() ? candidate : pos(); const tripoint &lower = candidate.z > posz() ? pos() : candidate; if( g->m.has_flag( TFLAG_GOES_DOWN, upper ) && g->m.has_flag( TFLAG_GOES_UP, lower ) ) { can_z_move = true; } } if( !can_z_move ) { continue; } } // A flag to allow non-stumbling critters to stumble when the most direct choice is bad. bool bad_choice = false; const Creature *target = g->critter_at( candidate, is_hallucination() ); if( target != nullptr ) { const Creature::Attitude att = attitude_to( *target ); if( att == A_HOSTILE ) { // When attacking an adjacent enemy, we're direct. moved = true; next_step = candidate; break; } else if( att == A_FRIENDLY && ( target->is_player() || target->is_npc() ) ) { continue; // Friendly firing the player or an NPC is illegal for gameplay reasons } else if( !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { // Bail out if there's a non-hostile monster in the way and we're not pushy. continue; } // Friendly fire and pushing are always bad choices - they take a lot of time bad_choice = true; } // Bail out if we can't move there and we can't bash. if( !pathed && !can_move_to( candidate ) ) { if( !can_bash ) { continue; } const int estimate = g->m.bash_rating( bash_estimate(), candidate ); if( estimate <= 0 ) { continue; } if( estimate < 5 ) { bad_choice = true; } } const float progress = distance_to_target - trig_dist( candidate, destination ); // The x2 makes the first (and most direct) path twice as likely, // since the chance of switching is 1/1, 1/4, 1/6, 1/8 switch_chance += progress * 2; // Randomly pick one of the viable squares to move to weighted by distance. if( !moved || x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. // Except if the direct path is bad, then check others // Or if the path is given by pathfinder if( !staggers && ( !bad_choice || pathed ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, get_stagger_adjust( pos(), destination, next_step ) ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); path.clear(); } }
int monster::trigger_sum(std::set<monster_trigger> *triggers) const { int ret = 0; bool check_terrain = false, check_meat = false, check_fire = false; for (auto trig = triggers->begin(); trig != triggers->end(); ++trig){ switch (*trig) { case MTRIG_STALK: if (anger > 0 && one_in(20)) { ret++; } break; case MTRIG_MEAT: check_terrain = true; check_meat = true; break; case MTRIG_PLAYER_CLOSE: if (rl_dist(_posx, _posy, g->u.posx, g->u.posy) <= 5) { ret += 5; } for (auto &i : g->active_npc) { if (rl_dist(_posx, _posy, i->posx, i->posy) <= 5) { ret += 5; } } break; case MTRIG_FIRE: check_terrain = true; check_fire = true; break; case MTRIG_PLAYER_WEAK: if (g->u.hp_percentage() <= 70) { ret += 10 - int(g->u.hp_percentage() / 10); } break; default: break; // The rest are handled when the impetus occurs } } if (check_terrain) { for (int x = _posx - 3; x <= _posx + 3; x++) { for (int y = _posy - 3; y <= _posy + 3; y++) { if (check_meat) { std::vector<item> *items = &(g->m.i_at(x, y)); for (size_t n = 0; n < items->size(); n++) { if ((*items)[n].type->id == "corpse" || (*items)[n].type->id == "meat" || (*items)[n].type->id == "meat_cooked" || (*items)[n].type->id == "human_flesh") { ret += 3; check_meat = false; } } } if (check_fire) { ret += ( 5 * g->m.get_field_strength( point(x, y), fd_fire) ); } } } if (check_fire) { if (g->u.has_amount("torch_lit", 1)) { ret += 49; } } } return ret; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) ) { if( !g->m.i_at( pos3() ).empty() ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); for( auto &elem : g->m.i_at( pos3() ) ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear( pos3() ); } } static const std::string pacified_string = "pacified"; const bool pacified = has_effect( pacified_string ); // First, use the special attack, if we can! for( size_t i = 0; i < sp_timeout.size(); ++i ) { if( sp_timeout[i] > 0 ) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && !pacified && !is_hallucination() ) { type->sp_attack[i]( this, i ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects(attacking) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } static const std::string stun_string = "stunned"; if( has_effect( stun_string ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos3() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( goal == i->pos3() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist(pos(), goal) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // CONCRETE PLANS - Most likely based on sight if( !wander() ) { destination = goal; moved = true; } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); tripoint tmp = wander_next(); if( tmp != pos() ) { destination = tmp; moved = true; } } tripoint next_step; if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { const Creature *target = g->critter_at( candidate, is_hallucination() ); // When attacking an adjacent enemy, we're direct. if( target != nullptr && attitude_to( *target ) == A_HOSTILE ) { moved = true; next_step = candidate; break; } // Bail out if we can't move there and we can't bash. if( !can_move_to( candidate ) && !(can_bash && g->m.bash_rating( bash_estimate(), candidate ) >= 0 ) ) { continue; } // Bail out if there's a non-hostile monster in the way and we're not pushy. if( target != nullptr && attitude_to( *target ) != A_HOSTILE && !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { continue; } float progress = distance_to_target - trig_dist( candidate, destination ); switch_chance += progress; // Randomly pick one of the viable squares to move to weighted by distance. if( x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. if( !has_flag( MF_STUMBLES ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen // move_to() uses the slope to determine some move speed scaling. const float slope = (destination.x > destination.y) ? (float)destination.y / (float)destination.x : (float)destination.x / (float)destination.y; const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, slope ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); } }
void monster::melee_attack(Creature &target, bool, matec_id) { mod_moves(-100); if (type->melee_dice == 0) { // We don't attack, so just return return; } add_effect("hit_by_player", 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect("run", 4); } bool u_see_me = g->u_see(this); body_part bp_hit; //int highest_hit = 0; damage_instance damage; if(!is_hallucination()) { if (type->melee_dice > 0) { damage.add_damage(DT_BASH, dice(type->melee_dice,type->melee_sides)); } if (type->melee_cut > 0) { damage.add_damage(DT_CUT, type->melee_cut); } } dealt_damage_instance dealt_dam; int hitspread = target.deal_melee_attack(this, hit_roll()); if (hitspread >= 0) { target.deal_melee_hit(this, hitspread, false, damage, dealt_dam); } bp_hit = dealt_dam.bp_hit; if (hitspread < 0) { // a miss // TODO: characters practice dodge when a hit misses 'em if (target.is_player()) { if (u_see_me) { add_msg(_("You dodge %s."), disp_name().c_str()); } else { add_msg(_("You dodge an attack from an unseen source.")); } } else { if (u_see_me) { add_msg(_("The %1$s dodges %2$s attack."), name().c_str(), target.disp_name(true).c_str()); } } //Hallucinations always produce messages but never actually deal damage } else if (is_hallucination() || dealt_dam.total_damage() > 0) { if (target.is_player()) { if (u_see_me) { //~ 1$s is attacker name, 2$s is bodypart name in accusative. add_msg(m_bad, _("The %1$s hits your %2$s."), name().c_str(), body_part_name_accusative(bp_hit).c_str()); } else { //~ %s is bodypart name in accusative. add_msg(m_bad, _("Something hits your %s."), body_part_name_accusative(bp_hit).c_str()); } } else { if (u_see_me) { //~ 1$s is attacker name, 2$s is target name, 3$s is bodypart name in accusative. add_msg(_("The %1$s hits %2$s %3$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name_accusative(bp_hit).c_str()); } } } else { if (target.is_player()) { if (u_see_me) { //~ 1$s is attacker name, 2$s is bodypart name in accusative, 3$s is armor name add_msg(_("The %1$s hits your %2$s, but your %3$s protects you."), name().c_str(), body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str()); } else { //~ 1$s is bodypart name in accusative, 2$s is armor name. add_msg(_("Something hits your %1$s, but your %2$s protects you."), body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str()); } } else { if (u_see_me) { //~ $1s is monster name, %2$s is that monster target name, //~ $3s is target bodypart name in accusative, 4$s is target armor name. add_msg(_("The %1$s hits %2$s %3$s but is stopped by %2$s %4$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str()); } } } if (is_hallucination()) { if(one_in(7)) { die( nullptr ); } return; } // 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 (size_t i = 0; i < g->num_zombies(); i++) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move(game *g) { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if (wandf > 0) wandf--; // First, use the special attack, if we can! if (sp_timeout > 0) sp_timeout--; if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; (ma.*type->sp_attack)(g, this); } if (moves < 0) return; if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect(ME_STUNNED)) { stumble(g, false); moves = 0; return; } if (has_effect(ME_DOWNED)) { moves = 0; return; } if (friendly != 0) { if (friendly > 0) friendly--; friendly_move(g); return; } moves -= 100; bool moved = false; point next; int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); if (friendly == 0) current_attitude = attitude(&(g->u)); // If our plans end in a player, set our attitude to consider that player if (plans.size() > 0) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) current_attitude = attitude(&(g->u)); else { for (int i = 0; i < g->active_npc.size(); i++) { if (plans.back().x == g->active_npc[i].posx && plans.back().y == g->active_npc[i].posy) current_attitude = attitude(&(g->active_npc[i])); } } } if (current_attitude == MATT_IGNORE || (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) { stumble(g, false); return; } if (plans.size() > 0 && !is_fleeing(g->u) && (mondex == -1 || g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g->m, plans[0].x, plans[0].y) || (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if (has_flag(MF_SMELLS)) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. point tmp = scent_move(g); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound point tmp = sound_move(g); if (tmp.x != posx || tmp.y != posy) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if (moved) { // Actual effects of moving to the square we've chosen mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); if (next.x == g->u.posx && next.y == g->u.posy && type->melee_dice > 0) hit_player(g, g->u); else if (mondex != -1 && type->melee_dice > 0 && (g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON))) hit_monster(g, mondex); else if (npcdex != -1 && type->melee_dice > 0) hit_player(g, g->active_npc[npcdex]); else if ((!can_move_to(g->m, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } else if (can_move_to(g->m, next.x, next.y) && g->is_empty(next.x, next.y)) move_to(g, next.x, next.y); else moves -= 100; } // If we're close to our target, we get focused and don't stumble if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.size() == 0)) || !moved) stumble(g, moved); }
void map::mon_in_field(int x, int y, game *g, monster *z) { if (z->has_flag(MF_DIGS)) return; // Digging monsters are immune to fields field *cur = &field_at(x, y); int dam = 0, j; switch (cur->type) { case fd_null: case fd_blood: // It doesn't actually do anything case fd_bile: // Ditto break; case fd_acid: if (!z->has_flag(MF_DIGS) && !z->has_flag(MF_ACIDPROOF)) { if (cur->density == 3) dam = rng(4, 10) + rng(2, 8); else dam = rng(cur->density, cur->density * 4); } break; case fd_fire: if (z->made_of(FLESH)) dam = 3; if (z->made_of(VEGGY)) dam = 12; if (z->made_of(PAPER) || z->made_of(LIQUID) || z->made_of(POWDER) || z->made_of(WOOD) || z->made_of(COTTON) || z->made_of(WOOL)) dam = 50; if (z->made_of(STONE) || z->made_of(KEVLAR) || z->made_of(STEEL)) dam = -25; if (z->has_flag(MF_FLIES)) dam -= 20; if (cur->density == 1) dam += rng(0, 8); else if (cur->density == 2) { dam += rng(3, 12); if (!z->has_flag(MF_FLIES)) { z->moves -= 20; if (!z->made_of(LIQUID) && !z->made_of(STONE) && !z->made_of(KEVLAR) && !z->made_of(STEEL) && !z->has_flag(MF_FIREY)) z->add_effect(ME_ONFIRE, rng(3, 8)); } } else if (cur->density == 3) { dam += rng(5, 18); if (!z->has_flag(MF_FLIES) || one_in(3)) { z->moves -= 40; if (!z->made_of(LIQUID) && !z->made_of(STONE) && !z->made_of(KEVLAR) && !z->made_of(STEEL) && !z->has_flag(MF_FIREY)) z->add_effect(ME_ONFIRE, rng(8, 12)); } } break; case fd_smoke: if (cur->density == 3) z->speed -= rng(10, 20); if (z->made_of(VEGGY)) // Plants suffer from smoke even worse z->speed -= rng(1, cur->density * 12); break; case fd_tear_gas: if (cur->density == 3) { z->speed -= rng(30, 60); dam = rng(8, 20); } else if (cur->density == 2) { z->speed -= rng(10, 25); dam = rng(4, 10); } else z->speed -= rng(0, 6); if (z->made_of(VEGGY)) { z->speed -= rng(cur->density * 5, cur->density * 12); dam += cur->density * 10; } break; case fd_nuke_gas: if (cur->density == 3) { z->speed -= rng(60, 120); dam = rng(30, 50); } else if (cur->density == 2) { z->speed -= rng(20, 50); dam = rng(10, 25); } else { z->speed -= rng(0, 15); dam = rng(0, 12); } if (z->made_of(VEGGY)) { z->speed -= rng(cur->density * 5, cur->density * 12); dam *= cur->density; } break; case fd_electricity: dam = rng(1, cur->density); if (one_in(8 - cur->density) && one_in(z->armor())) z->moves -= cur->density * 150; break; case fd_fatigue: if (rng(0, 2) < cur->density) { dam = cur->density; int tries = 0; int newposx, newposy; do { newposx = rng(z->posx - SEEX, z->posx + SEEX); newposy = rng(z->posy - SEEY, z->posy + SEEY); tries++; } while (g->m.move_cost(newposx, newposy) == 0 && tries != 10); if (tries == 10) g->explode_mon(g->mon_at(z->posx, z->posy)); else { int mon_hit = g->mon_at(newposx, newposy), t; if (mon_hit != -1) { if (g->u_see(z, t)) g->add_msg("The %s teleports into a %s, killing them both!", z->name().c_str(), g->z[mon_hit].name().c_str()); g->explode_mon(mon_hit); } else { z->posx = newposx; z->posy = newposy; } } } break; } z->hurt(dam); }