Skip to content

Commit 1734669

Browse files
authored
Merge pull request #17 from NicEastvillage/load_configs
Add helper function to load `match.toml` files and clean toml config parsing
2 parents f6afeeb + fe54be9 commit 1734669

File tree

10 files changed

+255
-162
lines changed

10 files changed

+255
-162
lines changed

rlbot/config.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import tomllib
2+
from pathlib import Path
3+
from typing import Any
4+
5+
import rlbot.flat as flat
6+
from rlbot.utils.logging import DEFAULT_LOGGER as logger
7+
from rlbot.utils.os_detector import CURRENT_OS, OS
8+
9+
10+
def __parse_enum(table: dict, key: str, enum: Any, default: int = 0) -> Any:
11+
if key not in table:
12+
return enum(0)
13+
try:
14+
for i in range(100000):
15+
if str(enum(i)).split('.')[-1].lower() == table[key].lower():
16+
return enum(i)
17+
except ValueError:
18+
logger.warning(f"Unknown value '{table[key]}' for key '{key}' using default ({enum(default)})")
19+
return enum(default)
20+
21+
22+
def load_match_config(config_path: Path | str) -> flat.MatchConfiguration:
23+
"""
24+
Reads the match toml file at the provided path and creates the corresponding MatchConfiguration.
25+
"""
26+
config_path = Path(config_path)
27+
with open(config_path, "rb") as f:
28+
config = tomllib.load(f)
29+
30+
rlbot_table = config.get("rlbot", dict())
31+
match_table = config.get("match", dict())
32+
mutator_table = config.get("mutators", dict())
33+
34+
players = []
35+
for car_table in config.get("cars", []):
36+
car_config = car_table.get("config")
37+
name = car_table.get("name", "")
38+
team = car_table.get("team", 0)
39+
try:
40+
team = int(team)
41+
except ValueError:
42+
team = {"blue": 0, "orange": 1}.get(team.lower())
43+
if team is None or team not in [0, 1]:
44+
logger.warning(f"Unknown team '{car_table.get("team")}' for player {len(players)}, using default team 0")
45+
46+
loadout_file = car_table.get("loadout_file")
47+
variant = car_table.get("type", "rlbot")
48+
skill = __parse_enum(car_table, "skill", flat.PsyonixSkill, int(flat.PsyonixSkill.AllStar))
49+
match variant:
50+
case "rlbot":
51+
if car_config is None:
52+
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
53+
players.append(flat.PlayerConfiguration(flat.CustomBot(), name, team, loadout=loadout))
54+
else:
55+
abs_config_path = (config_path.parent / car_config).resolve()
56+
players.append(load_player_config(abs_config_path, flat.CustomBot(), team, name, loadout_file))
57+
case "psyonix":
58+
if car_config is None:
59+
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
60+
players.append(flat.PlayerConfiguration(flat.Psyonix(skill), name, team, loadout=loadout))
61+
else:
62+
abs_config_path = (config_path.parent / car_config).resolve()
63+
players.append(load_player_config(abs_config_path, flat.Psyonix(skill), team, name, loadout_file))
64+
case "human":
65+
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
66+
players.append(flat.PlayerConfiguration(flat.Human(), name, team, loadout=loadout))
67+
68+
scripts = []
69+
for script_table in config.get("scripts", []):
70+
if script_config := script_table.get("config"):
71+
abs_config_path = (config_path.parent / script_config).resolve()
72+
scripts.append(load_script_config(abs_config_path))
73+
else:
74+
scripts.append(flat.ScriptConfiguration())
75+
76+
mutators = flat.MutatorSettings(
77+
match_length=__parse_enum(mutator_table, "match_length", flat.MatchLengthMutator),
78+
max_score=__parse_enum(mutator_table, "max_score", flat.MaxScoreMutator),
79+
multi_ball=__parse_enum(mutator_table, "multi_ball", flat.MultiBallMutator),
80+
overtime=__parse_enum(mutator_table, "overtime", flat.OvertimeMutator),
81+
series_length=__parse_enum(mutator_table, "series_length", flat.SeriesLengthMutator),
82+
game_speed=__parse_enum(mutator_table, "game_speed", flat.GameSpeedMutator),
83+
ball_max_speed=__parse_enum(mutator_table, "ball_max_speed", flat.BallMaxSpeedMutator),
84+
ball_type=__parse_enum(mutator_table, "ball_type", flat.BallTypeMutator),
85+
ball_weight=__parse_enum(mutator_table, "ball_weight", flat.BallWeightMutator),
86+
ball_size=__parse_enum(mutator_table, "ball_size", flat.BallSizeMutator),
87+
ball_bounciness=__parse_enum(mutator_table, "ball_bounciness", flat.BallBouncinessMutator),
88+
boost=__parse_enum(mutator_table, "boost_amount", flat.BoostMutator),
89+
rumble=__parse_enum(mutator_table, "rumble", flat.RumbleMutator),
90+
boost_strength=__parse_enum(mutator_table, "boost_strength", flat.BoostStrengthMutator),
91+
gravity=__parse_enum(mutator_table, "gravity", flat.GravityMutator),
92+
demolish=__parse_enum(mutator_table, "demolish", flat.DemolishMutator),
93+
respawn_time=__parse_enum(mutator_table, "respawn_time", flat.RespawnTimeMutator),
94+
max_time=__parse_enum(mutator_table, "max_time", flat.MaxTimeMutator),
95+
game_event=__parse_enum(mutator_table, "game_event", flat.GameEventMutator),
96+
audio=__parse_enum(mutator_table, "audio", flat.AudioMutator),
97+
)
98+
99+
return flat.MatchConfiguration(
100+
launcher=__parse_enum(rlbot_table, "launcher", flat.Launcher),
101+
launcher_arg=rlbot_table.get("launcher_arg", ""),
102+
auto_start_bots=rlbot_table.get("auto_start_bots", True),
103+
game_map_upk=match_table.get("game_map_upk", ""),
104+
player_configurations=players,
105+
script_configurations=scripts,
106+
game_mode=__parse_enum(match_table, "game_mode", flat.GameMode),
107+
skip_replays=match_table.get("skip_replays", False),
108+
instant_start=match_table.get("instant_start", False),
109+
mutators=mutators,
110+
existing_match_behavior=__parse_enum(match_table, "existing_match_behavior", flat.ExistingMatchBehavior),
111+
enable_rendering=match_table.get("enable_rendering", False),
112+
enable_state_setting=match_table.get("enable_state_setting", False),
113+
freeplay=match_table.get("freeplay", False),
114+
)
115+
116+
117+
def load_player_loadout(path: Path | str, team: int) -> flat.PlayerLoadout:
118+
"""
119+
Reads the loadout toml file at the provided path and extracts the `PlayerLoadout` for the given team.
120+
"""
121+
with open(path, "rb") as f:
122+
config = tomllib.load(f)
123+
124+
loadout = config["blue_loadout"] if team == 0 else config["orange_loadout"]
125+
paint = None
126+
if paint_table := loadout.get("paint", None):
127+
paint = flat.LoadoutPaint(
128+
car_paint_id=paint_table.get("car_paint_id", 0),
129+
decal_paint_id=paint_table.get("decal_paint_id", 0),
130+
wheels_paint_id=paint_table.get("wheels_paint_id", 0),
131+
boost_paint_id=paint_table.get("boost_paint_id", 0),
132+
antenna_paint_id=paint_table.get("antenna_paint_id", 0),
133+
hat_paint_id=paint_table.get("hat_paint_id", 0),
134+
trails_paint_id=paint_table.get("trails_paint_id", 0),
135+
goal_explosion_paint_id=paint_table.get("goal_explosion_paint_id", 0),
136+
)
137+
138+
return flat.PlayerLoadout(
139+
team_color_id=loadout.get("team_color_id", 0),
140+
custom_color_id=loadout.get("custom_color_id", 0),
141+
car_id=loadout.get("car_id", 0),
142+
decal_id=loadout.get("decal_id", 0),
143+
wheels_id=loadout.get("wheels_id", 0),
144+
boost_id=loadout.get("boost_id", 0),
145+
antenna_id=loadout.get("antenna_id", 0),
146+
hat_id=loadout.get("hat_id", 0),
147+
paint_finish_id=loadout.get("paint_finish_id", 0),
148+
custom_finish_id=loadout.get("custom_finish_id", 0),
149+
engine_audio_id=loadout.get("engine_audio_id", 0),
150+
trails_id=loadout.get("trails_id", 0),
151+
goal_explosion_id=loadout.get("goal_explosion_id", 0),
152+
loadout_paint=paint,
153+
)
154+
155+
156+
def load_player_config(
157+
path: Path | str, type: flat.CustomBot | flat.Psyonix, team: int,
158+
name_override: str | None = None, loadout_override: Path | str | None = None,
159+
) -> flat.PlayerConfiguration:
160+
"""
161+
Reads the bot toml file at the provided path and
162+
creates a `PlayerConfiguration` of the given type for the given team.
163+
"""
164+
path = Path(path)
165+
with open(path, "rb") as f:
166+
config = tomllib.load(f)
167+
168+
settings: dict[str, Any] = config["settings"]
169+
170+
root_dir = path.parent.absolute()
171+
if "root_dir" in settings:
172+
root_dir /= Path(settings["root_dir"])
173+
174+
run_command = settings.get("run_command", "")
175+
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
176+
run_command = settings["run_command_linux"]
177+
178+
loadout_path = path.parent / Path(settings["loadout_file"]) if "loadout_file" in settings else loadout_override
179+
loadout = load_player_loadout(loadout_path, team) if loadout_path is not None else None
180+
181+
return flat.PlayerConfiguration(
182+
type,
183+
settings.get("name", name_override or "Unnamed"),
184+
team,
185+
str(root_dir),
186+
str(run_command),
187+
loadout,
188+
0,
189+
settings.get("agent_id", ""),
190+
settings.get("hivemind", False),
191+
)
192+
193+
194+
def load_script_config(path: Path | str) -> flat.ScriptConfiguration:
195+
"""
196+
Reads the script toml file at the provided path and creates a `ScriptConfiguration` from it.
197+
"""
198+
path = Path(path)
199+
with open(path, "rb") as f:
200+
config = tomllib.load(f)
201+
202+
settings: dict[str, Any] = config["settings"]
203+
204+
root_dir = path.parent
205+
if "root_dir" in settings:
206+
root_dir /= Path(settings["root_dir"])
207+
208+
run_command = settings.get("run_command", "")
209+
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
210+
run_command = settings["run_command_linux"]
211+
212+
return flat.ScriptConfiguration(
213+
settings.get("name", "Unnamed"),
214+
str(root_dir),
215+
run_command,
216+
0,
217+
settings.get("agent_id", ""),
218+
)

rlbot/managers/match.py

Lines changed: 2 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,14 @@
1-
import tomllib
21
from pathlib import Path
32
from time import sleep
4-
from typing import Any, Optional
3+
from typing import Optional
54

65
import psutil
76

87
from rlbot import flat
98
from rlbot.interface import RLBOT_SERVER_IP, RLBOT_SERVER_PORT, SocketRelay
109
from rlbot.utils import fill_desired_game_state, gateway
1110
from rlbot.utils.logging import DEFAULT_LOGGER
12-
from rlbot.utils.os_detector import CURRENT_OS, MAIN_EXECUTABLE_NAME, OS
13-
14-
15-
def extract_loadout_paint(config: dict[str, Any]) -> flat.LoadoutPaint:
16-
"""
17-
Extracts a `LoadoutPaint` structure from a dictionary.
18-
"""
19-
return flat.LoadoutPaint(
20-
config.get("car_paint_id", 0),
21-
config.get("decal_paint_id", 0),
22-
config.get("wheels_paint_id", 0),
23-
config.get("boost_paint_id", 0),
24-
config.get("antenna_paint_id", 0),
25-
config.get("hat_paint_id", 0),
26-
config.get("trails_paint_id", 0),
27-
config.get("goal_explosion_paint_id", 0),
28-
)
29-
30-
31-
def get_player_loadout(path: str, team: int) -> flat.PlayerLoadout:
32-
"""
33-
Reads the loadout toml file at the provided path and extracts the `PlayerLoadout` for the given team.
34-
"""
35-
with open(path, "rb") as f:
36-
config = tomllib.load(f)
37-
38-
loadout = config["blue_loadout"] if team == 0 else config["orange_loadout"]
39-
paint = loadout.get("paint", None)
40-
41-
return flat.PlayerLoadout(
42-
loadout.get("team_color_id", 0),
43-
loadout.get("custom_color_id", 0),
44-
loadout.get("car_id", 0),
45-
loadout.get("decal_id", 0),
46-
loadout.get("wheels_id", 0),
47-
loadout.get("boost_id", 0),
48-
loadout.get("antenna_id", 0),
49-
loadout.get("hat_id", 0),
50-
loadout.get("paint_finish_id", 0),
51-
loadout.get("custom_finish_id", 0),
52-
loadout.get("engine_audio_id", 0),
53-
loadout.get("trails_id", 0),
54-
loadout.get("goal_explosion_id", 0),
55-
extract_loadout_paint(paint) if paint is not None else None,
56-
)
57-
58-
59-
def get_player_config(
60-
type: flat.CustomBot | flat.Psyonix, team: int, path: Path | str
61-
) -> flat.PlayerConfiguration:
62-
"""
63-
Reads the bot toml file at the provided path and
64-
creates a `PlayerConfiguration` of the given type for the given team.
65-
"""
66-
with open(path, "rb") as f:
67-
config = tomllib.load(f)
68-
69-
match path:
70-
case Path():
71-
parent = path.parent
72-
case _:
73-
parent = Path(path).parent
74-
75-
settings: dict[str, Any] = config["settings"]
76-
77-
root_dir = parent
78-
if "root_dir" in settings:
79-
root_dir /= settings["root_dir"]
80-
81-
run_command = settings.get("run_command", "")
82-
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
83-
run_command = settings["run_command_linux"]
84-
85-
loadout_path = settings.get("loadout_file", None)
86-
if loadout_path is not None:
87-
loadout_path = parent / loadout_path
88-
89-
loadout = (
90-
get_player_loadout(loadout_path, team)
91-
if loadout_path is not None and loadout_path.exists()
92-
else None
93-
)
94-
95-
return flat.PlayerConfiguration(
96-
type,
97-
settings["name"],
98-
team,
99-
str(root_dir),
100-
str(run_command),
101-
loadout,
102-
0,
103-
settings.get("agent_id", ""),
104-
settings.get("hivemind", False),
105-
)
106-
107-
108-
def get_script_config(path: Path | str) -> flat.ScriptConfiguration:
109-
"""
110-
Reads the script toml file at the provided path and creates a `ScriptConfiguration` from it.
111-
"""
112-
with open(path, "rb") as f:
113-
config = tomllib.load(f)
114-
115-
match path:
116-
case Path():
117-
parent = path.parent
118-
case _:
119-
parent = Path(path).parent
120-
121-
settings: dict[str, Any] = config["settings"]
122-
123-
root_dir = parent
124-
if "root_dir" in settings:
125-
root_dir /= settings["root_dir"]
126-
127-
run_command = settings.get("run_command", "")
128-
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
129-
run_command = settings["run_command_linux"]
130-
131-
return flat.ScriptConfiguration(
132-
settings["name"],
133-
str(root_dir),
134-
run_command,
135-
0,
136-
settings.get("agent_id", ""),
137-
)
11+
from rlbot.utils.os_detector import MAIN_EXECUTABLE_NAME
13812

13913

14014
class MatchManager:

rlbot/utils/logging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55
DEFAULT_LOGGER_NAME = "rlbot"
6-
DEFAULT_LOGGER = None
6+
DEFAULT_LOGGER = None # Set later
77

88
match os.environ.get("RLBOT_LOG_LEVEL"):
99
case "debug":

tests/default.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ existing_match_behavior = "Restart"
2222
enable_rendering = false
2323
enable_state_setting = true
2424
auto_save_replay = false
25-
# Whether or not to use freeplay instead of an exhibition match
25+
# Whether to use freeplay instead of an exhibition match
2626
freeplay = false
2727

2828
[mutators]

tests/read_toml.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from pathlib import Path
22

3-
from rlbot import flat
4-
from rlbot.managers.match import get_player_config
3+
from rlbot.config import load_match_config
54

6-
CURRENT_FILE = Path(__file__).parent
5+
DIR = Path(__file__).parent
6+
7+
MATCH_CONFIG_PATH = DIR / "rlbot.toml"
78

89
if __name__ == "__main__":
9-
print(get_player_config(flat.CustomBot(), 0, CURRENT_FILE / "necto/bot.toml"))
10+
print(load_match_config(MATCH_CONFIG_PATH))

0 commit comments

Comments
 (0)