def shot_valid(agent: MyHivemind, shot: Union[AerialShot, JumpShot, Aerial], threshold: float = 45) -> bool: # Returns True if the ball is still where the shot anticipates it to be # First finds the two closest slices in the ball prediction to shot's intercept_time # threshold controls the tolerance we allow the ball to be off by slices = agent.get_ball_prediction_struct().slices soonest = 0 latest = len(slices) - 1 while len(slices[soonest:latest + 1]) > 2: midpoint = (soonest + latest) // 2 if slices[midpoint].game_seconds > shot.intercept_time: latest = midpoint else: soonest = midpoint # preparing to interpolate between the selected slices dt = slices[latest].game_seconds - slices[soonest].game_seconds time_from_soonest = shot.intercept_time - slices[soonest].game_seconds slopes = (Vector3(slices[latest].physics.location) - Vector3(slices[soonest].physics.location)) * (1 / dt) # Determining exactly where the ball will be at the given shot's intercept_time predicted_ball_location = Vector3( slices[soonest].physics.location) + (slopes * time_from_soonest) # Comparing predicted location with where the shot expects the ball to be return (shot.ball_location - predicted_ball_location).magnitude() < threshold
def find_hits(drone: CarObject, agent: MyHivemind, targets): # find_hits takes a dict of (left,right) target pairs and finds routines that could hit the ball # between those target pairs # find_hits is only meant for routines that require a defined intercept time/place in the future # find_hits should not be called more than once in a given tick, # as it has the potential to use an entire tick to calculate # Example Useage: # targets = {"goal":(opponent_left_post,opponent_right_post), "anywhere_but_my_net":(my_right_post,my_left_post)} # hits = find_hits(agent,targets) # print(hits) # >{"goal":[a ton of jump and aerial routines,in order from soonest to latest], # "anywhere_but_my_net":[more routines and stuff]} hits = {name: [] for name in targets} struct = agent.get_ball_prediction_struct() # Begin looking at slices 0.25s into the future # The number of slices i = 15 while i < struct.num_slices: # Gather some data about the slice intercept_time = struct.slices[i].game_seconds time_remaining = intercept_time - agent.time if time_remaining > 0: ball_location = Vector3(struct.slices[i].physics.location) ball_velocity = Vector3( struct.slices[i].physics.velocity).magnitude() if abs(ball_location[1]) > 5250: break # abandon search if ball is scored at/after this point # determine the next slice we will look at, based on ball velocity (slower ball needs fewer slices) i += 15 - cap(int(ball_velocity // 150), 0, 13) car_to_ball = ball_location - drone.location # Adding a True to a vector's normalize will have it also return the magnitude of the vector direction, distance = car_to_ball.normalize(True) # How far the car must turn in order to face the ball, for forward and reverse forward_angle = direction.angle(drone.forward) backward_angle = math.pi - forward_angle # Accounting for the average time it takes to turn and face the ball # Backward is slightly longer as typically the car is moving forward and takes time to slow down forward_time = time_remaining - (forward_angle * 0.318) backward_time = time_remaining - (backward_angle * 0.418) # If the car only had to drive in a straight line, we ensure it has enough time to reach the ball # (a few assumptions are made) forward_flag = forward_time > 0.0 and ( distance * 1.05 / forward_time) < ( 2290 if drone.boost > distance / 100 else 1400) backward_flag = distance < 1500 and backward_time > 0.0 and ( distance * 1.05 / backward_time) < 1200 # Provided everything checks out, we begin to look at the target pairs if forward_flag or backward_flag: for pair in targets: # First we correct the target coordinates to account for the ball's radius # If swapped == True, the shot isn't possible because the ball wouldn't fit between the targets left, right, swapped = post_correction( ball_location, targets[pair][0], targets[pair][1]) if not swapped: # Now we find the best direction to hit the ball in order to land it between the target points left_vector = (left - ball_location).normalize() right_vector = (right - ball_location).normalize() best_shot_vector = direction.clamp( left_vector, right_vector) # Check to make sure our approach is inside the field if in_field(ball_location - (200 * best_shot_vector), 1): # The slope represents how close the car is to the chosen vector, higher = better # A slope of 1.0 would mean the car is 45 degrees off slope = find_slope(best_shot_vector, car_to_ball) if forward_flag: if ball_location[2] <= 300 and slope > 0.0: hits[pair].append( JumpShot(ball_location, intercept_time, best_shot_vector, slope)) if 300 < ball_location[ 2] < 600 and slope > 1.0 and ( ball_location[2] - 250) * 0.14 > drone.boost: hits[pair].append( AerialShot(ball_location, intercept_time, best_shot_vector)) elif backward_flag and ball_location[ 2] <= 280 and slope > 0.25: hits[pair].append( JumpShot(ball_location, intercept_time, best_shot_vector, slope, -1)) return hits