bool Creature_tracker::update_pos(const monster &critter, const int new_x_pos, const int new_y_pos) { if (critter.posx() == new_x_pos && critter.posy() == new_y_pos) { return true; // success? } bool success = false; const int dead_critter_id = dead_mon_at(point(critter.posx(), critter.posy())); const int live_critter_id = mon_at(point(critter.posx(), critter.posy())); const int critter_id = critter.dead ? dead_critter_id : live_critter_id; const int new_critter_id = mon_at(new_x_pos, new_y_pos); if (new_critter_id >= 0 && !_old_monsters_list[new_critter_id]->dead) { debugmsg("update_zombie_pos: new location %d,%d already has zombie %d", new_x_pos, new_y_pos, new_critter_id); } else if (critter_id >= 0) { if (&critter == _old_monsters_list[critter_id]) { _old_monsters_by_location.erase(point(critter.posx(), critter.posy())); _old_monsters_by_location[point(new_x_pos, new_y_pos)] = critter_id; success = true; } else { debugmsg("update_zombie_pos: old location %d,%d had zombie %d instead", critter.posx(), critter.posy(), critter_id); } } else { // We're changing the x/y coordinates of a zombie that hasn't been added // to the game yet. add_zombie() will update _old_monsters_by_location for us. debugmsg("update_zombie_pos: no such zombie at %d,%d (moving to %d,%d)", critter.posx(), critter.posy(), new_x_pos, new_y_pos); } return success; }
bool Creature_tracker::update_pos( const monster &critter, const tripoint &new_pos ) { const auto old_pos = critter.pos(); if( critter.is_dead() ) { // mon_at ignores dead critters anyway, changing their position in the // monsters_by_location map is useless. remove_from_location_map( critter ); return true; } const int critter_id = mon_at( old_pos ); const int new_critter_id = mon_at( new_pos ); if( new_critter_id >= 0 ) { auto &othermon = *monsters_list[new_critter_id]; if( othermon.is_hallucination() ) { othermon.die( nullptr ); } else { debugmsg( "update_zombie_pos: wanted to move %s to %d,%d,%d, but new location already has %s", critter.disp_name().c_str(), new_pos.x, new_pos.y, new_pos.z, othermon.disp_name().c_str() ); return false; } } if( critter_id >= 0 ) { if( &critter == monsters_list[critter_id] ) { monsters_by_location.erase( old_pos ); monsters_by_location[new_pos] = critter_id; return true; } else { const auto &othermon = *monsters_list[critter_id]; debugmsg( "update_zombie_pos: wanted to move %s from old location %d,%d,%d, but it had %s instead", critter.disp_name().c_str(), old_pos.x, old_pos.y, old_pos.z, othermon.disp_name().c_str() ); return false; } } else { // We're changing the x/y/z coordinates of a zombie that hasn't been added // to the game yet. add_zombie() will update monsters_by_location for us. debugmsg( "update_zombie_pos: no %s at %d,%d,%d (moving to %d,%d,%d)", critter.disp_name().c_str(), old_pos.x, old_pos.y, old_pos.z, new_pos.x, new_pos.y, new_pos.z ); // Rebuild cache in case the monster actually IS in the game, just bugged rebuild_cache(); return false; } return false; }
void game::draw_line(const int x, const int y, const point center_point, std::vector<point> ret) { if (u_see( x, y)) { for (int i = 0; i < ret.size(); i++) { int mondex = mon_at(ret[i].x, ret[i].y), npcdex = npc_at(ret[i].x, ret[i].y); // NPCs and monsters get drawn with inverted colors if (mondex != -1 && u_see(&(_active_monsters[mondex]))) { _active_monsters[mondex].draw(w_terrain, center_point.x, center_point.y, true); } else if (npcdex != -1) { active_npc[npcdex]->draw(w_terrain, center_point.x, center_point.y, true); } else { m.drawsq(w_terrain, u, ret[i].x, ret[i].y, true,true,center_point.x, center_point.y); } } } }
bool Creature_tracker::add( monster &critter ) { if( critter.type->id.is_null() ) { // Don't wanna spawn null monsters o.O return false; } if( critter.type->has_flag( MF_VERMIN ) ) { // Don't spawn vermin, they aren't implemented yet return false; } const int critter_id = mon_at( critter.pos() ); if( critter_id != -1 ) { // We can spawn stuff on hallucinations, but we need to kill them first if( monsters_list[critter_id]->is_hallucination() ) { monsters_list[critter_id]->die( nullptr ); // But don't remove - that would change the monster order and could segfault } else if( critter.is_hallucination() ) { return false; } else { debugmsg( "add_zombie: there's already a monster at %d,%d,%d", critter.posx(), critter.posy(), critter.posz() ); return false; } } if( MonsterGroupManager::monster_is_blacklisted( critter.type->id ) ) { return false; } monsters_by_location[critter.pos()] = monsters_list.size(); monsters_list.push_back( new monster( critter ) ); return true; }
void game::draw_line(const int x, const int y, const point center_point, std::vector<point> ret) { if (u_see( x, y)) { for (size_t i = 0; i < ret.size(); i++) { int mondex = mon_at(ret[i].x, ret[i].y), npcdex = npc_at(ret[i].x, ret[i].y); // NPCs and monsters get drawn with inverted colors if (mondex != -1 && u_see(&(critter_tracker.find(mondex)))) { critter_tracker.find(mondex).draw(w_terrain, center_point.x, center_point.y, true); } else if (npcdex != -1) { active_npc[npcdex]->draw(w_terrain, center_point.x, center_point.y, true); } else { m.drawsq(w_terrain, u, ret[i].x, ret[i].y, true,true,center_point.x, center_point.y); } } } tilecontext->init_draw_line(x,y,ret,"line_target", true); }
static Boolean teleok(Short x, Short y) { /* might throw him into a POOL */ return( !OUT_OF_BOUNDS(x,y) && !IS_ROCK(get_cell_type(floor_info[x][y])) && !mon_at(x,y) && !sobj_at(ENORMOUS_ROCK,x,y) && !trap_at(x,y) ); /* Note: gold is permitted (because of vaults) */ }
bool Creature_tracker::add(monster &critter) { if (critter.type->id == "mon_null") { // Don't wanna spawn null monsters o.O return false; } if (-1 != mon_at(critter.pos())) { debugmsg("add_zombie: there's already a monster at %d,%d", critter.posx(), critter.posy()); return false; } _old_monsters_by_location[point(critter.posx(), critter.posy())] = _old_monsters_list.size(); _old_monsters_list.push_back(new monster(critter)); return true; }
void Creature_tracker::swap_positions( monster &first, monster &second ) { const int first_mdex = mon_at( first.pos() ); const int second_mdex = mon_at( second.pos() ); remove_from_location_map( first ); remove_from_location_map( second ); bool ok = true; if( first_mdex == -1 || second_mdex == -1 || first_mdex == second_mdex ) { debugmsg( "Tried to swap monsters with invalid positions" ); ok = false; } tripoint temp = second.pos(); second.spawn( first.pos() ); first.spawn( temp ); if( ok ) { monsters_by_location[first.pos()] = first_mdex; monsters_by_location[second.pos()] = second_mdex; } else { // Try to avoid spamming error messages if something weird happens rebuild_cache(); } }
// ok, the SDOOR/SCORR part works. however, none of the rest is tested // (traps, mimics, swallowed, etc) void do_search() // was dosearch { Int8 x,y; trap_t *trap; monst_t *mtmp; UChar floor_type, tmp_type; if (you.uswallow) { message("What are you looking for? The exit?"); return; } for (x = you.ux - 1 ; x <= you.ux + 1 ; x++) for (y = you.uy - 1 ; y <= you.uy + 1 ; y++) { if (x == you.ux && y == you.uy) continue; floor_type = get_cell_type(floor_info[x][y]); if (floor_type == SDOOR || floor_type == SCORR) { if (rund(7)) continue; tmp_type = (floor_type == SDOOR) ? DOOR : CORR; set_cell_type(floor_info[x][y], tmp_type); floor_info[x][y] &= ~SEEN_CELL; /* force prl */ prl(x,y); nomul(0); } else { /* Be careful not to find anything in an SCORR or SDOOR */ mtmp = mon_at(x,y); if (mtmp && (mtmp->bitflags & M_IS_MIMIC)) { see_mimic(mtmp); message("You find a mimic."); return; } for (trap = ftrap ; trap ; trap = trap->ntrap) if (trap->tx == x && trap->ty == y && !(get_trap_seen(trap->trap_info)) && !rund(8)) { nomul(0); tmp_type = get_trap_type(trap->trap_info); StrPrintF(ScratchBuffer, "You find a%s.", traps[tmp_type]); message(ScratchBuffer); if (tmp_type == PIERC) { deltrap(trap); makemon(PM_PIERCER, x, y); return; } trap->trap_info |= SEEN_TRAP; if (!vism_at(x,y)) print(x, y, '^'); } } } }
bool Creature_tracker::update_pos(const monster &critter, const tripoint &new_pos) { const auto old_pos = critter.pos3(); if( critter.is_dead() ) { // mon_at ignores dead critters anyway, changing their position in the // monsters_by_location map is useless. remove_from_location_map( critter ); return true; } bool success = false; const int critter_id = mon_at( old_pos ); const int new_critter_id = mon_at( new_pos ); if( new_critter_id >= 0 ) { debugmsg("update_zombie_pos: new location %d,%d,%d already has zombie %d", new_pos.x, new_pos.y, new_pos.z, new_critter_id); } else if( critter_id >= 0 ) { if( &critter == monsters_list[critter_id] ) { monsters_by_location.erase( old_pos ); monsters_by_location[new_pos] = critter_id; success = true; } else { debugmsg("update_zombie_pos: old location %d,%d had zombie %d instead", old_pos.x, old_pos.y, critter_id); } } else { // We're changing the x/y/z coordinates of a zombie that hasn't been added // to the game yet. add_zombie() will update monsters_by_location for us. debugmsg("update_zombie_pos: no such zombie at %d,%d,%d (moving to %d,%d,%d)", old_pos.x, old_pos.y, old_pos.z, new_pos.x, new_pos.y, new_pos.z ); // Rebuild cache in case the monster actually IS in the game, just bugged rebuild_cache(); } return success; }
Short findit() /* returns number of things found */ { Short num; UChar zx,zy; trap_t *ttmp; monst_t *mtmp; UChar lx,hx,ly,hy; if (you.uswallow) return false; for (lx = you.ux; (num = get_cell_type(floor_info[lx-1][you.uy])) && num != CORR; lx--) ; for (hx = you.ux; (num = get_cell_type(floor_info[hx+1][you.uy])) && num != CORR; hx++) ; for (ly = you.uy; (num = get_cell_type(floor_info[you.ux][ly-1])) && num != CORR; ly--) ; for (hy = you.uy; (num = get_cell_type(floor_info[you.ux][hy+1])) && num != CORR; hy++) ; num = 0; for (zy = ly; zy <= hy; zy++) { for (zx = lx; zx <= hx; zx++) { if (get_cell_type(floor_info[zx][zy]) == SDOOR) { set_cell_type(floor_info[zx][zy], DOOR); print(zx, zy, DOOR_SYM); num++; } else if (get_cell_type(floor_info[zx][zy]) == SCORR) { set_cell_type(floor_info[zx][zy], CORR); print(zx, zy, CORR_SYM); num++; } else if ((ttmp = trap_at(zx, zy))) { if (get_trap_type(ttmp->trap_info) == PIERC) { makemon(PM_PIERCER, zx, zy); num++; deltrap(ttmp); } else if (!get_trap_seen(ttmp->trap_info)) { ttmp->trap_info |= SEEN_TRAP; if (!vism_at(zx, zy)) print(zx,zy,'^'); num++; } } else if ((mtmp = mon_at(zx,zy)) && (mtmp->bitflags & M_IS_MIMIC)) { see_mimic(mtmp); num++; } } } return num; }
// TODO: Shunt redundant drawing code elsewhere std::vector<point> game::target(int &x, int &y, int lowx, int lowy, int hix, int hiy, std::vector <monster> t, int &target, item *relevent) { std::vector<point> ret; int tarx, tary, junk, tart; int range=(hix-u.posx); // First, decide on a target among the monsters, if there are any in range if (!t.empty()) { // Check for previous target if (target == -1) { // If no previous target, target the closest there is double closest = -1; double dist; for (int i = 0; i < t.size(); i++) { dist = rl_dist(t[i].posx(), t[i].posy(), u.posx, u.posy); if (closest < 0 || dist < closest) { closest = dist; target = i; } } } x = t[target].posx(); y = t[target].posy(); } else target = -1; // No monsters in range, don't use target, reset to -1 bool sideStyle = use_narrow_sidebar(); int height = 13; int width = getmaxx(w_messages); int top = sideStyle ? getbegy(w_messages) : (getbegy(w_minimap) + getmaxy(w_minimap)); int left = getbegx(w_messages); WINDOW* w_target = newwin(height, width, top, left); draw_border(w_target); mvwprintz(w_target, 0, 2, c_white, "< "); if (!relevent) { // currently targetting vehicle to refill with fuel wprintz(w_target, c_red, _("Select a vehicle")); } else { if (relevent == &u.weapon && relevent->is_gun()) { wprintz(w_target, c_red, _("Firing %s (%d)"), // - %s (%d)", u.weapon.tname().c_str(),// u.weapon.curammo->name.c_str(), u.weapon.charges); } else { wprintz(w_target, c_red, _("Throwing %s"), relevent->tname().c_str()); } } wprintz(w_target, c_white, " >"); /* Annoying clutter @ 2 3 4. */ int text_y = getmaxy(w_target) - 4; if (is_mouse_enabled()) { --text_y; } mvwprintz(w_target, text_y++, 1, c_white, _("Move cursor to target with directional keys")); if (relevent) { mvwprintz(w_target, text_y++, 1, c_white, _("'<' '>' Cycle targets; 'f' or '.' to fire")); mvwprintz(w_target, text_y++, 1, c_white, _("'0' target self; '*' toggle snap-to-target")); } if (is_mouse_enabled()) { mvwprintz(w_target, text_y++, 1, c_white, _("Mouse: LMB: Target, Wheel: Cycle, RMB: Fire")); } wrefresh(w_target); bool snap_to_target = OPTIONS["SNAP_TO_TARGET"]; do { if (m.sees(u.posx, u.posy, x, y, -1, tart)) ret = line_to(u.posx, u.posy, x, y, tart); else ret = line_to(u.posx, u.posy, x, y, 0); if(trigdist && trig_dist(u.posx,u.posy, x,y) > range) { bool cont=true; int cx=x; int cy=y; for (int i = 0; i < ret.size() && cont; i++) { if(trig_dist(u.posx,u.posy, ret[i].x, ret[i].y) > range) { ret.resize(i); cont=false; } else { cx=0+ret[i].x; cy=0+ret[i].y; } } x=cx;y=cy; } point center; if (snap_to_target) center = point(x, y); else center = point(u.posx + u.view_offset_x, u.posy + u.view_offset_y); // Clear the target window. for (int i = 1; i < getmaxy(w_target) - 5; i++) { for (int j = 1; j < getmaxx(w_target) - 2; j++) mvwputch(w_target, i, j, c_white, ' '); } /* Start drawing w_terrain things -- possibly move out to centralized draw_terrain_window function as they all should be roughly similar*/ m.build_map_cache(); // part of the SDLTILES drawing code m.draw(w_terrain, center); // embedded in SDL drawing code // Draw the Monsters for (int i = 0; i < num_zombies(); i++) { if (u_see(&(zombie(i)))) { zombie(i).draw(w_terrain, center.x, center.y, false); } } // Draw the NPCs for (int i = 0; i < active_npc.size(); i++) { if (u_see(active_npc[i]->posx, active_npc[i]->posy)) active_npc[i]->draw(w_terrain, center.x, center.y, false); } if (x != u.posx || y != u.posy) { // Draw the player int atx = POSX + u.posx - center.x, aty = POSY + u.posy - center.y; if (atx >= 0 && atx < TERRAIN_WINDOW_WIDTH && aty >= 0 && aty < TERRAIN_WINDOW_HEIGHT) mvwputch(w_terrain, aty, atx, u.color(), '@'); // Only draw a highlighted trajectory if we can see the endpoint. // Provides feedback to the player, and avoids leaking information about tiles they can't see. draw_line(x, y, center, ret); /* if (u_see( x, y)) { for (int i = 0; i < ret.size(); i++) { int mondex = mon_at(ret[i].x, ret[i].y), npcdex = npc_at(ret[i].x, ret[i].y); // NPCs and monsters get drawn with inverted colors if (mondex != -1 && u_see(&(zombie(mondex)))) zombie(mondex).draw(w_terrain, center.x, center.y, true); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, true); else m.drawsq(w_terrain, u, ret[i].x, ret[i].y, true,true,center.x, center.y); } } //*/ // Print to target window if (!relevent) { // currently targetting vehicle to refill with fuel vehicle *veh = m.veh_at(x, y); if (veh) { mvwprintw(w_target, 1, 1, _("There is a %s"), veh->name.c_str()); } } else if (relevent == &u.weapon && relevent->is_gun()) { // firing a gun mvwprintw(w_target, 1, 1, _("Range: %d"), rl_dist(u.posx, u.posy, x, y)); // get the current weapon mode or mods std::string mode = ""; if (u.weapon.mode == "MODE_BURST") { mode = _("Burst"); } else { item* gunmod = u.weapon.active_gunmod(); if (gunmod != NULL) { mode = gunmod->type->name; } } if (mode != "") { mvwprintw(w_target, 1, 14, _("Firing mode: %s"), mode.c_str()); } } else { // throwing something mvwprintw(w_target, 1, 1, _("Range: %d"), rl_dist(u.posx, u.posy, x, y)); } const int zid = mon_at(x, y); if (zid == -1) { if (snap_to_target) mvwputch(w_terrain, POSY, POSX, c_red, '*'); else mvwputch(w_terrain, POSY + y - center.y, POSX + x - center.x, c_red, '*'); } else { if (u_see(&(zombie(zid)))) { zombie(zid).print_info(w_target,2); } } } wrefresh(w_target); wrefresh(w_terrain); wrefresh(w_status); refresh(); input_context ctxt("TARGET"); // "ANY_INPUT" should be added before any real help strings // Or strings will be writen on window border. ctxt.register_action("ANY_INPUT"); ctxt.register_directions(); ctxt.register_action("COORDINATE"); ctxt.register_action("SELECT"); ctxt.register_action("FIRE"); ctxt.register_action("NEXT_TARGET"); ctxt.register_action("PREV_TARGET"); ctxt.register_action("WAIT"); ctxt.register_action("CENTER"); ctxt.register_action("TOGGLE_SNAP_TO_TARGET"); ctxt.register_action("HELP_KEYBINDINGS"); ctxt.register_action("QUIT"); const std::string& action = ctxt.handle_input(); tarx = 0; tary = 0; // Our coordinates will either be determined by coordinate input(mouse), // by a direction key, or by the previous value. if (action == "SELECT" && ctxt.get_coordinates(g->w_terrain, tarx, tary)) { if (!OPTIONS["USE_TILES"] && snap_to_target) { // Snap to target doesn't currently work with tiles. tarx += x - u.posx; tary += y - u.posy; } tarx -= x; tary -= y; } else { ctxt.get_direction(tarx, tary, action); if(tarx == -2) { tarx = 0; tary = 0; } } /* More drawing to terrain */ if (tarx != 0 || tary != 0) { int mondex = mon_at(x, y), npcdex = npc_at(x, y); if (mondex != -1 && u_see(&(zombie(mondex)))) zombie(mondex).draw(w_terrain, center.x, center.y, false); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, false); else if (m.sees(u.posx, u.posy, x, y, -1, junk)) m.drawsq(w_terrain, u, x, y, false, true, center.x, center.y); else mvwputch(w_terrain, POSY, POSX, c_black, 'X'); x += tarx; y += tary; if (x < lowx) x = lowx; else if (x > hix) x = hix; if (y < lowy) y = lowy; else if (y > hiy) y = hiy; } else if ((action == "PREV_TARGET") && (target != -1)) { target--; if (target == -1) target = t.size() - 1; x = t[target].posx(); y = t[target].posy(); } else if ((action == "NEXT_TARGET") && (target != -1)) { target++; if (target == t.size()) target = 0; x = t[target].posx(); y = t[target].posy(); } else if (action == "WAIT" || action == "FIRE") { for (int i = 0; i < t.size(); i++) { if (t[i].posx() == x && t[i].posy() == y) target = i; } if (u.posx == x && u.posy == y) ret.clear(); break; } else if (action == "CENTER") { x = u.posx; y = u.posy; ret.clear(); } else if (action == "TOGGLE_SNAP_TO_TARGET") snap_to_target = !snap_to_target; else if (action == "QUIT") { // return empty vector (cancel) ret.clear(); break; } } while (true); return ret; }
/* * shk_move: return 1: he moved 0: he didnt -1: let m_move do it * (what about "return 2" ??? */ Short shk_move(monst_t *shkp) { monst_t *mtmp; permonst_t *mdat = shkp->data; UChar gx,gy,omx,omy,nx,ny,nix,niy; Int8 appr,i; Short udist; Short z; Int8 shkroom,chi,chcnt,cnt; Boolean uondoor=false, satdoor, avoid=false, badinv; coord poss[9]; Short info[9]; obj_t *ib = NULL; omx = shkp->mx; omy = shkp->my; if ((udist = dist(omx,omy)) < 3) { if (ANGRY(shkp)) { hit_you(shkp, dice(mdat->damn, mdat->damd)+1); return 0; } if (ESHK(shkp)->following) { if (StrNCompare(ESHK(shkp)->customer, plname, PL_NSIZ)) { StrPrintF(ScratchBuffer, "Hello %s! I was looking for %s.", plname, ESHK(shkp)->customer); message(ScratchBuffer); ESHK(shkp)->following = false; return 0; } if (!ESHK(shkp)->robbed) { /* impossible? */ ESHK(shkp)->following = false; return 0; } if (moves > followmsg+4) { StrPrintF(ScratchBuffer, "Hello %s! Didn't you forget to pay?", plname); message(ScratchBuffer); followmsg = moves; } if (udist < 2) return 0; } } shkroom = inroom(omx,omy); appr = 1; gx = ESHK(shkp)->shk.x; gy = ESHK(shkp)->shk.y; satdoor = (gx == omx && gy == omy); if (ESHK(shkp)->following || ((z = holetime()) >= 0 && z*z <= udist)){ gx = you.ux; gy = you.uy; if (shkroom < 0 || shkroom != inroom(you.ux,you.uy)) if (udist > 4) return -1; /* leave it to m_move */ } else if (ANGRY(shkp)) { Long saveBlind = Blind; Blind = 0; if ((shkp->mcansee_and_blinded & M_CAN_SEE) && !Invis && cansee(omx,omy)) { gx = you.ux; gy = you.uy; } Blind = saveBlind; avoid = false; } else { #define GDIST(x,y) ((x-gx)*(x-gx)+(y-gy)*(y-gy)) if (Invis) avoid = false; else { uondoor = (you.ux == ESHK(shkp)->shd.x && you.uy == ESHK(shkp)->shd.y); if (uondoor) { if (ESHK(shkp)->billct) { StrPrintF(ScratchBuffer, "Hello %s! Will you please pay before leaving?", plname); message(ScratchBuffer); } badinv = (carrying(PICK_AXE) || carrying(ICE_BOX)); if (satdoor && badinv) return 0; avoid = !badinv; } else { avoid = (you.uinshop && dist(gx,gy) > 8); badinv = false; } if (((!ESHK(shkp)->robbed && !ESHK(shkp)->billct) || avoid) && GDIST(omx,omy) < 3) { if (!badinv && !online(omx,omy)) return 0; if (satdoor) appr = gx = gy = 0; } } } if (omx == gx && omy == gy) return 0; if (shkp->bitflags & M_IS_CONFUSED) { avoid = false; appr = 0; } nix = omx; niy = omy; cnt = mfindpos(shkp,poss,info,ALLOW_SSM); if (avoid && uondoor) { /* perhaps we cannot avoid him */ for (i=0; i<cnt; i++) if (!(info[i] & NOTONL)) goto notonl_ok; avoid = false; notonl_ok: ; } chi = -1; chcnt = 0; for (i = 0 ; i < cnt ; i++) { nx = poss[i].x; ny = poss[i].y; if (get_cell_type(floor_info[nx][ny]) == ROOM || shkroom != ESHK(shkp)->shoproom || ESHK(shkp)->following) { #ifdef STUPID /* cater for stupid compilers */ Short zz; #endif STUPID if (uondoor && (ib = sobj_at(ICE_BOX, nx, ny))) { nix = nx; niy = ny; chi = i; break; } if (avoid && (info[i] & NOTONL)) continue; if ((!appr && !rund(++chcnt)) || #ifdef STUPID (appr && (zz = GDIST(nix,niy)) && zz > GDIST(nx,ny)) #else (appr && GDIST(nx,ny) < GDIST(nix,niy)) #endif STUPID ) { nix = nx; niy = ny; chi = i; } } } if (nix != omx || niy != omy) { if (info[chi] & ALLOW_M){ mtmp = mon_at(nix,niy); if (hitmm(shkp,mtmp) == 1 && rund(3) && hitmm(mtmp,shkp) == 2) return 2; return 0; } else if (info[chi] & ALLOW_U){ hit_you(shkp, dice(mdat->damn, mdat->damd)+1); return 0; } shkp->mx = nix; shkp->my = niy; pmon(shkp); if (ib) { unlink_obj(ib);//freeobj mpickobj(shkp, ib); } return 1; } return 0; }
std::vector<point> game::target(int &x, int &y, int lowx, int lowy, int hix, int hiy, std::vector <monster> t, int &target, item *relevent) { std::vector<point> ret; int tarx, tary, tart, junk; // TODO: [lightmap] Enable auto targeting based on lightmap int sight_dist = u.sight_range(light_level()); // First, decide on a target among the monsters, if there are any in range if (t.size() > 0) { // Check for previous target if (target == -1) { // If no previous target, target the closest there is double closest = -1; double dist; for (int i = 0; i < t.size(); i++) { dist = rl_dist(t[i].posx, t[i].posy, u.posx, u.posy); if (closest < 0 || dist < closest) { closest = dist; target = i; } } } x = t[target].posx; y = t[target].posy; } else target = -1; // No monsters in range, don't use target, reset to -1 WINDOW* w_target = newwin(13, 48, VIEW_OFFSET_Y + MINIMAP_HEIGHT, TERRAIN_WINDOW_WIDTH + 7 + VIEW_OFFSET_X); wborder(w_target, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); mvwprintz(w_target, 0, 2, c_white, "< "); if (!relevent) { // currently targetting vehicle to refill with fuel wprintz(w_target, c_red, "Select a vehicle"); } else { if (relevent == &u.weapon && relevent->is_gun()) { wprintz(w_target, c_red, "Firing %s (%d)", // - %s (%d)", u.weapon.tname().c_str(),// u.weapon.curammo->name.c_str(), u.weapon.charges); } else { wprintz(w_target, c_red, "Throwing %s", relevent->tname().c_str()); } } wprintz(w_target, c_white, " >"); /* Annoying clutter @ 2 3 4. */ mvwprintz(w_target, 9, 1, c_white, "Move cursor to target with directional keys."); if (relevent) { mvwprintz(w_target, 10, 1, c_white, "'<' '>' Cycle targets; 'f' or '.' to fire."); mvwprintz(w_target, 11, 1, c_white, "'0' target self; '*' toggle snap-to-target"); } wrefresh(w_target); char ch; bool snap_to_target = OPTIONS[OPT_SNAP_TO_TARGET]; // The main loop. do { point center; if (snap_to_target) center = point(x, y); else center = point(u.posx, u.posy); // Clear the target window. // for (int i = 5; i < 12; i++) { for (int i = 1; i < 8; i++) { for (int j = 1; j < 46; j++) mvwputch(w_target, i, j, c_white, ' '); } m.build_map_cache(this); m.draw(this, w_terrain, center); // Draw the Monsters for (int i = 0; i < z.size(); i++) { if (u_see(&(z[i])) && z[i].posx >= lowx && z[i].posy >= lowy && z[i].posx <= hix && z[i].posy <= hiy) z[i].draw(w_terrain, center.x, center.y, false); } // Draw the NPCs for (int i = 0; i < active_npc.size(); i++) { if (u_see(active_npc[i]->posx, active_npc[i]->posy)) active_npc[i]->draw(w_terrain, center.x, center.y, false); } if (x != u.posx || y != u.posy) { // Calculate the return vector (and draw it too) /* for (int i = 0; i < ret.size(); i++) m.drawsq(w_terrain, u, ret[i].x, ret[i].y, false, true, center.x, center.y); */ // Draw the player int atx = VIEWX + u.posx - center.x, aty = VIEWY + u.posy - center.y; if (atx >= 0 && atx < TERRAIN_WINDOW_WIDTH && aty >= 0 && aty < TERRAIN_WINDOW_HEIGHT) mvwputch(w_terrain, aty, atx, u.color(), '@'); if (m.sees(u.posx, u.posy, x, y, -1, tart)) {// Selects a valid line-of-sight ret = line_to(u.posx, u.posy, x, y, tart); // Sets the vector to that LOS // Draw the trajectory for (int i = 0; i < ret.size(); i++) { if (abs(ret[i].x - u.posx) <= sight_dist && abs(ret[i].y - u.posy) <= sight_dist ) { int mondex = mon_at(ret[i].x, ret[i].y), npcdex = npc_at(ret[i].x, ret[i].y); // NPCs and monsters get drawn with inverted colors if (mondex != -1 && u_see(&(z[mondex]))) z[mondex].draw(w_terrain, center.x, center.y, true); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, true); else m.drawsq(w_terrain, u, ret[i].x, ret[i].y, true,true,center.x, center.y); } } } if (!relevent) { // currently targetting vehicle to refill with fuel vehicle *veh = m.veh_at(x, y); if (veh) mvwprintw(w_target, 1, 1, "There is a %s", veh->name.c_str()); } else mvwprintw(w_target, 1, 1, "Range: %d", rl_dist(u.posx, u.posy, x, y)); if (mon_at(x, y) == -1) { // what? mvwprintw(w_status, 0, 9, " "); if (snap_to_target) mvwputch(w_terrain, VIEWY, VIEWX, c_red, '*'); else mvwputch(w_terrain, y + VIEWY - u.posy, x + VIEWX - u.posx, c_red, '*'); } else if (u_see(&(z[mon_at(x, y)]))) { // mvwprintw(w_target, 0, 1, "< %s >", z[mon_at(x, y)].name().c_str() ); z[mon_at(x, y)].print_info(this, w_target,2); } } wrefresh(w_target); wrefresh(w_terrain); wrefresh(w_status); refresh(); ch = input(); get_direction(this, tarx, tary, ch); if (tarx != -2 && tary != -2 && ch != '.') { // Direction character pressed int mondex = mon_at(x, y), npcdex = npc_at(x, y); if (mondex != -1 && u_see(&(z[mondex]))) z[mondex].draw(w_terrain, center.x, center.y, false); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, false); else if (m.sees(u.posx, u.posy, x, y, -1, junk)) m.drawsq(w_terrain, u, x, y, false, true, center.x, center.y); else mvwputch(w_terrain, VIEWY, VIEWX, c_black, 'X'); x += tarx; y += tary; if (x < lowx) x = lowx; else if (x > hix) x = hix; if (y < lowy) y = lowy; else if (y > hiy) y = hiy; } else if ((ch == '<') && (target != -1)) { target--; if (target == -1) target = t.size() - 1; x = t[target].posx; y = t[target].posy; } else if ((ch == '>') && (target != -1)) { target++; if (target == t.size()) target = 0; x = t[target].posx; y = t[target].posy; } else if (ch == '.' || ch == 'f' || ch == 'F' || ch == '\n') { for (int i = 0; i < t.size(); i++) { if (t[i].posx == x && t[i].posy == y) target = i; } return ret; } else if (ch == '0') { x = u.posx; y = u.posy; ret.clear(); } else if (ch == '*') snap_to_target = !snap_to_target; else if (ch == KEY_ESCAPE || ch == 'q') { // return empty vector (cancel) ret.clear(); return ret; } } while (true); }
void game::throw_item(player &p, int tarx, int tary, item &thrown, std::vector<point> &trajectory) { int deviation = 0; int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary); // Throwing attempts below "Basic Competency" level are extra-bad int skillLevel = p.skillLevel("throw"); if (skillLevel < 3) deviation += rng(0, 8 - skillLevel); if (skillLevel < 8) deviation += rng(0, 8 - skillLevel); else deviation -= skillLevel - 6; deviation += p.throw_dex_mod(); if (p.per_cur < 6) deviation += rng(0, 8 - p.per_cur); else if (p.per_cur > 8) deviation -= p.per_cur - 8; deviation += rng(0, p.encumb(bp_hands) * 2 + p.encumb(bp_eyes) + 1); if (thrown.volume() > 5) deviation += rng(0, 1 + (thrown.volume() - 5) / 4); if (thrown.volume() == 0) deviation += rng(0, 3); deviation += rng(0, 1 + abs(p.str_cur - thrown.weight())); double missed_by = .01 * deviation * trange; bool missed = false; int tart; if (missed_by >= 1) { // We missed D: // Shoot a random nearby space? if (missed_by > 9) missed_by = 9; tarx += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); tary += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, tarx, tary, -1, tart)) trajectory = line_to(p.posx, p.posy, tarx, tary, tart); else trajectory = line_to(p.posx, p.posy, tarx, tary, 0); missed = true; if (!p.is_npc()) add_msg("You miss!"); } else if (missed_by >= .6) { // Hit the space, but not necessarily the monster there missed = true; if (!p.is_npc()) add_msg("You barely miss!"); } std::string message; int dam = (thrown.weight() / 4 + thrown.type->melee_dam / 2 + p.str_cur / 2) / double(2 + double(thrown.volume() / 4)); if (dam > thrown.weight() * 3) dam = thrown.weight() * 3; int i = 0, tx = 0, ty = 0; for (i = 0; i < trajectory.size() && dam > -10; i++) { message = ""; double goodhit = missed_by; tx = trajectory[i].x; ty = trajectory[i].y; // If there's a monster in the path of our item, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it if (mon_at(tx, ty) != -1 && (!missed || one_in(7 - int(z[mon_at(tx, ty)].type->size)))) { if (rng(0, 100) < 20 + skillLevel * 12 && thrown.type->melee_cut > 0) { if (!p.is_npc()) { message += " You cut the "; message += z[mon_at(tx, ty)].name(); message += "!"; } if (thrown.type->melee_cut > z[mon_at(tx, ty)].armor_cut()) dam += (thrown.type->melee_cut - z[mon_at(tx, ty)].armor_cut()); } if (thrown.made_of(GLASS) && !thrown.active && // active = molotov, etc. rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) add_msg("The %s shatters!", thrown.tname().c_str()); for (int i = 0; i < thrown.contents.size(); i++) m.add_item(tx, ty, thrown.contents[i]); sound(tx, ty, 16, "glass breaking!"); int glassdam = rng(0, thrown.volume() * 2); if (glassdam > z[mon_at(tx, ty)].armor_cut()) dam += (glassdam - z[mon_at(tx, ty)].armor_cut()); } else m.add_item(tx, ty, thrown); if (i < trajectory.size() - 1) goodhit = double(double(rand() / RAND_MAX) / 2); if (goodhit < .1 && !z[mon_at(tx, ty)].has_flag(MF_NOHEAD)) { message = "Headshot!"; dam = rng(dam, dam * 3); p.practice(turn, "throw", 5); } else if (goodhit < .2) { message = "Critical!"; dam = rng(dam, dam * 2); p.practice(turn, "throw", 2); } else if (goodhit < .4) dam = rng(int(dam / 2), int(dam * 1.5)); else if (goodhit < .5) { message = "Grazing hit."; dam = rng(0, dam); } if (!p.is_npc()) add_msg("%s You hit the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); else if (u_see(tx, ty)) add_msg("%s hits the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); if (z[mon_at(tx, ty)].hurt(dam)) kill_mon(mon_at(tx, ty), !p.is_npc()); return; } else // No monster hit, but the terrain might be. m.shoot(this, tx, ty, dam, false, 0); if (m.move_cost(tx, ty) == 0) { if (i > 0) { tx = trajectory[i - 1].x; ty = trajectory[i - 1].y; } else { tx = u.posx; ty = u.posy; } i = trajectory.size(); } } if (m.move_cost(tx, ty) == 0) { if (i > 1) { tx = trajectory[i - 2].x; ty = trajectory[i - 2].y; } else { tx = u.posx; ty = u.posy; } } if (thrown.made_of(GLASS) && !thrown.active && // active means molotov, etc rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) add_msg("The %s shatters!", thrown.tname().c_str()); for (int i = 0; i < thrown.contents.size(); i++) m.add_item(tx, ty, thrown.contents[i]); sound(tx, ty, 16, "glass breaking!"); } else { sound(tx, ty, 8, "thud."); m.add_item(tx, ty, thrown); } }
void game::fire(player &p, int tarx, int tary, std::vector<point> &trajectory, bool burst) { item ammotmp; item* gunmod = p.weapon.active_gunmod(); it_ammo *curammo = NULL; item *weapon = NULL; if (p.weapon.has_flag(IF_CHARGE)) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = p.weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); tmpammo->range = 5 + charges * 5; if (charges <= 4) tmpammo->accuracy = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->accuracy = charges * (charges - 4); tmpammo->recoil = tmpammo->accuracy * .8; tmpammo->ammo_effects = 0; if (charges == 8) tmpammo->ammo_effects |= mfb(AMMO_EXPLOSIVE_BIG); else if (charges >= 6) tmpammo->ammo_effects |= mfb(AMMO_EXPLOSIVE); if (charges >= 5) tmpammo->ammo_effects |= mfb(AMMO_FLAME); else if (charges >= 4) tmpammo->ammo_effects |= mfb(AMMO_INCENDIARY); if (gunmod != NULL) { weapon = gunmod; } else { weapon = &p.weapon; } curammo = tmpammo; weapon->curammo = tmpammo; weapon->active = false; weapon->charges = 0; } else if (gunmod != NULL) { weapon = gunmod; curammo = weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = p.weapon.curammo; weapon = &p.weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!weapon->is_gun() && !weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", p.name.c_str(), weapon->tname().c_str()); return; } bool is_bolt = false; unsigned int effects = curammo->ammo_effects; // Bolts and arrows are silent if (curammo->type == AT_BOLT || curammo->type == AT_ARROW) is_bolt = true; int x = p.posx, y = p.posy; // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); if (p.has_trait(PF_TRIGGERHAPPY) && one_in(30)) burst = true; if (burst && weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto bool u_see_shooter = u_see(p.posx, p.posy); // Use different amounts of time depending on the type of gun and our skill p.moves -= time_to_fire(p, firing); // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = weapon->burst_size(); if (num_shots > weapon->num_charges() && !weapon->has_flag(IF_CHARGE)) num_shots = weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); // Set up a timespec for use in the nanosleep function below timespec ts; ts.tv_sec = 0; ts.tv_nsec = BULLET_SPEED; // Use up some ammunition int trange = trig_dist(p.posx, p.posy, tarx, tary); if (trange < int(firing->volume / 3) && firing->ammo != AT_SHOT) trange = int(firing->volume / 3); else if (p.has_bionic("bio_targeting")) { if (trange > LONG_RANGE) trange = int(trange * .65); else trange = int(trange * .8); } if (firing->skill_used == Skill::skill("rifle") && trange > LONG_RANGE) trange = LONG_RANGE + .6 * (trange - LONG_RANGE); std::string message = ""; bool missed = false; int tart; for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first if (curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0)) { std::vector<point> new_targets; int mondex; for (int radius = 1; radius <= 2 + p.skillLevel("gun") && new_targets.empty(); radius++) { for (int diff = 0 - radius; diff <= radius; diff++) { mondex = mon_at(tarx + diff, tary - radius); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary - radius) ); mondex = mon_at(tarx + diff, tary + radius); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary + radius) ); if (diff != 0 - radius && diff != radius) { // Corners were already checked mondex = mon_at(tarx - radius, tary + diff); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx - radius, tary + diff) ); mondex = mon_at(tarx + radius, tary + diff); if (mondex != -1 && z[mondex].hp > 0 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + radius, tary + diff) ); } } } if (!new_targets.empty()) { int target_picked = rng(0, new_targets.size() - 1); tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) trajectory = line_to(p.posx, p.posy, tarx, tary, tart); else trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } else if ((!p.has_trait(PF_TRIGGERHAPPY) || one_in(3)) && (p.skillLevel("gun") >= 7 || one_in(7 - p.skillLevel("gun")))) return; // No targets, so return } // Drop a shell casing if appropriate. itype_id casing_type = "null"; switch(curammo->type) { case AT_SHOT: casing_type = "shot_hull"; break; case AT_9MM: casing_type = "9mm_casing"; break; case AT_22: casing_type = "22_casing"; break; case AT_38: casing_type = "38_casing"; break; case AT_40: casing_type = "40_casing"; break; case AT_44: casing_type = "44_casing"; break; case AT_45: casing_type = "45_casing"; break; case AT_57: casing_type = "57mm_casing"; break; case AT_46: casing_type = "46mm_casing"; break; case AT_762: casing_type = "762_casing"; break; case AT_223: casing_type = "223_casing"; break; case AT_3006: casing_type = "3006_casing"; break; case AT_308: casing_type = "308_casing"; break; case AT_40MM: casing_type = "40mm_casing"; break; default: /*No casing for other ammo types.*/ break; } if (casing_type != "null") { int x = p.posx - 1 + rng(0, 2); int y = p.posy - 1 + rng(0, 2); std::vector<item>& items = m.i_at(x, y); int i; for (i = 0; i < items.size(); i++) if (items[i].typeId() == casing_type && items[i].charges < (dynamic_cast<it_ammo*>(items[i].type))->count) { items[i].charges++; break; } if (i == items.size()) { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; m.add_item(x, y, casing); } } // Use up a round (or 100) if (weapon->has_flag(IF_FIRE_100)) weapon->charges -= 100; else weapon->charges--; // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (one_in(2 << firing->durability)) { add_msg("Your weapon misfired!"); return; } make_gun_sound_effect(this, p, burst, weapon); int trange = calculate_range(p, tarx, tary); double missed_by = calculate_missed_by(p, trange, weapon); // Calculate a penalty based on the monster's speed double monster_speed_penalty = 1.; int target_index = mon_at(tarx, tary); if (target_index != -1) { monster_speed_penalty = double(z[target_index].speed) / 80.; if (monster_speed_penalty < 1.) monster_speed_penalty = 1.; } if (curshot > 0) { if (recoil_add(p) % 2 == 1) p.recoil++; p.recoil += recoil_add(p) / 2; } else p.recoil += recoil_add(p); if (missed_by >= 1.) { // We missed D: // Shoot a random nearby space? tarx += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); tary += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, x, y, -1, tart)) trajectory = line_to(p.posx, p.posy, tarx, tary, tart); else trajectory = line_to(p.posx, p.posy, tarx, tary, 0); missed = true; if (!burst) { if (&p == &u) add_msg("You miss!"); else if (u_see_shooter) add_msg("%s misses!", p.name.c_str()); } } else if (missed_by >= .7 / monster_speed_penalty) { // Hit the space, but not necessarily the monster there missed = true; if (!burst) { if (&p == &u) add_msg("You barely miss!"); else if (u_see_shooter) add_msg("%s barely misses!", p.name.c_str()); } } int dam = weapon->gun_damage(); for (int i = 0; i < trajectory.size() && (dam > 0 || (effects & AMMO_FLAME)); i++) { if (i > 0) m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true); // Drawing the bullet uses player u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if (u_see(trajectory[i].x, trajectory[i].y)) { char bullet = '*'; if (effects & mfb(AMMO_FLAME)) bullet = '#'; mvwputch(w_terrain, trajectory[i].y + VIEWY - u.posy, trajectory[i].x + VIEWX - u.posx, c_red, bullet); wrefresh(w_terrain); if (&p == &u) nanosleep(&ts, NULL); } if (dam <= 0) { // Ran out of momentum. ammo_effects(this, trajectory[i].x, trajectory[i].y, effects); if (is_bolt && ((curammo->m1 == WOOD && !one_in(4)) || (curammo->m1 != WOOD && !one_in(15)))) m.add_item(trajectory[i].x, trajectory[i].y, ammotmp); if (weapon->num_charges() == 0) weapon->curammo = NULL; return; } int tx = trajectory[i].x, ty = trajectory[i].y; // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it int mondex = mon_at(tx, ty); // If we shot us a monster... if (mondex != -1 && (!z[mondex].has_flag(MF_DIGS) || rl_dist(p.posx, p.posy, z[mondex].posx, z[mondex].posy) <= 1) && ((!missed && i == trajectory.size() - 1) || one_in((5 - int(z[mondex].type->size))))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; // Penalize for the monster's speed if (z[mondex].speed > 80) goodhit *= double( double(z[mondex].speed) / 80.); std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam, &z[mondex]); shoot_monster(this, p, z[mondex], dam, goodhit, weapon); } else if ((!missed || one_in(3)) && (npc_at(tx, ty) != -1 || (u.posx == tx && u.posy == ty))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; player *h; if (u.posx == tx && u.posy == ty) h = &u; else h = active_npc[npc_at(tx, ty)]; std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam); shoot_player(this, p, h, dam, goodhit); } else m.shoot(this, tx, ty, dam, i == trajectory.size() - 1, effects); } // Done with the trajectory! int lastx = trajectory[trajectory.size() - 1].x; int lasty = trajectory[trajectory.size() - 1].y; ammo_effects(this, lastx, lasty, effects); if (m.move_cost(lastx, lasty) == 0) { lastx = trajectory[trajectory.size() - 2].x; lasty = trajectory[trajectory.size() - 2].y; } if (is_bolt && ((curammo->m1 == WOOD && !one_in(5)) || (curammo->m1 != WOOD && !one_in(15)) )) m.add_item(lastx, lasty, ammotmp); } if (weapon->num_charges() == 0) weapon->curammo = NULL; }
void game::throw_item(player &p, int tarx, int tary, item &thrown, std::vector<point> &trajectory) { int deviation = 0; int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary); std::set<std::string> no_effects; // Throwing attempts below "Basic Competency" level are extra-bad int skillLevel = p.skillLevel("throw"); if (skillLevel < 3) { deviation += rng(0, 8 - skillLevel); } if (skillLevel < 8) { deviation += rng(0, 8 - skillLevel); } else { deviation -= skillLevel - 6; } deviation += p.throw_dex_mod(); if (p.per_cur < 6) { deviation += rng(0, 8 - p.per_cur); } else if (p.per_cur > 8) { deviation -= p.per_cur - 8; } deviation += rng(0, p.encumb(bp_hands) * 2 + p.encumb(bp_eyes) + 1); if (thrown.volume() > 5) { deviation += rng(0, 1 + (thrown.volume() - 5) / 4); } if (thrown.volume() == 0) { deviation += rng(0, 3); } deviation += rng(0, std::max( 0, p.str_cur - thrown.weight() / 113 ) ); double missed_by = .01 * deviation * trange; bool missed = false; int tart; if (missed_by >= 1) { // We missed D: // Shoot a random nearby space? if (missed_by > 9) { missed_by = 9; } tarx += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); tary += rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, tarx, tary, -1, tart)) { trajectory = line_to(p.posx, p.posy, tarx, tary, tart); } else { trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } missed = true; add_msg_if_player(&p,_("You miss!")); } else if (missed_by >= .6) { // Hit the space, but not necessarily the monster there missed = true; add_msg_if_player(&p,_("You barely miss!")); } std::string message; int real_dam = (thrown.weight() / 452 + thrown.type->melee_dam / 2 + p.str_cur / 2) / double(2 + double(thrown.volume() / 4)); if (real_dam > thrown.weight() / 40) { real_dam = thrown.weight() / 40; } if (p.has_active_bionic("bio_railgun") && (thrown.made_of("iron") || thrown.made_of("steel"))) { real_dam *= 2; } int dam = real_dam; int i = 0, tx = 0, ty = 0; for (i = 0; i < trajectory.size() && dam >= 0; i++) { message = ""; double goodhit = missed_by; tx = trajectory[i].x; ty = trajectory[i].y; const int zid = mon_at(tx, ty); // If there's a monster in the path of our item, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it if (zid != -1 && (!missed || one_in(7 - int(zombie(zid).type->size)))) { monster &z = zombie(zid); if (rng(0, 100) < 20 + skillLevel * 12 && thrown.type->melee_cut > 0) { if (!p.is_npc()) { message += string_format(_(" You cut the %s!"), z.name().c_str()); } if (thrown.type->melee_cut > z.get_armor_cut(bp_torso)) { dam += (thrown.type->melee_cut - z.get_armor_cut(bp_torso)); } } if (thrown.made_of("glass") && !thrown.active && // active = molotov, etc. rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) { add_msg(_("The %s shatters!"), thrown.tname().c_str()); } for (int i = 0; i < thrown.contents.size(); i++) { m.add_item_or_charges(tx, ty, thrown.contents[i]); } sound(tx, ty, 16, _("glass breaking!")); int glassdam = rng(0, thrown.volume() * 2); if (glassdam > z.get_armor_cut(bp_torso)) { dam += (glassdam - z.get_armor_cut(bp_torso)); } } else { m.add_item_or_charges(tx, ty, thrown); } if (i < trajectory.size() - 1) { goodhit = double(double(rand() / RAND_MAX) / 2); } if (goodhit < .1 && !z.has_flag(MF_NOHEAD)) { message = _("Headshot!"); dam = rng(dam, dam * 3); p.practice(turn, "throw", 5); p.lifetime_stats()->headshots++; } else if (goodhit < .2) { message = _("Critical!"); dam = rng(dam, dam * 2); p.practice(turn, "throw", 2); } else if (goodhit < .4) { dam = rng(int(dam / 2), int(dam * 1.5)); } else if (goodhit < .5) { message = _("Grazing hit."); dam = rng(0, dam); } if (u_see(tx, ty)) { g->add_msg_player_or_npc(&p, _("%s You hit the %s for %d damage."), _("%s <npcname> hits the %s for %d damage."), message.c_str(), z.name().c_str(), dam); } if (z.hurt(dam, real_dam)) { z.die(&p); } return; } else { // No monster hit, but the terrain might be. m.shoot(tx, ty, dam, false, no_effects); } // Collide with impassable terrain if (m.move_cost(tx, ty) == 0) { if (i > 0) { tx = trajectory[i - 1].x; ty = trajectory[i - 1].y; } else { tx = u.posx; ty = u.posy; } i = trajectory.size(); } if (p.has_active_bionic("bio_railgun") && (thrown.made_of("iron") || thrown.made_of("steel"))) { m.add_field(tx, ty, fd_electricity, rng(2,3)); } } if (thrown.made_of("glass") && !thrown.active && // active means molotov, etc rng(0, thrown.volume() + 8) - rng(0, p.str_cur) < thrown.volume()) { if (u_see(tx, ty)) { add_msg(_("The %s shatters!"), thrown.tname().c_str()); } for (int i = 0; i < thrown.contents.size(); i++) { m.add_item_or_charges(tx, ty, thrown.contents[i]); } sound(tx, ty, 16, _("glass breaking!")); } else { if(m.has_flag("LIQUID", tx, ty)) { sound(tx, ty, 10, _("splash!")); } else { sound(tx, ty, 8, _("thud.")); } m.add_item_or_charges(tx, ty, thrown); } }
int Creature_tracker::mon_at(int x_pos, int y_pos) const { return mon_at(point(x_pos, y_pos)); }
std::vector<point> game::target(int &x, int &y, int lowx, int lowy, int hix, int hiy, std::vector <monster> t, int &target, item *relevent) { std::vector<point> ret; int tarx, tary, junk, tart; int range=(hix-u.posx); // First, decide on a target among the monsters, if there are any in range if (t.size() > 0) { // Check for previous target if (target == -1) { // If no previous target, target the closest there is double closest = -1; double dist; for (int i = 0; i < t.size(); i++) { dist = rl_dist(t[i].posx, t[i].posy, u.posx, u.posy); if (closest < 0 || dist < closest) { closest = dist; target = i; } } } x = t[target].posx; y = t[target].posy; } else target = -1; // No monsters in range, don't use target, reset to -1 int sideStyle = (OPTIONS["SIDEBAR_STYLE"] == "Narrow"); int height = 13; int width = getmaxx(w_messages); int top = sideStyle ? getbegy(w_messages) : (getbegy(w_minimap) + getmaxy(w_minimap)); int left = getbegx(w_messages); WINDOW* w_target = newwin(height, width, top, left); wborder(w_target, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); mvwprintz(w_target, 0, 2, c_white, "< "); if (!relevent) { // currently targetting vehicle to refill with fuel wprintz(w_target, c_red, _("Select a vehicle")); } else { if (relevent == &u.weapon && relevent->is_gun()) { wprintz(w_target, c_red, _("Firing %s (%d)"), // - %s (%d)", u.weapon.tname().c_str(),// u.weapon.curammo->name.c_str(), u.weapon.charges); } else { wprintz(w_target, c_red, _("Throwing %s"), relevent->tname().c_str()); } } wprintz(w_target, c_white, " >"); /* Annoying clutter @ 2 3 4. */ mvwprintz(w_target, getmaxy(w_target) - 4, 1, c_white, _("Move cursor to target with directional keys")); if (relevent) { mvwprintz(w_target, getmaxy(w_target) - 3, 1, c_white, _("'<' '>' Cycle targets; 'f' or '.' to fire")); mvwprintz(w_target, getmaxy(w_target) - 2, 1, c_white, _("'0' target self; '*' toggle snap-to-target")); } wrefresh(w_target); char ch; bool snap_to_target = OPTIONS["SNAP_TO_TARGET"]; do { if (m.sees(u.posx, u.posy, x, y, -1, tart)) ret = line_to(u.posx, u.posy, x, y, tart); else ret = line_to(u.posx, u.posy, x, y, 0); if(trigdist && trig_dist(u.posx,u.posy, x,y) > range) { bool cont=true; int cx=x; int cy=y; for (int i = 0; i < ret.size() && cont; i++) { if(trig_dist(u.posx,u.posy, ret[i].x, ret[i].y) > range) { ret.resize(i); cont=false; } else { cx=0+ret[i].x; cy=0+ret[i].y; } } x=cx;y=cy; } point center; if (snap_to_target) center = point(x, y); else center = point(u.posx + u.view_offset_x, u.posy + u.view_offset_y); // Clear the target window. for (int i = 1; i < getmaxy(w_target) - 5; i++) { for (int j = 1; j < getmaxx(w_target) - 2; j++) mvwputch(w_target, i, j, c_white, ' '); } m.build_map_cache(this); m.draw(this, w_terrain, center); // Draw the Monsters for (int i = 0; i < z.size(); i++) { if (u_see(&(z[i]))) { z[i].draw(w_terrain, center.x, center.y, false); } } // Draw the NPCs for (int i = 0; i < active_npc.size(); i++) { if (u_see(active_npc[i]->posx, active_npc[i]->posy)) active_npc[i]->draw(w_terrain, center.x, center.y, false); } if (x != u.posx || y != u.posy) { // Draw the player int atx = VIEWX + u.posx - center.x, aty = VIEWY + u.posy - center.y; if (atx >= 0 && atx < TERRAIN_WINDOW_WIDTH && aty >= 0 && aty < TERRAIN_WINDOW_HEIGHT) mvwputch(w_terrain, aty, atx, u.color(), '@'); // Only draw a highlighted trajectory if we can see the endpoint. // Provides feedback to the player, and avoids leaking information about tiles they can't see. if (u_see( x, y)) { for (int i = 0; i < ret.size(); i++) { int mondex = mon_at(ret[i].x, ret[i].y), npcdex = npc_at(ret[i].x, ret[i].y); // NPCs and monsters get drawn with inverted colors if (mondex != -1 && u_see(&(z[mondex]))) z[mondex].draw(w_terrain, center.x, center.y, true); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, true); else m.drawsq(w_terrain, u, ret[i].x, ret[i].y, true,true,center.x, center.y); } } if (!relevent) { // currently targetting vehicle to refill with fuel vehicle *veh = m.veh_at(x, y); if (veh) mvwprintw(w_target, 1, 1, _("There is a %s"), veh->name.c_str()); } else mvwprintw(w_target, 1, 1, _("Range: %d"), rl_dist(u.posx, u.posy, x, y)); if (mon_at(x, y) == -1) { if (snap_to_target) mvwputch(w_terrain, VIEWY, VIEWX, c_red, '*'); else mvwputch(w_terrain, VIEWY + y - center.y, VIEWX + x - center.x, c_red, '*'); } else if (u_see(&(z[mon_at(x, y)]))) { z[mon_at(x, y)].print_info(this, w_target,2); } } wrefresh(w_target); wrefresh(w_terrain); wrefresh(w_status); refresh(); ch = input(); get_direction(this, tarx, tary, ch); if (tarx != -2 && tary != -2 && ch != '.') { // Direction character pressed int mondex = mon_at(x, y), npcdex = npc_at(x, y); if (mondex != -1 && u_see(&(z[mondex]))) z[mondex].draw(w_terrain, center.x, center.y, false); else if (npcdex != -1) active_npc[npcdex]->draw(w_terrain, center.x, center.y, false); else if (m.sees(u.posx, u.posy, x, y, -1, junk)) m.drawsq(w_terrain, u, x, y, false, true, center.x, center.y); else mvwputch(w_terrain, VIEWY, VIEWX, c_black, 'X'); x += tarx; y += tary; if (x < lowx) x = lowx; else if (x > hix) x = hix; if (y < lowy) y = lowy; else if (y > hiy) y = hiy; } else if ((ch == '<') && (target != -1)) { target--; if (target == -1) target = t.size() - 1; x = t[target].posx; y = t[target].posy; } else if ((ch == '>') && (target != -1)) { target++; if (target == t.size()) target = 0; x = t[target].posx; y = t[target].posy; } else if (ch == '.' || ch == 'f' || ch == 'F' || ch == '\n') { for (int i = 0; i < t.size(); i++) { if (t[i].posx == x && t[i].posy == y) target = i; } if (u.posx == x && u.posy == y) ret.clear(); break; } else if (ch == '0') { x = u.posx; y = u.posy; ret.clear(); } else if (ch == '*') snap_to_target = !snap_to_target; else if (ch == KEY_ESCAPE || ch == 'q') { // return empty vector (cancel) ret.clear(); break; } } while (true); return ret; }
void game::fire(player &p, int tarx, int tary, std::vector<point> &trajectory, bool burst) { item ammotmp; item* gunmod = p.weapon.active_gunmod(); it_ammo *curammo = NULL; item *weapon = NULL; if (p.weapon.has_flag("CHARGE")) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = p.weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); tmpammo->range = 5 + charges * 5; if (charges <= 4) tmpammo->dispersion = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->dispersion = charges * (charges - 4); tmpammo->recoil = tmpammo->dispersion * .8; if (charges == 8) { tmpammo->ammo_effects.insert("EXPLOSIVE_BIG"); } else if (charges >= 6) { tmpammo->ammo_effects.insert("EXPLOSIVE"); } if (charges >= 5){ tmpammo->ammo_effects.insert("FLAME"); } else if (charges >= 4) { tmpammo->ammo_effects.insert("INCENDIARY"); } if (gunmod != NULL) { weapon = gunmod; } else { weapon = &p.weapon; } curammo = tmpammo; weapon->curammo = tmpammo; weapon->active = false; weapon->charges = 0; } else if (gunmod != NULL) { weapon = gunmod; curammo = weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = p.weapon.curammo; weapon = &p.weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!weapon->is_gun() && !weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", p.name.c_str(), weapon->tname().c_str()); return; } bool is_bolt = false; std::set<std::string> *effects = &curammo->ammo_effects; // Bolts and arrows are silent if (curammo->type == "bolt" || curammo->type == "arrow") is_bolt = true; int x = p.posx, y = p.posy; // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); if (p.has_trait(PF_TRIGGERHAPPY) && one_in(30)) burst = true; if (burst && weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto // Use different amounts of time depending on the type of gun and our skill if (!effects->count("BOUNCE")) { p.moves -= time_to_fire(p, firing); } // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = weapon->burst_size(); if (num_shots > weapon->num_charges() && !weapon->has_flag("CHARGE")) num_shots = weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); // Set up a timespec for use in the nanosleep function below timespec ts; ts.tv_sec = 0; ts.tv_nsec = BULLET_SPEED; // Use up some ammunition int trange = rl_dist(p.posx, p.posy, tarx, tary); if (trange < int(firing->volume / 3) && firing->ammo != "shot") trange = int(firing->volume / 3); else if (p.has_bionic("bio_targeting")) { if (trange > LONG_RANGE) trange = int(trange * .65); else trange = int(trange * .8); } if (firing->skill_used == Skill::skill("rifle") && trange > LONG_RANGE) trange = LONG_RANGE + .6 * (trange - LONG_RANGE); std::string message = ""; bool missed = false; int tart; const bool debug_retarget = false; // this will inevitably be needed const bool wildly_spraying = false; // stub for now. later, rng based on stress/skill/etc at the start, int weaponrange = p.weapon.range(); // this is expensive, let's cache. todo: figure out if we need p.weapon.range(&p); for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first if ( curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0) ) { std::vector<point> new_targets; new_targets.clear(); if ( debug_retarget == true ) { mvprintz(curshot,5,c_red,"[%d] %s: retarget: mon_at(%d,%d)",curshot,p.name.c_str(),tarx,tary); if(mon_at(tarx, tary) == -1) { printz(c_red, " = -1"); } else { printz(c_red, ".hp=%d", z[mon_at(tarx, tary)].hp ); } } for ( int radius = 0; /* range from last target, not shooter! */ radius <= 2 + p.skillLevel("gun") && /* more skill: wider burst area? */ radius <= weaponrange && /* this seems redundant */ ( new_targets.empty() || /* got target? stop looking. However this breaks random selection, aka, wildly spraying, so: */ wildly_spraying == true ); /* lets set this based on rng && stress or whatever elsewhere */ radius++ ) { /* iterate from last target's position: makes sense for burst fire.*/ for (std::vector<monster>::iterator it = z.begin(); it != z.end(); it++) { int nt_range_to_me = rl_dist(p.posx, p.posy, it->posx, it->posy); int dummy; if (nt_range_to_me == 0 || nt_range_to_me > weaponrange || !pl_sees(&p, &(*it), dummy)) { /* reject out of range and unseen targets as well as MY FACE */ continue; } int nt_range_to_lt = rl_dist(tarx,tary,it->posx,it->posy); /* debug*/ if ( debug_retarget && nt_range_to_lt <= 5 ) printz(c_red, " r:%d/l:%d/m:%d ..", radius, nt_range_to_lt, nt_range_to_me ); if (nt_range_to_lt != radius) { continue; /* we're spiralling outward, catch you next iteration (maybe) */ } if (it->hp >0 && it->friendly == 0) { new_targets.push_back(point(it->posx, it->posy)); /* oh you're not dead and I don't like you. Hello! */ } } } if ( new_targets.empty() == false ) { /* new victim! or last victim moved */ int target_picked = rng(0, new_targets.size() - 1); /* 1 victim list unless wildly spraying */ tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) { trajectory = line_to(p.posx, p.posy, tarx, tary, tart); } else { trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } /* debug */ if (debug_retarget) printz(c_ltgreen, " NEW:(%d:%d,%d) %d,%d (%s)[%d] hp: %d", target_picked, new_targets[target_picked].x, new_targets[target_picked].y, tarx, tary, z[mon_at(tarx, tary)].name().c_str(), mon_at(tarx, tary), z[mon_at(tarx, tary)].hp); } else if ( ( !p.has_trait(PF_TRIGGERHAPPY) || /* double tap. TRIPLE TAP! wait, no... */ one_in(3) /* on second though...everyone double-taps at times. */ ) && ( p.skillLevel("gun") >= 7 || /* unless trained */ one_in(7 - p.skillLevel("gun")) /* ...sometimes */ ) ) { return; // No targets, so return } else if (debug_retarget) { printz(c_red, " new targets.empty()!"); } } else if (debug_retarget) { mvprintz(curshot,5,c_red,"[%d] %s: target == mon_at(%d,%d)[%d] %s hp %d",curshot, p.name.c_str(), tarx ,tary, mon_at(tarx, tary), z[mon_at(tarx, tary)].name().c_str(), z[mon_at(tarx, tary)].hp); } // Drop a shell casing if appropriate. itype_id casing_type = "null"; if( curammo->type == "shot" ) casing_type = "shot_hull"; else if( curammo->type == "9mm" ) casing_type = "9mm_casing"; else if( curammo->type == "22" ) casing_type = "22_casing"; else if( curammo->type == "38" ) casing_type = "38_casing"; else if( curammo->type == "40" ) casing_type = "40_casing"; else if( curammo->type == "44" ) casing_type = "44_casing"; else if( curammo->type == "45" ) casing_type = "45_casing"; else if( curammo->type == "454" ) casing_type = "454_casing"; else if( curammo->type == "500" ) casing_type = "500_casing"; else if( curammo->type == "57" ) casing_type = "57mm_casing"; else if( curammo->type == "46" ) casing_type = "46mm_casing"; else if( curammo->type == "762" ) casing_type = "762_casing"; else if( curammo->type == "223" ) casing_type = "223_casing"; else if( curammo->type == "3006" ) casing_type = "3006_casing"; else if( curammo->type == "308" ) casing_type = "308_casing"; else if( curammo->type == "40mm" ) casing_type = "40mm_casing"; if (casing_type != "null") { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; if( weapon->has_gunmod("brass_catcher") != -1 ) { p.i_add( casing ); } else { int x = p.posx - 1 + rng(0, 2); int y = p.posy - 1 + rng(0, 2); m.add_item_or_charges(x, y, casing); } } // Use up a round (or 100) if (weapon->has_flag("FIRE_100")) weapon->charges -= 100; else weapon->charges--; if (firing->skill_used != Skill::skill("archery") && firing->skill_used != Skill::skill("throw")) { // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (one_in(2 << firing->durability)) { add_msg_player_or_npc( &p, _("Your weapon misfires!"), _("<npcname>'s weapon misfires!") ); return; } } make_gun_sound_effect(this, p, burst, weapon); int trange = calculate_range(p, tarx, tary); double missed_by = calculate_missed_by(p, trange, weapon); // Calculate a penalty based on the monster's speed double monster_speed_penalty = 1.; int target_index = mon_at(tarx, tary); if (target_index != -1) { monster_speed_penalty = double(z[target_index].speed) / 80.; if (monster_speed_penalty < 1.) monster_speed_penalty = 1.; } if (curshot > 0) { if (recoil_add(p) % 2 == 1) p.recoil++; p.recoil += recoil_add(p) / 2; } else p.recoil += recoil_add(p); if (missed_by >= 1.) { // We missed D: // Shoot a random nearby space? int mtarx = tarx + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); int mtary = tary + rng(0 - int(sqrt(double(missed_by))), int(sqrt(double(missed_by)))); if (m.sees(p.posx, p.posy, x, y, -1, tart)) trajectory = line_to(p.posx, p.posy, mtarx, mtary, tart); else trajectory = line_to(p.posx, p.posy, mtarx, mtary, 0); missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You miss!"), _("<npcname> misses!") ); } } else if (missed_by >= .8 / monster_speed_penalty) { // Hit the space, but not necessarily the monster there missed = true; if (!burst) { add_msg_player_or_npc( &p, _("You barely miss!"), _("<npcname> barely misses!") ); } } int dam = weapon->gun_damage(); int tx = trajectory[0].x; int ty = trajectory[0].y; int px = trajectory[0].x; int py = trajectory[0].y; for (int i = 0; i < trajectory.size() && (dam > 0 || (effects->count("FLAME"))); i++) { px = tx; py = ty; tx = trajectory[i].x; ty = trajectory[i].y; // Drawing the bullet uses player u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if (u_see(tx, ty)) { if (i > 0) { m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true, u.posx + u.view_offset_x, u.posy + u.view_offset_y); } char bullet = '*'; if (effects->count("FLAME")) bullet = '#'; mvwputch(w_terrain, ty + VIEWY - u.posy - u.view_offset_y, tx + VIEWX - u.posx - u.view_offset_x, c_red, bullet); wrefresh(w_terrain); if (&p == &u) nanosleep(&ts, NULL); } if (dam <= 0 && !(effects->count("FLAME"))) { // Ran out of momentum. ammo_effects(this, tx, ty, *effects); if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(4)) || (curammo->m1 != "wood" && !one_in(15)))) m.add_item_or_charges(tx, ty, ammotmp); if (weapon->num_charges() == 0) weapon->curammo = NULL; return; } // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it int mondex = mon_at(tx, ty); // If we shot us a monster... if (mondex != -1 && (!z[mondex].has_flag(MF_DIGS) || rl_dist(p.posx, p.posy, z[mondex].posx, z[mondex].posy) <= 1) && ((!missed && i == trajectory.size() - 1) || one_in((5 - int(z[mondex].type->size))))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; // Penalize for the monster's speed if (z[mondex].speed > 80) goodhit *= double( double(z[mondex].speed) / 80.); std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam, &z[mondex]); shoot_monster(this, p, z[mondex], dam, goodhit, weapon); } else if ((!missed || one_in(3)) && (npc_at(tx, ty) != -1 || (u.posx == tx && u.posy == ty))) { double goodhit = missed_by; if (i < trajectory.size() - 1) // Unintentional hit goodhit = double(rand() / (RAND_MAX + 1.0)) / 2; player *h; if (u.posx == tx && u.posy == ty) h = &u; else h = active_npc[npc_at(tx, ty)]; if (h->power_level >= 10 && h->uncanny_dodge()) { h->power_level -= 7; // dodging bullets costs extra } else { std::vector<point> blood_traj = trajectory; blood_traj.insert(blood_traj.begin(), point(p.posx, p.posy)); splatter(this, blood_traj, dam); shoot_player(this, p, h, dam, goodhit); } } else m.shoot(this, tx, ty, dam, i == trajectory.size() - 1, *effects); } // Done with the trajectory! ammo_effects(this, tx, ty, *effects); if (effects->count("BOUNCE")) { for (int i = 0; i < z.size(); i++) { // search for monsters in radius 4 around impact site if (rl_dist(z[i].posx, z[i].posy, tx, ty) <= 4) { // don't hit targets that have already been hit if (!z[i].has_effect(ME_BOUNCED) && !z[i].dead) { add_msg(_("The attack bounced to %s!"), z[i].name().c_str()); trajectory = line_to(tx, ty, z[i].posx, z[i].posy, 0); if (weapon->charges > 0) fire(p, z[i].posx, z[i].posy, trajectory, false); break; } } } } if (m.move_cost(tx, ty) == 0) { tx = px; ty = py; } if (is_bolt && !(effects->count("IGNITE")) && !(effects->count("EXPLOSIVE")) && ((curammo->m1 == "wood" && !one_in(5)) || (curammo->m1 != "wood" && !one_in(15)) )) m.add_item_or_charges(tx, ty, ammotmp); } if (weapon->num_charges() == 0) weapon->curammo = NULL; }