Skip to content

Commit

Permalink
Merge pull request #17 from NicEastvillage/load_configs
Browse files Browse the repository at this point in the history
Add helper function to load `match.toml` files and clean toml config parsing
  • Loading branch information
VirxEC authored Feb 4, 2025
2 parents f6afeeb + fe54be9 commit 1734669
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 162 deletions.
218 changes: 218 additions & 0 deletions rlbot/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import tomllib
from pathlib import Path
from typing import Any

import rlbot.flat as flat
from rlbot.utils.logging import DEFAULT_LOGGER as logger
from rlbot.utils.os_detector import CURRENT_OS, OS


def __parse_enum(table: dict, key: str, enum: Any, default: int = 0) -> Any:
if key not in table:
return enum(0)
try:
for i in range(100000):
if str(enum(i)).split('.')[-1].lower() == table[key].lower():
return enum(i)
except ValueError:
logger.warning(f"Unknown value '{table[key]}' for key '{key}' using default ({enum(default)})")
return enum(default)


def load_match_config(config_path: Path | str) -> flat.MatchConfiguration:
"""
Reads the match toml file at the provided path and creates the corresponding MatchConfiguration.
"""
config_path = Path(config_path)
with open(config_path, "rb") as f:
config = tomllib.load(f)

rlbot_table = config.get("rlbot", dict())
match_table = config.get("match", dict())
mutator_table = config.get("mutators", dict())

players = []
for car_table in config.get("cars", []):
car_config = car_table.get("config")
name = car_table.get("name", "")
team = car_table.get("team", 0)
try:
team = int(team)
except ValueError:
team = {"blue": 0, "orange": 1}.get(team.lower())
if team is None or team not in [0, 1]:
logger.warning(f"Unknown team '{car_table.get("team")}' for player {len(players)}, using default team 0")

loadout_file = car_table.get("loadout_file")
variant = car_table.get("type", "rlbot")
skill = __parse_enum(car_table, "skill", flat.PsyonixSkill, int(flat.PsyonixSkill.AllStar))
match variant:
case "rlbot":
if car_config is None:
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
players.append(flat.PlayerConfiguration(flat.CustomBot(), name, team, loadout=loadout))
else:
abs_config_path = (config_path.parent / car_config).resolve()
players.append(load_player_config(abs_config_path, flat.CustomBot(), team, name, loadout_file))
case "psyonix":
if car_config is None:
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
players.append(flat.PlayerConfiguration(flat.Psyonix(skill), name, team, loadout=loadout))
else:
abs_config_path = (config_path.parent / car_config).resolve()
players.append(load_player_config(abs_config_path, flat.Psyonix(skill), team, name, loadout_file))
case "human":
loadout = load_player_loadout(loadout_file, team) if loadout_file else None
players.append(flat.PlayerConfiguration(flat.Human(), name, team, loadout=loadout))

scripts = []
for script_table in config.get("scripts", []):
if script_config := script_table.get("config"):
abs_config_path = (config_path.parent / script_config).resolve()
scripts.append(load_script_config(abs_config_path))
else:
scripts.append(flat.ScriptConfiguration())

mutators = flat.MutatorSettings(
match_length=__parse_enum(mutator_table, "match_length", flat.MatchLengthMutator),
max_score=__parse_enum(mutator_table, "max_score", flat.MaxScoreMutator),
multi_ball=__parse_enum(mutator_table, "multi_ball", flat.MultiBallMutator),
overtime=__parse_enum(mutator_table, "overtime", flat.OvertimeMutator),
series_length=__parse_enum(mutator_table, "series_length", flat.SeriesLengthMutator),
game_speed=__parse_enum(mutator_table, "game_speed", flat.GameSpeedMutator),
ball_max_speed=__parse_enum(mutator_table, "ball_max_speed", flat.BallMaxSpeedMutator),
ball_type=__parse_enum(mutator_table, "ball_type", flat.BallTypeMutator),
ball_weight=__parse_enum(mutator_table, "ball_weight", flat.BallWeightMutator),
ball_size=__parse_enum(mutator_table, "ball_size", flat.BallSizeMutator),
ball_bounciness=__parse_enum(mutator_table, "ball_bounciness", flat.BallBouncinessMutator),
boost=__parse_enum(mutator_table, "boost_amount", flat.BoostMutator),
rumble=__parse_enum(mutator_table, "rumble", flat.RumbleMutator),
boost_strength=__parse_enum(mutator_table, "boost_strength", flat.BoostStrengthMutator),
gravity=__parse_enum(mutator_table, "gravity", flat.GravityMutator),
demolish=__parse_enum(mutator_table, "demolish", flat.DemolishMutator),
respawn_time=__parse_enum(mutator_table, "respawn_time", flat.RespawnTimeMutator),
max_time=__parse_enum(mutator_table, "max_time", flat.MaxTimeMutator),
game_event=__parse_enum(mutator_table, "game_event", flat.GameEventMutator),
audio=__parse_enum(mutator_table, "audio", flat.AudioMutator),
)

return flat.MatchConfiguration(
launcher=__parse_enum(rlbot_table, "launcher", flat.Launcher),
launcher_arg=rlbot_table.get("launcher_arg", ""),
auto_start_bots=rlbot_table.get("auto_start_bots", True),
game_map_upk=match_table.get("game_map_upk", ""),
player_configurations=players,
script_configurations=scripts,
game_mode=__parse_enum(match_table, "game_mode", flat.GameMode),
skip_replays=match_table.get("skip_replays", False),
instant_start=match_table.get("instant_start", False),
mutators=mutators,
existing_match_behavior=__parse_enum(match_table, "existing_match_behavior", flat.ExistingMatchBehavior),
enable_rendering=match_table.get("enable_rendering", False),
enable_state_setting=match_table.get("enable_state_setting", False),
freeplay=match_table.get("freeplay", False),
)


def load_player_loadout(path: Path | str, team: int) -> flat.PlayerLoadout:
"""
Reads the loadout toml file at the provided path and extracts the `PlayerLoadout` for the given team.
"""
with open(path, "rb") as f:
config = tomllib.load(f)

loadout = config["blue_loadout"] if team == 0 else config["orange_loadout"]
paint = None
if paint_table := loadout.get("paint", None):
paint = flat.LoadoutPaint(
car_paint_id=paint_table.get("car_paint_id", 0),
decal_paint_id=paint_table.get("decal_paint_id", 0),
wheels_paint_id=paint_table.get("wheels_paint_id", 0),
boost_paint_id=paint_table.get("boost_paint_id", 0),
antenna_paint_id=paint_table.get("antenna_paint_id", 0),
hat_paint_id=paint_table.get("hat_paint_id", 0),
trails_paint_id=paint_table.get("trails_paint_id", 0),
goal_explosion_paint_id=paint_table.get("goal_explosion_paint_id", 0),
)

return flat.PlayerLoadout(
team_color_id=loadout.get("team_color_id", 0),
custom_color_id=loadout.get("custom_color_id", 0),
car_id=loadout.get("car_id", 0),
decal_id=loadout.get("decal_id", 0),
wheels_id=loadout.get("wheels_id", 0),
boost_id=loadout.get("boost_id", 0),
antenna_id=loadout.get("antenna_id", 0),
hat_id=loadout.get("hat_id", 0),
paint_finish_id=loadout.get("paint_finish_id", 0),
custom_finish_id=loadout.get("custom_finish_id", 0),
engine_audio_id=loadout.get("engine_audio_id", 0),
trails_id=loadout.get("trails_id", 0),
goal_explosion_id=loadout.get("goal_explosion_id", 0),
loadout_paint=paint,
)


def load_player_config(
path: Path | str, type: flat.CustomBot | flat.Psyonix, team: int,
name_override: str | None = None, loadout_override: Path | str | None = None,
) -> flat.PlayerConfiguration:
"""
Reads the bot toml file at the provided path and
creates a `PlayerConfiguration` of the given type for the given team.
"""
path = Path(path)
with open(path, "rb") as f:
config = tomllib.load(f)

settings: dict[str, Any] = config["settings"]

root_dir = path.parent.absolute()
if "root_dir" in settings:
root_dir /= Path(settings["root_dir"])

run_command = settings.get("run_command", "")
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
run_command = settings["run_command_linux"]

loadout_path = path.parent / Path(settings["loadout_file"]) if "loadout_file" in settings else loadout_override
loadout = load_player_loadout(loadout_path, team) if loadout_path is not None else None

return flat.PlayerConfiguration(
type,
settings.get("name", name_override or "Unnamed"),
team,
str(root_dir),
str(run_command),
loadout,
0,
settings.get("agent_id", ""),
settings.get("hivemind", False),
)


def load_script_config(path: Path | str) -> flat.ScriptConfiguration:
"""
Reads the script toml file at the provided path and creates a `ScriptConfiguration` from it.
"""
path = Path(path)
with open(path, "rb") as f:
config = tomllib.load(f)

settings: dict[str, Any] = config["settings"]

root_dir = path.parent
if "root_dir" in settings:
root_dir /= Path(settings["root_dir"])

run_command = settings.get("run_command", "")
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
run_command = settings["run_command_linux"]

return flat.ScriptConfiguration(
settings.get("name", "Unnamed"),
str(root_dir),
run_command,
0,
settings.get("agent_id", ""),
)
130 changes: 2 additions & 128 deletions rlbot/managers/match.py
Original file line number Diff line number Diff line change
@@ -1,140 +1,14 @@
import tomllib
from pathlib import Path
from time import sleep
from typing import Any, Optional
from typing import Optional

import psutil

from rlbot import flat
from rlbot.interface import RLBOT_SERVER_IP, RLBOT_SERVER_PORT, SocketRelay
from rlbot.utils import fill_desired_game_state, gateway
from rlbot.utils.logging import DEFAULT_LOGGER
from rlbot.utils.os_detector import CURRENT_OS, MAIN_EXECUTABLE_NAME, OS


def extract_loadout_paint(config: dict[str, Any]) -> flat.LoadoutPaint:
"""
Extracts a `LoadoutPaint` structure from a dictionary.
"""
return flat.LoadoutPaint(
config.get("car_paint_id", 0),
config.get("decal_paint_id", 0),
config.get("wheels_paint_id", 0),
config.get("boost_paint_id", 0),
config.get("antenna_paint_id", 0),
config.get("hat_paint_id", 0),
config.get("trails_paint_id", 0),
config.get("goal_explosion_paint_id", 0),
)


def get_player_loadout(path: str, team: int) -> flat.PlayerLoadout:
"""
Reads the loadout toml file at the provided path and extracts the `PlayerLoadout` for the given team.
"""
with open(path, "rb") as f:
config = tomllib.load(f)

loadout = config["blue_loadout"] if team == 0 else config["orange_loadout"]
paint = loadout.get("paint", None)

return flat.PlayerLoadout(
loadout.get("team_color_id", 0),
loadout.get("custom_color_id", 0),
loadout.get("car_id", 0),
loadout.get("decal_id", 0),
loadout.get("wheels_id", 0),
loadout.get("boost_id", 0),
loadout.get("antenna_id", 0),
loadout.get("hat_id", 0),
loadout.get("paint_finish_id", 0),
loadout.get("custom_finish_id", 0),
loadout.get("engine_audio_id", 0),
loadout.get("trails_id", 0),
loadout.get("goal_explosion_id", 0),
extract_loadout_paint(paint) if paint is not None else None,
)


def get_player_config(
type: flat.CustomBot | flat.Psyonix, team: int, path: Path | str
) -> flat.PlayerConfiguration:
"""
Reads the bot toml file at the provided path and
creates a `PlayerConfiguration` of the given type for the given team.
"""
with open(path, "rb") as f:
config = tomllib.load(f)

match path:
case Path():
parent = path.parent
case _:
parent = Path(path).parent

settings: dict[str, Any] = config["settings"]

root_dir = parent
if "root_dir" in settings:
root_dir /= settings["root_dir"]

run_command = settings.get("run_command", "")
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
run_command = settings["run_command_linux"]

loadout_path = settings.get("loadout_file", None)
if loadout_path is not None:
loadout_path = parent / loadout_path

loadout = (
get_player_loadout(loadout_path, team)
if loadout_path is not None and loadout_path.exists()
else None
)

return flat.PlayerConfiguration(
type,
settings["name"],
team,
str(root_dir),
str(run_command),
loadout,
0,
settings.get("agent_id", ""),
settings.get("hivemind", False),
)


def get_script_config(path: Path | str) -> flat.ScriptConfiguration:
"""
Reads the script toml file at the provided path and creates a `ScriptConfiguration` from it.
"""
with open(path, "rb") as f:
config = tomllib.load(f)

match path:
case Path():
parent = path.parent
case _:
parent = Path(path).parent

settings: dict[str, Any] = config["settings"]

root_dir = parent
if "root_dir" in settings:
root_dir /= settings["root_dir"]

run_command = settings.get("run_command", "")
if CURRENT_OS == OS.LINUX and "run_command_linux" in settings:
run_command = settings["run_command_linux"]

return flat.ScriptConfiguration(
settings["name"],
str(root_dir),
run_command,
0,
settings.get("agent_id", ""),
)
from rlbot.utils.os_detector import MAIN_EXECUTABLE_NAME


class MatchManager:
Expand Down
2 changes: 1 addition & 1 deletion rlbot/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys

DEFAULT_LOGGER_NAME = "rlbot"
DEFAULT_LOGGER = None
DEFAULT_LOGGER = None # Set later

match os.environ.get("RLBOT_LOG_LEVEL"):
case "debug":
Expand Down
2 changes: 1 addition & 1 deletion tests/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ existing_match_behavior = "Restart"
enable_rendering = false
enable_state_setting = true
auto_save_replay = false
# Whether or not to use freeplay instead of an exhibition match
# Whether to use freeplay instead of an exhibition match
freeplay = false

[mutators]
Expand Down
9 changes: 5 additions & 4 deletions tests/read_toml.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from pathlib import Path

from rlbot import flat
from rlbot.managers.match import get_player_config
from rlbot.config import load_match_config

CURRENT_FILE = Path(__file__).parent
DIR = Path(__file__).parent

MATCH_CONFIG_PATH = DIR / "rlbot.toml"

if __name__ == "__main__":
print(get_player_config(flat.CustomBot(), 0, CURRENT_FILE / "necto/bot.toml"))
print(load_match_config(MATCH_CONFIG_PATH))
Loading

0 comments on commit 1734669

Please sign in to comment.