def will_intersect(car, target): """If we went in a straight line towards the target, would we hit the ball?""" # TODO: Would be nice to use predicted speed instead of just our current speed. car_loc = car.location prev_car_loc = MyVec3(car_loc) dt = 1 / 60 vel = car.velocity.size collision_threshold = 175 # Draw a line in the direction we're moving # car.renderer.draw_line_3d(car_loc, car_loc+car.velocity, car.renderer.green()) throttle_accel = get_throttle_accel(car.velocity.size) total_accel = throttle_accel + ACCEL_BOOST * (car.boost > 0) # direction = car.velocity.normalized direction = MyVec3(target - car.location).normalized for ps in car.ball_prediction.slices: ball_loc = MyVec3(ps.physics.location) car_loc += direction * vel * dt # Debug.rect_2d_3d(car_loc, scale=10, color=car.renderer.red(), draw_2d=False) vel += total_accel * dt if distance(car_loc, ball_loc) < collision_threshold: # If we are likely to hit the ball, draw the line red. # car.renderer.draw_line_3d(prev_car_loc, car_loc, car.renderer.red()) return True return False
def local_coords(origin_object, target_location) -> MyVec3: """ Returns the target location as local coordinates of origin_object.""" # Originally by GooseFairy https://github.com/ddthj/Gosling/blob/master/Episode%203%20Code/Util.py origin_loc = MyVec3(origin_object.location) target_location = MyVec3(target_location) x = (target_location - origin_loc) * origin_object.rotation.matrix[0] y = (target_location - origin_loc) * origin_object.rotation.matrix[1] z = (target_location - origin_loc) * origin_object.rotation.matrix[2] return MyVec3(x, y, z)
def loc(obj) -> MyVec3: if isinstance(obj, MyVec3): return obj elif isinstance(obj, list) or isinstance(obj, tuple): return MyVec3(obj[0], obj[1], obj[2]) elif hasattr(obj, "location"): return MyVec3(obj.location.x, obj.location.y, obj.location.z) else: return MyVec3(obj.x, obj.y, obj.z)
class Strategy(DebugUtils): """ Base Class for Strategies. """ # Class Variables name = "StrategyName" viability = 0 # Stores the result of evaluate(). target = MyVec3(0, 0, 0) desired_speed = 2300 @classmethod def evaluate(cls, car): """Return how good this strategy seems to be right now. This can be any number, its absolute value doesn't matter, only how it relates to the evaluation of other strategies. Tweaking these values can be quite tricky.""" cls.viability = 0 return cls.viability @classmethod def find_target(cls, car): """Determine our target location - In the future, maybe we'll have sub-targets that make up a path, for more advanced planning ahead.""" cls.target = MyVec3(0, 0, 0) return cls.target @classmethod def control_car(cls, car): """ Set the car's inputs using Maneuvers. """ return M_Speed_On_Ground.control(car, cls.target, desired_speed=2300)
def get_out_of_goal(car, target): goal_sign = sign(car.location.y) ret = MyVec3(target) if abs(car.location.y) > 5200: ret.x = clamp(target.x, -800, 800) ret.y = 5000 * goal_sign ret.z = 17 return ret
def find_target(cls, car): soonest_reachable = find_soonest_reachable(car, car.ball_prediction) if not soonest_reachable: # TODO: If there is no soon reachable ball, this should not be the active strategy! return predicted_ball = soonest_reachable[0] dt = soonest_reachable[1] # Time until the ball becomes reachable. dist = distance(car.location, predicted_ball.physics.location) goal_average_speed = dist / max( 0.01, dt) # This MUST be our average speed on the way there. arrival_speed = 2300 #-500 # Desired speed at arrival # Since we're planning to dodge into the ball, we are looking for max_speed-500, since dodging gives us 500 speed. boost_time = car.boost * 0.03 # Amount of time we can spend boosting #boost_velocity = min(2300-self.speed, boost_time * ACCEL_BOOST) # Amount of velocity we can gain by using all of our boost (does not account for throttle acceleration) cls.desired_speed = goal_average_speed - 500 throttle_accel = get_throttle_accel( car.speed ) # Amount of velocity we are gaining from throttle right now. (0 if self.speed>1410) initial_speed = clamp(car.speed, 0, 1410) #boost_to_target_time = (throttle_accel + ACCEL_BOOST) / max(10, (arrival_speed - initial_speed)) # Time it would take to reach target speed with boost accel_dist_time = accel_distance(car.speed, cls.desired_speed, car.boost) distance_to_desired_speed = accel_dist_time[ 0] # Distance we would make until we reach the target speed time_to_desired_speed = accel_dist_time[ 1] # Time it would take until we reach the target speed ground_loc = MyVec3(ball.location.x, ball.location.y, 50) dist = distance(car.location, ground_loc) distance_before_accel = dist - distance_to_desired_speed # Distance we want to go before we start accelerating target_steady_speed = distance_before_accel / ( dt + 0.0000001 ) # Speed we want to maintain before we start accelerating for the arrival speed print("") print(dist) print(distance_to_desired_speed) # TODO: I haven't validated that the calculations this is relying on are working correctly, but I think there's definitely a logical flaw here. # basically going into the else: part seems rare, and instead Botato ends up almost standing still at a distance from the ball. # But once he does hit it, he's stuck going fast! # I think the problem is likely that to calculate distance_to_desired_speed, we use our current speed as the initial speed, which is super not ideal when we're already going faster than we need to be. # the max() in boost_to_target_time = is also part of this issue. For some reason we assumed that arrival_speped-car.speed being close to 0 causing issues was a problem, but it's kind of like the opposite. We need that thing to approach zero, and maintain speed when it does, and brake when it's negative(with that said, it could probably use a different name.) # It also doesn't take into account maximum velocity. # I think accel_distance() needs to be refactored such that instead of taking time, it takes a target speed, and returns the distance and time it took to get to it at full throttle and boost usage. # Then we can take it from there. if (dist <= distance_to_desired_speed): cls.desired_speed = target_steady_speed else: cls.desired_speed = arrival_speed """if(car.speed > goal_average_speed):
def find_target(cls, car): car_target_dist = (car.location - cls.target).size if (car_target_dist < 200 or cls.target.x == 0): # Pick a new target. arena = MyVec3(8200 * .9, 10280 * .9, 2050 * 0.1) random_point = MyVec3((random.random() - 0.5) * arena.x, (random.random() - 0.5) * arena.y, 200) cls.desired_speed = 500 + random.random() * 1800 cls.desired_speed = 2300 dist = distance(car.location, random_point) cls.ETA = distance_to_time(ACCEL_BOOST, dist, car.speed) cls.start_time = car.game_seconds cls.target = random_point return cls.target
def reachable(car, location, time_left): """This should be called on all predicted balls, to find the soonest predicted ball that we should drive towards.""" # TODO: We should try to make this accurate when Botato is aligned with the predicted(ground) ball. # If Botato is facing away from the ball, we should be focusing on turning around, finding a reachable ball is not important. global bounce_counter if (location.z > 94): return False # No aerialing :) else: ground_loc = MyVec3(location.x, location.y, 50) dist = distance(car.location, ground_loc) if (dist / (time_left + 0.001) < 2000): return True # To be more accurate, we want to get a good estimate of what average velocity we can achieve over some amount of time, given an amount of boost. arrival_speed = 2300 #-500 throttle_accel = get_throttle_accel( car.speed ) # Amount of velocity we are gaining from throttle right now. (0 if self.speed>1410) boost_to_target_time = (throttle_accel + ACCEL_BOOST) / max( 10, (arrival_speed - car.speed)) # Time it would take to reach target speed with boost distance_while_boosting = 0 #accel_distance(car.speed, car.boost, boost_to_target_time) # Distance we would make while we accelerate to the target ground_loc = MyVec3(location.x, location.y, 50) dist = distance(car.location, ground_loc) distance_before_accel = dist - distance_while_boosting # Distance we want to be before we start accelerating target_steady_speed = distance_before_accel / ( time_left + 0.0000001 ) # Speed we want to maintain before we start accelerating for the arrival speed boost_time = car.boost * 0.03 # Amount of time we can spend boosting boost_velocity = min( 2300 - car.speed, boost_time * ACCEL_BOOST ) # Amount of velocity we can gain by using all of our boost (does not account for throttle acceleration) achievable_steady_speed = car.speed + throttle_accel + boost_velocity return achievable_steady_speed > target_steady_speed speed = 1400 if car.boost < 30 else 2300 # Good enough for Botimus, good enough for me. ground_loc = MyVec3(location.x, location.y, 50) dist = distance(car.location, ground_loc) minimum_speed_to_reach = dist / (time_left + 0.0000001) return minimum_speed_to_reach < speed
def find_soonest_reachable(car, prediction): """ Find soonest reachable ball """ for ps in prediction.slices: location = ps.physics.location ground_loc = MyVec3(location.x, location.y, 120) dist = distance(car.location, ground_loc) dt = ps.game_seconds - car.game_seconds is_reachable = reachable(car, ps.physics.location, dt) if (is_reachable): return [ps, dt]
def raycast(loc1, loc2, debug=True) -> MyVec3: """Wrapper for easy raycasting against the Pitch's geo.""" """Casts a ray from loc1 to loc2. Returns the location of where the line intersected the Pitch. Returns loc1 if didn't intersect.""" # TODO: the default behaviour of raycasting from a start position towards a vector(rather than from A to B) will be useful too, maybe add a flag param to switch to that behavior. from RLUtilities.Simulation import Pitch, ray loc1 = MyVec3(loc1) loc2 = MyVec3(loc2) difference = loc2 - loc1 my_ray = ray(loc1, difference) ray_end = loc1 + difference myPitch = Pitch() my_raycast = myPitch.raycast_any(my_ray) if (str(my_raycast.start) == str(ray_end)): # If the raycast didn't intersect with anything, return the target location. return loc1 return MyVec3(my_raycast.start)
def find_target(cls, car): soonest_reachable = find_soonest_reachable(car, car.ball_prediction) if not soonest_reachable: # TODO: If there is no soon reachable ball, this should not be the active strategy! return predicted_ball = soonest_reachable[0] Debug.rect_2d_3d(predicted_ball.physics.location, color=car.renderer.green()) dt = soonest_reachable[1] # Time until the ball becomes reachable. dist = distance(car.location, predicted_ball.physics.location) # Change desired speed so that when dt is higher, make it lower, and when it's lower, make it higher?? cls.desired_speed = dist / max(0.01, dt) cls.target = MyVec3(predicted_ball.physics.location) # Debug.vector_2d_3d(MyVec3(predicted_ball.physics.location)) return cls.target
def __init__(self): self.location = MyVec3() self.rotation = Rotator() self.velocity = MyVec3()
def find_target(cls, car): # Old code to hit ball towards net. # Ideally, this Strategy will only become the most viable one when grabbing the target from this other strategy will be good enough. (No turning around or such involved) ball = Strat_TouchPredictedBall.find_target(car) if not ball: return # If there's no hittable ball, any strategy that relies on hitting a ball should evaluate to a hard 0, so that this doesn't happen. ####### Determine ball_target ####### ##################################### # Initialize ball target at the enemy goal. cls.ball_target = MyVec3(car.enemy_goal.location) # If we are facing away from the goal, shift the ball target along the goal line, towards the direction we are facing. # This is to avoid overshooting to the sides. car_angle_to_goal = angle_to(car, cls.ball_target) Debug.text_2d(25, 300, "Car to goal angle: " + str(round(car_angle_to_goal, 2))) # We want to linear interpolate from -90 to 90 degrees, as -892 to 892 on goal X. offset = car_angle_to_goal / 130 * 600 # This is a good start, but as usual, we probably need to use more factors than just the car's angle. cls.ball_target.x += offset Debug.rect_2d_3d(cls.ball_target, color="yellow") ####### Determine target ######## ################################# # We project a line from the ball_target towards the ball. # Find a point along this line that we want Botato to move towards. # The goal is to tweak the distance of this point along this line from the ball to get good hits in as many scenarios as possible. car_ball_dist = distance(car, ball) desired_distance_from_ball = car_ball_dist / 3 # Increase the desired distance from ball if the Car-Ball-Goal angle is tight (90 is tight, 180 is not tight) car_ball_goal_angle = get_angles_of_triangle(car.location, ball.location, cls.ball_target)[1] Debug.text_2d( 25, 330, "Car-Ball-Goal angle: " + str(round(car_ball_goal_angle, 2))) angle_tightness = (180 - car_ball_goal_angle) / 45 # desired_distance_from_ball *= angle_tightness*1.5 desired_distance_min = 0 desired_distance_max = 3000 if angle_tightness > 1: # If the angle is really quite tight, clamp the desired distance to a minimum of 1500. Debug.text_2d(25, 500, "TIGHT ANGLE, NEED SPACE!", color="red") desired_distance_min = 1500 desired_distance_from_ball = clamp(desired_distance_from_ball, desired_distance_min, desired_distance_max) target_to_ball_vec = cls.ball_target - ball.location # Initially determine the target by projecting a line from the ball target towards the ball, and overshooting it by the desired distance. cls.target = ball - (target_to_ball_vec.normalized * desired_distance_from_ball) # If Botato's angle to the ball is super wide, move the target closer to the car on the Y axis. # car_angle_to_ball = angle_to(car, ball) # cls.target.y += car.sign * abs(car_angle_to_ball)/90 * 500 if angle_tightness > 1: # If the angle is tight, move the target closer to the car. TODO: This was done in hopes of making Botato powerslide sooner, but it doesn't really work. I wonder if we could have a target_orientation so Botato knows he will have to arrive at the target at a certain orientation, and he could powerslide to achieve that. car_to_target = car.location - cls.target pushed_target = cls.target + car_to_target / 1.2 Debug.line_2d_3d(cls.target, pushed_target, color="lime") cls.target = pushed_target else: # Move the target closer to Botato on only the Y axis cls.target.y -= car.sign * car_ball_dist / 3 # Move the target closer to Botato # car_to_target_vec = car.location - cls.target # cls.target += car.sign * car_to_target_vec/3 # If we are inside the goalpost, move the target in front of the goal line cls.target = get_out_of_goal(car, cls.target) # Force the target to be on the ground. cls.target[2] = 17 # Force the target to be inside the pitch. cls.target = raycast(cls.target, ball) return cls.target
import math from botmath import * from Unreal import Rotator, MyVec3 from Objects import * import Debug from rlbot.utils.structures.quick_chats import QuickChats arena = MyVec3(8200, 10280, 2050) #test def will_intersect(car, target): """If we went in a straight line towards the target, would we hit the ball?""" # TODO: Would be nice to use predicted speed instead of just our current speed. car_loc = car.location prev_car_loc = MyVec3(car_loc) dt = 1 / 60 vel = car.velocity.size collision_threshold = 175 # Draw a line in the direction we're moving # car.renderer.draw_line_3d(car_loc, car_loc+car.velocity, car.renderer.green()) throttle_accel = get_throttle_accel(car.velocity.size) total_accel = throttle_accel + ACCEL_BOOST * (car.boost > 0) # direction = car.velocity.normalized direction = MyVec3(target - car.location).normalized for ps in car.ball_prediction.slices:
def avoid_ball(cls, car, target): """ The code for this got a bit out of hand, but I still don't think it deserves to be its own Strategy. But maybe""" new_target = MyVec3(target) need_to_avoid_ball = will_intersect(car, target) avoidance_distance = 350 # TODOs: This doesn't seem to do anything, and it should be set based on some factors rather than magic number. Debug.text_2d(25, 420, "Car-Ball Distance: " + str(distance(car, ball))) is_ball_between_car_and_own_goal = between(ball.location.y, car.location.y, car.own_goal.location.y) if distance(car, ball) > 1500 or not is_ball_between_car_and_own_goal: cls.keep_avoiding_ball = False if cls.keep_avoiding_ball: need_to_avoid_ball = True if need_to_avoid_ball and distance(car, ball) < 1500: cls.keep_avoiding_ball = True Debug.text_2d(25, 550, "KEEP AVOIDING BALL!", color="red") if need_to_avoid_ball and distance(car, ball) < 180: # If we need to avoid the ball but we hit it... car.send_quick_chats(QuickChats.CHAT_EVERYONE, QuickChats.Apologies_Whoops) if need_to_avoid_ball: Debug.text_2d(25, 500, "NEED TO AVOID BALL!", color="red") # Move the target on the opposite side of the ball's X direction. avoidance_direction = MyVec3(sign(ball.velocity.x), 0, 0).normalized # Currently, we only pick avoidance direction based on our location when the ball is not moving. Otherwise we will always pick based on our facing angle. This is not correct! Improve with testing. angle_to_ball = angle_to(car, ball) x_difference = car.location.x - ball.location.x if angle_to_ball < 2: cls.dont_dodge = True else: cls.dont_dodge = False if ball.velocity.size < 1: # If the ball is barely moving, avoid it on the side of the ball that we're more angled towards avoidance_direction = MyVec3(sign(angle_to_ball), 0, 0).normalized # TODO: This can still be improved a fair bit, by preferring avoiding the ball on goal side or boost side depending on the situation. cls.debugprint("Picking by angle") if abs(angle_to_ball) < 2: # If we are facing right at the ball, avoid it on the side we're closer to on the X axis. avoidance_direction = MyVec3(x_difference, 0.0, 0.0).normalized cls.debugprint("Picking by location") if x_difference < 30: # If we are perfectly aligned with the ball, just pick a side. cls.debugprint("Picking arbitrarily") avoidance_direction = MyVec3(1, 0, 0) new_target = ball.location - avoidance_direction * avoidance_distance # In case Botato decided to dodge while we were avoiding a ball, make sure we don't change target mid-dodge. NOTE: This should be redundant due to keep_avoiding_ball feature. if car.wheel_contact: cls.target_before_jump = new_target elif cls.target_before_jump: new_target = cls.target_before_jump return new_target
def find_target(cls, car): """Determine our target location - In the future, maybe we'll have sub-targets that make up a path, for more advanced planning ahead.""" cls.target = MyVec3(0, 0, 0) return cls.target
def __init__(self): self.location = MyVec3() self.timer = 0 self.big = False
class M_Dodge_For_Speed(Maneuver): """Dodge towards a target for the sake of gaining speed - Not for shooting!""" wheel_contact_delay = 0.3 # Time that has to pass after landing before we should dodge again. This will probably be universal to all controller states, but it could be a factor of how hard we landed(Z velocity) when we last landed. jumped = False dodged = False last_jump = 1 # Time of our last jump (Time of our last dodge is not stored currently) last_jump_loc = MyVec3(0, 0, 0) controls = ["pitch", "yaw", "roll", "jump"] @classmethod def get_output(cls, car, target, desired_speed=2300) -> SimpleControllerState: controller = cls.controller controller.pitch = car.controller.pitch controller.yaw = car.controller.yaw controller.roll = car.controller.roll dodge_duration = 1.3 # Rough expected duration of a dodge. dodge_distance = min( car.speed + 500, 2299 ) * dodge_duration # Expected dodge distance based on our current speed. (Dodging adds 500 to our speed) # Speed toward target velocity_at_car = ( car.location + car.velocity / 120 ) # per tick, rather than per second. Feels like it shouldn't matter, but I guess it does. TODO still not really sure if this is the right way to do this, but it does what I wanted it to. distance_now = distance(car.location, target) distance_next = distance(velocity_at_car, target) speed_toward_target = (distance_now - distance_next) * 120 speed_toward_target_ratio = 0 if car.speed == 0 else speed_toward_target / car.speed # The amount of our speed which is in the target's direction. dodge_steering_threshold = 0.51 # Don't try to dodge when the car is steering harder than this. dodge_speed_threshold = 1000 # Don't try to dodge when the car is going slower than this. speed_toward_target_ratio_threshold = 0.97 # Don't try to dodge if most of our speed isn't towards the target. TODO: This number seems unneccessarily high. dodge_delay = 0.20 - ( 2300 - car.speed ) / 20000 # Time between jumping and flipping. If this value is too low, it causes Botato to scrape his nose on the floor. If this value is too high, Botato will dodge slower than optimal. The value has to be higher when our speed is lower. This calculates to 0.13 when our speed is 1300, and to 0.18 when our speed is 2300. # TODO: I tried increasing this, but he's still scraping his nose on the floor, wtf? overshoot_threshold = 500 # We're allowed to overshoot the target by this distance. TODO: parameterize, implement local_target_unit_vec = local_coords( car, car.active_strategy.target).normalized if (cls.jumped): # Step 2 - Tilt & wait for dodge delay. if ((car.game_seconds - cls.last_jump) <= dodge_delay): # It's not time to dodge yet. controller.pitch = -1 #controller.roll = local_target_unit_vec.y # Try to dodge roughly towards target. TODO: This is not the best. controller.jump = True # "Hold" the jump key # Step 2.5 - Release the jump key just before the dodge. if (dodge_delay >= car.game_seconds - cls.last_jump >= dodge_delay - 0.05 # FIXME: If this number is too low, some of Botato's flips will fail. It's better to dodge late than never, so this should be fixed. and not cls.dodged): controller.jump = False # Step 3 - Dodge, continue air steering in the target direction until we land. This runs ONCE! elif (car.game_seconds - cls.last_jump >= dodge_delay # It's time to dodge. and not cls.dodged): # We haven't dodge yet. controller.pitch = -1 controller.roll = local_target_unit_vec.y # Try to dodge roughly towards target. TODO: This is not the best. controller.yaw = 0 #print(local_target_unit_vec) controller.jump = True cls.dodged = True # Step 4 - Before landing, continue steering toward the target. elif (cls.dodged # We already dodged and not car.wheel_contact): # But we haven't landed yet. controller.yaw = controller.steer elif (cls.dodged and car.wheel_contact): #print("dodge duration from jump to landing:") #print(car.game_seconds - cls.last_jump) #print("dodge distance") #print((car.location - car.last_jump_loc).size) cls.jumped = False cls.dodged = False controller.jump = False controller.roll = 0 controller.yaw = 0 # Step 1 - Jump elif (all([ car.speed > dodge_speed_threshold, # We are going fast enough (Dodging while slow is not worth it) abs(car.av.z) < 1500, # We aren't spinning like crazy car.speed + 500 < desired_speed, # TODO: At high enough distances, it could be worth it to dodge and over-accelerate, then decelerate to correct for it. speed_toward_target_ratio > speed_toward_target_ratio_threshold, # We are moving towards the target with most of our speed. TODO: this should be covered by angular velocity checks instead, I feel like. car.yaw_to_target < 40, # We are more or less facing the target. car.distance_from_target > 1500, # We are far enough away from the target TODO: dodge_distance + overshoot_threshold car.location.z < 18, # We are on the floor car. wheel_contact, # We are touching the floor (slightly redundant, yes) car.game_seconds - car.last_wheel_contact > cls. wheel_contact_delay, # We haven't just landed (Trying to jump directly after landing will result in disaster, except after Wavedashing). abs(controller.steer) < dodge_steering_threshold, # We aren't steering very hard car.controller.handbrake == False, # We aren't powersliding ])): #print("speed: " + str(car.speed)) #print("ratio: " + str(speed_toward_target_ratio)) #print("expected dodge distance: " ) #print(dodge_distance) cls.last_jump_loc = car.location controller.jump = True controller.pitch = -1 cls.jumped = True cls.last_jump = car.game_seconds return cls.controller
self.rotation = Rotator() self.velocity = MyVec3() class BoostPad: def __init__(self): self.location = MyVec3() self.timer = 0 self.big = False ball = GameObject() blue_goal = GameObject() orange_goal = GameObject() ball.radius = 93 ball.av = MyVec3() arena_x, arena_y, arena_z = 8200, 10280, 2050 arena = MyVec3(4100, 5140, 2050) center = MyVec3(0, 0, 0) goal_dimensions = MyVec3(892, 5120, 642) blue_goal.location = MyVec3(0, -5120, 0) blue_goal.left_post = MyVec3(892, -5120, 0) blue_goal.right_post = MyVec3(-892, -5120, 0) orange_goal.location = MyVec3(0, 5120, 0) orange_goal.left_post = MyVec3(-892, 5120, 0) orange_goal.right_post = MyVec3(892, 5120, 0)