|
| 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 | + ) |
0 commit comments