void iuse::marloss(game *g, player *p, item *it, bool t) { if (p->is_npc()) return; // If we have the marloss in our veins, we are a "breeder" and will spread // alien lifeforms. if (p->has_trait(PF_MARLOSS)) { g->add_msg("As you eat the berry, you have a near-religious experience, feeling at one with your surroundings..."); p->add_morale(MORALE_MARLOSS, 250, 1000); p->hunger = -100; monster goo(g->mtypes[mon_blob]); goo.friendly = -1; int goo_spawned = 0; for (int x = p->posx - 4; x <= p->posx + 4; x++) { for (int y = p->posy - 4; y <= p->posy + 4; y++) { if (rng(0, 10) > trig_dist(x, y, p->posx, p->posy) && rng(0, 10) > trig_dist(x, y, p->posx, p->posy) ) g->m.marlossify(x, y); if (one_in(10 + 5 * trig_dist(x, y, p->posx, p->posy)) && (goo_spawned == 0 || one_in(goo_spawned * 2))) { goo.spawn(x, y); g->z.push_back(goo); goo_spawned++; } } } return; } /* If we're not already carriers of Marloss, roll for a random effect: * 1 - Mutate * 2 - Mutate * 3 - Mutate * 4 - Purify * 5 - Purify * 6 - Cleanse radiation + Purify * 7 - Fully satiate * 8 - Vomit * 9 - Give Marloss mutation */ int effect = rng(1, 9); if (effect <= 3) { g->add_msg("This berry tastes extremely strange!"); p->mutate(g); } else if (effect <= 6) { // Radiation cleanse is below g->add_msg("This berry makes you feel better all over."); p->pkill += 30; this->purifier(g, p, it, t); } else if (effect == 7) { g->add_msg("This berry is delicious, and very filling!"); p->hunger = -100; } else if (effect == 8) { g->add_msg("You take one bite, and immediately vomit!"); p->vomit(g); } else if (!p->has_trait(PF_MARLOSS)) { g->add_msg("You feel a strange warmth spreading throughout your body..."); p->toggle_trait(PF_MARLOSS); } if (effect == 6) p->radiation = 0; }
/** * Method to make monster movement speed consistent in the face of staggering behavior and * differing distance metrics. * It works by scaling the cost to take a step by * how much that step reduces the distance to your goal. * Since it incorporates the current distance metric, * it also scales for diagonal vs orthoganal movement. **/ static float get_stagger_adjust( const tripoint &source, const tripoint &destination, const tripoint &next_step ) { // TODO: push this down into rl_dist const float initial_dist = trigdist ? trig_dist( source, destination ) : rl_dist( source, destination ); const float new_dist = trigdist ? trig_dist( next_step, destination ) : rl_dist( next_step, destination ); // If we return 0, it wil cancel the action. return std::max( 0.01f, initial_dist - new_dist ); }
int rl_dist(const tripoint loc1, const tripoint loc2) { if(trigdist) { return trig_dist(loc1, loc2); } return square_dist(loc1, loc2); }
int rl_dist(int x1, int y1, int x2, int y2) { if(trigdist) { return trig_dist( x1, y1, x2, y2 ); } return square_dist( x1, y1, x2, y2 ); }
void iuse::radio_on(game *g, player *p, item *it, bool t) { if (t) { // Normal use int best_signal = 0; std::string message = "Radio: Kssssssssssssh."; for (int k = 0; k < g->cur_om.radios.size(); k++) { int signal = g->cur_om.radios[k].strength - trig_dist(g->cur_om.radios[k].x, g->cur_om.radios[k].y, g->levx, g->levy); if (signal > best_signal) { best_signal = signal; message = g->cur_om.radios[k].message; } } if (best_signal > 0) { for (int j = 0; j < message.length(); j++) { if (dice(10, 100) > dice(10, best_signal * 5)) { if (!one_in(10)) message[j] = '#'; else message[j] = char(rng('a', 'z')); } } message = "radio: " + message; } point p = g->find_item(it); g->sound(p.x, p.y, 6, message.c_str()); } else { // Turning it off g->add_msg("The radio dies."); it->make(g->itypes[itm_radio]); it->active = false; } }
int rl_dist(int x1, int y1, int x2, int y2) { if(trigdist) { return trig_dist( x1, y1, x2, y2 ); } int dx = abs(x1 - x2), dy = abs(y1 - y2); if (dx > dy) return dx; return dy; }
void draw_circle( std::function<void( const int, const int )>set, int x, int y, int rad ) { for( int i = x - rad; i <= x + rad; i++ ) { for( int j = y - rad; j <= y + rad; j++ ) { if( trig_dist( x, y, i, j ) <= rad ) { set( i, j ); } } } }
void map::apply_light_ray(bool lit[LIGHTMAP_CACHE_X][LIGHTMAP_CACHE_Y], int sx, int sy, int ex, int ey, float luminance, bool trig_brightcalc) { int ax = abs(ex - sx) << 1; int ay = abs(ey - sy) << 1; int dx = (sx < ex) ? 1 : -1; int dy = (sy < ey) ? 1 : -1; int x = sx; int y = sy; if( sx == ex && sy == ey ) { return; } float transparency = LIGHT_TRANSPARENCY_CLEAR; float light = 0.0; int td = 0; // TODO: [lightmap] Pull out the common code here rather than duplication if (ax > ay) { int t = ay - (ax >> 1); do { if(t >= 0) { y += dy; t -= ax; } x += dx; t += ay; if (INBOUNDS(x, y)) { if (!lit[x][y]) { // Multiple rays will pass through the same squares so we need to record that lit[x][y] = true; // We know x is the longest angle here and squares can ignore the abs calculation light = 0.0; if ( trig_brightcalc ) { td = trig_dist(sx, sy, x, y); light = luminance / ( td * td ); } else { light = luminance / ((sx - x) * (sx - x)); } lm[x][y] += light * transparency; } transparency *= light_transparency(x, y); } if (transparency <= LIGHT_TRANSPARENCY_SOLID) { break; } } while(!(x == ex && y == ey)); } else {
// returns the normalized dx, dy, dz for the current line vector. // ret.second contains z and can be ignored if unused. std::pair<std::pair<double, double>, double> slope_of(const std::vector<tripoint> &line) { assert(!line.empty() && line.front() != line.back()); const double len = trig_dist(line.front(), line.back()); double normDx = (line.back().x - line.front().x) / len; double normDy = (line.back().y - line.front().y) / len; double normDz = (line.back().z - line.front().z) / len; std::pair<double, double> retXY = std::make_pair(normDx, normDy); // slope of <x, y> z std::pair<std::pair<double, double>, double> ret = std::make_pair(retXY, normDz); 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(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; }
int trig_dist(const int x1, const int y1, const int x2, const int y2) { return trig_dist(tripoint(x1, y1, 0),tripoint(x2, y2, 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, 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; }
// 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; }
void iuse::two_way_radio(game *g, player *p, item *it, bool t) { WINDOW* w = newwin(6, 36, 9, 5); wborder(w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); // TODO: More options here. Thoughts... // > Respond to the SOS of an NPC // > Report something to a faction // > Call another player mvwprintz(w, 1, 1, c_white, "1: Radio a faction for help..."); mvwprintz(w, 2, 1, c_white, "2: Call Acquaitance..."); mvwprintz(w, 3, 1, c_white, "3: General S.O.S."); mvwprintz(w, 4, 1, c_white, "0: Cancel"); wrefresh(w); char ch = getch(); if (ch == '1') { p->moves -= 300; faction* fac = g->list_factions("Call for help..."); if (fac == NULL) { it->charges++; return; } int bonus = 0; if (fac->goal == FACGOAL_CIVILIZATION) bonus += 2; if (fac->has_job(FACJOB_MERCENARIES)) bonus += 4; if (fac->has_job(FACJOB_DOCTORS)) bonus += 2; if (fac->has_value(FACVAL_CHARITABLE)) bonus += 3; if (fac->has_value(FACVAL_LONERS)) bonus -= 3; if (fac->has_value(FACVAL_TREACHERY)) bonus -= rng(0, 8); bonus += fac->respects_u + 3 * fac->likes_u; if (bonus >= 25) { popup("They reply, \"Help is on the way!\""); g->add_event(EVENT_HELP, g->turn + fac->response_time(g), fac->id, -1, -1); fac->respects_u -= rng(0, 8); fac->likes_u -= rng(3, 5); } else if (bonus >= -5) { popup("They reply, \"Sorry, you're on your own!\""); fac->respects_u -= rng(0, 5); } else { popup("They reply, \"Hah! We hope you die!\""); fac->respects_u -= rng(1, 8); } } else if (ch == '2') { // Call Acquaitance // TODO: Implement me! } else if (ch == '3') { // General S.O.S. p->moves -= 150; std::vector<npc*> in_range; for (int i = 0; i < g->cur_om.npcs.size(); i++) { if (g->cur_om.npcs[i].op_of_u.value >= 4 && trig_dist(g->levx, g->levy, g->cur_om.npcs[i].mapx, g->cur_om.npcs[i].mapy) <= 30) in_range.push_back(&(g->cur_om.npcs[i])); } if (in_range.size() > 0) { npc* coming = in_range[rng(0, in_range.size() - 1)]; popup("A reply! %s says, \"I'm on my way; give me %d minutes!\"", coming->name.c_str(), coming->minutes_to_u(g)); coming->mission = NPC_MISSION_RESCUE_U; } else popup("No-one seems to reply..."); } else it->charges++; // Canceled the call, get our charge back werase(w); wrefresh(w); delwin(w); refresh(); }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) ) { if( !g->m.i_at( pos3() ).empty() ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); for( auto &elem : g->m.i_at( pos3() ) ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear( pos3() ); } } static const std::string pacified_string = "pacified"; const bool pacified = has_effect( pacified_string ); // First, use the special attack, if we can! for( size_t i = 0; i < sp_timeout.size(); ++i ) { if( sp_timeout[i] > 0 ) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && !pacified && !is_hallucination() ) { type->sp_attack[i]( this, i ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects(attacking) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } static const std::string stun_string = "stunned"; if( has_effect( stun_string ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos3() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( goal == i->pos3() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist(pos(), goal) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // CONCRETE PLANS - Most likely based on sight if( !wander() ) { destination = goal; moved = true; } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); tripoint tmp = wander_next(); if( tmp != pos() ) { destination = tmp; moved = true; } } tripoint next_step; if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { const Creature *target = g->critter_at( candidate, is_hallucination() ); // When attacking an adjacent enemy, we're direct. if( target != nullptr && attitude_to( *target ) == A_HOSTILE ) { moved = true; next_step = candidate; break; } // Bail out if we can't move there and we can't bash. if( !can_move_to( candidate ) && !(can_bash && g->m.bash_rating( bash_estimate(), candidate ) >= 0 ) ) { continue; } // Bail out if there's a non-hostile monster in the way and we're not pushy. if( target != nullptr && attitude_to( *target ) != A_HOSTILE && !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { continue; } float progress = distance_to_target - trig_dist( candidate, destination ); switch_chance += progress; // Randomly pick one of the viable squares to move to weighted by distance. if( x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. if( !has_flag( MF_STUMBLES ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen // move_to() uses the slope to determine some move speed scaling. const float slope = (destination.x > destination.y) ? (float)destination.y / (float)destination.x : (float)destination.x / (float)destination.y; const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, slope ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); } }
bool leap_actor::call( monster &z ) const { if( !z.can_act() ) { return false; } std::vector<tripoint> options; tripoint target = z.move_target(); float best_float = trig_dist( z.pos(), target ); if( best_float < min_consider_range || best_float > max_consider_range ) { return false; } // We wanted the float for range check // int here will make the jumps more random int best = ( int )best_float; if( !allow_no_target && z.attack_target() == nullptr ) { return false; } for( const tripoint &dest : g->m.points_in_radius( z.pos(), max_range ) ) { if( dest == z.pos() ) { continue; } if( !z.sees( dest ) ) { continue; } if( !g->is_empty( dest ) ) { continue; } int cur_dist = rl_dist( target, dest ); if( cur_dist > best ) { continue; } if( trig_dist( z.pos(), dest ) < min_range ) { continue; } bool blocked_path = false; // check if monster has a clear path to the proposed point std::vector<tripoint> line = g->m.find_clear_path( z.pos(), dest ); for( auto &i : line ) { if( g->m.impassable( i ) ) { blocked_path = true; break; } } if( blocked_path ) { continue; } if( cur_dist < best ) { // Better than any earlier one options.clear(); } options.push_back( dest ); best = cur_dist; } if( options.empty() ) { return false; // Nowhere to leap! } z.moves -= move_cost; const tripoint chosen = random_entry( options ); bool seen = g->u.sees( z ); // We can see them jump... z.setpos( chosen ); seen |= g->u.sees( z ); // ... or we can see them land if( seen ) { add_msg( _( "The %s leaps!" ), z.name().c_str() ); } return true; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) { if( g->u.sees( *this ) ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); } static const auto volume_per_hp = units::from_milliliter( 250 ); for( auto &elem : g->m.i_at( pos() ) ) { hp += elem.volume() / volume_per_hp; // Yeah this means it can get more HP than normal. } g->m.i_clear( pos() ); } const bool pacified = has_effect( effect_pacified ); // First, use the special attack, if we can! // The attack may change `monster::special_attacks` (e.g. by transforming // this into another monster type). Therefor we can not iterate over it // directly and instead iterate over the map from the monster type // (properties of monster types should never change). for( const auto &sp_type : type->special_attacks ) { const std::string &special_name = sp_type.first; const auto local_iter = special_attacks.find( special_name ); if( local_iter == special_attacks.end() ) { continue; } mon_special_attack &local_attack_data = local_iter->second; if( !local_attack_data.enabled ) { continue; } if( local_attack_data.cooldown > 0 ) { local_attack_data.cooldown--; } if( local_attack_data.cooldown == 0 && !pacified && !is_hallucination() ) { if( !sp_type.second->call( *this ) ) { continue; } // `special_attacks` might have changed at this point. Sadly `reset_special` // doesn't check the attack name, so we need to do it here. if( special_attacks.count( special_name ) == 0 ) { continue; } reset_special( special_name ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects( attacking ) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( effect_stunned ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( goal == i->pos() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist( pos(), goal ) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // If true, don't try to greedily avoid locally bad paths bool pathed = false; if( !wander() ) { while( !path.empty() && path.front() == pos() ) { path.erase( path.begin() ); } const auto &pf_settings = get_pathfinding_settings(); if( pf_settings.max_dist >= rl_dist( pos(), goal ) && ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != goal ) ) { // We need a new path path = g->m.route( pos(), goal, pf_settings, get_path_avoid() ); } // Try to respect old paths, even if we can't pathfind at the moment if( !path.empty() && path.back() == goal ) { destination = path.front(); moved = true; pathed = true; } else { // Straight line forward, probably because we can't pathfind (well enough) destination = goal; moved = true; } } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); if( wander_pos != pos() ) { destination = wander_pos; moved = true; } } if( !g->m.has_zlevels() ) { // Otherwise weird things happen destination.z = posz(); } tripoint next_step; const bool staggers = has_flag( MF_STUMBLES ); if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = bash_skill() > 0; // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { if( candidate.z != posz() ) { bool can_z_move = true; if( !g->m.valid_move( pos(), candidate, false, true ) ) { // Can't phase through floor can_z_move = false; } if( can_z_move && !can_fly && candidate.z > posz() && !g->m.has_floor_or_support( candidate ) ) { // Can't "jump" up a whole z-level can_z_move = false; } // Last chance - we can still do the z-level stair teleport bullshit that isn't removed yet // @todo Remove z-level stair bullshit teleport after aligning all stairs if( !can_z_move && posx() / ( SEEX * 2 ) == candidate.x / ( SEEX * 2 ) && posy() / ( SEEY * 2 ) == candidate.y / ( SEEY * 2 ) ) { const tripoint &upper = candidate.z > posz() ? candidate : pos(); const tripoint &lower = candidate.z > posz() ? pos() : candidate; if( g->m.has_flag( TFLAG_GOES_DOWN, upper ) && g->m.has_flag( TFLAG_GOES_UP, lower ) ) { can_z_move = true; } } if( !can_z_move ) { continue; } } // A flag to allow non-stumbling critters to stumble when the most direct choice is bad. bool bad_choice = false; const Creature *target = g->critter_at( candidate, is_hallucination() ); if( target != nullptr ) { const Creature::Attitude att = attitude_to( *target ); if( att == A_HOSTILE ) { // When attacking an adjacent enemy, we're direct. moved = true; next_step = candidate; break; } else if( att == A_FRIENDLY && ( target->is_player() || target->is_npc() ) ) { continue; // Friendly firing the player or an NPC is illegal for gameplay reasons } else if( !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { // Bail out if there's a non-hostile monster in the way and we're not pushy. continue; } // Friendly fire and pushing are always bad choices - they take a lot of time bad_choice = true; } // Bail out if we can't move there and we can't bash. if( !pathed && !can_move_to( candidate ) ) { if( !can_bash ) { continue; } const int estimate = g->m.bash_rating( bash_estimate(), candidate ); if( estimate <= 0 ) { continue; } if( estimate < 5 ) { bad_choice = true; } } const float progress = distance_to_target - trig_dist( candidate, destination ); // The x2 makes the first (and most direct) path twice as likely, // since the chance of switching is 1/1, 1/4, 1/6, 1/8 switch_chance += progress * 2; // Randomly pick one of the viable squares to move to weighted by distance. if( moved == false || x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. // Except if the direct path is bad, then check others // Or if the path is given by pathfinder if( !staggers && ( !bad_choice || pathed ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, get_stagger_adjust( pos(), destination, next_step ) ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); path.clear(); } }