Skip to content

Commit 8aff627

Browse files
authored
Add RLDojo at v1.1.0 to RLBotPack (#338)
1 parent abd766e commit 8aff627

23 files changed

Lines changed: 3954 additions & 0 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.10.4

RLBotPack/RLDojo/Dojo/constants.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Game field constants
2+
SIDE_WALL = 4096
3+
BACK_WALL = -5120 # From perspective of default scenario - blue team defending
4+
GOAL_WIDTH = 893
5+
GOAL_DEPTH = 880
6+
CORNER_OFFSET = 1152
7+
8+
# Game timing constants
9+
DEFAULT_TIMEOUT = 10.0
10+
FREE_GOAL_TIMEOUT = 7.0
11+
DEFAULT_PAUSE_TIME = 1.0
12+
13+
# UI constants
14+
MENU_START_X = 20
15+
MENU_START_Y = 400
16+
MENU_WIDTH = 500
17+
MENU_HEIGHT = 500
18+
19+
SCORE_BOX_START_X = 50
20+
SCORE_BOX_START_Y = 100
21+
SCORE_BOX_WIDTH = 240
22+
SCORE_BOX_HEIGHT = 110
23+
24+
CUSTOM_MODE_MENU_START_X = 20
25+
CUSTOM_MODE_MENU_START_Y = 600
26+
CUSTOM_MODE_MENU_WIDTH = 400
27+
CUSTOM_MODE_MENU_HEIGHT = 200
28+
29+
CONTROLS_MENU_WIDTH = 350
30+
CONTROLS_MENU_HEIGHT = 200
31+
32+
# Physics constants
33+
MIN_VELOCITY = 1000
34+
MAX_VELOCITY = 2000
35+
BALL_GROUND_THRESHOLD = 100
36+
GOAL_DETECTION_THRESHOLD = 40
37+
38+
# Default trial counts
39+
DEFAULT_TRIAL_OPTIONS = [100, 50, 25, 10]
40+
DEFAULT_NUM_TRIALS = 100
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
"""
2+
Custom playlist creation and management system for Dojo.
3+
4+
This module provides functionality for users to create, edit, and save
5+
custom playlists that persist between sessions.
6+
"""
7+
8+
import json
9+
import os
10+
from typing import List, Dict, Any, Optional, Tuple
11+
from playlist import Playlist, ScenarioConfig, PlaylistSettings, PlayerRole
12+
from scenario import OffensiveMode, DefensiveMode
13+
from menu import MenuRenderer, UIElement
14+
from pydantic import BaseModel, Field, ValidationError
15+
from custom_scenario import CustomScenario, get_custom_scenarios
16+
17+
class CustomPlaylistManager:
18+
def __init__(self, renderer, main_menu_renderer):
19+
self.renderer = renderer
20+
self.main_menu_renderer = main_menu_renderer
21+
# Current playlist being created/edited
22+
self.current_playlist_name = ""
23+
self.current_scenarios = []
24+
self.current_custom_scenarios = []
25+
self.current_boost_range = [12, 100] # Default boost range
26+
self.current_timeout = 7.0
27+
self.current_rule_zero = False
28+
29+
def load_custom_playlists(self):
30+
"""Load custom playlists from disk and return a list of all custom playlists"""
31+
custom_playlists = {}
32+
for file in os.listdir(_get_custom_playlists_path()):
33+
if file.endswith(".json"):
34+
with open(os.path.join(_get_custom_playlists_path(), file), "r") as f:
35+
custom_playlists[file.replace(".json", "")] = Playlist.model_validate_json(f.read())
36+
return custom_playlists
37+
38+
def create_playlist_creation_menu(self):
39+
"""Create the main playlist creation menu"""
40+
menu = MenuRenderer(self.renderer, columns=1, render_function=self._render_playlist_details)
41+
menu.add_element(UIElement("Create Custom Playlist", header=True))
42+
menu.add_element(UIElement("Set Playlist Name", submenu=self._create_name_input_menu(), display_value_function=self.get_current_playlist_name))
43+
menu.add_element(UIElement("Add PresetScenarios", submenu=self._create_scenario_selection_menu()))
44+
menu.add_element(UIElement("Add Custom Scenario", submenu=self._create_custom_scenario_selection_menu()))
45+
menu.add_element(UIElement("Set Boost Range", submenu=self._create_boost_range_menu(), display_value_function=self.get_current_playlist_boost_range))
46+
menu.add_element(UIElement("Set Timeout", submenu=self._create_timeout_menu(), display_value_function=self.get_current_playlist_timeout))
47+
menu.add_element(UIElement("Toggle Rule Zero", function=self._toggle_rule_zero, display_value_function=self.get_current_playlist_rule_zero))
48+
menu.add_element(UIElement("Save Playlist", function=self._save_current_playlist))
49+
menu.add_element(UIElement("Cancel", function=self._cancel_playlist_creation))
50+
return menu
51+
52+
### Element value retrieval functions
53+
def get_current_playlist_name(self):
54+
return self.current_playlist_name
55+
56+
57+
def get_current_playlist_boost_range(self):
58+
return self.current_boost_range
59+
60+
def get_current_playlist_timeout(self):
61+
return self.current_timeout
62+
63+
def get_current_playlist_rule_zero(self):
64+
return self.current_rule_zero
65+
66+
def _render_playlist_details(self):
67+
# Create a playlist out of current settings
68+
playlist = Playlist(
69+
name=self.current_playlist_name,
70+
description=f"Custom playlist with {len(self.current_scenarios)} scenarios",
71+
scenarios=self.current_scenarios.copy(),
72+
custom_scenarios=self.current_custom_scenarios.copy(),
73+
settings=PlaylistSettings(timeout=self.current_timeout, shuffle=True, boost_range=self.current_boost_range, rule_zero=self.current_rule_zero)
74+
)
75+
playlist.render_details(self.renderer)
76+
77+
def _create_name_input_menu(self):
78+
"""Create menu for setting playlist name"""
79+
menu = MenuRenderer(self.renderer, columns=1, text_input=True, text_input_callback=self._set_playlist_name)
80+
return menu
81+
82+
def _create_scenario_selection_menu(self):
83+
"""Create menu for selecting scenarios to add"""
84+
menu = MenuRenderer(self.renderer, columns=3, show_selections=True, render_function=self._render_playlist_details)
85+
86+
# Column 1: Offensive modes
87+
menu.add_element(UIElement("Offensive Mode", header=True), column=0)
88+
for mode in OffensiveMode:
89+
menu.add_element(UIElement(
90+
mode.name.replace('_', ' ').title(),
91+
function=self._set_temp_offensive_mode,
92+
function_args=mode,
93+
chooseable=True,
94+
), column=0)
95+
96+
# Column 2: Defensive modes
97+
menu.add_element(UIElement("Defensive Mode", header=True), column=1)
98+
for mode in DefensiveMode:
99+
menu.add_element(UIElement(
100+
mode.name.replace('_', ' ').title(),
101+
function=self._set_temp_defensive_mode,
102+
function_args=mode,
103+
chooseable=True,
104+
), column=1)
105+
106+
# Column 3: Player role and actions
107+
menu.add_element(UIElement("Player Role", header=True), column=2)
108+
menu.add_element(UIElement("Offense", function=self._set_temp_player_role, function_args=PlayerRole.OFFENSE, chooseable=True), column=2)
109+
menu.add_element(UIElement("Defense", function=self._set_temp_player_role, function_args=PlayerRole.DEFENSE, chooseable=True), column=2)
110+
menu.add_element(UIElement("", header=True), column=2) # Spacer
111+
menu.add_element(UIElement("Add Scenario", function=self._add_current_scenario), column=2)
112+
113+
114+
return menu
115+
116+
def _create_custom_scenario_selection_menu(self):
117+
"""Create menu for selecting custom scenarios to add"""
118+
menu = MenuRenderer(self.renderer, columns=2, show_selections=True, render_function=self._render_playlist_details)
119+
custom_scenarios = get_custom_scenarios()
120+
121+
# Column 1: Custom scenarios
122+
for scenario_name in custom_scenarios:
123+
menu.add_element(UIElement(scenario_name, function=self._add_custom_scenario, function_args=scenario_name))
124+
125+
# Column 2: Player role and actions
126+
return menu
127+
128+
def _create_boost_range_menu(self):
129+
"""Create menu for setting boost range"""
130+
menu = MenuRenderer(self.renderer, columns=2, render_function=self._render_playlist_details)
131+
132+
# Column 1: Min boost
133+
menu.add_element(UIElement("Min Boost", header=True), column=0)
134+
for boost in [0, 12, 20, 30, 40, 50, 60, 70]:
135+
menu.add_element(UIElement(
136+
str(boost),
137+
function=self._set_min_boost,
138+
function_args=boost
139+
), column=0)
140+
141+
# Column 2: Max boost
142+
menu.add_element(UIElement("Max Boost", header=True), column=1)
143+
for boost in [50, 60, 70, 80, 90, 100]:
144+
menu.add_element(UIElement(
145+
str(boost),
146+
function=self._set_max_boost,
147+
function_args=boost
148+
), column=1)
149+
150+
return menu
151+
152+
def _create_timeout_menu(self):
153+
"""Create menu for setting timeout"""
154+
menu = MenuRenderer(self.renderer, columns=1, render_function=self._render_playlist_details)
155+
menu.add_element(UIElement("Set Timeout (seconds)", header=True))
156+
157+
for timeout in [5.0, 7.0, 10.0, 15.0, 20.0, 30.0]:
158+
menu.add_element(UIElement(
159+
f"{timeout}s",
160+
function=self._set_timeout,
161+
function_args=timeout
162+
))
163+
164+
return menu
165+
166+
# Temporary variables for scenario creation
167+
temp_offensive_mode = None
168+
temp_defensive_mode = None
169+
temp_player_role = None
170+
171+
def _set_playlist_name(self, name):
172+
self.current_playlist_name = name
173+
print(f"Playlist name set to: {name}")
174+
175+
def _set_temp_offensive_mode(self, mode):
176+
self.temp_offensive_mode = mode
177+
print(f"Selected offensive mode: {mode.name}")
178+
179+
def _set_temp_defensive_mode(self, mode):
180+
self.temp_defensive_mode = mode
181+
print(f"Selected defensive mode: {mode.name}")
182+
183+
def _set_temp_player_role(self, role):
184+
self.temp_player_role = role
185+
print(f"Selected player role: {role.name}")
186+
187+
def _add_current_scenario(self):
188+
"""Add the currently selected scenario configuration"""
189+
if self.temp_offensive_mode and self.temp_defensive_mode and self.temp_player_role:
190+
scenario = ScenarioConfig(
191+
offensive_mode=self.temp_offensive_mode,
192+
defensive_mode=self.temp_defensive_mode,
193+
player_role=self.temp_player_role
194+
)
195+
self.current_scenarios.append(scenario)
196+
print(f"Added scenario: {self.temp_offensive_mode.name} vs {self.temp_defensive_mode.name} ({self.temp_player_role.name})")
197+
198+
# Reset temp variables
199+
self.temp_offensive_mode = None
200+
self.temp_defensive_mode = None
201+
self.temp_player_role = None
202+
203+
# Exit the submenu
204+
if self.main_menu_renderer:
205+
self.main_menu_renderer.handle_back_key()
206+
else:
207+
print("Please select offensive mode, defensive mode, and player role first")
208+
209+
210+
211+
def _set_min_boost(self, boost):
212+
"""Set minimum boost value"""
213+
self.current_boost_range = (boost, max(boost + 10, self.current_boost_range[1]))
214+
print(f"Set boost range: {self.current_boost_range}")
215+
216+
def _set_max_boost(self, boost):
217+
"""Set maximum boost value"""
218+
self.current_boost_range = (min(boost - 10, self.current_boost_range[0]), boost)
219+
print(f"Set boost range: {self.current_boost_range}")
220+
221+
def _set_timeout(self, timeout):
222+
"""Set scenario timeout"""
223+
self.current_timeout = timeout
224+
print(f"Set timeout: {timeout}s")
225+
226+
def _toggle_rule_zero(self):
227+
"""Toggle rule zero setting"""
228+
self.current_rule_zero = not self.current_rule_zero
229+
print(f"Rule zero: {'ON' if self.current_rule_zero else 'OFF'}")
230+
231+
232+
def _save_current_playlist(self):
233+
"""Save the currently configured playlist to file, and register it in the playlist registry"""
234+
if not self.current_playlist_name:
235+
print("Please set a playlist name first")
236+
return
237+
238+
# Register the playlist in the playlist registry
239+
# and save it to file
240+
playlist = Playlist(
241+
name=self.current_playlist_name,
242+
description=f"Custom playlist with {len(self.current_scenarios)} scenarios",
243+
scenarios=self.current_scenarios.copy(),
244+
custom_scenarios=self.current_custom_scenarios.copy(),
245+
settings=PlaylistSettings(timeout=self.current_timeout, shuffle=True, boost_range=self.current_boost_range, rule_zero=self.current_rule_zero)
246+
)
247+
248+
with open(os.path.join(_get_custom_playlists_path(), f"{self.current_playlist_name}.json"), "w") as f:
249+
f.write(playlist.model_dump_json())
250+
251+
def _cancel_playlist_creation(self):
252+
"""Cancel playlist creation and reset"""
253+
self._reset_current_playlist()
254+
print("Playlist creation cancelled")
255+
256+
def _reset_current_playlist(self):
257+
"""Reset current playlist creation data"""
258+
self.current_playlist_name = ""
259+
self.current_scenarios = []
260+
self.current_custom_scenarios = []
261+
self.current_boost_range = [12, 100]
262+
self.current_timeout = 7.0
263+
self.current_rule_zero = False
264+
self.temp_offensive_mode = None
265+
self.temp_defensive_mode = None
266+
self.temp_player_role = None
267+
268+
def _add_custom_scenario(self, scenario_name):
269+
"""Add a custom scenario"""
270+
self.current_custom_scenarios.append(CustomScenario.load(scenario_name))
271+
print(f"Added custom scenario: {scenario_name}")
272+
273+
def get_custom_playlists(self):
274+
"""Get all custom playlists"""
275+
# Load all custom playlists from disk
276+
custom_playlists = {}
277+
for file in os.listdir(_get_custom_playlists_path()):
278+
if file.endswith(".json"):
279+
with open(os.path.join(_get_custom_playlists_path(), file), "r") as f:
280+
custom_playlists[file.replace(".json", "")] = Playlist.model_validate_json(f.read())
281+
return custom_playlists
282+
283+
284+
def _get_custom_playlists_path():
285+
appdata_path = os.path.expandvars("%APPDATA%")
286+
if not os.path.exists(os.path.join(appdata_path, "RLBot", "Dojo", "Playlists")):
287+
os.makedirs(os.path.join(appdata_path, "RLBot", "Dojo", "Playlists"))
288+
return os.path.join(appdata_path, "RLBot", "Dojo", "Playlists")

0 commit comments

Comments
 (0)