/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble( bool moved ) { // don't stumble every turn. every 3rd turn, or 8th when walking. if( ( moved && !one_in( 8 ) ) || !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( "SWIMMABLE", dest ) && !g->m.has_flag( "SWIMMABLE", pos3() ) ) && g->critter_at( dest ) == nullptr ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up // It should happen during "shambling around", but not as actual stumbling if( !moved && one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int bresenham_slope, junk; if( !plans.empty() ) { if( g->m.sees( pos3(), plans.back(), -1, bresenham_slope, junk ) ) { set_dest( plans.back(), bresenham_slope ); } else if( sees( g->u, bresenham_slope ) ) { set_dest( g->u.pos(), bresenham_slope ); } else { //durr, i'm suddenly calm. what was i doing? plans.clear(); } } }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(bool moved) { // don't stumble every turn. every 3rd turn, or 8th when walking. if((moved && !one_in(8)) || !one_in(3)) { return; } std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { const int nx = posx() + i; const int ny = posy() + j; if ((i || j) && can_move_to(nx, ny) && /* Don't ever stumble into impassable terrain, even if we normally could * smash it, as this is uncoordinated movement (and is forced). */ g->m.move_cost(nx, ny) != 0 && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !(has_flag(MF_NO_BREATHE) && !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC) && g->m.has_flag("SWIMMABLE", nx, ny) && !g->m.has_flag("SWIMMABLE", posx(), posy())) && (g->u.posx != nx || g->u.posy != ny) && (g->mon_at(nx, ny) == -1)) { point tmp(nx, ny); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.size() == 0) //nowhere to stumble? { return; } int choice = rng(0, valid_stumbles.size() - 1); int cx = valid_stumbles[choice].x; int cy = valid_stumbles[choice].y; moves -= calc_movecost(posx(), posy(), cx, cy); setpos(cx, cy); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int tc; if (plans.size() > 0) { if (g->m.sees(posx(), posy(), plans.back().x, plans.back().y, -1, tc)) set_dest(plans.back().x, plans.back().y, tc); else if (sees_player( tc )) set_dest(g->u.posx, g->u.posy, tc); else //durr, i'm suddenly calm. what was i doing? plans.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( const auto &trigger : *triggers ) { switch( trigger ) { case MTRIG_STALK: if (anger > 0 && one_in(20)) { ret++; } break; case MTRIG_MEAT: check_terrain = true; check_meat = true; break; case MTRIG_FIRE: check_terrain = true; check_fire = true; 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) { auto items = g->m.i_at(x, y); for( auto &item : items ) { if( item.is_corpse() || item.type->id == "meat" || item.type->id == "meat_cooked" || item.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; }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(bool moved) { // don't stumble every turn. every 3rd turn, or 8th when walking. if((moved && !one_in(8)) || !one_in(3)) { return; } std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { const int nx = posx() + i; const int ny = posy() + j; if ((i || j) && can_move_to(nx, ny) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !(has_flag(MF_NO_BREATHE) && !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC) && g->m.has_flag("SWIMMABLE", nx, ny) && !g->m.has_flag("SWIMMABLE", posx(), posy())) && (g->u.posx() != nx || g->u.posy() != ny) && (g->mon_at(nx, ny) == -1) && (g->npc_at(nx, ny) == -1) ) { point tmp(nx, ny); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.empty()) //nowhere to stumble? { return; } int choice = rng(0, valid_stumbles.size() - 1); int cx = valid_stumbles[choice].x; int cy = valid_stumbles[choice].y; move_to( cx, cy, false ); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int bresenham_slope; if (!plans.empty()) { if (g->m.sees( pos(), plans.back(), -1, bresenham_slope)) set_dest(plans.back().x, plans.back().y, bresenham_slope); else if (sees( g->u, bresenham_slope )) set_dest(g->u.posx(), g->u.posy(), bresenham_slope); else //durr, i'm suddenly calm. what was i doing? plans.clear(); } }
point monster::scent_move() { std::vector<point> smoves; int maxsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if (has_flag(MF_KEENNOSE)) { maxsmell = 1; smell_threshold = 400; } int minsmell = 9999; point pbuff, next(-1, -1); unsigned int smell; const bool fleeing = is_fleeing(g->u); if( !fleeing && g->scent( posx(), posy() ) > smell_threshold ) { return next; } for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { const int nx = posx() + x; const int ny = posy() + y; smell = g->scent(nx, ny); int mon = g->mon_at(nx, ny); if ((mon == -1 || g->zombie(mon).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(nx, ny) || (nx == g->u.posx && ny == g->u.posy) || (g->m.has_flag("BASHABLE", nx, ny) && has_flag(MF_BASHES)))) { if ((!fleeing && smell > maxsmell ) || ( fleeing && smell < minsmell ) ) { smoves.clear(); pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); maxsmell = smell; minsmell = smell; } else if ((!fleeing && smell == maxsmell ) || ( fleeing && smell == minsmell ) ) { pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); } } } } if (smoves.size() > 0) { int nextsq = rng(0, smoves.size() - 1); next = smoves[nextsq]; } return next; }
void Enemy::Process() { auto obj = GetMap()->GetNearest(pixel_x(), pixel_y()); if (obj != nullptr) ProcessSpeed(obj->posx() * 32, obj->posy() * 32); ProcessMove(); ProcessHealth(); }
int monster::turns_to_reach(int x, int y) { std::vector<point> path = g->m.route(posx(), posy(), x, y, has_flag(MF_BASHES)); if (path.size() == 0) return 999; double turns = 0.; for (int i = 0; i < path.size(); i++) { if (g->m.move_cost(path[i].x, path[i].y) == 0) // We have to bash through turns += 5; else if (i == 0) turns += double(calc_movecost(posx(), posy(), path[i].x, path[i].y)) / speed; else turns += double(calc_movecost(path[i-1].x, path[i-1].y, path[i].x, path[i].y)) / speed; } return int(turns + .9); // Round up }
/** * Stumble in a random direction, but with some caveats. */ void monster::stumble( ) { // Only move every 3rd turn. if( !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( TFLAG_SWIMMABLE, dest ) && !g->m.has_flag( TFLAG_SWIMMABLE, pos3() ) ) && ( g->critter_at( dest, is_hallucination() ) == nullptr ) ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up if( one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); }
int monster::turns_to_reach(int x, int y) { std::vector<point> path = g->m.route(posx(), posy(), x, y, false); if (path.empty()) return 999; double turns = 0.; for (size_t i = 0; i < path.size(); i++) { if (g->m.move_cost(path[i].x, path[i].y) == 0) { // We have to bash through turns += 5; } else if (i == 0) { turns += double(calc_movecost(posx(), posy(), path[i].x, path[i].y)) / get_speed(); } else { turns += double(calc_movecost(path[i-1].x, path[i-1].y, path[i].x, path[i].y)) / get_speed(); } } return int(turns + .9); // Round up }
void monster::drop_items_on_death() { if(is_hallucination()) { return; } if (type->death_drops.empty()) { return; } g->m.put_items_from_loc( type->death_drops, posx(), posy(), calendar::turn ); }
bool monster::setpos(const int x, const int y, const bool level_change) { if (!level_change && x == posx() && y == posy()) { return true; } bool ret = level_change ? true : g->update_zombie_pos(*this, x, y); position.x = x; position.y = y; return ret; }
/* * Drawing-related functions */ void Creature::draw(WINDOW *w, int player_x, int player_y, bool inverted) const { int draw_x = getmaxx(w) / 2 + posx() - player_x; int draw_y = getmaxy(w) / 2 + posy() - player_y; if(inverted) { mvwputch_inv(w, draw_y, draw_x, basic_symbol_color(), symbol()); } else if(is_symbol_highlighted()) { mvwputch_hi(w, draw_y, draw_x, basic_symbol_color(), symbol()); } else { mvwputch(w, draw_y, draw_x, symbol_color(), symbol() ); } }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach(int x, int y) { monster_attitude att = attitude(&(g->u)); if (att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND) return false; if (has_flag(MF_DIGS)) return false; if (has_flag(MF_IMMOBILE) && (posx() != x || posy() != y)) return false; std::vector<point> path = g->m.route(posx(), posy(), x, y, has_flag(MF_BASHES)); if (path.size() == 0) return false; if (has_flag(MF_SMELLS) && g->scent(posx(), posy()) > 0 && g->scent(x, y) > g->scent(posx(), posy())) return true; if (can_hear() && wandf > 0 && rl_dist(wandx, wandy, x, y) <= 2 && rl_dist(posx(), posy(), wandx, wandy) <= wandf) return true; int t; if (can_see() && g->m.sees(posx(), posy(), x, y, g->light_level(), t)) return true; return false; }
void monster::draw(WINDOW *w, int plx, int ply, bool inv) { int x = getmaxx(w)/2 + posx() - plx; int y = getmaxy(w)/2 + posy() - ply; nc_color color = type->color; if (friendly != 0 && !inv) mvwputch_hi(w, y, x, color, type->sym); else if (inv) mvwputch_inv(w, y, x, color, type->sym); else { color = color_with_effects(); mvwputch(w, y, x, color, type->sym); } }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach( int x, int y ) { monster_attitude att = attitude( &( g->u ) ); if( att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND && att != MATT_ZLAVE ) { return false; } if( has_flag( MF_DIGS ) || has_flag( MF_AQUATIC ) ) { return false; } if( has_flag( MF_IMMOBILE ) && ( posx() != x || posy() != y ) ) { return false; } std::vector<tripoint> path = g->m.route( pos(), tripoint(x, y, posz()), 0, 100 ); if( path.empty() ) { return false; } if( has_flag( MF_SMELLS ) && g->scent( pos3() ) > 0 && g->scent( { x, y, posz() } ) > g->scent( pos3() ) ) { return true; } if( can_hear() && wandf > 0 && rl_dist( wander_pos.x, wander_pos.y, x, y ) <= 2 && rl_dist( posx(), posy(), wander_pos.x, wander_pos.y ) <= wandf ) { return true; } int t; if( can_see() && g->m.sees( posx(), posy(), x, y, g->light_level(), t ) ) { return true; } return false; }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach( int x, int y ) { monster_attitude att = attitude( &( g->u ) ); if( att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND && att != MATT_ZLAVE ) { return false; } if( has_flag( MF_DIGS ) || has_flag( MF_AQUATIC ) ) { return false; } if( has_flag( MF_IMMOBILE ) && ( posx() != x || posy() != y ) ) { return false; } auto path = g->m.route( pos(), tripoint( x, y, posz() ), get_pathfinding_settings() ); if( path.empty() ) { return false; } if( has_flag( MF_SMELLS ) && g->scent.get( pos() ) > 0 && g->scent.get( { x, y, posz() } ) > g->scent.get( pos() ) ) { return true; } if( can_hear() && wandf > 0 && rl_dist( wander_pos.x, wander_pos.y, x, y ) <= 2 && rl_dist( posx(), posy(), wander_pos.x, wander_pos.y ) <= wandf ) { return true; } if( can_see() && sees( tripoint( x, y, posz() ) ) ) { return true; } return false; }
void Creature::draw( const catacurses::window &w, const tripoint &p, bool inverted ) const { if (is_draw_tiles_mode()) { return; } int draw_x = getmaxx(w) / 2 + posx() - p.x; int draw_y = getmaxy(w) / 2 + posy() - p.y; if(inverted) { mvwputch_inv(w, draw_y, draw_x, basic_symbol_color(), symbol()); } else if(is_symbol_highlighted()) { mvwputch_hi(w, draw_y, draw_x, basic_symbol_color(), symbol()); } else { mvwputch(w, draw_y, draw_x, symbol_color(), symbol() ); } }
bool Character::i_add_or_drop(item& it, int qty) { bool retval = true; bool drop = false; inv.assign_empty_invlet(it); for (int i = 0; i < qty; ++i) { if (!drop && (!can_pickWeight(it.weight(), !OPTIONS["DANGEROUS_PICKUPS"]) || !can_pickVolume(it.volume()))) { drop = true; } if (drop) { retval &= g->m.add_item_or_charges(posx(), posy(), it); } else { i_add(it); } } return retval; }
void print_counter() { UBYTE cnt; /* Ensure mutual exclusion (not really necessary in this example)... */ disable_interrupts(); cnt = tim_cnt; enable_interrupts(); printf(" TIM %u", cnt); gotoxy(9, posy()); /* Ensure mutual exclusion (not really necessary in this example)... */ disable_interrupts(); cnt = vbl_cnt; enable_interrupts(); printf("- VBL %u\n", cnt); }
/** Create or adjust "pos" parameter for a component * Assumed that name either equals "x", "y" or "z" otherwise this * method will not add or modify "pos" parameter * @param comp :: Component * @param name :: name of the parameter * @param value :: value * @param pDescription :: a pointer (may be NULL) to a string, containing * parameter's * description. If provided, the contents of the string is copied to the * parameters * memory */ void ParameterMap::addPositionCoordinate( const IComponent *comp, const std::string &name, const double value, const std::string *const pDescription) { Parameter_sptr param = get(comp, pos()); V3D position; if (param) { // so "pos" already defined position = param->value<V3D>(); } else { // so "pos" is not defined - therefore get position from component position = comp->getPos(); } // adjust position if (name.compare(posx()) == 0) position.setX(value); else if (name.compare(posy()) == 0) position.setY(value); else if (name.compare(posz()) == 0) position.setZ(value); else { g_log.warning() << "addPositionCoordinate() called with unrecognized " "coordinate symbol: " << name; // set description if one is provided if (pDescription) { param->setDescription(*pDescription); } return; } // clear the position cache clearPositionSensitiveCaches(); // finally add or update "pos" parameter addV3D(comp, pos(), position, pDescription); }
void Bezier() { glClear(GL_COLOR_BUFFER_BIT); glLineWidth(5); float PuntosdeControl[7][3] = { {411.0,posy(249.0),0.0}, {505.0,posy(274.0),0.0}, {597.0,posy(239.0),0.0}, {620.0,posy(137.0),0.0}, {518.0,posy(84.0),0.0}, {414.0,posy(150.0),0.0}, {412.0,posy(248.0),0.0} }; glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,7,*PuntosdeControl); glEnable(GL_MAP1_VERTEX_3); glMapGrid1f(100,0.0,1.0); glColor3f(1,0,0); glEvalMesh1(GL_LINE,0,100); glDisable(GL_MAP1_VERTEX_3); glFlush(); }
bool monster::digging() { return has_flag(MF_DIGS) || (has_flag(MF_CAN_DIG) && g->m.has_flag("DIGGABLE", posx(), posy())); }
void monster::plan(const std::vector<int> &friendlies) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc = 0; int stc = 0; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0, numz = g->num_zombies(); i < numz; i++) { monster *tmp = &(g->zombie(i)); if (tmp->friendly == 0) { int d = rl_dist(posx(), posy(), tmp->posx(), tmp->posy()); if (d < dist && g->m.sees(posx(), posy(), tmp->posx(), tmp->posy(), sightrange, tc)) { closest = i; dist = d; stc = tc; } } } if (has_effect("docile")) { closest = -1; } if (closest >= 0) { set_dest(g->zombie(closest).posx(), g->zombie(closest).posy(), stc); } else if (friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if (friendly < 0 && sees_player( tc ) ) { if (rl_dist(posx(), posy(), g->u.posx, g->u.posy) > 2) { set_dest(g->u.posx, g->u.posy, tc); } else { plans.clear(); } } return; } // If we can see, and we can see a character, move toward them or flee. if (can_see() && sees_player( tc ) ) { dist = rl_dist(posx(), posy(), g->u.posx, g->u.posy); if (is_fleeing(g->u)) { // Wander away. fleeing = true; set_dest(posx() * 2 - g->u.posx, posy() * 2 - g->u.posy, tc); } else { // Chase the player. closest = -2; stc = tc; } } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx(), posy(), me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx(), posy(), me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; set_dest(posx() * 2 - me->posx, posy() * 2 - me->posy, tc); \ } else { closest = i; stc = tc; } dist = medist; } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; if (can_see()) { for (int f = 0, numf = friendlies.size(); f < numf; f++) { const int i = friendlies[f]; monster *mon = &(g->zombie(i)); int mondist = rl_dist(posx(), posy(), mon->posx(), mon->posy()); if (mondist < dist && g->m.sees(posx(), posy(), mon->posx(), mon->posy(), sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx() * 2 - mon->posx(); wandy = posy() * 2 - mon->posy(); wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (closest == -2) { if (one_in(2)) {//random for the diversity of the trajectory ++stc; } else { --stc; } set_dest(g->u.posx, g->u.posy, stc); } else if (closest <= -3) set_dest(g->zombie(-3 - closest).posx(), g->zombie(-3 - closest).posy(), stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
void monster::knock_back_from(int x, int y) { if (x == posx() && y == posy()) return; // No effect point to(posx(), posy()); if (x < posx()) to.x++; if (x > posx()) to.x--; if (y < posy()) to.y++; if (y > posy()) to.y--; bool u_see = g->u_see(to.x, to.y); // First, see if we hit another monster int mondex = g->mon_at(to.x, to.y); if (mondex != -1) { monster *z = &(g->zombie(mondex)); hurt(z->type->size); add_effect("stunned", 1); if (type->size > 1 + z->type->size) { z->knock_back_from(posx(), posy()); // Chain reaction! z->hurt(type->size); z->add_effect("stunned", 1); } else if (type->size > z->type->size) { z->hurt(type->size); z->add_effect("stunned", 1); } if (u_see) g->add_msg(_("The %s bounces off a %s!"), name().c_str(), z->name().c_str()); return; } int npcdex = g->npc_at(to.x, to.y); if (npcdex != -1) { npc *p = g->active_npc[npcdex]; hurt(3); add_effect("stunned", 1); p->hit(this, bp_torso, -1, type->size, 0); if (u_see) g->add_msg(_("The %s bounces off %s!"), name().c_str(), p->name.c_str()); return; } // If we're still in the function at this point, we're actually moving a tile! if (g->m.ter_at(to.x, to.y).has_flag(TFLAG_DEEP_WATER)) { if (g->m.has_flag("LIQUID", to.x, to.y) && can_drown()) { hurt(9999); if (u_see) { g->add_msg(_("The %s drowns!"), name().c_str()); } } else if (has_flag(MF_AQUATIC)) { // We swim but we're NOT in water hurt(9999); if (u_see) { g->add_msg(_("The %s flops around and dies!"), name().c_str()); } } } if (g->m.move_cost(to.x, to.y) == 0) { // It's some kind of wall. hurt(type->size); add_effect("stunned", 2); if (u_see) { g->add_msg(_("The %s bounces off a %s."), name().c_str(), g->m.tername(to.x, to.y).c_str()); } } else { // It's no wall setpos(to); } }
int monster::move_to(int x, int y, bool force) { // Make sure that we can move there, unless force is true. if(!force) if(!g->is_empty(x, y) || !can_move_to(x, y)) { return 0; } if (has_effect("beartrap")) { moves = 0; return 0; } if (plans.size() > 0) { plans.erase(plans.begin()); } if (!force) { moves -= calc_movecost(posx(), posy(), x, y); } //Check for moving into/out of water bool was_water = g->m.is_divable(posx(), posy()); bool will_be_water = g->m.is_divable(x, y); if(was_water && !will_be_water && g->u_see(x, y)) { //Use more dramatic messages for swimming monsters g->add_msg(_("A %s %s from the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("leaps") : _("emerges"), g->m.tername(posx(), posy()).c_str()); } else if(!was_water && will_be_water && g->u_see(x, y)) { g->add_msg(_("A %s %s into the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("dives") : _("sinks"), g->m.tername(x, y).c_str()); } setpos(x, y); footsteps(x, y); if(is_hallucination()) { //Hallucinations don't do any of the stuff after this point return 1; } if (type->size != MS_TINY && g->m.has_flag("SHARP", posx(), posy()) && !one_in(4)) { hurt(rng(2, 3)); } if (type->size != MS_TINY && g->m.has_flag("ROUGH", posx(), posy()) && one_in(6)) { hurt(rng(1, 2)); } if (!digging() && !has_flag(MF_FLIES) && g->m.tr_at(posx(), posy()) != tr_null) { // Monster stepped on a trap! trap* tr = g->traps[g->m.tr_at(posx(), posy())]; if (dice(3, type->sk_dodge + 1) < dice(3, tr->avoidance)) { trapfuncm f; (f.*(tr->actm))(this, posx(), posy()); } } // 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(posx(), posy(), t_dirtmound); } } // Acid trail monsters leave... a trail of acid if (has_flag(MF_ACIDTRAIL)) { g->m.add_field(posx(), posy(), fd_acid, 3); } if (has_flag(MF_SLUDGETRAIL)) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { const int fstr = 3 - (abs(dx) + abs(dy)); if (fstr >= 2) { g->m.add_field(posx() + dx, posy() + dy, fd_sludge, fstr); } } } } return 1; }
point monster::wander_next() { point next; bool xbest = true; if (abs(wandy - posy()) > abs(wandx - posx()))// which is more important xbest = false; next.x = posx(); next.y = posy(); int x = posx(), x2 = posx() - 1, x3 = posx() + 1; int y = posy(), y2 = posy() - 1, y3 = posy() + 1; if (wandx < posx()) { x--; x2++; } if (wandx > posx()) { x++; x2++; x3 -= 2; } if (wandy < posy()) { y--; y2++; } if (wandy > posy()) { y++; y2++; y3 -= 2; } if (xbest) { if (can_move_to(x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y))) { next.x = x; next.y = y; } else if (can_move_to(x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y3))) { next.x = x; next.y = y3; } else if (can_move_to(x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x3, y))) { next.x = x3; next.y = y; } } else { if (can_move_to(x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y))) { next.x = x; next.y = y; } else if (can_move_to(x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x3, y))) { next.x = x3; next.y = y; } else if (can_move_to(x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y3))) { next.x = x; next.y = y3; } } return next; }
bool Character::move_effects(bool attacking) { if (has_effect("downed")) { ///\EFFECT_DEX increases chance to stand up when knocked down ///\EFFECT_STR increases chance to stand up when knocked down, slightly if (rng(0, 40) > get_dex() + get_str() / 2) { add_msg_if_player(_("You struggle to stand.")); } else { add_msg_player_or_npc(m_good, _("You stand up."), _("<npcname> stands up.")); remove_effect("downed"); } return false; } if (has_effect("webbed")) { ///\EFFECT_STR increases chance to escape webs if (x_in_y(get_str(), 6 * get_effect_int("webbed"))) { add_msg_player_or_npc(m_good, _("You free yourself from the webs!"), _("<npcname> frees themselves from the webs!")); remove_effect("webbed"); } else { add_msg_if_player(_("You try to free yourself from the webs, but can't get loose!")); } return false; } if (has_effect("lightsnare")) { ///\EFFECT_STR increases chance to escape light snare ///\EFFECT_DEX increases chance to escape light snare if(x_in_y(get_str(), 12) || x_in_y(get_dex(), 8)) { remove_effect("lightsnare"); add_msg_player_or_npc(m_good, _("You free yourself from the light snare!"), _("<npcname> frees themselves from the light snare!")); item string("string_36", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), string); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the light snare, but can't get loose!")); } return false; } if (has_effect("heavysnare")) { ///\EFFECT_STR increases chance to escape heavy snare, slightly ///\EFFECT_DEX increases chance to escape light snare if(x_in_y(get_str(), 32) || x_in_y(get_dex(), 16)) { remove_effect("heavysnare"); add_msg_player_or_npc(m_good, _("You free yourself from the heavy snare!"), _("<npcname> frees themselves from the heavy snare!")); item rope("rope_6", calendar::turn); item snare("snare_trigger", calendar::turn); g->m.add_item_or_charges(posx(), posy(), rope); g->m.add_item_or_charges(posx(), posy(), snare); } else { add_msg_if_player(m_bad, _("You try to free yourself from the heavy snare, but can't get loose!")); } return false; } if (has_effect("beartrap")) { /* Real bear traps can't be removed without the proper tools or immense strength; eventually this should allow normal players two options: removal of the limb or removal of the trap from the ground (at which point the player could later remove it from the leg with the right tools). As such we are currently making it a bit easier for players and NPC's to get out of bear traps. */ ///\EFFECT_STR increases chance to escape bear trap if(x_in_y(get_str(), 100)) { remove_effect("beartrap"); add_msg_player_or_npc(m_good, _("You free yourself from the bear trap!"), _("<npcname> frees themselves from the bear trap!")); item beartrap("beartrap", calendar::turn); g->m.add_item_or_charges(posx(), posy(), beartrap); } else { add_msg_if_player(m_bad, _("You try to free yourself from the bear trap, but can't get loose!")); } return false; } if (has_effect("crushed")) { ///\EFFECT_STR increases chance to escape crushing rubble ///\EFFECT_DEX increases chance to escape crushing rubble, slightly if(x_in_y(get_str() + get_dex() / 4, 100)) { remove_effect("crushed"); add_msg_player_or_npc(m_good, _("You free yourself from the rubble!"), _("<npcname> frees themselves from the rubble!")); } else { add_msg_if_player(m_bad, _("You try to free yourself from the rubble, but can't get loose!")); } return false; } // Below this point are things that allow for movement if they succeed // Currently we only have one thing that forces movement if you succeed, should we get more // than this will need to be reworked to only have success effects if /all/ checks succeed if (has_effect("in_pit")) { ///\EFFECT_STR increases chance to escape pit ///\EFFECT_DEX increases chance to escape pit, slightly if (rng(0, 40) > get_str() + get_dex() / 2) { add_msg_if_player(m_bad, _("You try to escape the pit, but slip back in.")); return false; } else { add_msg_player_or_npc(m_good, _("You escape the pit!"), _("<npcname> escapes the pit!")); remove_effect("in_pit"); } } if( has_effect( "grabbed" ) && !attacking ) { int zed_number = 0; for( auto &&dest : g->m.points_in_radius( pos(), 1, 0 ) ){ if( g->mon_at( dest ) != -1 && ( g->zombie( g->mon_at( dest ) ).has_flag( MF_GRABS ) || g->zombie( g->mon_at( dest ) ).type->has_special_attack( "GRAB" ) ) ) { zed_number ++; } } if( zed_number == 0 ) { add_msg_player_or_npc( m_good, _( "You find yourself no longer grabbed." ), _( "<npcname> finds themselves no longer grabbed." ) ); remove_effect( "grabbed" ); ///\EFFECT_DEX increases chance to escape grab, if >STR ///\EFFECT_STR increases chance to escape grab, if >DEX } else if( rng( 0, std::max( get_dex(), get_str() ) ) < rng( get_effect_int( "grabbed" ), 8 ) ) { // Randomly compare higher of dex or str to grab intensity. add_msg_player_or_npc( m_bad, _( "You try break out of the grab, but fail!" ), _( "<npcname> tries to break out of the grab, but fails!" ) ); return false; } else { add_msg_player_or_npc( m_good, _( "You break out of the grab!" ), _( "<npcname> breaks out of the grab!" ) ); remove_effect( "grabbed" ); } } return Creature::move_effects( attacking ); }
// 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(); return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } //If this monster has the ability to heal in combat, do it now. if (has_flag(MF_REGENERATES_50)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s is visibly regenerating!"), name().c_str()); } hp += 50; if(hp > type->hp) { hp = type->hp; } } } if (has_flag(MF_REGENERATES_10)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s seems a little healthier."), name().c_str()); } hp += 10; if(hp > type->hp) { hp = type->hp; } } } // If this critter dies in sunlight, check & assess damage. if (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { g->add_msg(_("The %s burns horribly in the sunlight!"), name().c_str()); hp -= 100; if(hp < 0) { hp = 0 ; } } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack)(this); } } if (moves < 0) { return; } if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect("stunned")) { stumble(false); moves = 0; return; } if (has_effect("downed")) { moves = 0; return; } if (has_effect("bouldering")) { moves -= 20; if (moves < 0) { return; } } if (friendly != 0) { if (friendly > 0) { friendly--; } friendly_move(); return; } 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)) { moves -= 100; stumble(false); return; } if (plans.size() > 0 && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(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. plans.clear(); point 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(); point tmp = wander_next(); 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 // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); 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.size() == 0)) || !moved) { stumble(moved); } }
Creature *Creature::auto_find_hostile_target( int range, int &boo_hoo, int area ) { Creature *target = nullptr; player &u = g->u; // Could easily protect something that isn't the player constexpr int hostile_adj = 2; // Priority bonus for hostile targets const int iff_dist = ( range + area ) * 3 / 2 + 6; // iff check triggers at this distance int iff_hangle = 15 + area; // iff safety margin (degrees). less accuracy, more paranoia float best_target_rating = -1.0f; // bigger is better int u_angle = 0; // player angle relative to turret boo_hoo = 0; // how many targets were passed due to IFF. Tragically. bool self_area_iff = false; // Need to check if the target is near the vehicle we're a part of bool area_iff = false; // Need to check distance from target to player bool angle_iff = true; // Need to check if player is in a cone between us and target int pldist = rl_dist( pos(), g->u.pos() ); vehicle *in_veh = is_fake() ? veh_pointer_or_null( g->m.veh_at( pos() ) ) : nullptr; if( pldist < iff_dist && sees( g->u ) ) { area_iff = area > 0; angle_iff = true; // Player inside vehicle won't be hit by shots from the roof, // so we can fire "through" them just fine. const optional_vpart_position vp = g->m.veh_at( u.pos() ); if( in_veh && veh_pointer_or_null( vp ) == in_veh && vp->is_inside() ) { angle_iff = false; // No angle IFF, but possibly area IFF } else if( pldist < 3 ) { iff_hangle = (pldist == 2 ? 30 : 60); // granularity increases with proximity } u_angle = g->m.coord_to_angle(posx(), posy(), u.posx(), u.posy()); } if( area > 0 && in_veh != nullptr ) { self_area_iff = true; } std::vector<Creature*> targets = g->get_creatures_if( [&]( const Creature &critter ) { if( const monster *const mon_ptr = dynamic_cast<const monster*>( &critter ) ) { // friendly to the player, not a target for us return mon_ptr->friendly == 0; } if( const npc *const npc_ptr = dynamic_cast<const npc*>( &critter ) ) { // friendly to the player, not a target for us return npc_ptr->get_attitude() == NPCATT_KILL; } //@todo: what about g->u? return false; } ); for( auto &m : targets ) { if( !sees( *m ) ) { // can't see nor sense it continue; } int dist = rl_dist( pos(), m->pos() ) + 1; // rl_dist can be 0 if( dist > range + 1 || dist < area ) { // Too near or too far continue; } // Prioritize big, armed and hostile stuff float mon_rating = m->power_rating(); float target_rating = mon_rating / dist; if( mon_rating + hostile_adj <= 0 ) { // We wouldn't attack it even if it was hostile continue; } if( in_veh != nullptr && veh_pointer_or_null( g->m.veh_at( m->pos() ) ) == in_veh ) { // No shooting stuff on vehicle we're a part of continue; } if( area_iff && rl_dist( u.pos(), m->pos() ) <= area ) { // Player in AoE boo_hoo++; continue; } // Hostility check can be expensive, but we need to inform the player of boo_hoo // only when the target is actually "hostile enough" bool maybe_boo = false; if( angle_iff ) { int tangle = g->m.coord_to_angle(posx(), posy(), m->posx(), m->posy()); int diff = abs(u_angle - tangle); // Player is in the angle and not too far behind the target if( ( diff + iff_hangle > 360 || diff < iff_hangle ) && ( dist * 3 / 2 + 6 > pldist ) ) { maybe_boo = true; } } if( !maybe_boo && ( ( mon_rating + hostile_adj ) / dist <= best_target_rating ) ) { // "Would we skip the target even if it was hostile?" // Helps avoid (possibly expensive) attitude calculation continue; } if( m->attitude_to( u ) == A_HOSTILE ) { target_rating = ( mon_rating + hostile_adj ) / dist; if( maybe_boo ) { boo_hoo++; continue; } } if( target_rating <= best_target_rating || target_rating <= 0 ) { continue; // Handle this late so that boo_hoo++ can happen } // Expensive check for proximity to vehicle if( self_area_iff && overlaps_vehicle( in_veh->get_points(), m->pos(), area ) ) { continue; } target = m; best_target_rating = target_rating; } return target; }
/** * 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; }