Skip to content

Commit

Permalink
Adding Noob Black (#332)
Browse files Browse the repository at this point in the history
* Add files via upload

* Update bot.cfg
  • Loading branch information
rocketeeroof authored Dec 27, 2024
1 parent 14cea34 commit 9b29b71
Show file tree
Hide file tree
Showing 13 changed files with 985 additions and 0 deletions.
Empty file.
53 changes: 53 additions & 0 deletions RLBotPack/Noob_Black/src/appearance.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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
car_id = 23
decal_id = 0
wheels_id = 1565
boost_id = 35
antenna_id = 0
hat_id = 0
paint_finish_id = 1681
custom_finish_id = 1681
engine_audio_id = 0
trails_id = 3220
goal_explosion_id = 3018

[Bot Loadout Orange]
team_color_id = 3
custom_color_id = 0
car_id = 23
decal_id = 0
wheels_id = 1565
boost_id = 35
antenna_id = 0
hat_id = 0
paint_finish_id = 1681
custom_finish_id = 1681
engine_audio_id = 0
trails_id = 3220
goal_explosion_id = 3018

[Bot Paint Blue]
car_paint_id = 12
decal_paint_id = 0
wheels_paint_id = 7
boost_paint_id = 7
antenna_paint_id = 0
hat_paint_id = 0
trails_paint_id = 2
goal_explosion_paint_id = 0

[Bot Paint Orange]
car_paint_id = 12
decal_paint_id = 0
wheels_paint_id = 14
boost_paint_id = 14
antenna_paint_id = 0
hat_paint_id = 0
trails_paint_id = 14
goal_explosion_paint_id = 0
33 changes: 33 additions & 0 deletions RLBotPack/Noob_Black/src/bot.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[Locations]
# Path to loadout config. Can use relative path from here.
looks_config = ./appearance.cfg

# Path to python file. Can use relative path from here.
python_file = ./bot.py

# Name of the bot in-game
name = Noob Black

# The maximum number of ticks per second that your bot wishes to receive.
maximum_tick_rate_preference = 120

[Details]
# These values are optional but useful metadata for helper programs
# Name of the bot's creator/developer
developer = Scumclass

# Short description of the bot
description = Noob Bot V3, made with code from the black market.
As always, mainly designed for 3s.

# Fun fact about the bot
fun_fact = The bot has a Roblox background.

# Tags
tags = teamplay

# Link to github repository
github = https://github.com/RLBot/RLBotPythonExample

# Programming language
language = python
528 changes: 528 additions & 0 deletions RLBotPack/Noob_Black/src/bot.py

Large diffs are not rendered by default.

Binary file added RLBotPack/Noob_Black/src/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
48 changes: 48 additions & 0 deletions RLBotPack/Noob_Black/src/util/ball_prediction_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Callable

from rlbot.utils.structures.ball_prediction_struct import BallPrediction, Slice

# field length(5120) + ball radius(93) = 5213 however that results in false positives
GOAL_THRESHOLD = 5235

# We will jump this number of frames when looking for a moment where the ball is inside the goal.
# Big number for efficiency, but not so big that the ball could go in and then back out during that
# time span. Unit is the number of frames in the ball prediction, and the prediction is at 60 frames per second.
GOAL_SEARCH_INCREMENT = 20


def find_slice_at_time(ball_prediction: BallPrediction, game_time: float):
"""
This will find the future position of the ball at the specified time. The returned
Slice object will also include the ball's velocity, etc.
"""
start_time = ball_prediction.slices[0].game_seconds
approx_index = int((game_time - start_time) * 60) # We know that there are 60 slices per second.
if 0 <= approx_index < ball_prediction.num_slices:
return ball_prediction.slices[approx_index]
return None


def predict_future_goal(ball_prediction: BallPrediction):
"""
Analyzes the ball prediction to see if the ball will enter one of the goals. Only works on standard arenas.
Will return the first ball slice which appears to be inside the goal, or None if it does not enter a goal.
"""
return find_matching_slice(ball_prediction, 0, lambda s: abs(s.physics.location.y) >= GOAL_THRESHOLD,
search_increment=20)


def find_matching_slice(ball_prediction: BallPrediction, start_index: int, predicate: Callable[[Slice], bool],
search_increment=1):
"""
Tries to find the first slice in the ball prediction which satisfies the given predicate. For example,
you could find the first slice below a certain height. Will skip ahead through the packet by search_increment
for better efficiency, then backtrack to find the exact first slice.
"""
for coarse_index in range(start_index, ball_prediction.num_slices, search_increment):
if predicate(ball_prediction.slices[coarse_index]):
for j in range(max(start_index, coarse_index - search_increment), coarse_index):
ball_slice = ball_prediction.slices[j]
if predicate(ball_slice):
return ball_slice
return None
43 changes: 43 additions & 0 deletions RLBotPack/Noob_Black/src/util/boost_pad_tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import List

from rlbot.utils.structures.game_data_struct import GameTickPacket, FieldInfoPacket

from util.vec import Vec3


@dataclass
class BoostPad:
location: Vec3
is_full_boost: bool
is_active: bool # Active means it's available to be picked up
timer: float # Counts the number of seconds that the pad has been *inactive*


class BoostPadTracker:
"""
This class merges together the boost pad location info with the is_active info so you can access it
in one convenient list. For it to function correctly, you need to call initialize_boosts once when the
game has started, and then update_boost_status every frame so that it knows which pads are active.
"""

def __init__(self):
self.boost_pads: List[BoostPad] = []
self._full_boosts_only: List[BoostPad] = []

def initialize_boosts(self, game_info: FieldInfoPacket):
raw_boosts = [game_info.boost_pads[i] for i in range(game_info.num_boosts)]
self.boost_pads: List[BoostPad] = [BoostPad(Vec3(rb.location), rb.is_full_boost, False, 0) for rb in raw_boosts]
# Cache the list of full boosts since they're commonly requested.
# They reference the same objects in the boost_pads list.
self._full_boosts_only: List[BoostPad] = [bp for bp in self.boost_pads if bp.is_full_boost]

def update_boost_status(self, packet: GameTickPacket):
for i in range(packet.num_boost):
our_pad = self.boost_pads[i]
packet_pad = packet.game_boosts[i]
our_pad.is_active = packet_pad.is_active
our_pad.timer = packet_pad.timer

def get_full_boosts(self) -> List[BoostPad]:
return self._full_boosts_only
25 changes: 25 additions & 0 deletions RLBotPack/Noob_Black/src/util/drive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import math

from rlbot.utils.structures.game_data_struct import PlayerInfo

from util.orientation import Orientation, relative_location
from util.vec import Vec3


def limit_to_safe_range(value: float) -> float:
"""
Controls like throttle, steer, pitch, yaw, and roll need to be in the range of -1 to 1.
This will ensure your number is in that range. Something like 0.45 will stay as it is,
but a value of -5.6 would be changed to -1.
"""
if value < -1:
return -1
if value > 1:
return 1
return value


def steer_toward_target(car: PlayerInfo, target: Vec3) -> float:
relative = relative_location(Vec3(car.physics.location), Orientation(car.physics.rotation), target)
angle = math.atan2(relative.y, relative.x)
return limit_to_safe_range(angle * 5)
47 changes: 47 additions & 0 deletions RLBotPack/Noob_Black/src/util/orientation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import math

from util.vec import Vec3


# This is a helper class for calculating directions relative to your car. You can extend it or delete if you want.
class Orientation:
"""
This class describes the orientation of an object from the rotation of the object.
Use this to find the direction of cars: forward, right, up.
It can also be used to find relative locations.
"""

def __init__(self, rotation):
self.yaw = float(rotation.yaw)
self.roll = float(rotation.roll)
self.pitch = float(rotation.pitch)

cr = math.cos(self.roll)
sr = math.sin(self.roll)
cp = math.cos(self.pitch)
sp = math.sin(self.pitch)
cy = math.cos(self.yaw)
sy = math.sin(self.yaw)

self.forward = Vec3(cp * cy, cp * sy, sp)
self.right = Vec3(cy*sp*sr-cr*sy, sy*sp*sr+cr*cy, -cp*sr)
self.up = Vec3(-cr*cy*sp-sr*sy, -cr*sy*sp+sr*cy, cp*cr)


# Sometimes things are easier, when everything is seen from your point of view.
# This function lets you make any location the center of the world.
# For example, set center to your car's location and ori to your car's orientation, then the target will be
# relative to your car!
def relative_location(center: Vec3, ori: Orientation, target: Vec3) -> Vec3:
"""
Returns target as a relative location from center's point of view, using the given orientation. The components of
the returned vector describes:
* x: how far in front
* y: how far right
* z: how far above
"""
x = (target - center).dot(ori.forward)
y = (target - center).dot(ori.right)
z = (target - center).dot(ori.up)
return Vec3(x, y, z)
63 changes: 63 additions & 0 deletions RLBotPack/Noob_Black/src/util/sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from dataclasses import dataclass
from typing import List

from rlbot.agents.base_agent import SimpleControllerState
from rlbot.utils.structures.game_data_struct import GameTickPacket


@dataclass
class StepResult:
controls: SimpleControllerState
done: bool


class Step:
def tick(self, packet: GameTickPacket) -> StepResult:
"""
Return appropriate controls for this step in the sequence. If the step is over, you should
set done to True in the result, and we'll move on to the next step during the next frame.
If you panic and can't return controls at all, you may return None and we will move on to
the next step immediately.
"""
raise NotImplementedError


class ControlStep(Step):
"""
This allows you to repeat the same controls every frame for some specified duration. It's useful for
scheduling the button presses needed for kickoffs / dodges / etc.
"""
def __init__(self, duration: float, controls: SimpleControllerState):
self.duration = duration
self.controls = controls
self.start_time: float = None

def tick(self, packet: GameTickPacket) -> StepResult:
if self.start_time is None:
self.start_time = packet.game_info.seconds_elapsed
elapsed_time = packet.game_info.seconds_elapsed - self.start_time
return StepResult(controls=self.controls, done=elapsed_time > self.duration)


class Sequence:
def __init__(self, steps: List[Step]):
self.steps = steps
self.index = 0
self.done = False

def tick(self, packet: GameTickPacket):
while self.index < len(self.steps):
step = self.steps[self.index]
result = step.tick(packet)
if result is None or result.controls is None or result.done:
self.index += 1
if self.index >= len(self.steps):
# The bot will know not to use this sequence next frame, even though we may be giving it controls.
self.done = True
if result is not None and result.controls is not None:
# If the step was able to give us controls, return them to the bot.
return result.controls
# Otherwise we will loop to the next step in the sequence.
# If we reach here, we ran out of steps to attempt.
self.done = True
return None
36 changes: 36 additions & 0 deletions RLBotPack/Noob_Black/src/util/spikes.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 9b29b71

Please sign in to comment.