Skip to content

Commit e4ee178

Browse files
committed
Updates
1 parent d112de5 commit e4ee178

11 files changed

+81
-48
lines changed

dev.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#:schema https://rlbot.org/schemas/match.json
12
[rlbot]
2-
launcher = "steam"
3+
launcher = "Steam"
34
# We'll run the bot ourself so we don't have restart the match
45
# `python src/bot.py` in our case
56
auto_start_agents = false

rlbot.toml

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
#:schema https://rlbot.org/schemas/match.json
12
[rlbot]
23
# use this along with launcher = "custom"
34
# launcher_arg = "legendary"
45
# "Steam", "Epic", "Custom", "NoLaunch"
5-
launcher = "steam"
6+
launcher = "Steam"
67
# Should RLBot start the bot processes automatically, or will a separate script start them
78
auto_start_agents = true
89
# Should RLBot wait for the bot processes to start before starting the match
@@ -92,11 +93,11 @@ assist_goal_score = "Zero"
9293
input_restriction = "Default"
9394

9495
[[cars]]
95-
type = "human"
96-
team = 0
96+
type = "Human"
97+
team = "Blue"
9798

9899
[[cars]]
99100
config_file = "src/bot.toml"
100-
team = 1
101+
team = "Orange"
101102
# If RLBot should start this bot automatically with its specified `run_command` or not
102103
auto_start = true

src/bot.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from rlbot.flat import BallAnchor, ControllerState, GamePacket
22
from rlbot.managers import Bot
3+
from rlbot_flatbuffers import CarAnchor
34

45
from util.ball_prediction_analysis import find_slice_at_time
56
from util.boost_pad_tracker import BoostPadTracker
@@ -46,6 +47,8 @@ def get_output(self, packet: GamePacket) -> ControllerState:
4647
# By default we will chase the ball, but target_location can be changed later
4748
target_location = ball_location
4849

50+
self.renderer.begin_rendering()
51+
4952
if car_location.dist(ball_location) > 1500:
5053
# We're far away from the ball, let's try to lead it a little bit
5154
# self.ball_prediction can predict bounces, etc
@@ -65,10 +68,12 @@ def get_output(self, packet: GamePacket) -> ControllerState:
6568
)
6669

6770
# Draw some things to help understand what the bot is thinking
68-
self.renderer.draw_line_3d(car_location, target_location, self.renderer.white)
71+
self.renderer.draw_line_3d(
72+
CarAnchor(self.index), target_location, self.renderer.white
73+
)
6974
self.renderer.draw_string_3d(
7075
f"Speed: {car_velocity.length():.1f}",
71-
car_location,
76+
CarAnchor(self.index),
7277
1,
7378
self.renderer.white,
7479
)
@@ -78,9 +83,11 @@ def get_output(self, packet: GamePacket) -> ControllerState:
7883
self.renderer.cyan,
7984
)
8085

86+
self.renderer.end_rendering()
87+
8188
if 750 < car_velocity.length() < 800:
8289
# We'll do a front flip if the car is moving at a certain speed.
83-
return self.begin_front_flip(packet) # type: ignore
90+
return self.begin_front_flip(packet)
8491

8592
controls = ControllerState()
8693
controls.steer = steer_toward_target(my_car, target_location)
@@ -89,7 +96,7 @@ def get_output(self, packet: GamePacket) -> ControllerState:
8996

9097
return controls
9198

92-
def begin_front_flip(self, packet: GamePacket):
99+
def begin_front_flip(self, packet: GamePacket) -> ControllerState:
93100
# Send some quickchat just for fun
94101
# There won't be any content of the message for other bots,
95102
# but "I got it!" will be display for a human to see!
@@ -109,7 +116,7 @@ def begin_front_flip(self, packet: GamePacket):
109116
)
110117

111118
# Return the controls associated with the beginning of the sequence so we can start right away.
112-
return self.active_sequence.tick(packet)
119+
return self.active_sequence.tick(packet) # type: ignore
113120

114121

115122
if __name__ == "__main__":

src/bot.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
#:schema https://rlbot.org/schemas/agent.json
12
[settings]
23
# The name that will be displayed in game
3-
name = "PyExampleBot"
4+
name = "Python Example"
45
# Path to loadout config from runner
56
loadout_file = "loadout.toml"
67
# (OPTIONAL) - what you want the working directory set to
@@ -17,6 +18,10 @@ run_command_linux = "../venv/bin/python bot.py"
1718
# This must be a unqiue identifier for your bot
1819
# recommended format is "developer/botname"
1920
agent_id = "rlbot_community/python_example"
21+
# This is the path to your bot's logo image,
22+
# which will be displayed in the GUI
23+
# The example bot does not have a logo
24+
logo_file = ""
2025

2126
# These values are optional but useful metadata for helper programs
2227
[details]

src/loadout.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# You don't have to manually edit this file!
22
# RLBotGUI has an appearance editor with a nice colorpicker, database of items and more!
3-
# To open it up, simply click the (i) icon next to your bot's name and then click Edit Appearance
3+
# To open it up, simply click the (i) icon next to your bot's name and then click Edit Loadout
44

55
[blue_loadout]
66
# Primary Color selection

src/util/ball_prediction_analysis.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
GOAL_SEARCH_INCREMENT = 40
1212

1313

14-
def find_slice_at_time(ball_prediction: BallPrediction, game_time: float):
14+
def find_slice_at_time(
15+
ball_prediction: BallPrediction, game_time: float
16+
) -> PredictionSlice | None:
1517
"""
1618
This will find the future position of the ball at the specified time. The returned
1719
Slice object will also include the ball's velocity, etc.
@@ -22,10 +24,9 @@ def find_slice_at_time(ball_prediction: BallPrediction, game_time: float):
2224
) # We know that there are 120 slices per second.
2325
if 0 <= approx_index < len(ball_prediction.slices):
2426
return ball_prediction.slices[approx_index]
25-
return None
2627

2728

28-
def predict_future_goal(ball_prediction: BallPrediction):
29+
def predict_future_goal(ball_prediction: BallPrediction) -> PredictionSlice | None:
2930
"""
3031
Analyzes the ball prediction to see if the ball will enter one of the goals. Only works on standard arenas.
3132
Will return the first ball slice which appears to be inside the goal, or None if it does not enter a goal.
@@ -43,7 +44,7 @@ def find_matching_slice(
4344
start_index: int,
4445
predicate: Callable[[PredictionSlice], bool],
4546
search_increment=1,
46-
):
47+
) -> PredictionSlice | None:
4748
"""
4849
Tries to find the first slice in the ball prediction which satisfies the given predicate. For example,
4950
you could find the first slice below a certain height. Will skip ahead through the packet by search_increment
@@ -59,4 +60,3 @@ def find_matching_slice(
5960
ball_slice = ball_prediction.slices[j]
6061
if predicate(ball_slice):
6162
return ball_slice
62-
return None

src/util/boost_pad_tracker.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
from dataclasses import dataclass
1+
from dataclasses import dataclass, field
22

33
from rlbot.flat import FieldInfo, GamePacket
44

55
from util.vec import Vec3
66

77

8-
@dataclass
8+
@dataclass(slots=True)
99
class BoostPad:
1010
location: Vec3
1111
is_full_boost: bool
1212
is_active: bool # Active means it's available to be picked up
1313
timer: float # Counts the number of seconds that the pad has been *inactive*
1414

1515

16+
@dataclass(init=False, slots=True)
1617
class BoostPadTracker:
1718
"""
1819
This class merges together the boost pad location info with the is_active info so you can access it
1920
in one convenient list. For it to function correctly, you need to call initialize_boosts once when the
2021
game has started, and then update_boost_status every frame so that it knows which pads are active.
2122
"""
2223

23-
def __init__(self):
24-
self.boost_pads: list[BoostPad] = []
25-
self._full_boosts_only: list[BoostPad] = []
24+
boost_pads: list[BoostPad] = field(default_factory=lambda: [])
25+
_full_boosts_only: list[BoostPad] = field(default_factory=lambda: [])
2626

2727
def initialize_boosts(self, game_info: FieldInfo):
2828
self.boost_pads: list[BoostPad] = [

src/util/orientation.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
import math
2+
from dataclasses import dataclass
3+
4+
from rlbot.flat import Rotator
25

36
from util.vec import Vec3
47

58

69
# This is a helper class for calculating directions relative to your car. You can extend it or delete if you want.
10+
@dataclass(init=False, slots=True)
711
class Orientation:
812
"""
913
This class describes the orientation of an object from the rotation of the object.
1014
Use this to find the direction of cars: forward, right, up.
1115
It can also be used to find relative locations.
1216
"""
1317

14-
def __init__(self, rotation):
18+
yaw: float
19+
roll: float
20+
pitch: float
21+
22+
forward: Vec3
23+
right: Vec3
24+
up: Vec3
25+
26+
def __init__(self, rotation: Rotator):
27+
self.pitch = float(rotation.pitch)
1528
self.yaw = float(rotation.yaw)
1629
self.roll = float(rotation.roll)
17-
self.pitch = float(rotation.pitch)
1830

1931
cr = math.cos(self.roll)
2032
sr = math.sin(self.roll)

src/util/sequence.py

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
from dataclasses import dataclass
1+
from abc import ABC, abstractmethod
2+
from dataclasses import dataclass, field
23

3-
from rlbot.flat import GamePacket, ControllerState
4+
from rlbot.flat import ControllerState, GamePacket
45

56

6-
@dataclass
7+
@dataclass(slots=True)
78
class StepResult:
89
controls: ControllerState
910
done: bool
1011

1112

12-
class Step:
13+
class Step(ABC):
14+
@abstractmethod
1315
def tick(self, packet: GamePacket) -> StepResult:
1416
"""
1517
Return appropriate controls for this step in the sequence. If the step is over, you should
@@ -20,16 +22,17 @@ def tick(self, packet: GamePacket) -> StepResult:
2022
raise NotImplementedError
2123

2224

25+
@dataclass(slots=True)
2326
class ControlStep(Step):
2427
"""
2528
This allows you to repeat the same controls every frame for some specified duration. It's useful for
2629
scheduling the button presses needed for kickoffs / dodges / etc.
2730
"""
2831

29-
def __init__(self, duration: float, controls: ControllerState):
30-
self.duration = duration
31-
self.controls = controls
32-
self.start_time: float | None = None
32+
duration: float
33+
controls: ControllerState
34+
35+
start_time: float | None = field(default=None, init=False)
3336

3437
def tick(self, packet: GamePacket) -> StepResult:
3538
if self.start_time is None:
@@ -38,11 +41,12 @@ def tick(self, packet: GamePacket) -> StepResult:
3841
return StepResult(controls=self.controls, done=elapsed_time > self.duration)
3942

4043

44+
@dataclass(slots=True)
4145
class Sequence:
42-
def __init__(self, steps: list[Step]):
43-
self.steps = steps
44-
self.index = 0
45-
self.done = False
46+
steps: list[Step]
47+
48+
index: int = field(default=0, init=False)
49+
done: bool = field(default=False, init=False)
4650

4751
def tick(self, packet: GamePacket) -> ControllerState | None:
4852
while self.index < len(self.steps):

src/util/spikes.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import math
2+
from dataclasses import dataclass
3+
14
from rlbot.flat import GamePacket, PlayerInfo
25

36
from util.vec import Vec3
@@ -11,16 +14,16 @@
1114
MAX_DISTANCE_WHEN_SPIKED = 200
1215

1316

17+
@dataclass(init=False, slots=True)
1418
class SpikeWatcher:
15-
def __init__(self):
16-
self.carrying_car: PlayerInfo | None = None
17-
self.spike_moment = 0
18-
self.carry_duration = 0
19+
carrying_car: PlayerInfo | None = None
20+
spike_moment: float = 0
21+
carry_duration: float = 0
1922

2023
def read_packet(self, packet: GamePacket):
2124
ball_location = Vec3(packet.balls[0].physics.location)
2225
closest_candidate: PlayerInfo | None = None
23-
closest_distance = 999999
26+
closest_distance = math.inf
2427

2528
for car in packet.players:
2629
car_location = Vec3(car.physics.location)

src/util/vec.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ class Vec3(Vector3):
77
"""
88
This class should provide you with all the basic vector operations that you need, but feel free to extend its
99
functionality when needed.
10-
The vectors found in the GameTickPacket will be flatbuffer vectors. Cast them to Vec3 like this:
10+
The vectors found in the GamePacket will be flatbuffer vectors. Cast them to Vec3 like this:
1111
`car_location = Vec3(car.physics.location)`.
1212
1313
Remember that the in-game axis are left-handed.
1414
15-
When in doubt visit the wiki: https://github.com/RLBot/RLBot/wiki/Useful-Game-Values
15+
When in doubt visit the wiki: https://wiki.rlbot.org/botmaking/useful-game-values/
1616
"""
1717

1818
def __new__(
@@ -57,25 +57,25 @@ def __truediv__(self, scale: float) -> "Vec3":
5757
scale = 1 / float(scale)
5858
return self * scale
5959

60-
def __str__(self):
60+
def __str__(self) -> str:
6161
return f"Vec3({self.x:.2f}, {self.y:.2f}, {self.z:.2f})"
6262

63-
def __repr__(self):
63+
def __repr__(self) -> str:
6464
return self.__str__()
6565

66-
def flat(self):
66+
def flat(self) -> "Vec3":
6767
"""Returns a new Vec3 that equals this Vec3 but projected onto the ground plane. I.e. where z=0."""
6868
return Vec3(self.x, self.y, 0)
6969

70-
def length(self):
70+
def length(self) -> float:
7171
"""Returns the length of the vector. Also called magnitude and norm."""
7272
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
7373

7474
def dist(self, other: "Vec3") -> float:
7575
"""Returns the distance between this vector and another vector using pythagoras."""
7676
return (self - other).length()
7777

78-
def normalized(self):
78+
def normalized(self) -> "Vec3":
7979
"""Returns a vector with the same direction but a length of one."""
8080
return self / self.length()
8181

0 commit comments

Comments
 (0)