From a9361d0a443a1f422d72782dacabf8c9d22e7776 Mon Sep 17 00:00:00 2001 From: FlamingFury00 Date: Mon, 10 Jul 2023 18:49:00 +0200 Subject: [PATCH 1/2] Added Defensive strategy + example --- .../Defensive_example_strategy/example.py | 24 ++ util/common.py | 98 +++-- util/objects.py | 121 ++++-- util/routines.py | 370 +++++++++++++----- 4 files changed, 440 insertions(+), 173 deletions(-) create mode 100644 Examples/Defensive_example_strategy/example.py diff --git a/Examples/Defensive_example_strategy/example.py b/Examples/Defensive_example_strategy/example.py new file mode 100644 index 0000000..47d0523 --- /dev/null +++ b/Examples/Defensive_example_strategy/example.py @@ -0,0 +1,24 @@ +# This file is for strategy + +from util.objects import * +from util.routines import * + + +class Bot(GoslingAgent): + # This function runs every in-game tick (every time the game updates anything) + def run(self): + self.defend_flag = is_ball_going_towards_goal(self) + + # set_intent tells the bot what it's trying to do + if self.intent is not None: + return + + if self.kickoff_flag: + self.set_intent(kickoff()) + return + + print(self.defend_flag) + + if self.defend_flag: + self.set_intent(defend_shot()) + return diff --git a/util/common.py b/util/common.py index 9ccf085..78c4152 100644 --- a/util/common.py +++ b/util/common.py @@ -1,4 +1,5 @@ import math + from util.objects import Vector3 # This file is for small utilities for math and movement @@ -8,7 +9,7 @@ def backsolve(target, car, time, gravity=650): # Finds the acceleration required for a car to reach a target in a specific amount of time velocity_required = (target - car.location) / time acceleration_required = velocity_required - car.velocity - acceleration_required[2] += (gravity * time) + acceleration_required[2] += gravity * time return acceleration_required @@ -31,15 +32,13 @@ def defaultPD(agent, local_target, direction=1.0): math.atan2(local_target[2], local_target[0]), # angle required to yaw towards target math.atan2(local_target[1], local_target[0]), - math.atan2(up[1], up[2])] # angle required to roll upright + math.atan2(up[1], up[2]), + ] # angle required to roll upright # Once we have the angles we need to rotate, we feed them into PD loops to determing the controller inputs agent.controller.steer = steerPD(target_angles[1], 0) * direction - agent.controller.pitch = steerPD( - target_angles[0], agent.me.angular_velocity[1]/4) - agent.controller.yaw = steerPD( - target_angles[1], -agent.me.angular_velocity[2]/4) - agent.controller.roll = steerPD( - target_angles[2], agent.me.angular_velocity[0]/2) + agent.controller.pitch = steerPD(target_angles[0], agent.me.angular_velocity[1] / 4) + agent.controller.yaw = steerPD(target_angles[1], -agent.me.angular_velocity[2] / 4) + agent.controller.roll = steerPD(target_angles[2], agent.me.angular_velocity[0] / 2) # Returns the angles, which can be useful for other purposes return target_angles @@ -48,8 +47,12 @@ def defaultThrottle(agent, target_speed, direction=1.0): # accelerates the car to a desired speed using throttle and boost car_speed = agent.me.local(agent.me.velocity)[0] t = (target_speed * direction) - car_speed - agent.controller.throttle = cap((t**2) * sign(t)/1000, -1.0, 1.0) - agent.controller.boost = True if t > 150 and car_speed < 2275 and agent.controller.throttle == 1.0 else False + agent.controller.throttle = cap((t**2) * sign(t) / 1000, -1.0, 1.0) + agent.controller.boost = ( + True + if t > 150 and car_speed < 2275 and agent.controller.throttle == 1.0 + else False + ) return car_speed @@ -74,7 +77,7 @@ def find_slope(shot_vector, car_to_target): # 1.0 = you're about 45 degrees offcenter d = shot_vector.dot(car_to_target) e = abs(shot_vector.cross((0, 0, 1)).dot(car_to_target)) - return cap(d / e if e != 0 else 10*sign(d), -3.0, 3.0) + return cap(d / e if e != 0 else 10 * sign(d), -3.0, 3.0) def post_correction(ball_location, left_target: Vector3, right_target: Vector3): @@ -83,32 +86,39 @@ def post_correction(ball_location, left_target: Vector3, right_target: Vector3): # We purposely make this a bit larger so that our shots have a higher chance of success ball_radius = 110 goal_line_perp = (right_target - left_target).cross((0, 0, 1)) - left_adjusted = left_target + \ - ((left_target - ball_location).normalize().cross((0, 0, -1))*ball_radius) - right_adjusted = right_target + \ - ((right_target - ball_location).normalize().cross((0, 0, 1))*ball_radius) - left_corrected = left_target if ( - left_adjusted-left_target).dot(goal_line_perp) > 0.0 else left_adjusted - right_corrected = right_target if ( - right_adjusted-right_target).dot(goal_line_perp) > 0.0 else right_adjusted - - difference = (right_corrected - left_corrected) + left_adjusted = left_target + ( + (left_target - ball_location).normalize().cross((0, 0, -1)) * ball_radius + ) + right_adjusted = right_target + ( + (right_target - ball_location).normalize().cross((0, 0, 1)) * ball_radius + ) + left_corrected = ( + left_target + if (left_adjusted - left_target).dot(goal_line_perp) > 0.0 + else left_adjusted + ) + right_corrected = ( + right_target + if (right_adjusted - right_target).dot(goal_line_perp) > 0.0 + else right_adjusted + ) + + difference = right_corrected - left_corrected new_goal_line = difference.normalize() new_goal_width = difference.magnitude() - new_goal_perp = (new_goal_line.cross((0, 0, 1))) + new_goal_perp = new_goal_line.cross((0, 0, 1)) goal_center = left_corrected + (new_goal_line * new_goal_width * 0.5) ball_to_goal = (goal_center - ball_location).normalize() - ball_fits = new_goal_width * \ - abs(new_goal_perp.dot(ball_to_goal)) > ball_radius * 2 + ball_fits = new_goal_width * abs(new_goal_perp.dot(ball_to_goal)) > ball_radius * 2 return left_corrected, right_corrected, ball_fits def quadratic(a, b, c): # Returns the two roots of a quadratic - inside = math.sqrt((b*b) - (4*a*c)) + inside = math.sqrt((b * b) - (4 * a * c)) if a != 0: - return (-b + inside)/(2*a), (-b - inside)/(2*a) + return (-b + inside) / (2 * a), (-b - inside) / (2 * a) else: return -1, -1 @@ -119,9 +129,9 @@ def shot_valid(agent, shot, threshold=45): # 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 + 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: @@ -129,15 +139,33 @@ def shot_valid(agent, shot, threshold=45): # 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) + 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) + 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 is_ball_going_towards_goal(agent): + if not agent.ball.velocity[1]: + return False + if agent.ball.velocity[1] * side(agent.team) < 0: + return False + ball_position_x = agent.ball.location[0] * side(agent.team) + ball_position_y = agent.ball.location[1] * side(agent.team) + ball_velocity_x = agent.ball.velocity[0] * side(agent.team) + ball_velocity_y = agent.ball.velocity[1] * side(agent.team) + m = ball_velocity_x / ball_velocity_y + y = 5120 - ball_position_y + x = m * y + ball_position_x + return (x >= -996 and x <= 993) or agent.ball.location.y * side(agent.team) > 0 + + def side(x): # returns -1 for blue team and 1 for orange team if x == 0: @@ -157,7 +185,7 @@ def sign(x): def steerPD(angle, rate): # A Proportional-Derivative control loop used for defaultPD - return cap(((35*(angle+rate))**3)/10, -1.0, 1.0) + return cap(((35 * (angle + rate)) ** 3) / 10, -1.0, 1.0) def lerp(a, b, t): @@ -171,4 +199,4 @@ def invlerp(a, b, v): # Inverse linear interpolation from a to b with value v # For instance, it returns 0 if v == a, and returns 1 if v == b, and returns 0.5 if v is exactly between a and b # Works for both numbers and Vector3s - return (v - a)/(b - a) + return (v - a) / (b - a) diff --git a/util/objects.py b/util/objects.py index 091bb06..b275c14 100644 --- a/util/objects.py +++ b/util/objects.py @@ -1,11 +1,12 @@ import math + import rlbot.utils.structures.game_data_struct as game_data_struct from rlbot.agents.base_agent import BaseAgent, SimpleControllerState - # This file holds all of the objects used in gosling utils # Includes custom vector and matrix objects + class GoslingAgent(BaseAgent): # This is the main object of Gosling Utils. It holds/updates information about the game and runs routines # All utils rely on information being structured and accessed the same way as configured in this class @@ -47,9 +48,16 @@ def get_ready(self, packet): def refresh_player_lists(self, packet): # makes new freind/foe lists # Useful to keep separate from get_ready because humans can join/leave a match - self.friends = [car_object(i, packet) for i in range(packet.num_cars) if - packet.game_cars[i].team == self.team and i != self.index] - self.foes = [car_object(i, packet) for i in range(packet.num_cars) if packet.game_cars[i].team != self.team] + self.friends = [ + car_object(i, packet) + for i in range(packet.num_cars) + if packet.game_cars[i].team == self.team and i != self.index + ] + self.foes = [ + car_object(i, packet) + for i in range(packet.num_cars) + if packet.game_cars[i].team != self.team + ] def set_intent(self, routine): self.intent = routine @@ -70,7 +78,9 @@ def pop(self): def line(self, start, end, color=None): color = color if color != None else [255, 255, 255] - self.renderer.draw_line_3d(start.copy(), end.copy(), self.renderer.create_color(255, *color)) + self.renderer.draw_line_3d( + start.copy(), end.copy(), self.renderer.create_color(255, *color) + ) def debug_intent(self): # Draws the stack on the screen @@ -84,19 +94,29 @@ def clear(self): def preprocess(self, packet): # Calling the update functions for all of the objects - if packet.num_cars != len(self.friends) + len(self.foes) + 1: self.refresh_player_lists(packet) - for car in self.friends: car.update(packet) - for car in self.foes: car.update(packet) - for pad in self.boosts: pad.update(packet) + if packet.num_cars != len(self.friends) + len(self.foes) + 1: + self.refresh_player_lists(packet) + for car in self.friends: + car.update(packet) + for car in self.foes: + car.update(packet) + for pad in self.boosts: + pad.update(packet) self.ball.update(packet) self.me.update(packet) self.game.update(packet) self.time = packet.game_info.seconds_elapsed # When a new kickoff begins we empty the stack - if self.kickoff_flag == False and packet.game_info.is_round_active and packet.game_info.is_kickoff_pause: + if ( + self.kickoff_flag == False + and packet.game_info.is_round_active + and packet.game_info.is_kickoff_pause + ): self.clear_intent() # Tells us when to go for kickoff - self.kickoff_flag = packet.game_info.is_round_active and packet.game_info.is_kickoff_pause + self.kickoff_flag = ( + packet.game_info.is_round_active and packet.game_info.is_kickoff_pause + ) def get_output(self, packet): # Reset controller @@ -146,14 +166,28 @@ def local(self, value): def update(self, packet): car = packet.game_cars[self.index] - self.location.data = [car.physics.location.x, - car.physics.location.y, car.physics.location.z] - self.velocity.data = [car.physics.velocity.x, - car.physics.velocity.y, car.physics.velocity.z] + self.location.data = [ + car.physics.location.x, + car.physics.location.y, + car.physics.location.z, + ] + self.velocity.data = [ + car.physics.velocity.x, + car.physics.velocity.y, + car.physics.velocity.z, + ] self.orientation = Matrix3( - car.physics.rotation.pitch, car.physics.rotation.yaw, car.physics.rotation.roll) + car.physics.rotation.pitch, + car.physics.rotation.yaw, + car.physics.rotation.roll, + ) self.angular_velocity = self.orientation.dot( - [car.physics.angular_velocity.x, car.physics.angular_velocity.y, car.physics.angular_velocity.z]).data + [ + car.physics.angular_velocity.x, + car.physics.angular_velocity.y, + car.physics.angular_velocity.z, + ] + ).data self.demolished = car.is_demolished self.airborne = not car.has_wheel_contact self.supersonic = car.is_super_sonic @@ -186,10 +220,16 @@ def __init__(self): def update(self, packet): ball = packet.game_ball - self.location.data = [ball.physics.location.x, - ball.physics.location.y, ball.physics.location.z] - self.velocity.data = [ball.physics.velocity.x, - ball.physics.velocity.y, ball.physics.velocity.z] + self.location.data = [ + ball.physics.location.x, + ball.physics.location.y, + ball.physics.location.z, + ] + self.velocity.data = [ + ball.physics.velocity.x, + ball.physics.velocity.y, + ball.physics.velocity.z, + ] self.latest_touched_time = ball.latest_touch.time_seconds self.latest_touched_team = ball.latest_touch.team @@ -255,14 +295,17 @@ def __init__(self, pitch, yaw, roll): self.data = ( Vector3(cp * cy, cp * sy, sp), Vector3(cy * sp * sr - cr * sy, sy * sp * sr + cr * cy, -cp * sr), - Vector3(-cr * cy * sp - sr * sy, -cr * sy * sp + sr * cy, cp * cr)) + Vector3(-cr * cy * sp - sr * sy, -cr * sy * sp + sr * cy, cp * cr), + ) self.forward, self.left, self.up = self.data def __getitem__(self, key): return self.data[key] def dot(self, vector): - return Vector3(self.forward.dot(vector), self.left.dot(vector), self.up.dot(vector)) + return Vector3( + self.forward.dot(vector), self.left.dot(vector), self.up.dot(vector) + ) class Vector3: @@ -377,13 +420,17 @@ def __abs__(self): def magnitude(self): # Magnitude() returns the length of the vector - return math.sqrt((self[0] * self[0]) + (self[1] * self[1]) + (self[2] * self[2])) + return math.sqrt( + (self[0] * self[0]) + (self[1] * self[1]) + (self[2] * self[2]) + ) def normalize(self): # Normalize() returns a Vector3 that shares the same direction but has a length of 1.0 magnitude = self.magnitude() if magnitude != 0: - return Vector3(self[0] / magnitude, self[1] / magnitude, self[2] / magnitude) + return Vector3( + self[0] / magnitude, self[1] / magnitude, self[2] / magnitude + ) return Vector3(0, 0, 0) # Linear algebra functions @@ -392,8 +439,11 @@ def dot(self, value): def cross(self, value): # A .cross((0, 0, 1)) will rotate the vector counterclockwise by 90 degrees - return Vector3((self[1] * value[2]) - (self[2] * value[1]), (self[2] * value[0]) - (self[0] * value[2]), - (self[0] * value[1]) - (self[1] * value[0])) + return Vector3( + (self[1] * value[2]) - (self[2] * value[1]), + (self[2] * value[0]) - (self[0] * value[2]), + (self[0] * value[1]) - (self[1] * value[0]), + ) def flatten(self): # Sets Z (Vector3[2]) to 0 @@ -409,13 +459,18 @@ def copy(self): def angle(self, value): # Returns the angle between this Vector3 and another Vector3 - return math.acos(round(self.flatten().normalize().dot(value.flatten().normalize()), 4)) + return math.acos( + round(self.flatten().normalize().dot(value.flatten().normalize()), 4) + ) def rotate(self, angle): # Rotates this Vector3 by the given angle in radians # Note that this is only 2D, in the x and y axis - return Vector3((math.cos(angle) * self[0]) - (math.sin(angle) * self[1]), - (math.sin(angle) * self[0]) + (math.cos(angle) * self[1]), self[2]) + return Vector3( + (math.cos(angle) * self[0]) - (math.sin(angle) * self[1]), + (math.sin(angle) * self[0]) + (math.cos(angle) * self[1]), + self[2], + ) def clamp(self, start, end): # Similar to integer clamping, Vector3's clamp() forces the Vector3's direction between a start and end Vector3 @@ -424,7 +479,11 @@ def clamp(self, start, end): s = self.normalize() right = s.dot(end.cross((0, 0, -1))) < 0 left = s.dot(start.cross((0, 0, -1))) > 0 - if (right and left) if end.dot(start.cross((0, 0, -1))) > 0 else (right or left): + if ( + (right and left) + if end.dot(start.cross((0, 0, -1))) > 0 + else (right or left) + ): return self if start.dot(s) < end.dot(s): return end diff --git a/util/routines.py b/util/routines.py index 5cfff1b..75b84a9 100644 --- a/util/routines.py +++ b/util/routines.py @@ -2,7 +2,8 @@ # This file holds all of the mechanical tasks, called "routines", that the bot can do -class drive(): + +class drive: def __init__(self, speed, target=None) -> None: self.speed = speed self.target = target @@ -14,7 +15,7 @@ def run(self, agent): defaultPD(agent, agent.me.local(relative_target)) -class atba(): +class atba: # An example routine that just drives towards the ball at max speed def run(self, agent): relative_target = agent.ball.location - agent.me.location @@ -23,7 +24,7 @@ def run(self, agent): defaultThrottle(agent, 2300) -class aerial_shot(): +class aerial_shot: # Very similar to jump_shot(), but instead designed to hit targets above 300uu # ***This routine is a WIP*** It does not currently hit the ball very hard, nor does it like to be accurate above 600uu or so def __init__(self, ball_location, intercept_time, shot_vector, ratio): @@ -51,26 +52,35 @@ def run(self, agent): car_to_intercept = self.intercept - agent.me.location car_to_intercept_perp = car_to_intercept.cross( - (0, 0, side_of_shot)) # perpendicular + (0, 0, side_of_shot) + ) # perpendicular flat_distance_remaining = car_to_intercept.flatten().magnitude() speed_required = flat_distance_remaining / time_remaining # When still on the ground we pretend gravity doesn't exist, for better or worse - acceleration_required = backsolve( - self.intercept, agent.me, time_remaining, 325) + acceleration_required = backsolve(self.intercept, agent.me, time_remaining, 325) local_acceleration_required = agent.me.local(acceleration_required) # The adjustment causes the car to circle around the dodge point in an effort to line up with the shot vector # The adjustment slowly decreases to 0 as the bot nears the time to jump - adjustment = car_to_intercept.angle( - self.shot_vector) * flat_distance_remaining / 1.57 # size of adjustment + adjustment = ( + car_to_intercept.angle(self.shot_vector) * flat_distance_remaining / 1.57 + ) # size of adjustment # factoring in how close to jump we are - adjustment *= (cap(self.jump_threshold - - (acceleration_required[2]), 0.0, self.jump_threshold) / self.jump_threshold) + adjustment *= ( + cap( + self.jump_threshold - (acceleration_required[2]), + 0.0, + self.jump_threshold, + ) + / self.jump_threshold + ) # we don't adjust the final target if we are already jumping - final_target = self.intercept + \ - ((car_to_intercept_perp.normalize() * adjustment) - if self.jump_time == 0 else 0) + final_target = self.intercept + ( + (car_to_intercept_perp.normalize() * adjustment) + if self.jump_time == 0 + else 0 + ) # Some extra adjustment to the final target to ensure it's inside the field and we don't try to dirve through any goalposts to reach it if abs(agent.me.location[1]) > 5150: @@ -80,41 +90,65 @@ def run(self, agent): # drawing debug lines to show the dodge point and final target (which differs due to the adjustment) agent.line(agent.me.location, self.intercept) - agent.line(self.intercept-Vector3(0, 0, 100), - self.intercept+Vector3(0, 0, 100), [255, 0, 0]) - agent.line(final_target-Vector3(0, 0, 100), - final_target+Vector3(0, 0, 100), [0, 255, 0]) + agent.line( + self.intercept - Vector3(0, 0, 100), + self.intercept + Vector3(0, 0, 100), + [255, 0, 0], + ) + agent.line( + final_target - Vector3(0, 0, 100), + final_target + Vector3(0, 0, 100), + [0, 255, 0], + ) angles = defaultPD(agent, local_final_target) if self.jump_time == 0: defaultThrottle(agent, speed_required) - agent.controller.boost = False if abs( - angles[1]) > 0.3 or agent.me.airborne else agent.controller.boost - agent.controller.handbrake = True if abs( - angles[1]) > 2.3 else agent.controller.handbrake + agent.controller.boost = ( + False + if abs(angles[1]) > 0.3 or agent.me.airborne + else agent.controller.boost + ) + agent.controller.handbrake = ( + True if abs(angles[1]) > 2.3 else agent.controller.handbrake + ) velocity_required = car_to_intercept / time_remaining - good_slope = velocity_required[2] / cap( - abs(velocity_required[0]) + abs(velocity_required[1]), 1, 10000) > 0.15 - if good_slope and (local_acceleration_required[2]) > self.jump_threshold and agent.me.velocity.flatten().normalize().dot(acceleration_required.flatten().normalize()) > 0.8: + good_slope = ( + velocity_required[2] + / cap(abs(velocity_required[0]) + abs(velocity_required[1]), 1, 10000) + > 0.15 + ) + if ( + good_slope + and (local_acceleration_required[2]) > self.jump_threshold + and agent.me.velocity.flatten() + .normalize() + .dot(acceleration_required.flatten().normalize()) + > 0.8 + ): # Switch into the jump when the upward acceleration required reaches our threshold, hopefully we have aligned already... self.jump_time = agent.time else: time_since_jump = agent.time - self.jump_time # While airborne we boost if we're within 30 degrees of our local acceleration requirement - if agent.me.airborne and local_acceleration_required.magnitude() * time_remaining > 90: + if ( + agent.me.airborne + and local_acceleration_required.magnitude() * time_remaining > 90 + ): angles = defaultPD(agent, local_acceleration_required) if abs(angles[0]) + abs(angles[1]) < 0.45: agent.controller.boost = True else: final_target -= Vector3(0, 0, 45) - local_final_target = agent.me.local( - final_target - agent.me.location) + local_final_target = agent.me.local(final_target - agent.me.location) angles = defaultPD(agent, local_final_target) - if self.counter == 0 and (time_since_jump <= 0.2 and local_acceleration_required[2] > 0): + if self.counter == 0 and ( + time_since_jump <= 0.2 and local_acceleration_required[2] > 0 + ): # hold the jump button up to 0.2 seconds to get the most acceleration from the first jump agent.controller.jump = True elif time_since_jump > 0.2 and self.counter < 3: @@ -138,7 +172,7 @@ def run(self, agent): agent.clear_intent() -class flip(): +class flip: # Flip takes a vector in local coordinates and flips/dodges in that direction # cancel causes the flip to cancel halfway through, which can be used to half-flip def __init__(self, vector, cancel=False): @@ -170,7 +204,7 @@ def run(self, agent): agent.set_intent(recovery()) -class goto(): +class goto: # Drives towards a designated (stationary) target # Optional vector controls where the car should be pointing upon reaching the target # TODO - slow down if target is inside our turn radius @@ -183,17 +217,17 @@ def run(self, agent): car_to_target = self.target - agent.me.location distance_remaining = car_to_target.flatten().magnitude() - agent.line(self.target - Vector3(0, 0, 500), - self.target + Vector3(0, 0, 500), [255, 0, 255]) + agent.line( + self.target - Vector3(0, 0, 500), + self.target + Vector3(0, 0, 500), + [255, 0, 255], + ) if self.vector != None: # See commends for adjustment in jump_shot or aerial for explanation - side_of_vector = sign(self.vector.cross( - (0, 0, 1)).dot(car_to_target)) - car_to_target_perp = car_to_target.cross( - (0, 0, side_of_vector)).normalize() - adjustment = car_to_target.angle( - self.vector) * distance_remaining / 3.14 + side_of_vector = sign(self.vector.cross((0, 0, 1)).dot(car_to_target)) + car_to_target_perp = car_to_target.cross((0, 0, side_of_vector)).normalize() + adjustment = car_to_target.angle(self.vector) * distance_remaining / 3.14 final_target = self.target + (car_to_target_perp * adjustment) else: final_target = self.target @@ -208,13 +242,19 @@ def run(self, agent): defaultThrottle(agent, 2300, self.direction) agent.controller.boost = False - agent.controller.handbrake = True if abs( - angles[1]) > 2.3 else agent.controller.handbrake + agent.controller.handbrake = ( + True if abs(angles[1]) > 2.3 else agent.controller.handbrake + ) - velocity = 1+agent.me.velocity.magnitude() + velocity = 1 + agent.me.velocity.magnitude() if distance_remaining < 350: agent.clear_intent() - elif abs(angles[1]) < 0.05 and velocity > 600 and velocity < 2150 and distance_remaining / velocity > 2.0: + elif ( + abs(angles[1]) < 0.05 + and velocity > 600 + and velocity < 2150 + and distance_remaining / velocity > 2.0 + ): agent.set_intent(flip(local_target)) elif abs(angles[1]) > 2.8 and velocity < 200: agent.set_intent(flip(local_target, True)) @@ -222,7 +262,7 @@ def run(self, agent): agent.set_intent(recovery(self.target)) -class goto_boost(): +class goto_boost: # very similar to goto() but designed for grabbing boost # if a target is provided the bot will try to be facing the target as it passes over the boost def __init__(self, boost, target=None): @@ -233,17 +273,18 @@ def run(self, agent): car_to_boost = self.boost.location - agent.me.location distance_remaining = car_to_boost.flatten().magnitude() - agent.line(self.boost.location - Vector3(0, 0, 500), - self.boost.location + Vector3(0, 0, 500), [0, 255, 0]) + agent.line( + self.boost.location - Vector3(0, 0, 500), + self.boost.location + Vector3(0, 0, 500), + [0, 255, 0], + ) if self.target != None: vector = (self.target - self.boost.location).normalize() side_of_vector = sign(vector.cross((0, 0, 1)).dot(car_to_boost)) - car_to_boost_perp = car_to_boost.cross( - (0, 0, side_of_vector)).normalize() + car_to_boost_perp = car_to_boost.cross((0, 0, side_of_vector)).normalize() adjustment = car_to_boost.angle(vector) * distance_remaining / 3.14 - final_target = self.boost.location + \ - (car_to_boost_perp * adjustment) + final_target = self.boost.location + (car_to_boost_perp * adjustment) car_to_target = (self.target - agent.me.location).magnitude() else: adjustment = 9999 @@ -259,25 +300,39 @@ def run(self, agent): angles = defaultPD(agent, local_target) defaultThrottle(agent, 2300) - agent.controller.boost = self.boost.large if abs( - angles[1]) < 0.3 else False - agent.controller.handbrake = True if abs( - angles[1]) > 2.3 else agent.controller.handbrake - - velocity = 1+agent.me.velocity.magnitude() - if self.boost.active == False or agent.me.boost >= 99.0 or distance_remaining < 350: + agent.controller.boost = self.boost.large if abs(angles[1]) < 0.3 else False + agent.controller.handbrake = ( + True if abs(angles[1]) > 2.3 else agent.controller.handbrake + ) + + velocity = 1 + agent.me.velocity.magnitude() + if ( + self.boost.active == False + or agent.me.boost >= 99.0 + or distance_remaining < 350 + ): agent.clear_intent() elif agent.me.airborne: agent.set_intent(recovery(self.target)) - elif abs(angles[1]) < 0.05 and velocity > 600 and velocity < 2150 and (distance_remaining / velocity > 2.0 or (adjustment < 90 and car_to_target/velocity > 2.0)): + elif ( + abs(angles[1]) < 0.05 + and velocity > 600 + and velocity < 2150 + and ( + distance_remaining / velocity > 2.0 + or (adjustment < 90 and car_to_target / velocity > 2.0) + ) + ): agent.set_intent(flip(local_target)) -class jump_shot(): +class jump_shot: # Hits a target point at a target time towards a target direction # Target must be no higher than 300uu unless you're feeling lucky - #TODO - speed - def __init__(self, ball_location, intercept_time, shot_vector, ratio, direction=1, speed=2300): + # TODO - speed + def __init__( + self, ball_location, intercept_time, shot_vector, ratio, direction=1, speed=2300 + ): self.ball_location = ball_location self.intercept_time = intercept_time # The direction we intend to hit the ball in @@ -293,7 +348,7 @@ def __init__(self, ball_location, intercept_time, shot_vector, ratio, direction= self.speed_desired = speed # controls how soon car will jump based on acceleration required. max 584 # bigger = later, which allows more time to align with shot vector - #smaller = sooner + # smaller = sooner self.jump_threshold = 400 # Flags for what part of the routine we are in self.jumping = False @@ -310,25 +365,36 @@ def run(self, agent): car_to_dodge_point = self.dodge_point - agent.me.location car_to_dodge_perp = car_to_dodge_point.cross( - (0, 0, side_of_shot)) # perpendicular + (0, 0, side_of_shot) + ) # perpendicular distance_remaining = car_to_dodge_point.magnitude() speed_required = distance_remaining / time_remaining acceleration_required = backsolve( - self.dodge_point, agent.me, time_remaining, 0 if not self.jumping else 650) + self.dodge_point, agent.me, time_remaining, 0 if not self.jumping else 650 + ) local_acceleration_required = agent.me.local(acceleration_required) # The adjustment causes the car to circle around the dodge point in an effort to line up with the shot vector # The adjustment slowly decreases to 0 as the bot nears the time to jump - adjustment = car_to_dodge_point.angle( - self.shot_vector) * distance_remaining / 2.0 # size of adjustment + adjustment = ( + car_to_dodge_point.angle(self.shot_vector) * distance_remaining / 2.0 + ) # size of adjustment # factoring in how close to jump we are - adjustment *= (cap(self.jump_threshold - - (acceleration_required[2]), 0.0, self.jump_threshold) / self.jump_threshold) + adjustment *= ( + cap( + self.jump_threshold - (acceleration_required[2]), + 0.0, + self.jump_threshold, + ) + / self.jump_threshold + ) # we don't adjust the final target if we are already jumping - final_target = self.dodge_point + \ - ((car_to_dodge_perp.normalize() * adjustment) - if not self.jumping else 0) + Vector3(0, 0, 50) + final_target = ( + self.dodge_point + + ((car_to_dodge_perp.normalize() * adjustment) if not self.jumping else 0) + + Vector3(0, 0, 50) + ) # Ensuring our target isn't too close to the sides of the field, where our car would get messed up by the radius of the curves # Some adjustment to the final target to ensure it's inside the field and we don't try to dirve through any goalposts to reach it @@ -339,38 +405,68 @@ def run(self, agent): # drawing debug lines to show the dodge point and final target (which differs due to the adjustment) agent.line(agent.me.location, self.dodge_point) - agent.line(self.dodge_point-Vector3(0, 0, 100), - self.dodge_point+Vector3(0, 0, 100), [255, 0, 0]) - agent.line(final_target-Vector3(0, 0, 100), - final_target+Vector3(0, 0, 100), [0, 255, 0]) - agent.line(agent.ball.location, agent.ball.location + - (self.shot_vector * 300)) + agent.line( + self.dodge_point - Vector3(0, 0, 100), + self.dodge_point + Vector3(0, 0, 100), + [255, 0, 0], + ) + agent.line( + final_target - Vector3(0, 0, 100), + final_target + Vector3(0, 0, 100), + [0, 255, 0], + ) + agent.line(agent.ball.location, agent.ball.location + (self.shot_vector * 300)) # Calling our drive utils to get us going towards the final target angles = defaultPD(agent, local_final_target, self.direction) defaultThrottle(agent, speed_required, self.direction) - agent.line(agent.me.location, agent.me.location + - (self.shot_vector*200), [255, 255, 255]) - - agent.controller.boost = False if abs( - angles[1]) > 0.3 or agent.me.airborne else agent.controller.boost - agent.controller.handbrake = True if abs( - angles[1]) > 2.3 and self.direction == 1 else agent.controller.handbrake + agent.line( + agent.me.location, + agent.me.location + (self.shot_vector * 200), + [255, 255, 255], + ) + + agent.controller.boost = ( + False + if abs(angles[1]) > 0.3 or agent.me.airborne + else agent.controller.boost + ) + agent.controller.handbrake = ( + True + if abs(angles[1]) > 2.3 and self.direction == 1 + else agent.controller.handbrake + ) if not self.jumping: - if raw_time_remaining <= 0.0 or (speed_required - 2300) * time_remaining > 60 or not shot_valid(agent, self): + if ( + raw_time_remaining <= 0.0 + or (speed_required - 2300) * time_remaining > 60 + or not shot_valid(agent, self) + ): # If we're out of time or not fast enough to be within 45 units of target at the intercept time, we reset agent.clear_intent() if agent.me.airborne: agent.set_intent(recovery()) - elif local_acceleration_required[2] > self.jump_threshold and local_acceleration_required[2] > local_acceleration_required.flatten().magnitude(): + elif ( + local_acceleration_required[2] > self.jump_threshold + and local_acceleration_required[2] + > local_acceleration_required.flatten().magnitude() + ): # Switch into the jump when the upward acceleration required reaches our threshold, and our lateral acceleration is negligible self.jumping = True else: - if (raw_time_remaining > 0.2 and not shot_valid(agent, self, 150)) or raw_time_remaining <= -0.9 or (not agent.me.airborne and self.counter > 0): + if ( + (raw_time_remaining > 0.2 and not shot_valid(agent, self, 150)) + or raw_time_remaining <= -0.9 + or (not agent.me.airborne and self.counter > 0) + ): agent.set_intent(recovery()) - elif self.counter == 0 and local_acceleration_required[2] > 0.0 and raw_time_remaining > 0.083: + elif ( + self.counter == 0 + and local_acceleration_required[2] > 0.0 + and raw_time_remaining > 0.083 + ): # Initial jump to get airborne + we hold the jump button for extra power as required agent.controller.jump = True elif self.counter < 3: @@ -390,21 +486,22 @@ def run(self, agent): agent.controller.yaw = self.y if abs(self.y) > 0.3 else 0 -class kickoff(): +class kickoff: # A simple 1v1 kickoff that just drives up behind the ball and dodges # misses the boost on the slight-offcenter kickoffs haha def run(self, agent): - target = agent.ball.location + Vector3(0, 200*side(agent.team), 0) + target = agent.ball.location + Vector3(0, 200 * side(agent.team), 0) local_target = agent.me.local(target - agent.me.location) defaultPD(agent, local_target) defaultThrottle(agent, 2300) if local_target.magnitude() < 650: # flip towards opponent goal agent.set_intent( - flip(agent.me.local(agent.foe_goal.location - agent.me.location))) + flip(agent.me.local(agent.foe_goal.location - agent.me.location)) + ) -class recovery(): +class recovery: # Point towards our velocity vector and land upright, unless we aren't moving very fast # A vector can be provided to control where the car points when it lands def __init__(self, target=None): @@ -412,8 +509,7 @@ def __init__(self, target=None): def run(self, agent): if self.target != None: - local_target = agent.me.local( - (self.target-agent.me.location).flatten()) + local_target = agent.me.local((self.target - agent.me.location).flatten()) else: local_target = agent.me.local(agent.me.velocity.flatten()) @@ -423,7 +519,7 @@ def run(self, agent): agent.clear_intent() -class short_shot(): +class short_shot: # This routine drives towards the ball and attempts to hit it towards a given target # It does not require ball prediction and kinda guesses at where the ball will be on its own def __init__(self, target): @@ -434,8 +530,7 @@ def run(self, agent): distance = (agent.ball.location - agent.me.location).magnitude() ball_to_target = (self.target - agent.ball.location).normalize() - relative_velocity = car_to_ball.dot( - agent.me.velocity-agent.ball.velocity) + relative_velocity = car_to_ball.dot(agent.me.velocity - agent.ball.velocity) if relative_velocity != 0.0: eta = cap(distance / cap(relative_velocity, 400, 2300), 0.0, 1.5) else: @@ -445,23 +540,84 @@ def run(self, agent): left_vector = car_to_ball.cross((0, 0, 1)) right_vector = car_to_ball.cross((0, 0, -1)) target_vector = -ball_to_target.clamp(left_vector, right_vector) - final_target = agent.ball.location + (target_vector*(distance/2)) + final_target = agent.ball.location + (target_vector * (distance / 2)) # Some adjustment to the final target to ensure we don't try to dirve through any goalposts to reach it if abs(agent.me.location[1]) > 5150: final_target[0] = cap(final_target[0], -750, 750) - agent.line(final_target-Vector3(0, 0, 100), final_target + - Vector3(0, 0, 100), [255, 255, 255]) + agent.line( + final_target - Vector3(0, 0, 100), + final_target + Vector3(0, 0, 100), + [255, 255, 255], + ) + + angles = defaultPD(agent, agent.me.local(final_target - agent.me.location)) + defaultThrottle( + agent, + 2300 if distance > 1600 else 2300 - cap(1600 * abs(angles[1]), 0, 2050), + ) + agent.controller.boost = ( + False + if agent.me.airborne or abs(angles[1]) > 0.3 + else agent.controller.boost + ) + agent.controller.handbrake = ( + True if abs(angles[1]) > 2.3 else agent.controller.handbrake + ) + + if abs(angles[1]) < 0.05 and (eta < 0.45 or distance < 150): + agent.set_intent(flip(agent.me.local(car_to_ball))) + + +class defend_shot: + # This routine drives towards the ball and attempts to hit it towards a given target + # It does not require ball prediction and kinda guesses at where the ball will be on its own + + def run(self, agent): + self.target = agent.foe_goal.location + + car_to_ball = (agent.ball.location - agent.me.location).normalize() + distance = (agent.ball.location - agent.me.location).magnitude() + ball_to_target = (self.target - agent.ball.location).normalize() + + relative_velocity = car_to_ball.dot(agent.me.velocity - agent.ball.velocity) + if relative_velocity != 0.0: + eta = cap(distance / cap(relative_velocity, 400, 2300), 0.0, 1.5) + else: + eta = 1.5 + + # If we are approaching the ball from the wrong side the car will try to only hit the very edge of the ball + left_vector = car_to_ball.cross((0, 0, 1)) + right_vector = car_to_ball.cross((0, 0, -1)) + target_vector = -ball_to_target.clamp(left_vector, right_vector) + final_target = agent.ball.location + (target_vector * (distance / 2)) + + # Some adjustment to the final target to ensure we don't try to dirve through any goalposts to reach it + if abs(agent.me.location[1]) > 5150: + final_target[0] = cap(final_target[0], -750, 750) - angles = defaultPD(agent, agent.me.local( - final_target-agent.me.location)) - defaultThrottle(agent, 2300 if distance > - 1600 else 2300-cap(1600*abs(angles[1]), 0, 2050)) - agent.controller.boost = False if agent.me.airborne or abs( - angles[1]) > 0.3 else agent.controller.boost - agent.controller.handbrake = True if abs( - angles[1]) > 2.3 else agent.controller.handbrake + agent.line( + final_target - Vector3(0, 0, 100), + final_target + Vector3(0, 0, 100), + [255, 255, 255], + ) + + angles = defaultPD(agent, agent.me.local(final_target - agent.me.location)) + defaultThrottle( + agent, + 2300 if distance > 1600 else 2300 - cap(1600 * abs(angles[1]), 0, 2050), + ) + agent.controller.boost = ( + False + if agent.me.airborne or abs(angles[1]) > 0.3 + else agent.controller.boost + ) + agent.controller.handbrake = ( + True if abs(angles[1]) > 2.3 else agent.controller.handbrake + ) if abs(angles[1]) < 0.05 and (eta < 0.45 or distance < 150): agent.set_intent(flip(agent.me.local(car_to_ball))) + if distance >= 1000: + agent.set_intent(goto(agent.ball.location, agent.foe_goal.location)) From db940dbb20820f8ac0dcc5db91e4ceff56054825 Mon Sep 17 00:00:00 2001 From: FlamingFury00 <119819259+FlamingFury00@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:00:50 +0200 Subject: [PATCH 2/2] Update routines.py --- util/routines.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/routines.py b/util/routines.py index 75b84a9..95ac052 100644 --- a/util/routines.py +++ b/util/routines.py @@ -571,7 +571,8 @@ def run(self, agent): class defend_shot: - # This routine drives towards the ball and attempts to hit it towards a given target + # This routine drives towards the ball and attempts to hit it away from its net. + # If the ball is near the oppopnent net, it can also shoot it. # It does not require ball prediction and kinda guesses at where the ball will be on its own def run(self, agent):