/** * Check state changes related to movement */ void BehaviorStandard::checkMove() { // dying enemies can't move if (e->stats.cur_state == ENEMY_DEAD || e->stats.cur_state == ENEMY_CRITDEAD) return; // stunned enemies can't act if (e->stats.effects.stun) return; // handle not being in combat and (not patrolling waypoints or waiting at waypoint) if (!e->stats.hero_ally && !e->stats.in_combat && (e->stats.waypoints.empty() || e->stats.waypoint_pause_ticks > 0)) { if (e->stats.cur_state == ENEMY_MOVE) { e->newState(ENEMY_STANCE); } // currently enemies only move while in combat or patrolling return; } // clear current space to allow correct movement mapr->collider.unblock(e->stats.pos.x, e->stats.pos.y); // update direction if (e->stats.facing) { if (++e->stats.turn_ticks > e->stats.turn_delay) { // if blocked, face in pathfinder direction instead if (!mapr->collider.line_of_movement(e->stats.pos.x, e->stats.pos.y, pursue_pos.x, pursue_pos.y, e->stats.movement_type)) { // if a path is returned, target first waypoint bool recalculate_path = false; //if theres no path, it needs to be calculated if(path.empty()) recalculate_path = true; //if the target moved more than 1 tile away, recalculate if(calcDist(map_to_collision(prev_target), map_to_collision(pursue_pos)) > 1.f) recalculate_path = true; //if a collision ocurred then recalculate if(collided) recalculate_path = true; //add a 5% chance to recalculate on every frame. This prevents reclaulating lots of entities in the same frame chance_calc_path += 5; if(percentChance(chance_calc_path)) recalculate_path = true; //dont recalculate if we were blocked and no path was found last time //this makes sure that pathfinding calculation is not spammed when the target is unreachable and the entity is as close as its going to get if(!path_found && collided && !percentChance(chance_calc_path)) recalculate_path = false; else//reset the collision flag only if we dont want the cooldown in place collided = false; prev_target = pursue_pos; // target first waypoint if(recalculate_path) { chance_calc_path = -100; path.clear(); path_found = mapr->collider.compute_path(e->stats.pos, pursue_pos, path, e->stats.movement_type); } if(!path.empty()) { pursue_pos = path.back(); //if distance to node is lower than a tile size, the node is going to be passed and can be removed if(calcDist(e->stats.pos, pursue_pos) <= 1.f) path.pop_back(); } } else { path.clear(); } if(fleeing) e->stats.direction = calcDirection(pursue_pos, e->stats.pos); else e->stats.direction = calcDirection(e->stats.pos, pursue_pos); e->stats.turn_ticks = 0; } } // try to start moving if (e->stats.cur_state == ENEMY_STANCE) { checkMoveStateStance(); } // already moving else if (e->stats.cur_state == ENEMY_MOVE) { checkMoveStateMove(); } // if patrolling waypoints and has reached a waypoint, cycle to the next one if (!e->stats.waypoints.empty()) { FPoint waypoint = e->stats.waypoints.front(); FPoint pos = e->stats.pos; // if the patroller is close to the waypoint if (fabs(waypoint.x - pos.x) <= 0.5f && fabs(waypoint.y - pos.y) <= 0.5f) { e->stats.waypoints.pop(); // pick a new random point if we're wandering if (e->stats.wander) { waypoint = getWanderPoint(); } e->stats.waypoints.push(waypoint); e->stats.waypoint_pause_ticks = e->stats.waypoint_pause; } } // re-block current space to allow correct movement mapr->collider.block(e->stats.pos.x, e->stats.pos.y, e->stats.hero_ally); }
/** * Compute a path from (x1,y1) to (x2,y2) * Store waypoint inside path * limit is the maximum number of explored node * @return true if a path is found */ bool MapCollision::compute_path(FPoint start_pos, FPoint end_pos, std::vector<FPoint> &path, MOVEMENTTYPE movement_type, unsigned int limit) { if (is_outside_map(end_pos.x, end_pos.y)) return false; if (limit == 0) limit = std::max(map_size.x, map_size.y); // path must be empty if (!path.empty()) path.clear(); // convert start & end to MapCollision precision Point start = map_to_collision(start_pos); Point end = map_to_collision(end_pos); // if the target square has an entity, temporarily clear it to compute the path bool target_blocks = false; int target_blocks_type = colmap[end.x][end.y]; if (colmap[end.x][end.y] == BLOCKS_ENTITIES || colmap[end.x][end.y] == BLOCKS_ENEMIES) { target_blocks = true; unblock(end_pos.x, end_pos.y); } Point current = start; AStarNode* node = new AStarNode(start); node->setActualCost(0); node->setEstimatedCost((float)calcDist(start,end)); node->setParent(current); AStarContainer open(map_size.x, map_size.y, limit); AStarCloseContainer close(map_size.x, map_size.y, limit); open.add(node); while (!open.isEmpty() && (unsigned)close.getSize() < limit) { node = open.get_shortest_f(); current.x = node->getX(); current.y = node->getY(); close.add(node); open.remove(node); if ( current.x == end.x && current.y == end.y) break; //path found ! //limit evaluated nodes to the size of the map std::list<Point> neighbours = node->getNeighbours(map_size.x, map_size.y); // for every neighbour of current node for (std::list<Point>::iterator it=neighbours.begin(); it != neighbours.end(); ++it) { Point neighbour = *it; // do not exceed the node limit when adding nodes if ((unsigned)open.getSize() >= limit) { break; } // if neighbour is not free of any collision, skip it if (!is_valid_tile(neighbour.x,neighbour.y,movement_type, false)) continue; // if nabour is already in close, skip it if(close.exists(neighbour)) continue; // if neighbour isn't inside open, add it as a new Node if(!open.exists(neighbour)) { AStarNode* newNode = new AStarNode(neighbour.x,neighbour.y); newNode->setActualCost(node->getActualCost()+(float)calcDist(current,neighbour)); newNode->setParent(current); newNode->setEstimatedCost((float)calcDist(neighbour,end)); open.add(newNode); } // else, update it's cost if better else { AStarNode* i = open.get(neighbour.x, neighbour.y); if (node->getActualCost()+(float)calcDist(current,neighbour) < i->getActualCost()) { Point pos(i->getX(), i->getY()); Point parent_pos(node->getX(), node->getY()); open.updateParent(pos, parent_pos, node->getActualCost()+(float)calcDist(current,neighbour)); } } } } if (current.x != end.x || current.y != end.y) { //couldnt find the target so map a path to the closest node found node = close.get_shortest_h(); current.x = node->getX(); current.y = node->getY(); while (current.x != start.x || current.y != start.y) { path.push_back(collision_to_map(current)); current = close.get(current.x, current.y)->getParent(); } } else { // store path from end to start path.push_back(collision_to_map(end)); while (current.x != start.x || current.y != start.y) { path.push_back(collision_to_map(current)); current = close.get(current.x, current.y)->getParent(); } } // reblock target if needed if (target_blocks) block(end_pos.x, end_pos.y, target_blocks_type == BLOCKS_ENEMIES); return !path.empty(); }