Repository: robojackets/robocup-software
Issue: Don't attempt to score right away on indirect kicks

Right now we do, which would count against us if we were able to make it.  The ball must touch another robot before we can score.


Changed files:
soccer/gameplay/skills/line_kick.py, soccer/gameplay/tactics/coordinated_pass.py, soccer/gameplay/evaluation/touchpass_positioning.py, soccer/gameplay/skills/_kick.py, soccer/gameplay/plays/restarts/our_free_kick.py

diff --git a/soccer/gameplay/evaluation/touchpass_positioning.py b/soccer/gameplay/evaluation/touchpass_positioning.py
index 02093a924c..e010c21bef 100644
--- a/soccer/gameplay/evaluation/touchpass_positioning.py
+++ b/soccer/gameplay/evaluation/touchpass_positioning.py
@@ -19,7 +19,7 @@ import evaluation.passing
 # This rectangle will only include points with a lower y value that the ball's current location, and will be on the side of the field
 # opposite to the ball.
 #
-# Takes a current ball position/initial kick position (robocup.Point)
+# @param kick_point current ball position/initial kick position (robocup.Point)
 def generate_default_rectangle(kick_point):
     offset_from_edge = 0.25
     offset_from_ball = 0.4
@@ -61,7 +61,7 @@ def get_segments_from_rect(rect, threshold=0.75):
                 currentx, rect.min_y())):
             continue
         while constants.Field.TheirGoalZoneShape.contains_point(robocup.Point(
-                currentx, currenty)):
+            currentx, currenty)):
             currenty = currenty - threshold
 
         candiate = robocup.Segment(
@@ -94,8 +94,11 @@ def eval_single_point(kick_point, receive_point, ignore_robots=[]):
 
 
 ## Finds the best receive point for a bounce-pass.
-#
-# Takes in an initial kick point and an optional evaluation zone.
+# @param kick_point the point we will kick from (robocup.Point)
+# @param evaluation_zone the zone that will be evaluated to try to find points.
+# If none, it will try to guess a good receive area.
+# This is a robocup.Rect
+# @ignore_robots a list of robots to be ignored when trying to find the best receive point.
 def eval_best_receive_point(kick_point,
                             evaluation_zone=None,
                             ignore_robots=[]):
diff --git a/soccer/gameplay/plays/restarts/our_free_kick.py b/soccer/gameplay/plays/restarts/our_free_kick.py
index e138426622..19b5e31639 100644
--- a/soccer/gameplay/plays/restarts/our_free_kick.py
+++ b/soccer/gameplay/plays/restarts/our_free_kick.py
@@ -2,16 +2,24 @@ import standard_play
 import behavior
 import skills.move
 import skills.pivot_kick
-import tactics.defense
 import constants
 import robocup
 import main
+import tactics.coordinated_pass
+import evaluation.touchpass_positioning
 
 
 class OurFreeKick(standard_play.StandardPlay):
+
+    running = False
+
     def __init__(self):
         super().__init__(continuous=True)
 
+        # If we are indirect we don't want to shoot directly into the goal
+        gs = main.game_state()
+        self.indirect = gs.is_indirect()
+
         self.add_transition(behavior.Behavior.State.start,
                             behavior.Behavior.State.running, lambda: True,
                             'immediately')
@@ -21,6 +29,7 @@ class OurFreeKick(standard_play.StandardPlay):
         # kicker.use_chipper = True
         kicker.min_chip_range = 0.3
         kicker.max_chip_range = 3.0
+        # This will be reset to something else if indirect on the first iteration
         kicker.target = constants.Field.TheirGoalSegment
         self.add_subbehavior(kicker, 'kicker', required=False, priority=5)
 
@@ -28,17 +37,39 @@ class OurFreeKick(standard_play.StandardPlay):
         center1 = skills.move.Move(robocup.Point(0, 1.5))
         self.add_subbehavior(center1, 'center1', required=False, priority=4)
         center2 = skills.move.Move(robocup.Point(0, 1.5))
-        self.add_subbehavior(center1, 'center2', required=False, priority=3)
+        self.add_subbehavior(center2, 'center2', required=False, priority=3)
+
+        lastKicker = kicker
+        if self.indirect:
+            receive_pt, target_point, probability = evaluation.touchpass_positioning.eval_best_receive_point(
+                main.ball().pos)
+            pass_behavior = tactics.coordinated_pass.CoordinatedPass(
+                receive_pt, None, (kicker, lambda x: True))
+            # We don't need to manage this anymore
+            self.remove_subbehavior('kicker')
+
+            self.add_subbehavior(pass_behavior,
+                                 'receiver',
+                                 required=False,
+                                 priority=5)
+            kicker.target = receive_pt
+            lastKicker = pass_behavior
 
         self.add_transition(
             behavior.Behavior.State.running, behavior.Behavior.State.completed,
-            lambda: kicker.is_done_running(), 'kicker completes')
+            lambda: lastKicker.is_done_running(), 'kicker completes')
 
     @classmethod
     def score(cls):
         gs = main.game_state()
-        return 0 if gs.is_ready_state() and gs.is_our_free_kick() else float(
-            "inf")
+        return 0 if OurFreeKick.running or (
+            gs.is_ready_state() and gs.is_our_free_kick()) else float("inf")
+
+    def on_enter_running(self):
+        OurFreeKick.running = True
+
+    def on_exit_running(self):
+        OurFreeKick.running = False
 
     @classmethod
     def is_restart(cls):
diff --git a/soccer/gameplay/skills/_kick.py b/soccer/gameplay/skills/_kick.py
index acd49a79f8..893cd9047e 100644
--- a/soccer/gameplay/skills/_kick.py
+++ b/soccer/gameplay/skills/_kick.py
@@ -24,6 +24,8 @@ class _Kick(single_robot_behavior.SingleRobotBehavior):
 
         self.shot_obstacle_ignoring_robots = []
 
+        self.aim_params = {}
+
     ## if True, uses the window evaluator to choose the best place to aim at target_segment
     # Default: True
     @property
@@ -76,6 +78,16 @@ class _Kick(single_robot_behavior.SingleRobotBehavior):
     def aim_target_point(self):
         return self._aim_target_point
 
+    ## Generic compatibility with pivot_kick
+    # This method should be overriden if possible
+    def current_shot_point(self):
+        return self.aim_target_point
+
+    ## Generic compatibilty with pivot_kick
+    # This method should be overriden if possible
+    def is_steady(self):
+        return True
+
     ## we're aiming at a particular point on our target segment, what is this point?
     def recalculate_aim_target_point(self):
         if self.robot != None:
diff --git a/soccer/gameplay/skills/line_kick.py b/soccer/gameplay/skills/line_kick.py
index 7fd3697dda..1b899d7f5a 100644
--- a/soccer/gameplay/skills/line_kick.py
+++ b/soccer/gameplay/skills/line_kick.py
@@ -44,7 +44,7 @@ class LineKick(skills._kick._Kick):
 
         self.add_transition(
             LineKick.State.setup, LineKick.State.charge,
-            lambda: (not self.robot_is_between_ball_and_target()) and self._target_line.dist_to(self.robot.pos) < self.ChargeThresh and not self.robot.just_kicked(),
+            lambda: self.enable_kick and (not self.robot_is_between_ball_and_target()) and self._target_line.dist_to(self.robot.pos) < self.ChargeThresh and not self.robot.just_kicked(),
             "robot on line")
 
         self.add_transition(
diff --git a/soccer/gameplay/tactics/coordinated_pass.py b/soccer/gameplay/tactics/coordinated_pass.py
index cfe3e24c1d..706dee1b39 100644
--- a/soccer/gameplay/tactics/coordinated_pass.py
+++ b/soccer/gameplay/tactics/coordinated_pass.py
@@ -10,7 +10,7 @@ import enum
 import logging
 
 
-# This handles passing from one bot to another
+## This handles passing from one bot to another
 # Simply run it and set it's receive point, the rest is handled for you
 # It starts out by assigning a kicker and a receiver and instructing them to lineup for the pass
 # Once they're aligned, the kicker kicks and the receiver adjusts itself based on the ball's movement
@@ -30,9 +30,17 @@ class CoordinatedPass(composite_behavior.CompositeBehavior):
         kicking = 2  # waiting for the kicker to kick
         receiving = 3  # the kicker has kicked and the receiver is trying to get the ball
 
-    ## Skillreceiver is a class that will handle the receiving robot. See pass_receive and angle_receive.
+    ## Init method for CoordinatedPass
+    # @param skillreceiver an instance of a class that will handle the receiving robot. See pass_receive and angle_receive for examples.
     # Using this, you can change what the receiving robot does (rather than just receiving the ball, it can pass or shoot it).
-    def __init__(self, receive_point=None, skillreceiver=None):
+    # Subclasses of pass_receive are preferred, but check the usage of this variable to be sure.
+    # @param receive_point The point that will be kicked too. (Target point)
+    # @param skillkicker A tuple of this form (kicking_class instance, ready_lambda). If none, it will use (pivot_kick lambda x: x == pivot_kick.State.aimed).
+    # The lambda equation is called (passed with the state of your class instance) to see if your class is ready. Simple implementations will just compare it to your ready state.
+    def __init__(self,
+                 receive_point=None,
+                 skillreceiver=None,
+                 skillkicker=None):
         super().__init__(continuous=False)
 
         # This creates a new instance of skillreceiver every time the constructor is
@@ -40,8 +48,14 @@ class CoordinatedPass(composite_behavior.CompositeBehavior):
         if skillreceiver == None:
             skillreceiver = skills.pass_receive.PassReceive()
 
+        if skillkicker == None:
+            skillkicker = (
+                skills.pivot_kick.PivotKick(),
+                lambda x: x == skills.pivot_kick.PivotKick.State.aimed)
+
         self.receive_point = receive_point
         self.skillreceiver = skillreceiver
+        self.skillkicker = skillkicker
 
         for state in CoordinatedPass.State:
             self.add_state(state, behavior.Behavior.State.running)
@@ -52,7 +66,7 @@ class CoordinatedPass(composite_behavior.CompositeBehavior):
 
         self.add_transition(
             CoordinatedPass.State.preparing, CoordinatedPass.State.kicking,
-            lambda: (self.subbehavior_with_name('kicker').state == skills.pivot_kick.PivotKick.State.aimed and self.subbehavior_with_name('receiver').state == self.skillreceiver.State.aligned),
+            lambda: (skillkicker[1](self.subbehavior_with_name('kicker').state) and self.subbehavior_with_name('receiver').state == self.skillreceiver.State.aligned),
             'kicker and receiver ready')
 
         self.add_transition(
@@ -100,7 +114,7 @@ class CoordinatedPass(composite_behavior.CompositeBehavior):
         self.subbehavior_with_name('kicker').enable_kick = True
 
     def on_enter_preparing(self):
-        kicker = skills.pivot_kick.PivotKick()
+        kicker = self.skillkicker[0]
         kicker.target = self.receive_point
         kickpower = (main.ball().pos - self.receive_point).mag() / 8
         if (kickpower < 0.2):
@@ -152,8 +166,7 @@ class CoordinatedPass(composite_behavior.CompositeBehavior):
         # we set the receive point to the point the kicker is currently aiming at
         if kicker.current_shot_point(
         ) != None and not self._has_renegotiated_receive_point:
-            if (not kicker.is_steady() and
-                    kicker.state == skills.pivot_kick.PivotKick.State.aiming):
+            if (not kicker.is_steady() and self.skillkicker[1](kicker.state)):
                 self._last_unsteady_time = time.time()
 
             if (self._last_unsteady_time != None and