void monster::friendly_move(game *g) { point next; bool moved = false; moves -= 100; if (plans.size() > 0 && (plans[0].x != g->u.posx || plans[0].y != g->u.posy) && (can_move_to(g->m, plans[0].x, plans[0].y) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ next = plans[0]; plans.erase(plans.begin()); moved = true; } else stumble(g, moved); if (moved) { int mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); if (mondex != -1 && g->z[mondex].friendly == 0 && type->melee_dice > 0) hit_monster(g, mondex); else if (npcdex != -1 && type->melee_dice > 0) hit_player(g, g->active_npc[g->npc_at(next.x, next.y)]); else if (mondex == -1 && npcdex == -1 && can_move_to(g->m, next.x, next.y)) move_to(g, next.x, next.y); else if ((!can_move_to(g->m, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } } }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble( bool moved ) { // don't stumble every turn. every 3rd turn, or 8th when walking. if( ( moved && !one_in( 8 ) ) || !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( "SWIMMABLE", dest ) && !g->m.has_flag( "SWIMMABLE", pos3() ) ) && g->critter_at( dest ) == nullptr ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up // It should happen during "shambling around", but not as actual stumbling if( !moved && one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int bresenham_slope, junk; if( !plans.empty() ) { if( g->m.sees( pos3(), plans.back(), -1, bresenham_slope, junk ) ) { set_dest( plans.back(), bresenham_slope ); } else if( sees( g->u, bresenham_slope ) ) { set_dest( g->u.pos(), bresenham_slope ); } else { //durr, i'm suddenly calm. what was i doing? plans.clear(); } } }
/* Function: friendly_move The per-turn movement and action calculation of any friendly monsters. */ void monster::friendly_move(game *g) { point next; bool moved = false; //If we sucessfully calculated a plan in the generic monster movement function, begin executing it. if (plans.size() > 0 && (plans[0].x != g->u.posx || plans[0].y != g->u.posy) && (can_move_to(g, plans[0].x, plans[0].y) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ next = plans[0]; plans.erase(plans.begin()); moved = true; } else { //Otherwise just stumble around randomly until we formulate a plan. moves -= 100; stumble(g, moved); } if (moved) { //We have a plan. int mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); //If there is an unfriendly mosnter in the target square we want to move into, hit them if we have a melee attack. if (mondex != -1 && g->z[mondex].friendly == 0 && type->melee_dice > 0){ hit_monster(g, mondex); } //If there is an npc (any npc?) we hit them assuming we have a melee attack. else if (npcdex != -1 && type->melee_dice > 0){ hit_player(g, *g->active_npc[g->npc_at(next.x, next.y)]); } //If no one is there and its a walkable square, walk there. else if (mondex == -1 && npcdex == -1 && can_move_to(g, next.x, next.y)){ move_to(g, next.x, next.y); } //If there is a bashable object in our way, bash it down. else if ((!can_move_to(g, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); moves -= 100; } //If there is a destroyable object in our way, destroy it. else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } //If all else fails in our plan (an issue with pathfinding maybe) stumble around instead. else { stumble(g, moved); moves -= 100; } } }
// 敵機を自機の方にレーン変更 // 一番外側が レーン0,一番内側が レーン4 void change_lane(Car &car) { if( car.m_laneChanged || // 2回連続レーンチェンジ不可 car.m_lane == g_car.m_lane || !can_move_to(car.m_pos + car.m_v) ) // 行き止まりの場合 { car.m_laneChanged = false; return; } if( car.m_lane > g_car.m_lane ) { // 外側のレーンに移動 if( car.m_v.second == 0 ) { // 水平方向に移動している場合 if( car.m_pos.second < MAP_HT/2 ) { if( can_move_to(car.m_pos - Vec2(0, 1)) ) { car.m_pos.second -= 2; --car.m_lane; car.m_laneChanged = true; } } else { if( !g_level ) return; if( can_move_to(car.m_pos + Vec2(0, 1)) ) { car.m_pos.second += 2; --car.m_lane; car.m_laneChanged = true; } } } else { } } else { // 内側のレーンに移動 if( car.m_v.second == 0 ) { // 水平方向に移動している場合 if( car.m_pos.second < MAP_HT/2 ) { if( can_move_to(car.m_pos + Vec2(0, 1)) ) { car.m_pos.second += 2; ++car.m_lane; car.m_laneChanged = true; } } else { if( !g_level ) return; if( can_move_to(car.m_pos - Vec2(0, 1)) ) { car.m_pos.second -= 2; ++car.m_lane; car.m_laneChanged = true; } } } else { } } assert(car.m_lane >= 0 && car.m_lane < 5); }
void move_car(Car &car) { Vec2 p = car.m_pos + car.m_v; // 次の位置 if( !can_move_to(p) ) { // 壁にぶつかった場合 Vec2 v = rot_right90(car.m_v); // 速度ベクターを90度回転 p = car.m_pos + v; if( can_move_to(p) ) car.m_v = v; else { // 行き止まりは無いと仮定 car.m_v = rot_left90(car.m_v); // 速度ベクターを90度回転 p = car.m_pos + car.m_v; } } car.m_pos = p; }
int reenter_from_bar(const GameState* state, int player, int d) { if (d) { assert(d>=1&&d<=6); if (state->bar[player]) { MoveResult move = can_move_to(state, player, 24-d); if (move) { DEBUG("P%d entering from bar @ %d\n", player, 24-d); DEC(state->bar[player]); int dest = PT(player,24-d); switch (move) { case MOVE_UNOCCUPIED: case MOVE_MERGE: SET(state->points[dest], MAKEPOINT(player, state->points[dest].count+1)); return 1; case MOVE_HITBLOT: SET(state->points[dest], MAKEPOINT(player, 1)); INC(state->bar[player^1]); return 1; default: assert(0); } return 1; } } } return 0; }
bool monster::bash_at( const tripoint &p ) { if( p.z != posz() ) { return false; // TODO: Remove this } if( has_effect( "pacified" ) ) { return false; } //Hallucinations can't bash stuff. if( is_hallucination() ) { return false; } bool try_bash = !can_move_to( p ) || one_in( 3 ); bool can_bash = g->m.is_bashable( p ) && ( has_flag( MF_BASHES ) || has_flag( MF_BORES ) ); if( try_bash && can_bash ) { int bashskill = group_bash_skill( p ); g->m.bash( p, bashskill ); moves -= 100; return true; } return false; }
/* Function: friendly_move The per-turn movement and action calculation of any friendly monsters. */ void monster::friendly_move() { point next; bool moved = false; //If we sucessfully calculated a plan in the generic monster movement function, begin executing it. if (plans.size() > 0 && (plans[0].x != g->u.posx || plans[0].y != g->u.posy) && (can_move_to(plans[0].x, plans[0].y) || (g->m.has_flag("BASHABLE", plans[0].x, plans[0].y) && has_flag(MF_BASHES)))) { next = plans[0]; plans.erase(plans.begin()); moved = true; } else { //Otherwise just stumble around randomly until we formulate a plan. moves -= 100; stumble(moved); } if (moved) { int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); //If all else fails in our plan (an issue with pathfinding maybe) stumble around instead. if(!did_something) { stumble(moved); moves -= 100; } } }
/* Function: friendly_move The per-turn movement and action calculation of any friendly monsters. */ void monster::friendly_move() { tripoint p; bool moved = false; //If we successfully calculated a plan in the generic monster movement function, begin executing it. if( !plans.empty() && ( plans[0] != g->u.pos3() ) && ( can_move_to( plans[0] ) || ( ( has_flag( MF_BASHES ) || has_flag( MF_BORES ) ) && g->m.bash_rating( bash_estimate(), plans[0] ) >= 0 ) ) ) { p = plans[0]; plans.erase( plans.begin() ); moved = true; } else { //Otherwise just stumble around randomly until we formulate a plan. moves -= 100; stumble( moved ); } if( moved ) { bool did_something = attack_at( p ) || bash_at( p ) || move_to( p ); //If all else fails in our plan (an issue with pathfinding maybe) stumble around instead. if( !did_something ) { stumble( moved ); moves -= 100; } } }
void status_machine_imp::move_to(int status) { REQUIRE(can_move_to(status)); _origin_status = _current_status; _current_status = status; }
/** * Stumble in a random direction, but with some caveats. */ void monster::stumble( ) { // Only move every 3rd turn. if( !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( TFLAG_SWIMMABLE, dest ) && !g->m.has_flag( TFLAG_SWIMMABLE, pos3() ) ) && ( g->critter_at( dest, is_hallucination() ) == nullptr ) ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up if( one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(bool moved) { // don't stumble every turn. every 3rd turn, or 8th when walking. if((moved && !one_in(8)) || !one_in(3)) { return; } std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { const int nx = posx() + i; const int ny = posy() + j; if ((i || j) && can_move_to(nx, ny) && /* Don't ever stumble into impassable terrain, even if we normally could * smash it, as this is uncoordinated movement (and is forced). */ g->m.move_cost(nx, ny) != 0 && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !(has_flag(MF_NO_BREATHE) && !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC) && g->m.has_flag("SWIMMABLE", nx, ny) && !g->m.has_flag("SWIMMABLE", posx(), posy())) && (g->u.posx != nx || g->u.posy != ny) && (g->mon_at(nx, ny) == -1)) { point tmp(nx, ny); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.size() == 0) //nowhere to stumble? { return; } int choice = rng(0, valid_stumbles.size() - 1); int cx = valid_stumbles[choice].x; int cy = valid_stumbles[choice].y; moves -= calc_movecost(posx(), posy(), cx, cy); setpos(cx, cy); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int tc; if (plans.size() > 0) { if (g->m.sees(posx(), posy(), plans.back().x, plans.back().y, -1, tc)) set_dest(plans.back().x, plans.back().y, tc); else if (sees_player( tc )) set_dest(g->u.posx, g->u.posy, tc); else //durr, i'm suddenly calm. what was i doing? plans.clear(); } }
MoveResult is_valid_move(const GameState* state, int player, int index, int d) { assert(index>=0 && index<24); assert(state->points[PT(player,index)].occupied == player+1); if (d == 0 || index-d < -1) return 0; // out of bounds else if (index-d == -1) return MOVE_BEAROFF; return can_move_to(state, player, index-d); }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(bool moved) { // don't stumble every turn. every 3rd turn, or 8th when walking. if((moved && !one_in(8)) || !one_in(3)) { return; } std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { const int nx = posx() + i; const int ny = posy() + j; if ((i || j) && can_move_to(nx, ny) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !(has_flag(MF_NO_BREATHE) && !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC) && g->m.has_flag("SWIMMABLE", nx, ny) && !g->m.has_flag("SWIMMABLE", posx(), posy())) && (g->u.posx() != nx || g->u.posy() != ny) && (g->mon_at(nx, ny) == -1) && (g->npc_at(nx, ny) == -1) ) { point tmp(nx, ny); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.empty()) //nowhere to stumble? { return; } int choice = rng(0, valid_stumbles.size() - 1); int cx = valid_stumbles[choice].x; int cy = valid_stumbles[choice].y; move_to( cx, cy, false ); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int bresenham_slope; if (!plans.empty()) { if (g->m.sees( pos(), plans.back(), -1, bresenham_slope)) set_dest(plans.back().x, plans.back().y, bresenham_slope); else if (sees( g->u, bresenham_slope )) set_dest(g->u.posx(), g->u.posy(), bresenham_slope); else //durr, i'm suddenly calm. what was i doing? plans.clear(); } }
point monster::scent_move() { std::vector<point> smoves; int maxsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if (has_flag(MF_KEENNOSE)) { maxsmell = 1; smell_threshold = 400; } int minsmell = 9999; point pbuff, next(-1, -1); unsigned int smell; const bool fleeing = is_fleeing(g->u); if( !fleeing && g->scent( posx(), posy() ) > smell_threshold ) { return next; } for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { const int nx = posx() + x; const int ny = posy() + y; smell = g->scent(nx, ny); int mon = g->mon_at(nx, ny); if ((mon == -1 || g->zombie(mon).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(nx, ny) || (nx == g->u.posx && ny == g->u.posy) || (g->m.has_flag("BASHABLE", nx, ny) && has_flag(MF_BASHES)))) { if ((!fleeing && smell > maxsmell ) || ( fleeing && smell < minsmell ) ) { smoves.clear(); pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); maxsmell = smell; minsmell = smell; } else if ((!fleeing && smell == maxsmell ) || ( fleeing && smell == minsmell ) ) { pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); } } } } if (smoves.size() > 0) { int nextsq = rng(0, smoves.size() - 1); next = smoves[nextsq]; } return next; }
int monster::bash_at(int x, int y) { //Hallucinations can't bash stuff. if(is_hallucination()) { return 0; } bool try_bash = !can_move_to(g, x, y) || one_in(3); bool can_bash = g->m.has_flag("BASHABLE", x, y) && has_flag(MF_BASHES); if(try_bash && can_bash) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); // pileup = more bashskill, but only help bashing mob directly infront of target const int max_helper_depth = 5; const std::vector<point> bzone = get_bashing_zone( point(x, y), pos(), max_helper_depth ); int diffx = pos().x - x; int diffy = pos().y - y; int mo_bash = 0; for( int i = 0; i < bzone.size(); i++ ) { if ( g->mon_at( bzone[i] ) != -1 ) { monster & helpermon = g->zombie( g->mon_at( bzone[i] ) ); // trying for the same door and can bash; put on helper hat if ( helpermon.wandx == wandx && helpermon.wandy == wandy && helpermon.has_flag(MF_BASHES) ) { // helpers lined up behind primary basher add full strength, so do those at either shoulder, others add 50% //addbash *= ( bzone[i].x == pos().x || bzone[i].y == pos().y ? 2 : 1 ); int addbash = int(helpermon.type->melee_dice * helpermon.type->melee_sides); // helpers lined up behind primary basher add full strength, others 50% addbash *= ( ( diffx == 0 && bzone[i].x == pos().x ) || ( diffy == 0 && bzone[i].y == pos().y ) ) ? 2 : 1; mo_bash += addbash; // g->add_msg("+ bashhelp: %d,%d : +%d = %d", bzone[i].x, bzone[i].y, addbash/2, mo_bash/2 ); } } } // by our powers combined... bashskill += int (mo_bash / 2); g->m.bash(x, y, bashskill, bashsound); g->sound(x, y, 18, bashsound); moves -= 100; return 1; } else if (g->m.move_cost(x, y) == 0 && !g->m.is_divable(x, y) && //No smashing water into rubble! has_flag(MF_DESTROYS)) { g->m.destroy(g, x, y, true); //todo: add bash info without BASHABLE flag to walls etc, balanced to these guys moves -= 250; return 1; } return 0; }
int monster::bash_at(int x, int y) { if (has_effect("pacified")) return 0; //Hallucinations can't bash stuff. if(is_hallucination()) { return 0; } bool try_bash = !can_move_to(x, y) || one_in(3); bool can_bash = g->m.is_bashable(x, y) && has_flag(MF_BASHES); if(try_bash && can_bash) { int bashskill = bash_skill(); // pileup = more bashskill, but only help bashing mob directly infront of target const int max_helper_depth = 5; const std::vector<point> bzone = get_bashing_zone( point(x, y), pos(), max_helper_depth ); int diffx = pos().x - x; int diffy = pos().y - y; int mo_bash = 0; for( size_t i = 0; i < bzone.size(); ++i ) { if ( g->mon_at( bzone[i] ) != -1 ) { monster & helpermon = g->zombie( g->mon_at( bzone[i] ) ); // trying for the same door and can bash; put on helper hat if ( helpermon.wandx == wandx && helpermon.wandy == wandy && helpermon.has_flag(MF_BASHES) ) { // helpers lined up behind primary basher add full strength, // so do those at either shoulder, others add 50% int addbash = int(helpermon.type->melee_dice * helpermon.type->melee_sides); if (helpermon.has_flag(MF_DESTROYS)) { addbash *= 2.5; } // helpers lined up behind primary basher add full strength, others 50% addbash *= ( ( diffx == 0 && bzone[i].x == pos().x ) || ( diffy == 0 && bzone[i].y == pos().y ) ) ? 2 : 1; mo_bash += addbash; } } } // by our powers combined... bashskill += int (mo_bash / 2); g->m.bash( x, y, bashskill ); moves -= 100; return 1; } return 0; }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(game *g, bool moved) { // don't stumble every turn. every 3rd turn, or 8th when walking. if((moved && !one_in(8)) || !one_in(3)) { return; } std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (can_move_to(g, posx + i, posy + j) && (g->u.posx != posx + i || g->u.posy != posy + j) && (g->mon_at(posx + i, posy + j) == -1 || (i == 0 && j == 0))) { point tmp(posx + i, posy + j); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.size() == 0) //nowhere to stumble? { return; } int choice = rng(0, valid_stumbles.size() - 1); int cx = valid_stumbles[choice].x; int cy = valid_stumbles[choice].y; moves -= calc_movecost(g, posx, posy, cx, cy); posx = cx; posy = cy; // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int tc; if (plans.size() > 0) { if (g->m.sees(posx, posy, plans.back().x, plans.back().y, -1, tc)) set_dest(plans.back().x, plans.back().y, tc); else if (g->sees_u(posx, posy, tc)) set_dest(g->u.posx, g->u.posy, tc); else //durr, i'm suddenly calm. what was i doing? plans.clear(); } }
void Monster::move_towards(int target_x, int target_y) { Generic_map move_map = GAME.map->get_movement_map(get_intelligence()); Pathfinder pf(move_map); // Simple, dumb movement - suitable for zombies at least Point move; Point to(target_x, target_y), from = get_position(); switch (get_intelligence()) { case INTEL_NULL: case INTEL_PLANT: // Mobile plants just drunken walk. move.x = rng(posx - 1, posx + 1); move.y = rng(posy - 1, posy + 1); break; case INTEL_ZOMBIE: move = pf.get_step(PATH_LINE, from, to); break; case INTEL_ANIMAL: case INTEL_HUMAN: move = pf.get_step(PATH_A_STAR, from, to); break; default: debugmsg("No AI movement coded for Intel_level %s", intel_level_name(get_intelligence()).c_str()); } // TODO: Add a "Stumble" flag that occasionally randomly picks, rather than // picking the best available. if (can_move_to( GAME.map, move.x, move.y )) { move_to( GAME.map, move.x, move.y ); // TODO: Add a "smashes terrain" flag, and if we can't move then smash } else if (GAME.map->is_smashable(move.x, move.y)) { std::string sound = GAME.map->smash(move.x, move.y, base_attack().roll_damage()); GAME.make_sound(sound, move.x, move.y); use_ap(100); } else { pause(); } }
tripoint monster::scent_move() { // @todo Remove when scentmap is 3D if( abs( posz() - g->get_levz() ) > 1 ) { return { -1, -1, INT_MIN }; } std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent.get( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent.get( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = bash_skill() > 0; for( const auto &dest : g->m.points_in_radius( pos(), 1, 1 ) ) { int smell = g->scent.get( dest ); if( g->m.valid_move( pos(), dest, can_bash, true ) && ( can_move_to( dest ) || ( dest == g->u.pos() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) > 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
point monster::scent_move(game *g) { plans.clear(); std::vector<point> smoves; int maxsmell = 2; // Squares with smell 0 are not eligable targets if (has_flag(MF_KEENNOSE)) { maxsmell = 1; } int minsmell = 9999; point pbuff, next(-1, -1); unsigned int smell; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { smell = g->scent(posx + x, posy + y); int mon = g->mon_at(posx + x, posy + y); if ((mon == -1 || g->z[mon].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g, posx + x, posy + y) || (posx + x == g->u.posx && posx + y == g->u.posy) || (g->m.has_flag(bashable, posx + x, posy + y) && has_flag(MF_BASHES)))) { if ((!is_fleeing(g->u) && smell > maxsmell) || ( is_fleeing(g->u) && smell < minsmell) ) { smoves.clear(); pbuff.x = posx + x; pbuff.y = posy + y; smoves.push_back(pbuff); maxsmell = smell; minsmell = smell; } else if ((!is_fleeing(g->u) && smell == maxsmell) || ( is_fleeing(g->u) && smell == minsmell) ) { pbuff.x = posx + x; pbuff.y = posy + y; smoves.push_back(pbuff); } } } } if (smoves.size() > 0) { int nextsq = rng(0, smoves.size() - 1); next = smoves[nextsq]; } return next; }
int monster::bash_at(int x, int y) { if (has_effect("pacified")) return 0; //Hallucinations can't bash stuff. if(is_hallucination()) { return 0; } bool try_bash = !can_move_to(x, y) || one_in(3); bool can_bash = g->m.is_bashable(x, y) && (has_flag(MF_BASHES) || has_flag(MF_BORES)); if( try_bash && can_bash ) { int bashskill = group_bash_skill( point(x, y) ); g->m.bash( x, y, bashskill ); moves -= 100; return 1; } return 0; }
bool monster::bash_at( const tripoint &p ) { if( p.z != posz() ) { return false; // TODO: Remove this } //Hallucinations can't bash stuff. if( is_hallucination() ) { return false; } bool try_bash = !can_move_to( p ) || one_in( 3 ); bool can_bash = g->m.is_bashable( p ) && bash_skill() > 0; if( try_bash && can_bash ) { int bashskill = group_bash_skill( p ); g->m.bash( p, bashskill ); moves -= 100; return true; } return false; }
tripoint monster::scent_move() { std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); for( const auto &dest : g->m.points_in_radius( pos(), 1 ) ) { int smell = g->scent( dest ); int mon = g->mon_at( dest ); if( ( mon == -1 || g->zombie( mon ).friendly != 0 || has_flag( MF_ATTACKMON ) ) && ( can_move_to( dest ) || ( dest == g->u.pos3() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) >= 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble(game *g, bool moved) { std::vector <point> valid_stumbles; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (can_move_to(g->m, posx + i, posy + j) && (g->u.posx != posx + i || g->u.posy != posy + j) && (g->mon_at(posx + i, posy + j) == -1 || (i == 0 && j == 0))) { point tmp(posx + i, posy + j); valid_stumbles.push_back(tmp); } } } if (valid_stumbles.size() > 0 && (one_in(8) || (!moved && one_in(3)))) { int choice = rng(0, valid_stumbles.size() - 1); posx = valid_stumbles[choice].x; posy = valid_stumbles[choice].y; if (!has_flag(MF_DIGS) || !has_flag(MF_FLIES)) moves -= (g->m.move_cost(posx, posy) - 2) * 50; // Here we have to fix our plans[] list, trying to get back to the last point // Otherwise the stumble will basically have no effect! if (plans.size() > 0) { int tc; if (g->m.sees(posx, posy, plans[0].x, plans[0].y, -1, tc)) { // Copy out old plans... std::vector <point> plans2; for (int i = 0; i < plans.size(); i++) plans2.push_back(plans[i]); // Set plans to a route between where we are now, and where we were set_dest(plans[0].x, plans[0].y, tc); // Append old plans to the new plans for (int index = 0; index < plans2.size(); index++) plans.push_back(plans2[index]); } else plans.clear(); } } }
int monster::bash_at(int x, int y) { //Hallucinations can't bash stuff. if(is_hallucination()) { return 0; } bool try_bash = !can_move_to(g, x, y) || one_in(3); bool can_bash = g->m.has_flag(bashable, x, y) && has_flag(MF_BASHES); if(try_bash && can_bash) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(x, y, bashskill, bashsound); g->sound(x, y, 18, bashsound); moves -= 100; return 1; } else if (g->m.move_cost(x, y) == 0 && !g->m.is_divable(x, y) && //No smashing water into rubble! has_flag(MF_DESTROYS)) { g->m.destroy(g, x, y, true); moves -= 250; return 1; } return 0; }
void change_lane(Car &car, int &key) { Vec2 p = car.m_pos; if( can_move_to(p) && can_move_to(p + car.m_v) ) { // 行き止まりでない場合 if( car.m_v.second == 0 ) { // 水平方向に移動している場合 if( key == VK_UP && can_move_to(p - Vec2(0, 1)) ) { if( (p.second -= 2) < MAP_HT/2 ) --car.m_lane; // 外側のレーンに移動 else { ++car.m_lane; } key = 0; } else if( key == VK_DOWN && can_move_to(p + Vec2(0, 1)) ) { if( (p.second += 2) > MAP_HT/2 ) --car.m_lane; // 外側のレーンに移動 else ++car.m_lane; key = 0; } } else { // 垂直方向に移動している場合 if( key == VK_LEFT && can_move_to(p - Vec2(1, 0)) ) { if( (p.first -= 2) < MAP_WD/2 ) --car.m_lane; // 外側のレーンに移動 else ++car.m_lane; key = 0; } else if( key == VK_RIGHT && can_move_to(p + Vec2(1, 0)) ) { if( (p.first += 2) > MAP_WD/2 ) --car.m_lane; // 外側のレーンに移動 else ++car.m_lane; key = 0; } } } car.m_pos = p; }
int monster::move_to(int x, int y, bool force) { // Make sure that we can move there, unless force is true. if(!force) if(!g->is_empty(x, y) || !can_move_to(x, y)) { return 0; } if (has_effect("beartrap")) { moves = 0; return 0; } if (plans.size() > 0) { plans.erase(plans.begin()); } if (!force) { moves -= calc_movecost(posx(), posy(), x, y); } //Check for moving into/out of water bool was_water = g->m.is_divable(posx(), posy()); bool will_be_water = g->m.is_divable(x, y); if(was_water && !will_be_water && g->u_see(x, y)) { //Use more dramatic messages for swimming monsters g->add_msg(_("A %s %s from the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("leaps") : _("emerges"), g->m.tername(posx(), posy()).c_str()); } else if(!was_water && will_be_water && g->u_see(x, y)) { g->add_msg(_("A %s %s into the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("dives") : _("sinks"), g->m.tername(x, y).c_str()); } setpos(x, y); footsteps(x, y); if(is_hallucination()) { //Hallucinations don't do any of the stuff after this point return 1; } if (type->size != MS_TINY && g->m.has_flag("SHARP", posx(), posy()) && !one_in(4)) { hurt(rng(2, 3)); } if (type->size != MS_TINY && g->m.has_flag("ROUGH", posx(), posy()) && one_in(6)) { hurt(rng(1, 2)); } if (!digging() && !has_flag(MF_FLIES) && g->m.tr_at(posx(), posy()) != tr_null) { // Monster stepped on a trap! trap* tr = g->traps[g->m.tr_at(posx(), posy())]; if (dice(3, type->sk_dodge + 1) < dice(3, tr->avoidance)) { trapfuncm f; (f.*(tr->actm))(this, posx(), posy()); } } // Diggers turn the dirt into dirtmound if (digging()) { int factor = 0; switch (type->size) { case MS_TINY: factor = 100; break; case MS_SMALL: factor = 30; break; case MS_MEDIUM: factor = 6; break; case MS_LARGE: factor = 3; break; case MS_HUGE: factor = 1; break; } if (has_flag(MF_VERMIN)) { factor *= 100; } if (one_in(factor)) { g->m.ter_set(posx(), posy(), t_dirtmound); } } // Acid trail monsters leave... a trail of acid if (has_flag(MF_ACIDTRAIL)) { g->m.add_field(posx(), posy(), fd_acid, 3); } if (has_flag(MF_SLUDGETRAIL)) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { const int fstr = 3 - (abs(dx) + abs(dy)); if (fstr >= 2) { g->m.add_field(posx() + dx, posy() + dy, fd_sludge, fstr); } } } } return 1; }
point monster::wander_next() { point next; bool xbest = true; if (abs(wandy - posy()) > abs(wandx - posx()))// which is more important xbest = false; next.x = posx(); next.y = posy(); int x = posx(), x2 = posx() - 1, x3 = posx() + 1; int y = posy(), y2 = posy() - 1, y3 = posy() + 1; if (wandx < posx()) { x--; x2++; } if (wandx > posx()) { x++; x2++; x3 -= 2; } if (wandy < posy()) { y--; y2++; } if (wandy > posy()) { y++; y2++; y3 -= 2; } if (xbest) { if (can_move_to(x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y))) { next.x = x; next.y = y; } else if (can_move_to(x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y3))) { next.x = x; next.y = y3; } else if (can_move_to(x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x3, y))) { next.x = x3; next.y = y; } } else { if (can_move_to(x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y))) { next.x = x; next.y = y; } else if (can_move_to(x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x3, y))) { next.x = x3; next.y = y; } else if (can_move_to(x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag("BASHABLE", x, y3))) { next.x = x; next.y = y3; } } return next; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if (wandf > 0) { wandf--; } //Hallucinations have a chance of disappearing each turn if (is_hallucination() && one_in(25)) { die(); return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } //If this monster has the ability to heal in combat, do it now. if (has_flag(MF_REGENERATES_50)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s is visibly regenerating!"), name().c_str()); } hp += 50; if(hp > type->hp) { hp = type->hp; } } } if (has_flag(MF_REGENERATES_10)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s seems a little healthier."), name().c_str()); } hp += 10; if(hp > type->hp) { hp = type->hp; } } } // If this critter dies in sunlight, check & assess damage. if (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { g->add_msg(_("The %s burns horribly in the sunlight!"), name().c_str()); hp -= 100; if(hp < 0) { hp = 0 ; } } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack)(this); } } if (moves < 0) { return; } if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect("stunned")) { stumble(false); moves = 0; return; } if (has_effect("downed")) { moves = 0; return; } if (has_effect("bouldering")) { moves -= 20; if (moves < 0) { return; } } if (friendly != 0) { if (friendly > 0) { friendly--; } friendly_move(); return; } bool moved = false; point next; int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); if (friendly == 0) { current_attitude = attitude(&(g->u)); } // If our plans end in a player, set our attitude to consider that player if (plans.size() > 0) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) { current_attitude = attitude(&(g->u)); } else { for (int i = 0; i < g->active_npc.size(); i++) { if (plans.back().x == g->active_npc[i]->posx && plans.back().y == g->active_npc[i]->posy) { current_attitude = attitude((g->active_npc[i])); } } } } if (current_attitude == MATT_IGNORE || (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) { moves -= 100; stumble(false); return; } if (plans.size() > 0 && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(plans[0].x, plans[0].y) || (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || (g->m.has_flag("BASHABLE", plans[0].x, plans[0].y) && has_flag(MF_BASHES)))) { // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if (has_flag(MF_SMELLS)) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. plans.clear(); point tmp = scent_move(); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); point tmp = wander_next(); if (tmp.x != posx() || tmp.y != posy()) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if (moved) { // Actual effects of moving to the square we've chosen // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); if(!did_something) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; } // If we're close to our target, we get focused and don't stumble if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.size() == 0)) || !moved) { stumble(moved); } }