Repository: PokemonGoF/PokemonGo-Bot

Issue:
FollowPath: execute path only once
### Short Description

Sometime I prefer the bot to follow the path only once, instead of looping. This could be useful for having the bot walk to a new location following a number of waypoints, and then go chasing pokemon and pokestops at the new location.
### Possible solution

In the FollowPath configuration, implement path_mode = 'single'. In follow_path.py in the work() function, check for this path_mode in the piece of code that handles the situation where the end of the path is reached. If so, return WorkerResult.SUCCESS to allow lower priority tasks to run.
### How it would help others

It would help if you want your bot to walk to another location once, and there in the new area after the path is finished.



Discussion:

Resolving patch:
diff --git a/pokemongo_bot/cell_workers/follow_path.py b/pokemongo_bot/cell_workers/follow_path.py
--- a/pokemongo_bot/cell_workers/follow_path.py
+++ b/pokemongo_bot/cell_workers/follow_path.py
@@ -3,21 +3,29 @@
 import gpxpy
 import gpxpy.gpx
 import json
+import time
 from pokemongo_bot.base_task import BaseTask
 from pokemongo_bot.cell_workers.utils import distance, i2f, format_dist
 from pokemongo_bot.human_behaviour import sleep
 from pokemongo_bot.walkers.walker_factory import walker_factory
+from pokemongo_bot.worker_result import WorkerResult
 from pgoapi.utilities import f2i
 from random import uniform
 from utils import getSeconds
 from datetime import datetime as dt, timedelta
 
+STATUS_MOVING = 0
+STATUS_LOITERING = 1
+STATUS_FINISHED = 2
+ 
 class FollowPath(BaseTask):
     SUPPORTED_TASK_API_VERSION = 1
 
     def initialize(self):
         self._process_config()
         self.points = self.load_path()
+        self.status = STATUS_MOVING
+        self.loiter_end_time = 0
 
         if self.path_start_mode == 'closest':
             self.ptr = self.find_closest_point_idx(self.points)
@@ -53,7 +61,7 @@ def load_json(self):
         with open(self.path_file) as data_file:
             points=json.load(data_file)
         # Replace Verbal Location with lat&lng.
-        for index, point in enumerate(points):
+        for point in enumerate(points):
             point_tuple = self.bot.get_pos_by_name(point['location'])
             self.emit_event(
                 'location_found',
@@ -64,12 +72,12 @@ def load_json(self):
                     'position': point_tuple
                 }
             )
-            points[index] = self.point_tuple_to_dict(point_tuple)
+            # Keep point['location']
+            point["lat"] = float(point_tuple[0])
+            point["lng"] = float(point_tuple[1])
+            point["alt"] = float(point_tuple[2])
         return points
 
-    def point_tuple_to_dict(self, tpl):
-        return {'lat': tpl[0], 'lng': tpl[1], 'alt': tpl[2]}
-
     def load_gpx(self):
         gpx_file = open(self.path_file, 'r')
         gpx = gpxpy.parse(gpx_file)
@@ -81,20 +89,20 @@ def load_gpx(self):
         track = gpx.tracks[0]
         for segment in track.segments:
             for point in segment.points:
-                points.append({"lat": point.latitude, "lng": point.longitude, "alt": point.elevation})
+                points.append({"lat": point.latitude, "lng": point.longitude,
+                    "alt": point.elevation, "location": point.name})
 
         return points
 
     def find_closest_point_idx(self, points):
-
         return_idx = 0
         min_distance = float("inf");
         for index in range(len(points)):
             point = points[index]
             botlat = self.bot.api._position_lat
             botlng = self.bot.api._position_lng
-            lat = float(point['lat'])
-            lng = float(point['lng'])
+            lat = point['lat']
+            lng = point['lng']
 
             dist = distance(
                 botlat,
@@ -127,13 +135,20 @@ def endLaps(self):
         self.bot.login()
 
     def work(self):
+        # If done or loitering allow the next task to run
+        if self.status == STATUS_FINISHED:
+            return WorkerResult.SUCCESS
+
+        if self.status == STATUS_LOITERING and time.time() < self.loiter_end_time:
+            return WorkerResult.SUCCESS
+
         last_lat = self.bot.api._position_lat
         last_lng = self.bot.api._position_lng
         last_alt = self.bot.api._position_alt
 
         point = self.points[self.ptr]
-        lat = float(point['lat'])
-        lng = float(point['lng'])
+        lat = point['lat']
+        lng = point['lng']
 
         if 'alt' in point:
             alt = float(point['alt'])
@@ -173,8 +188,16 @@ def work(self):
             }
         )
         
-        if dist <= 1 or (self.bot.config.walk_min > 0 and is_at_destination):
+        if dist <= 1 or (self.bot.config.walk_min > 0 and is_at_destination) or (self.status == STATUS_LOITERING and time.time() >= self.loiter_end_time):
+            if "loiter" in point and self.status != STATUS_LOITERING: 
+                print("Loitering {} seconds".format(point["loiter"]))
+                self.status = STATUS_LOITERING
+                self.loiter_end_time = time.time() + point["loiter"]
+                return WorkerResult.SUCCESS
             if (self.ptr + 1) == len(self.points):
+                if self.path_mode == 'single':
+                    self.status = STATUS_FINISHED
+                    return WorkerResult.SUCCESS
                 self.ptr = 0
                 if self.path_mode == 'linear':
                     self.points = list(reversed(self.points))
@@ -193,4 +216,5 @@ def work(self):
             else:
                 self.ptr += 1
         
-        return [lat, lng]
+        self.status = STATUS_MOVING
+        return WorkerResult.RUNNING


Test patch: