diff --git a/requirements.txt b/requirements.txt index 25c0c97..008fac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # Include everything the framework requires # You will automatically get updates for all versions starting with "1.". rlbot==1.* +rlbot_gui rlbottraining # This will cause pip to auto-upgrade and stop scaring people with warning messages diff --git a/run-gui.bat b/run-gui.bat deleted file mode 100644 index a1c00d9..0000000 --- a/run-gui.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -@rem Change the working directory to the location of this file so that relative paths will work -cd /D "%~dp0" - -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -python run.py gui - -pause diff --git a/run-gui.sh b/run-gui.sh deleted file mode 100644 index 89c6a0b..0000000 --- a/run-gui.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" - -python3 ./run.py gui \ No newline at end of file diff --git a/run.bat b/run.bat deleted file mode 100644 index b3318d9..0000000 --- a/run.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -@rem Change the working directory to the location of this file so that relative paths will work -cd /D "%~dp0" - -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -python run.py - -pause diff --git a/run.py b/run.py index 061aa9c..23ea98a 100644 --- a/run.py +++ b/run.py @@ -25,14 +25,8 @@ subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: - if len(sys.argv) > 1 and sys.argv[1] == 'gui': - from rlbot.gui.qt_root import RLBotQTGui - - RLBotQTGui.main() - else: - from rlbot import runner - - runner.main() + from rlbot import runner + runner.main() except Exception as e: print("Encountered exception: ", e) print("Press enter to close.") diff --git a/run.sh b/run.sh deleted file mode 100644 index 4598972..0000000 --- a/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" - -python3 ./run.py \ No newline at end of file diff --git a/run_gui.py b/run_gui.py new file mode 100644 index 0000000..a3bced8 --- /dev/null +++ b/run_gui.py @@ -0,0 +1,7 @@ +from rlbot_gui import gui + +# This is a useful way to start up RLBotGUI directly from your bot project. You can use it to +# arrange a match with the settings you like, and if you have a good IDE like PyCharm, +# you can do breakpoint debugging on your bot. +if __name__ == '__main__': + gui.start() diff --git a/src/appearance.cfg b/src/appearance.cfg index 468bbe3..45840e3 100644 --- a/src/appearance.cfg +++ b/src/appearance.cfg @@ -1,3 +1,7 @@ +# You don't have to manually edit this file! +# RLBotGUI has an appearance editor with a nice colorpicker, database of items and more! +# To open it up, simply click the (i) icon next to your bot's name and then click Edit Appearance + [Bot Loadout] team_color_id = 60 custom_color_id = 0 diff --git a/src/bot.py b/src/bot.py index 3e2aa43..8d21345 100644 --- a/src/bot.py +++ b/src/bot.py @@ -31,7 +31,7 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: # This is good to keep at the beginning of get_output. It will allow you to continue # any sequences that you may have started during a previous call to get_output. - if self.active_sequence and not self.active_sequence.done: + if self.active_sequence is not None and not self.active_sequence.done: controls = self.active_sequence.tick(packet) if controls is not None: return controls @@ -42,14 +42,19 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: car_velocity = Vec3(my_car.physics.velocity) ball_location = Vec3(packet.game_ball.physics.location) + # By default we will chase the ball, but target_location can be changed later + target_location = ball_location + if car_location.dist(ball_location) > 1500: # We're far away from the ball, let's try to lead it a little bit ball_prediction = self.get_ball_prediction_struct() # This can predict bounces, etc ball_in_future = find_slice_at_time(ball_prediction, packet.game_info.seconds_elapsed + 2) - target_location = Vec3(ball_in_future.physics.location) - self.renderer.draw_line_3d(ball_location, target_location, self.renderer.cyan()) - else: - target_location = ball_location + + # ball_in_future might be None if we don't have an adequate ball prediction right now, like during + # replays, so check it to avoid errors. + if ball_in_future is not None: + target_location = Vec3(ball_in_future.physics.location) + self.renderer.draw_line_3d(ball_location, target_location, self.renderer.cyan()) # Draw some things to help understand what the bot is thinking self.renderer.draw_line_3d(car_location, target_location, self.renderer.white()) diff --git a/src/util/spikes.py b/src/util/spikes.py new file mode 100644 index 0000000..582c30a --- /dev/null +++ b/src/util/spikes.py @@ -0,0 +1,36 @@ +from rlbot.utils.structures.game_data_struct import PlayerInfo, GameTickPacket + +from util.vec import Vec3 + +# When the ball is attached to a car's spikes, the distance will vary a bit depending on whether the ball is +# on the front bumper, the roof, etc. It tends to be most far away when the ball is on one of the front corners +# and that distance is a little under 200. We want to be sure that it's never over 200, otherwise bots will +# suffer from bad bugs when they don't think the ball is spiked to them but it actually is; they'll probably +# drive in circles. The opposite problem, where they think it's spiked before it really is, is not so bad because +# they usually spike it for real a split second later. +MAX_DISTANCE_WHEN_SPIKED = 200 + +class SpikeWatcher: + def __init__(self): + self.carrying_car: PlayerInfo = None + self.spike_moment = 0 + self.carry_duration = 0 + + def read_packet(self, packet: GameTickPacket): + ball_location = Vec3(packet.game_ball.physics.location) + closest_candidate: PlayerInfo = None + closest_distance = 999999 + for i in range(packet.num_cars): + car = packet.game_cars[i] + car_location = Vec3(car.physics.location) + distance = car_location.dist(ball_location) + if distance < MAX_DISTANCE_WHEN_SPIKED: + if distance < closest_distance: + closest_candidate = car + closest_distance = distance + if closest_candidate != self.carrying_car and closest_candidate is not None: + self.spike_moment = packet.game_info.seconds_elapsed + + self.carrying_car = closest_candidate + if self.carrying_car is not None: + self.carry_duration = packet.game_info.seconds_elapsed - self.spike_moment diff --git a/training/hello_world_training.py b/training/hello_world_training.py index c1ea9e3..f1cba03 100644 --- a/training/hello_world_training.py +++ b/training/hello_world_training.py @@ -16,7 +16,7 @@ def make_match_config_with_my_bot() -> MatchConfig: # Makes a config which only has our bot in it for now. - # For more defails: https://youtu.be/uGFmOZCpel8?t=375 + # For more details: https://youtu.be/uGFmOZCpel8?t=375 match_config = make_empty_match_config() match_config.player_configs = [ PlayerConfig.bot_config( @@ -26,6 +26,17 @@ def make_match_config_with_my_bot() -> MatchConfig: ] return match_config + +def add_my_bot_to_playlist(exercises: Playlist) -> Playlist: + """ + Updates the match config for each excercise to include + the bot from this project + """ + for exercise in exercises: + exercise.match_config = make_match_config_with_my_bot() + return exercises + + @dataclass class StrikerPatience(StrikerExercise): """ @@ -90,7 +101,4 @@ def make_default_playlist() -> Playlist: DrivesToBallExercise('Get close to ball'), DrivesToBallExercise('Get close-ish to ball', grader=DriveToBallGrader(min_dist_to_pass=1000)) ] - for exercise in exercises: - exercise.match_config = make_match_config_with_my_bot() - - return exercises + return add_my_bot_to_playlist(exercises) diff --git a/training/unit_tests.py b/training/unit_tests.py index a147c35..9326280 100644 --- a/training/unit_tests.py +++ b/training/unit_tests.py @@ -3,7 +3,7 @@ from rlbot.training.training import Pass, Fail from rlbottraining.exercise_runner import run_playlist -from hello_world_training import StrikerPatience +from hello_world_training import StrikerPatience, add_my_bot_to_playlist class PatienceTest(unittest.TestCase): """ @@ -18,7 +18,8 @@ class PatienceTest(unittest.TestCase): """ def test_patience_required(self): - result_iter = run_playlist([StrikerPatience(name='patience required')]) + playlist = [StrikerPatience(name='patience required')] + result_iter = run_playlist(add_my_bot_to_playlist(playlist)) results = list(result_iter) self.assertEqual(len(results), 1) result = results[0] @@ -26,7 +27,8 @@ def test_patience_required(self): self.assertIsInstance(result.grade, Fail) # If you make the bot is smarter, update this assert that we pass. def test_no_patience_required(self): - result_iter = run_playlist([StrikerPatience(name='no patience required', car_start_x=-1000)]) + playlist = [StrikerPatience(name='no patience required', car_start_x=-1000)] + result_iter = run_playlist(add_my_bot_to_playlist(playlist)) results = list(result_iter) self.assertEqual(len(results), 1) result = results[0]