You do not have enough teammates to create a team for this challenge.
28 | Recruit teammates from the "teammate" tab or play older matches to earn
29 | more currency
36 | If you get stuck on this message, you're probably having an issue with javascript or connecting to the backend.
37 | If you've disabled javascript in your browser, you may need to re-enable it. If you're not sure,
38 | you can collect some information and ask for help on Discord.
39 | We'll want to know:
40 |
41 |
42 |
What's in the console output? (black window with text)
43 |
Are there any errors in the javascript console? Press F12 and look in the 'console' area.
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/rlbot_gui/match_runner/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/match_runner/__init__.py
--------------------------------------------------------------------------------
/rlbot_gui/match_runner/custom_maps.py:
--------------------------------------------------------------------------------
1 | """
2 | Helpers to load and set up matches in custom maps
3 | """
4 |
5 | from contextlib import contextmanager
6 | from datetime import datetime
7 | from os import path
8 |
9 | from typing import List, Optional
10 |
11 | import glob
12 | import shutil
13 | import os
14 |
15 | from rlbot.setup_manager import (
16 | SetupManager,
17 | RocketLeagueLauncherPreference,
18 | try_get_steam_executable_path,
19 | )
20 | from rlbot.gamelaunch.epic_launch import locate_epic_games_launcher_rocket_league_binary
21 | from rlbot.utils import logging_utils
22 |
23 | from rlbot_gui.persistence.settings import load_settings, BOT_FOLDER_SETTINGS_KEY
24 |
25 | CUSTOM_MAP_TARGET = {"filename": "Labs_Utopia_P.upk", "game_map": "UtopiaRetro"}
26 |
27 | logger = logging_utils.get_logger("custom_maps")
28 |
29 |
30 | @contextmanager
31 | def prepare_custom_map(custom_map_file: str, rl_directory: str):
32 | """
33 | Provides a context manager. It will swap out the custom_map_file
34 | for an existing map in RL and it will return the `game_map`
35 | name that should be used in a MatchConfig.
36 |
37 | Once the context is left, the original map is replaced back.
38 | The context should be left as soon as the match has started
39 | """
40 |
41 | # check if there metadata for the custom file
42 | expected_config_name = "_" + path.basename(custom_map_file)[:-4] + ".cfg"
43 | config_path = path.join(path.dirname(custom_map_file), expected_config_name)
44 | additional_info = {
45 | "original_path": custom_map_file,
46 | }
47 | if path.exists(config_path):
48 | additional_info["config_path"] = config_path
49 |
50 |
51 | real_map_file = path.join(rl_directory, CUSTOM_MAP_TARGET["filename"])
52 | timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
53 | temp_filename = real_map_file + "." + timestamp
54 |
55 | shutil.copy2(real_map_file, temp_filename)
56 | logger.info("Copied real map to %s", temp_filename)
57 | shutil.copy2(custom_map_file, real_map_file)
58 | logger.info("Copied custom map from %s", custom_map_file)
59 |
60 | try:
61 | yield CUSTOM_MAP_TARGET["game_map"], additional_info
62 | finally:
63 | os.replace(temp_filename, real_map_file)
64 | logger.info("Reverted real map to %s", real_map_file)
65 |
66 |
67 | def convert_custom_map_to_path(custom_map: str) -> Optional[str]:
68 | """
69 | Search through user's selected folders to find custom_map
70 | Return none if not found, full path if found
71 | """
72 | custom_map_file = None
73 | folders = get_search_folders()
74 | for folder in folders:
75 | scan_query = path.join(glob.escape(folder), "**", custom_map)
76 | for match in glob.iglob(scan_query, recursive=True):
77 | custom_map_file = match
78 |
79 | if not custom_map_file:
80 | logger.warning("%s - map doesn't exist", custom_map)
81 |
82 | return custom_map_file
83 |
84 |
85 | def find_all_custom_maps() -> List[str]:
86 | """
87 | Ignores maps starting with _
88 | """
89 | folders = get_search_folders()
90 | maps = []
91 | for folder in folders:
92 | scan_query = path.join(glob.escape(folder), "**", "*.u[pd]k")
93 | for match in glob.iglob(scan_query, recursive=True):
94 | basename = path.basename(match)
95 | if basename.startswith("_"):
96 | continue
97 | maps.append(basename)
98 | return maps
99 |
100 |
101 | def get_search_folders() -> List[str]:
102 | """Get all folders to search for maps"""
103 | bot_folders_setting = load_settings().value(BOT_FOLDER_SETTINGS_KEY, type=dict)
104 | folders = {}
105 | if "folders" in bot_folders_setting:
106 | folders = bot_folders_setting["folders"]
107 | return [k for k, v in folders.items() if v['visible']]
108 |
109 |
110 | def identify_map_directory(launcher_pref: RocketLeagueLauncherPreference):
111 | """Find RocketLeague map directory"""
112 | final_path = None
113 | if launcher_pref.preferred_launcher == RocketLeagueLauncherPreference.STEAM:
114 | steam = try_get_steam_executable_path()
115 | suffix = r"steamapps\common\rocketleague\TAGame\CookedPCConsole"
116 | if not steam:
117 | return None
118 |
119 | # TODO: Steam can install RL on a different disk. Need to
120 | # read libraryfolders.vdf to detect this situation
121 | # It's a human-readable but custom format so not trivial to parse
122 |
123 | final_path = path.join(path.dirname(steam), suffix)
124 | else:
125 | rl_executable = locate_epic_games_launcher_rocket_league_binary()
126 | suffix = r"TAGame\CookedPCConsole"
127 | if not rl_executable:
128 | return None
129 |
130 | # Binaries/Win64/ is what we want to strip off
131 | final_path = path.join(path.dirname(rl_executable), "..", "..", suffix)
132 |
133 | if not path.exists(final_path):
134 | logger.warning("%s - directory doesn't exist", final_path)
135 | return None
136 | return final_path
137 |
--------------------------------------------------------------------------------
/rlbot_gui/persistence/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/persistence/__init__.py
--------------------------------------------------------------------------------
/rlbot_gui/persistence/settings.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import QSettings
2 | from rlbot.setup_manager import DEFAULT_LAUNCHER_PREFERENCE, RocketLeagueLauncherPreference
3 |
4 | BOT_FOLDER_SETTINGS_KEY = 'bot_folder_settings'
5 | MATCH_SETTINGS_KEY = 'match_settings'
6 | LAUNCHER_SETTINGS_KEY = 'launcher_settings'
7 | TEAM_SETTINGS_KEY = 'team_settings'
8 | FAVORITES_KEY = 'favorite_runnables'
9 |
10 |
11 | def load_settings() -> QSettings:
12 | return QSettings('rlbotgui', 'preferences')
13 |
14 |
15 | def launcher_preferences_from_map(launcher_preference_map: dict) -> RocketLeagueLauncherPreference:
16 | exe_path_key = 'rocket_league_exe_path'
17 | exe_path = None
18 | if exe_path_key in launcher_preference_map:
19 | exe_path = launcher_preference_map[exe_path_key]
20 | if not exe_path:
21 | exe_path = None # Don't pass in an empty string, pass None instead so it will be ignored.
22 |
23 | return RocketLeagueLauncherPreference(
24 | launcher_preference_map['preferred_launcher'],
25 | use_login_tricks=True, # Epic launch now ONLY works with login tricks
26 | rocket_league_exe_path=exe_path,
27 | )
28 |
29 |
30 | def load_launcher_settings():
31 | settings = load_settings()
32 | launcher_settings = settings.value(LAUNCHER_SETTINGS_KEY, type=dict)
33 | return launcher_settings if launcher_settings else DEFAULT_LAUNCHER_PREFERENCE.__dict__
34 |
--------------------------------------------------------------------------------
/rlbot_gui/story/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/story/__init__.py
--------------------------------------------------------------------------------
/rlbot_gui/story/bots-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "psyonix-pro": {
3 | "name": "Psyonix Pro",
4 | "type": "psyonix",
5 | "skill": 0.5
6 | },
7 | "psyonix-allstar-name": {
8 | "name": "Psyonix Allstar",
9 | "type": "psyonix",
10 | "skill": 1
11 | },
12 | "adversity": {
13 | "name": "AdversityBot",
14 | "type": "rlbot",
15 | "path": ["$RLBOTPACKROOT", "RLBotPack", "ReliefBotFamily", "README", "adversity_bot.cfg"]
16 | },
17 | "airbud": {
18 | "name": "Airbud",
19 | "type": "rlbot",
20 | "path": ["$RLBOTPACKROOT", "RLBotPack", "ReliefBotFamily", "README", "air_bud.cfg"]
21 | },
22 | "reliefbot": {
23 | "name": "ReliefBot",
24 | "type": "rlbot",
25 | "path": ["$RLBOTPACKROOT", "RLBotPack", "ReliefBotFamily", "README", "relief_bot.cfg"]
26 | },
27 | "sdc": {
28 | "name": "Self-Driving Car",
29 | "type": "rlbot",
30 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Self-driving car", "self-driving-car.cfg"]
31 | },
32 | "kamael": {
33 | "name": "Kamael",
34 | "type": "rlbot",
35 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Kamael_family", "Kamael.cfg"]
36 | },
37 | "botimus": {
38 | "name": "BotimusPrime",
39 | "type": "rlbot",
40 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Botimus&Bumblebee", "botimus.cfg"]
41 | },
42 | "bumblebee": {
43 | "name": "Bumblebee",
44 | "type": "rlbot",
45 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Botimus&Bumblebee", "bumblebee.cfg"]
46 | },
47 | "baf": {
48 | "name": "Flying Panda",
49 | "type": "rlbot",
50 | "path": ["$RLBOTPACKROOT", "RLBotPack", "blind_and_deaf", "_story_mode_bot.cfg"]
51 | },
52 | "tbd": {
53 | "name": "Psyonix Allstar",
54 | "type": "psyonix",
55 | "skill": 1
56 | },
57 | "skybot": {
58 | "name": "Skybot",
59 | "type": "rlbot",
60 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Skybot", "SkyBot.cfg"]
61 | },
62 | "wildfire": {
63 | "name": "Wildfire",
64 | "type": "rlbot",
65 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Wildfire_Lightfall_Fix", "python", "wildfire.cfg"]
66 | },
67 | "diablo": {
68 | "name": "Diablo",
69 | "type": "rlbot",
70 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Diablo", "diablo.cfg"]
71 | },
72 | "rashbot": {
73 | "name": "rashBot",
74 | "type": "rlbot",
75 | "path": ["$RLBOTPACKROOT", "RLBotPack", "MarvinBots", "rashBot", "rashBot.cfg"]
76 | },
77 | "stick": {
78 | "name": "Stick",
79 | "type": "rlbot",
80 | "path": ["$RLBOTPACKROOT", "RLBotPack", "MarvinBots", "Stick", "stick.cfg"]
81 | },
82 | "leaf": {
83 | "name": "Leaf",
84 | "type": "rlbot",
85 | "path": ["$RLBOTPACKROOT", "RLBotPack", "MarvinBots", "Leaf", "leaf.cfg"]
86 | },
87 | "lanfear": {
88 | "name": "Lanfear",
89 | "type": "rlbot",
90 | "path": ["$RLBOTPACKROOT", "RLBotPack", "The Forsaken", "Lanfear.cfg"]
91 | },
92 | "phoenix": {
93 | "name": "Phoenix",
94 | "type": "rlbot",
95 | "path": ["$RLBOTPACKROOT", "RLBotPack", "PhoenixCS", "phoenix.cfg"]
96 | },
97 | "broccoli": {
98 | "name": "BroccoliBot",
99 | "type": "rlbot",
100 | "path": ["$RLBOTPACKROOT", "RLBotPack", "BroccoliBot", "BroccoliBot.cfg"]
101 | },
102 | "bribble": {
103 | "name": "Bribblebot",
104 | "type": "rlbot",
105 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Bribblebot", "bot.cfg"]
106 | },
107 | "atlas": {
108 | "name": "Atlas",
109 | "type": "rlbot",
110 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Atlas_Wintertide_Patch", "AtlasAgent", "Atlas.cfg"]
111 | },
112 | "sniper": {
113 | "name": "Sniper",
114 | "type": "rlbot",
115 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Sniper", "sniper.cfg"]
116 | },
117 | "snek": {
118 | "name": "Snek",
119 | "type": "rlbot",
120 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Snek", "snek.cfg"]
121 | },
122 | "nombot": {
123 | "name": "NomBot",
124 | "type": "rlbot",
125 | "path": ["$RLBOTPACKROOT", "RLBotPack", "DomNomNom", "NomBot_v1.0", "NomBot_v1.cfg"]
126 | },
127 | "beast": {
128 | "name": "Beast from the East",
129 | "type": "rlbot",
130 | "path": ["$RLBOTPACKROOT", "RLBotPack", "beastbot", "beastbot.cfg"]
131 | },
132 | "noobblue": {
133 | "name": "Noob Blue",
134 | "type": "rlbot",
135 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Noob_Blue", "src", "bot.cfg"]
136 | },
137 | "monkey": {
138 | "name": "Monkey",
139 | "type": "rlbot",
140 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Monkey", "bot.cfg"]
141 | },
142 | "cryo": {
143 | "name": "Codename Cryo",
144 | "type": "rlbot",
145 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Codename_Cryo", "Codename_Cryo.cfg"]
146 | },
147 | "penguin": {
148 | "name": "PenguinBot",
149 | "type": "rlbot",
150 | "path": ["$RLBOTPACKROOT", "RLBotPack", "PenguinBot", "penguin_config.cfg"]
151 | },
152 | "peter": {
153 | "name": "St. Peter",
154 | "type": "rlbot",
155 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Kamael_family", "peter.cfg"]
156 | },
157 | "oneborg": {
158 | "name": "Oneborg",
159 | "type": "rlbot",
160 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Oneborg", "src", "bot.cfg"]
161 | },
162 | "invisibot": {
163 | "name": "Invisibot",
164 | "type": "rlbot",
165 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Invisibot", "src", "invisibot.cfg"]
166 | },
167 | "molten": {
168 | "name": "Molten",
169 | "type": "rlbot",
170 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Molten", "bot.cfg"]
171 | },
172 | "king": {
173 | "name": "king.",
174 | "type": "rlbot",
175 | "path": ["$RLBOTPACKROOT", "RLBotPack", "King", "King.cfg"]
176 | },
177 | "element": {
178 | "name": "Element",
179 | "type": "rlbot",
180 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Element", "bot.cfg"]
181 | },
182 | "nexto": {
183 | "name": "Nexto",
184 | "type": "rlbot",
185 | "path": ["$RLBOTPACKROOT", "RLBotPack", "Necto", "Nexto", "bot.cfg"]
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/rlbot_gui/story/load_story_descriptions.py:
--------------------------------------------------------------------------------
1 | from functools import lru_cache
2 | from os import path
3 | from copy import deepcopy
4 |
5 | import json
6 |
7 | @lru_cache(maxsize=8)
8 | def read_json(filepath):
9 | with open(filepath) as fh:
10 | return json.load(fh)
11 |
12 |
13 | def story_id_to_file(story_id):
14 | if isinstance(story_id, str):
15 | filepath = path.join(path.dirname(__file__), f"story-{story_id}.json")
16 | else:
17 | # custom story
18 | filepath = story_id["storyPath"]
19 |
20 | return filepath
21 |
22 |
23 | def get_story_config(story_id):
24 | specific_challenges_file = story_id_to_file(story_id)
25 | return read_json(specific_challenges_file)
26 |
27 |
28 | def get_cities(story_id):
29 | """
30 | Get the challenges file specified by the story_id
31 | """
32 | return get_story_config(story_id)["cities"]
33 |
34 |
35 | def get_story_settings(story_id):
36 | """
37 | Return the settings associated with this story config
38 | """
39 | config = get_story_config(story_id)
40 | return config.get("settings", {})
41 |
42 |
43 | def get_challenges_by_id(story_id):
44 | cities = get_cities(story_id)
45 | challenges_by_id = { }
46 | for city_id, city in cities.items():
47 | for challenge in city["challenges"]:
48 | challenges_by_id[challenge["id"]] = deepcopy(challenge)
49 | challenges_by_id[challenge["id"]]["city_description"] = city["description"]
50 | return challenges_by_id
51 |
52 |
53 | def get_bots_configs(story_id):
54 | """
55 | Get the base bots config and merge it with the bots in the
56 | story config
57 | """
58 | specific_bots_file = story_id_to_file(story_id)
59 | base_bots_file = path.join(path.dirname(__file__), f"bots-base.json")
60 |
61 | bots: dict = read_json(base_bots_file)
62 | if path.exists(specific_bots_file):
63 | bots.update(read_json(specific_bots_file)["bots"])
64 |
65 | return bots
66 |
67 |
68 | def get_scripts_configs(story_id):
69 | """
70 | Get the scripts in the story config
71 | """
72 | specific_scripts_file = story_id_to_file(story_id)
73 |
74 | scripts: dict = {}
75 | if path.exists(specific_scripts_file):
76 | specific_scripts_file_json = read_json(specific_scripts_file)
77 | if "scripts" in specific_scripts_file_json: # A "scripts" field is optional
78 | scripts.update(specific_scripts_file_json["scripts"])
79 |
80 | return scripts
81 |
--------------------------------------------------------------------------------
/rlbot_gui/story/story-default-with-cmaps.json:
--------------------------------------------------------------------------------
1 | {
2 | "settings": {
3 | "min_map_pack_revision": 3
4 | },
5 | "bots": { },
6 | "cities": {
7 | "INTRO": {
8 | "description": {
9 | "message": "Shoddy field for shoddy players. No boost available.",
10 | "prereqs": []
11 | },
12 | "challenges": [
13 | {
14 | "id": "INTRO-1",
15 | "humanTeamSize": 1,
16 | "opponentBots": ["skybot"],
17 | "max_score": "3 Goals",
18 | "map": "BeckwithPark",
19 | "disabledBoost": true,
20 | "display": "Win something so we know you can drive"
21 | }
22 | ]
23 | },
24 | "TRYHARD": {
25 | "description": {
26 | "message": "Place to start making your name! But know that everyone else is trying to do the same!",
27 | "prereqs": ["INTRO"],
28 | "color": 16
29 | },
30 | "challenges": [
31 | {
32 | "id": "TRYHARD-1",
33 | "humanTeamSize": 2,
34 | "opponentBots": ["nombot", "beast"],
35 | "map": "UrbanCentral",
36 | "display": "Beat local up-and-comers in a 2v2."
37 | },
38 | {
39 | "id": "TRYHARD-2",
40 | "humanTeamSize": 2,
41 | "opponentBots": ["penguin", "beast"],
42 | "completionConditions": {
43 | "win": true,
44 | "scoreDifference": 3
45 | },
46 | "map": "UrbanCentral_Dawn",
47 | "display": "Upgrade your reputation by beating your opponents by 3 or more goals"
48 | }
49 | ]
50 | },
51 | "PBOOST": {
52 | "description": {
53 | "message": "This city is usually going through a storm. Legend says, Boost is how they have managed to prosper despite those conditions.",
54 | "prereqs": ["INTRO"],
55 | "color": 32
56 | },
57 | "challenges": [
58 | {
59 | "id": "PBOOST-1",
60 | "humanTeamSize": 2,
61 | "opponentBots": ["cryo", "cryo", "cryo"],
62 | "map": "UtopiaColiseum_Snowy",
63 | "display": "Bundle up to survive the deep freeze"
64 | },
65 | {
66 | "id": "PBOOST-2",
67 | "humanTeamSize": 2,
68 | "opponentBots": ["rashbot", "stick", "leaf"],
69 | "map": "Mannfield_Stormy",
70 | "display": "2v3! Get through this storm of Marvin bots!"
71 | }
72 | ]
73 | },
74 | "WASTELAND": {
75 | "description": {
76 | "message": "Don't expect politeness here. Home of the demo experts!",
77 | "prereqs": ["TRYHARD", "PBOOST"],
78 | "color": 36
79 | },
80 | "challenges": [
81 | {
82 | "id": "WASTELAND-1",
83 | "humanTeamSize": 2,
84 | "opponentBots": ["adversity", "adversity"],
85 | "completionConditions": {
86 | "win": true,
87 | "selfDemoCount": 0
88 | },
89 | "map": "Wasteland",
90 | "display": "Win without getting demoed the whole game"
91 | },
92 | {
93 | "id": "WASTELAND-2",
94 | "humanTeamSize": 3,
95 | "opponentBots": ["adversity", "diablo", "wildfire"],
96 | "completionConditions": {
97 | "win": true,
98 | "demoAchievedCount": 2
99 | },
100 | "map": "Wasteland",
101 | "display": "Win and get at least two demos"
102 | }
103 | ]
104 | },
105 | "CAMPANDSNIPE": {
106 | "description": {
107 | "message": "This city has a different feel. Sometimes the ball is possesed, other times its the cars. Be careful, you may be next!",
108 | "prereqs": ["TRYHARD", "PBOOST"],
109 | "color": 39
110 | },
111 | "challenges": [
112 | {
113 | "id": "CS-1",
114 | "humanTeamSize": 2,
115 | "opponentBots": ["adversity", "baf"],
116 | "limitations": ["half-field"],
117 | "map": "AquaDome",
118 | "display": "The ball is moving a lot faster! Win this heatseeker match!"
119 | },
120 | {
121 | "id": "CS-2",
122 | "humanTeamSize": 1,
123 | "opponentBots": ["kamael", "baf"],
124 | "limitations": ["half-field"],
125 | "completionConditions": {
126 | "goalsScored": 1
127 | },
128 | "map": "AquaDome",
129 | "display": "You are making the saves alone! Win heatseeker 1v2!"
130 | },
131 | {
132 | "id": "CS-4",
133 | "humanTeamSize": 2,
134 | "opponentBots": ["invisibot", "invisibot", "peter"],
135 | "map": "NeoTokyo",
136 | "display": "Wait where did that car go?"
137 | },
138 | {
139 | "id": "CS-3",
140 | "humanTeamSize": 2,
141 | "opponentBots": ["sniper", "sniper", "nombot"],
142 | "max_score": "3 Goals",
143 | "map": "NeoTokyo",
144 | "display": "There's something unnatural about these bots..."
145 | },
146 | {
147 | "id": "CS-5",
148 | "humanTeamSize": 1,
149 | "opponentBots": ["snek", "peter"],
150 | "max_score": "3 Goals",
151 | "map": "NeoTokyo",
152 | "display": "There's something unnatural-er about these bots"
153 | }
154 | ]
155 | },
156 | "CHAMPIONSIAN": {
157 | "description": {
158 | "message": "You have made it far but this is the next level. The odds are stacked against you but if you win here, you will be the Champion of this world.",
159 | "prereqs": ["WASTELAND", "CAMPANDSNIPE"],
160 | "color": 69
161 | },
162 | "challenges": [
163 | {
164 | "id": "CHAMP-1",
165 | "humanTeamSize": 3,
166 | "opponentBots": ["airbud", "reliefbot", "adversity"],
167 | "map": "ChampionsField",
168 | "display": "Take on the Relief family in a 3v3!"
169 | },
170 | {
171 | "id": "CHAMP-2",
172 | "humanTeamSize": 2,
173 | "opponentBots": ["botimus", "sdc", "kamael"],
174 | "map": "ChampionsField_Day",
175 | "display": "Things are getting unfair. Can you handle a 2v3 against some of the best solo players?"
176 | },
177 | {
178 | "id": "CHAMP-5",
179 | "humanTeamSize": 2,
180 | "opponentBots": ["snek", "sniper", "invisibot"],
181 | "map": "ChampionsField_Day",
182 | "display": "These masters of space and time have united to stop you!"
183 | },
184 | {
185 | "id": "CHAMP-3",
186 | "humanTeamSize": 3,
187 | "opponentBots": ["kamael", "diablo", "lanfear", "phoenix", "atlas"],
188 | "map": "ChampionsField_Day",
189 | "display": "These are mythical and magical cars. Can you take them on in a 3v5?"
190 | },
191 | {
192 | "id": "CHAMP-4",
193 | "humanTeamSize": 1,
194 | "opponentBots": ["bumblebee", "bumblebee", "bumblebee"],
195 | "map": "ChampionsField",
196 | "display": "1v3! Time to face the beehive."
197 | }
198 | ]
199 | }
200 | }
201 | }
--------------------------------------------------------------------------------
/rlbot_gui/story/story-easy-with-cmaps.json:
--------------------------------------------------------------------------------
1 | {
2 | "settings": {
3 | "min_map_pack_revision": 3
4 | },
5 | "bots": { },
6 | "cities": {
7 | "INTRO": {
8 | "description": {
9 | "message": "Shoddy field for shoddy players. No boost available.",
10 | "prereqs": []
11 | },
12 | "challenges": [
13 | {
14 | "id": "INTRO-1",
15 | "humanTeamSize": 1,
16 | "opponentBots": ["skybot"],
17 | "max_score": "3 Goals",
18 | "map": "BeckwithPark",
19 | "disabledBoost": true,
20 | "display": "Win something so we know you can drive"
21 | }
22 | ]
23 | },
24 | "TRYHARD": {
25 | "description": {
26 | "message": "Place to start making your name! But know that everyone else is trying to do the same!",
27 | "prereqs": ["INTRO"],
28 | "color": 16
29 | },
30 | "challenges": [
31 | {
32 | "id": "TRYHARD-1",
33 | "humanTeamSize": 2,
34 | "opponentBots": ["nombot", "beast"],
35 | "map": "UrbanCentral",
36 | "display": "Beat local up-and-comers in a 2v2."
37 | },
38 | {
39 | "id": "TRYHARD-2",
40 | "humanTeamSize": 2,
41 | "opponentBots": ["penguin", "beast"],
42 | "completionConditions": {
43 | "win": true,
44 | "scoreDifference": 3
45 | },
46 | "map": "UrbanCentral_Dawn",
47 | "display": "Upgrade your reputation by beating your opponents by 3 or more goals"
48 | }
49 | ]
50 | },
51 | "PBOOST": {
52 | "description": {
53 | "message": "This city is usually going through a storm. Legend says, Boost is how they have managed to prosper despite those conditions.",
54 | "prereqs": ["INTRO"],
55 | "color": 32
56 | },
57 | "challenges": [
58 | {
59 | "id": "PBOOST-2",
60 | "humanTeamSize": 2,
61 | "opponentBots": ["rashbot", "stick", "leaf"],
62 | "map": "Mannfield_Stormy",
63 | "display": "2v3! Get through this storm of Marvin bots!"
64 | },
65 | {
66 | "id": "PBOOST-1",
67 | "humanTeamSize": 3,
68 | "opponentBots": ["cryo", "cryo", "cryo"],
69 | "map": "UtopiaColiseum_Snowy",
70 | "display": "Bundle up to survive the deep freeze"
71 | }
72 | ]
73 | },
74 | "WASTELAND": {
75 | "description": {
76 | "message": "Don't expect politeness here. Home of the demo experts!",
77 | "prereqs": ["TRYHARD", "PBOOST"],
78 | "color": 36
79 | },
80 | "challenges": [
81 | {
82 | "id": "WASTELAND-1",
83 | "humanTeamSize": 2,
84 | "opponentBots": ["adversity", "wildfire"],
85 | "completionConditions": {
86 | "win": true,
87 | "selfDemoCount": 1
88 | },
89 | "map": "Wasteland",
90 | "display": "Win without getting demoed more than once"
91 | },
92 | {
93 | "id": "WASTELAND-2",
94 | "humanTeamSize": 3,
95 | "opponentBots": ["adversity", "diablo", "wildfire"],
96 | "completionConditions": {
97 | "win": true,
98 | "demoAchievedCount": 2
99 | },
100 | "map": "Wasteland",
101 | "display": "Win and get at least two demos"
102 | }
103 | ]
104 | },
105 | "CAMPANDSNIPE": {
106 | "description": {
107 | "message": "This city has a different feel. Sometimes the ball is possesed, other times its the cars. Be careful, you may be next!",
108 | "prereqs": ["TRYHARD", "PBOOST"],
109 | "color": 39
110 | },
111 | "challenges": [
112 | {
113 | "id": "CS-1",
114 | "humanTeamSize": 2,
115 | "opponentBots": ["kamael", "baf"],
116 | "limitations": ["half-field"],
117 | "map": "AquaDome",
118 | "display": "The ball is moving a lot faster! Win this heatseeker match!"
119 | },
120 | {
121 | "id": "CS-4",
122 | "humanTeamSize": 2,
123 | "opponentBots": ["invisibot", "invisibot", "peter"],
124 | "map": "NeoTokyo",
125 | "display": "Wait where did that car go?"
126 | },
127 | {
128 | "id": "CS-3",
129 | "humanTeamSize": 2,
130 | "opponentBots": ["sniper", "sniper", "nombot"],
131 | "max_score": "3 Goals",
132 | "map": "NeoTokyo",
133 | "display": "There's something unnatural about these bots..."
134 | },
135 | {
136 | "id": "CS-5",
137 | "humanTeamSize": 2,
138 | "opponentBots": ["snek", "snek"],
139 | "max_score": "3 Goals",
140 | "map": "NeoTokyo",
141 | "display": "There's something unnatural-er about these bots"
142 | }
143 | ]
144 | },
145 | "CHAMPIONSIAN": {
146 | "description": {
147 | "message": "You have made it far but this is the next level. The odds are stacked against you but if you win here, you will be the Champion of this world.",
148 | "prereqs": ["WASTELAND", "CAMPANDSNIPE"],
149 | "color": 69
150 | },
151 | "challenges": [
152 | {
153 | "id": "CHAMP-1",
154 | "humanTeamSize": 3,
155 | "opponentBots": ["airbud", "reliefbot", "adversity"],
156 | "map": "ChampionsField",
157 | "display": "Take on the Relief family in a 3v3!"
158 | },
159 | {
160 | "id": "CHAMP-2",
161 | "humanTeamSize": 3,
162 | "opponentBots": ["botimus", "sdc", "kamael"],
163 | "map": "ChampionsField_Day",
164 | "display": "Take on a crew of some of the best solo players!"
165 | },
166 | {
167 | "id": "CHAMP-5",
168 | "humanTeamSize": 2,
169 | "opponentBots": ["snek", "sniper", "invisibot"],
170 | "map": "ChampionsField_Day",
171 | "display": "These masters of space and time have united to stop you!"
172 | },
173 | {
174 | "id": "CHAMP-3",
175 | "humanTeamSize": 3,
176 | "opponentBots": ["diablo", "lanfear", "phoenix", "atlas"],
177 | "map": "ChampionsField_Day",
178 | "display": "These are mythical and magical cars. Can you take them on in a 3v4?"
179 | },
180 | {
181 | "id": "CHAMP-4",
182 | "humanTeamSize": 3,
183 | "opponentBots": ["bumblebee", "bumblebee", "bumblebee"],
184 | "map": "ChampionsField",
185 | "display": "Time to face the beehive and prove yourself."
186 | }
187 | ]
188 | }
189 | }
190 | }
--------------------------------------------------------------------------------
/rlbot_gui/story/story-easy.json:
--------------------------------------------------------------------------------
1 | {
2 | "bots": { },
3 | "cities": {
4 | "INTRO": {
5 | "description": {
6 | "message": "Shoddy field for shoddy players. No boost available.",
7 | "prereqs": []
8 | },
9 | "challenges": [
10 | {
11 | "id": "INTRO-1",
12 | "humanTeamSize": 1,
13 | "opponentBots": ["skybot"],
14 | "max_score": "3 Goals",
15 | "map": "BeckwithPark",
16 | "disabledBoost": true,
17 | "display": "Win something so we know you can drive"
18 | }
19 | ]
20 | },
21 | "TRYHARD": {
22 | "description": {
23 | "message": "Place to start making your name! But know that everyone else is trying to do the same!",
24 | "prereqs": ["INTRO"],
25 | "color": 16
26 | },
27 | "challenges": [
28 | {
29 | "id": "TRYHARD-1",
30 | "optional": true,
31 | "humanTeamSize": 2,
32 | "opponentBots": ["nombot", "beast"],
33 | "map": "UrbanCentral",
34 | "display": "Beat local up-and-comers in a 2v2."
35 | },
36 | {
37 | "id": "TRYHARD-2",
38 | "optional": true,
39 | "humanTeamSize": 2,
40 | "opponentBots": ["penguin", "beast"],
41 | "completionConditions": {
42 | "win": true,
43 | "scoreDifference": 3
44 | },
45 | "map": "UrbanCentral_Dawn",
46 | "display": "Upgrade your reputation by beating your opponents by 3 or more goals"
47 | }
48 | ]
49 | },
50 | "PBOOST": {
51 | "description": {
52 | "message": "This city is usually going through a storm. Legend says, Boost is how they have managed to prosper despite those conditions.",
53 | "prereqs": ["INTRO"],
54 | "color": 32
55 | },
56 | "challenges": [
57 | {
58 | "id": "PBOOST-2",
59 | "humanTeamSize": 2,
60 | "opponentBots": ["rashbot", "stick", "leaf"],
61 | "map": "Mannfield_Stormy",
62 | "display": "2v3! Get through this storm of Marvin bots!"
63 | },
64 | {
65 | "id": "PBOOST-1",
66 | "humanTeamSize": 3,
67 | "opponentBots": ["cryo", "cryo", "cryo"],
68 | "map": "UtopiaColiseum_Snowy",
69 | "display": "Bundle up to survive the deep freeze"
70 | }
71 | ]
72 | },
73 | "WASTELAND": {
74 | "description": {
75 | "message": "Don't expect politeness here. Home of the demo experts!",
76 | "prereqs": ["TRYHARD", "PBOOST"],
77 | "color": 36
78 | },
79 | "challenges": [
80 | {
81 | "id": "WASTELAND-1",
82 | "humanTeamSize": 2,
83 | "opponentBots": ["adversity", "wildfire"],
84 | "completionConditions": {
85 | "win": true,
86 | "selfDemoCount": 1
87 | },
88 | "map": "Wasteland",
89 | "display": "Win without getting demoed more than once"
90 | },
91 | {
92 | "id": "WASTELAND-2",
93 | "humanTeamSize": 3,
94 | "opponentBots": ["adversity", "beast", "wildfire"],
95 | "completionConditions": {
96 | "win": true,
97 | "demoAchievedCount": 2
98 | },
99 | "map": "Wasteland",
100 | "display": "Win and get at least two demos"
101 | }
102 | ]
103 | },
104 | "CAMPANDSNIPE": {
105 | "description": {
106 | "message": "This city has a different feel. Sometimes the ball is possesed, other times its the cars. Be careful, you may be next!",
107 | "prereqs": ["TRYHARD", "PBOOST"],
108 | "color": 39
109 | },
110 | "challenges": [
111 | {
112 | "id": "CS-1",
113 | "humanTeamSize": 2,
114 | "opponentBots": ["kamael", "baf"],
115 | "limitations": ["half-field"],
116 | "map": "AquaDome",
117 | "display": "The ball is moving a lot faster! Win this heatseeker match!"
118 | },
119 | {
120 | "id": "CS-3",
121 | "humanTeamSize": 2,
122 | "opponentBots": ["sniper", "sniper", "nombot"],
123 | "max_score": "3 Goals",
124 | "map": "NeoTokyo",
125 | "display": "There's something unnatural about these bots..."
126 | },
127 | {
128 | "id": "CS-5",
129 | "humanTeamSize": 2,
130 | "opponentBots": ["snek", "snek"],
131 | "max_score": "3 Goals",
132 | "map": "NeoTokyo",
133 | "display": "There's something unnatural-er about these bots"
134 | }
135 | ]
136 | },
137 | "CHAMPIONSIAN": {
138 | "description": {
139 | "message": "You have made it far but this is the next level. The odds are stacked against you but if you win here, you will be the Champion of this world.",
140 | "prereqs": ["WASTELAND", "CAMPANDSNIPE"],
141 | "color": 69
142 | },
143 | "challenges": [
144 | {
145 | "id": "CHAMP-1",
146 | "humanTeamSize": 3,
147 | "opponentBots": ["airbud", "reliefbot", "adversity"],
148 | "map": "ChampionsField",
149 | "display": "Take on the Relief family in a 3v3!"
150 | },
151 | {
152 | "id": "CHAMP-2",
153 | "humanTeamSize": 3,
154 | "opponentBots": ["botimus", "sdc", "king"],
155 | "map": "ChampionsField_Day",
156 | "display": "Take on a crew of some of the best solo players!"
157 | },
158 | {
159 | "id": "CHAMP-5",
160 | "humanTeamSize": 2,
161 | "opponentBots": ["snek", "sniper", "adversity"],
162 | "map": "ChampionsField_Day",
163 | "display": "These masters of space and time have united to stop you!"
164 | },
165 | {
166 | "id": "CHAMP-3",
167 | "humanTeamSize": 3,
168 | "opponentBots": ["beast", "lanfear", "phoenix", "atlas"],
169 | "map": "ChampionsField_Day",
170 | "display": "These are mythical and magical cars. Can you take them on in a 3v4?"
171 | },
172 | {
173 | "id": "CHAMP-4",
174 | "humanTeamSize": 3,
175 | "opponentBots": ["element"],
176 | "map": "ChampionsField_Day",
177 | "display": "This ML prodigy would like to have a word with you."
178 | },
179 | {
180 | "id": "CHAMP-5",
181 | "humanTeamSize": 3,
182 | "opponentBots": ["bumblebee", "bumblebee", "bumblebee"],
183 | "map": "ChampionsField",
184 | "display": "Time to face the beehive and prove yourself."
185 | },
186 | {
187 | "id": "CHAMP-6",
188 | "humanTeamSize": 2,
189 | "opponentBots": ["nexto"],
190 | "map": "ChampionsField_Day",
191 | "display": "You made it to the top. Time to face the champion."
192 | }
193 | ]
194 | }
195 | }
196 | }
--------------------------------------------------------------------------------
/rlbot_gui/story/story_runner.py:
--------------------------------------------------------------------------------
1 | """
2 | Manages the story
3 | """
4 | from datetime import datetime
5 | import json
6 | from os import path
7 |
8 | import eel
9 | from PyQt5.QtCore import QSettings
10 |
11 | from rlbot_gui.persistence.settings import load_launcher_settings, launcher_preferences_from_map
12 | from rlbot_gui.story.story_challenge_setup import run_challenge, configure_challenge
13 | from rlbot_gui.story.load_story_descriptions import (
14 | get_bots_configs,
15 | get_cities,
16 | get_story_settings,
17 | get_challenges_by_id,
18 | get_scripts_configs
19 | )
20 |
21 |
22 | CURRENT_STATE = None
23 |
24 | ##### EEL -- these are the hooks exposed to JS
25 | @eel.expose
26 | def story_story_test():
27 | print("In story_story_test()")
28 | # easy way to trigger python code
29 |
30 |
31 | @eel.expose
32 | def get_cities_json(story_id):
33 | return get_cities(story_id)
34 |
35 |
36 | @eel.expose
37 | def get_bots_json(story_id):
38 | return get_bots_configs(story_id)
39 |
40 | @eel.expose
41 | def get_story_settings_json(story_id):
42 | return get_story_settings(story_id)
43 |
44 |
45 | @eel.expose
46 | def story_load_save():
47 | """Loads a previous save if available."""
48 | global CURRENT_STATE
49 | settings = QSettings("rlbotgui", "story_save")
50 | state = settings.value("save")
51 | if state:
52 | print(f"Save state: {state}")
53 | CURRENT_STATE = StoryState.from_dict(state)
54 | # default values should get added if missing
55 | state = CURRENT_STATE.__dict__
56 | return state
57 |
58 |
59 | @eel.expose
60 | def story_new_save(player_settings, story_settings):
61 | global CURRENT_STATE
62 | CURRENT_STATE = StoryState.new(player_settings, story_settings)
63 | return story_save_state()
64 |
65 |
66 | @eel.expose
67 | def story_delete_save():
68 | global CURRENT_STATE
69 | CURRENT_STATE = None
70 | QSettings("rlbotgui", "story_save").remove("save")
71 |
72 |
73 | @eel.expose
74 | def story_save_state():
75 | settings = QSettings("rlbotgui", "story_save")
76 | serialized = CURRENT_STATE.__dict__
77 | settings.setValue("save", serialized)
78 | return serialized
79 |
80 |
81 | @eel.expose
82 | def story_save_fake_state(state):
83 | """Only use for debugging.
84 | Normally state should just flow from python to JS"""
85 | global CURRENT_STATE
86 | settings = QSettings("rlbotgui", "story_save")
87 | CURRENT_STATE = CURRENT_STATE.from_dict(state)
88 | settings.setValue("save", state)
89 |
90 |
91 | @eel.expose
92 | def launch_challenge(challenge_id, pickedTeammates):
93 | launch_challenge_with_config(challenge_id, pickedTeammates)
94 |
95 |
96 | @eel.expose
97 | def purchase_upgrade(id, current_currency, cost):
98 | CURRENT_STATE.add_purchase(id, current_currency, cost)
99 | flush_save_state()
100 |
101 |
102 | @eel.expose
103 | def recruit(id, current_currency):
104 | CURRENT_STATE.add_recruit(id, current_currency)
105 | flush_save_state()
106 |
107 |
108 | ##### Reverse eel's
109 |
110 |
111 | def flush_save_state():
112 | serialized = story_save_state()
113 | eel.loadUpdatedSaveState(serialized)
114 |
115 |
116 | #################################
117 |
118 |
119 | class StoryState:
120 | """Represents users game state"""
121 |
122 | def __init__(self):
123 | self.version = 1
124 | self.story_config = "default" # can be dict for custom config
125 | self.team_info = {"name": "", "color_secondary": ""}
126 | self.teammates = []
127 | self.challenges_attempts = {} # many entries per challenge
128 | self.challenges_completed = {} # one entry per challenge
129 |
130 | self.upgrades = {"currency": 0}
131 |
132 | def add_purchase(self, id, current_currency, cost):
133 | """The only validation we do is to make sure current_currency is correct.
134 | This is NOT a security mechanism, this is a bug prevention mechanism to
135 | avoid accidental double clicks.
136 | """
137 | if self.upgrades["currency"] == current_currency:
138 | self.upgrades[id] = True
139 | self.upgrades["currency"] -= cost
140 |
141 | def add_recruit(self, id, current_currency):
142 | """The only validation we do is to make sure current_currency is correct.
143 | This is NOT a security mechanism, this is a bug prevention mechanism to
144 | avoid accidental double clicks.
145 | """
146 | if self.upgrades["currency"] == current_currency:
147 | self.teammates.append(id)
148 | self.upgrades["currency"] -= 1
149 |
150 | def add_match_result(
151 | self, challenge_id: str, challenge_completed: bool, game_results
152 | ):
153 | """game_results should be the output of packet_to_game_results.
154 | You have to call it anyways to figure out if the player
155 | completed the challenge so that's why we don't call it again here.
156 | """
157 | if challenge_id not in self.challenges_attempts:
158 | # no defaultdict because we serialize the data
159 | self.challenges_attempts[challenge_id] = []
160 |
161 | self.challenges_attempts[challenge_id].append(
162 | {"game_results": game_results, "challenge_completed": challenge_completed}
163 | )
164 |
165 | if challenge_completed:
166 | index = len(self.challenges_attempts[challenge_id]) - 1
167 | self.challenges_completed[challenge_id] = index
168 | self.upgrades["currency"] += 2
169 |
170 | @staticmethod
171 | def new(player_settings, story_settings):
172 | s = StoryState()
173 |
174 | name = player_settings["name"]
175 | color_secondary = player_settings["color"]
176 | s.team_info = {"name": name, "color_secondary": color_secondary}
177 |
178 | story_id = story_settings["story_id"]
179 | custom_config = story_settings["custom_config"]
180 | use_custom_maps = story_settings["use_custom_maps"]
181 | if story_id == 'custom':
182 | s.story_config = custom_config
183 | else:
184 | s.story_config = story_id
185 | if use_custom_maps:
186 | s.story_config = f"{story_id}-with-cmaps"
187 | return s
188 |
189 | @staticmethod
190 | def from_dict(source):
191 | """No validation done here."""
192 | s = StoryState()
193 | s.__dict__.update(source)
194 | return s
195 |
196 |
197 | def launch_challenge_with_config(challenge_id, picked_teammates):
198 | print(f"In launch_challenge {challenge_id}")
199 |
200 | story_id = CURRENT_STATE.story_config
201 | challenge = get_challenges_by_id(story_id)[challenge_id]
202 | all_bots = get_bots_configs(story_id)
203 | all_scripts = get_scripts_configs(story_id)
204 |
205 | match_config = configure_challenge(challenge, CURRENT_STATE, picked_teammates, all_bots, all_scripts)
206 | launcher_settings_map = load_launcher_settings()
207 | launcher_prefs = launcher_preferences_from_map(launcher_settings_map)
208 | completed, results = run_challenge(match_config, challenge, CURRENT_STATE.upgrades, launcher_prefs)
209 | CURRENT_STATE.add_match_result(challenge_id, completed, results)
210 |
211 | flush_save_state()
212 |
--------------------------------------------------------------------------------
/rlbot_gui/type_translation/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/type_translation/__init__.py
--------------------------------------------------------------------------------
/rlbot_gui/type_translation/packet_translation.py:
--------------------------------------------------------------------------------
1 |
2 | def convert_packet_to_dict(ctypes_packet):
3 | """
4 | Here we're selectively converting parts of the game tick packet into dict format.
5 | We're skipping over parts that are currently not needed by the GUI, e.g. boost pad state.
6 | """
7 | dict_version = {}
8 | dict_version['game_ball'] = getdict(ctypes_packet.game_ball)
9 | dict_version['game_info'] = getdict(ctypes_packet.game_info)
10 | cars = []
11 | for i in range(ctypes_packet.num_cars):
12 | cars.append(getdict(ctypes_packet.game_cars[i]))
13 | dict_version['game_cars'] = cars
14 | return dict_version
15 |
16 |
17 | def getdict(struct):
18 | """
19 | This will convert a ctypes struct into a python dict. After that, it can be converted easily to json.
20 | Taken from https://stackoverflow.com/a/34301571/280852
21 | """
22 | result = {}
23 |
24 | def get_value(value):
25 | if (type(value) not in [int, float, bool]) and not bool(value):
26 | # it's a null pointer
27 | value = None
28 | elif hasattr(value, "_length_") and hasattr(value, "_type_"):
29 | # Probably an array
30 | # print value
31 | value = get_array(value)
32 | elif hasattr(value, "_fields_"):
33 | # Probably another struct
34 | value = getdict(value)
35 | return value
36 |
37 | def get_array(array):
38 | ar = []
39 | for value in array:
40 | value = get_value(value)
41 | ar.append(value)
42 | return ar
43 |
44 | for f in struct._fields_:
45 | field = f[0]
46 | value = getattr(struct, field)
47 | # if the type is not a primitive and it evaluates to False ...
48 | value = get_value(value)
49 | result[field] = value
50 | return result
51 |
--------------------------------------------------------------------------------
/rlbot_gui/type_translation/set_state_translation.py:
--------------------------------------------------------------------------------
1 | from rlbot.utils.game_state_util import GameState, BallState, CarState, GameInfoState, Physics, Vector3, Rotator
2 |
3 |
4 | def dict_to_game_state(state_dict):
5 | gs = GameState()
6 | if 'ball' in state_dict:
7 | gs.ball = BallState()
8 | if 'physics' in state_dict['ball']:
9 | gs.ball.physics = dict_to_physics(state_dict['ball']['physics'])
10 | if 'cars' in state_dict:
11 | gs.cars = {}
12 | for index, car in state_dict['cars'].items():
13 | car_state = CarState()
14 | if 'physics' in car:
15 | car_state.physics = dict_to_physics(car['physics'])
16 | if 'boost_amount' in car:
17 | car_state.boost_amount = car['boost_amount']
18 | gs.cars[int(index)] = car_state
19 | if 'game_info' in state_dict:
20 | gs.game_info = GameInfoState()
21 | if 'paused' in state_dict['game_info']:
22 | gs.game_info.paused = state_dict['game_info']['paused']
23 | if 'world_gravity_z' in state_dict['game_info']:
24 | gs.game_info.world_gravity_z = state_dict['game_info']['world_gravity_z']
25 | if 'game_speed' in state_dict['game_info']:
26 | gs.game_info.game_speed = state_dict['game_info']['game_speed']
27 | if 'console_commands' in state_dict:
28 | gs.console_commands = state_dict['console_commands']
29 | return gs
30 |
31 |
32 | def dict_to_physics(physics_dict):
33 | phys = Physics()
34 | if 'location' in physics_dict:
35 | phys.location = dict_to_vec(physics_dict['location'])
36 | if 'velocity' in physics_dict:
37 | phys.velocity = dict_to_vec(physics_dict['velocity'])
38 | if 'angular_velocity' in physics_dict:
39 | phys.angular_velocity = dict_to_vec(physics_dict['angular_velocity'])
40 | if 'rotation' in physics_dict:
41 | phys.rotation = dict_to_rot(physics_dict['rotation'])
42 | return phys
43 |
44 |
45 | def dict_to_vec(v):
46 | vec = Vector3()
47 | if 'x' in v:
48 | vec.x = v['x']
49 | if 'y' in v:
50 | vec.y = v['y']
51 | if 'z' in v:
52 | vec.z = v['z']
53 | return vec
54 |
55 |
56 | def dict_to_rot(r):
57 | rot = Rotator()
58 | if 'pitch' in r:
59 | rot.pitch = r['pitch']
60 | if 'yaw' in r:
61 | rot.yaw = r['yaw']
62 | if 'roll' in r:
63 | rot.roll = r['roll']
64 | return rot
65 |
--------------------------------------------------------------------------------
/rlbot_gui/upgrade/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/upgrade/__init__.py
--------------------------------------------------------------------------------
/rlbot_gui/upgrade/upgrade_replacer.py:
--------------------------------------------------------------------------------
1 | import shutil
2 | from pathlib import Path
3 |
4 | import os
5 |
6 |
7 | def replace_upgrade_file():
8 | """
9 | RLBotGUI installations on windows generally have a file at ./pynsist_helpers/upgrade.py which is
10 | NOT part of the rlbot_gui python package. It lives outside the package for the sake of upgrading
11 | the package. However, sometimes we wish to push updates to upgrade.py without requiring users to
12 | manually reinstall. This function takes a version of the upgrade script vended inside rlbot_gui
13 | and copies it to the external location.
14 | """
15 | dir_path = os.path.dirname(os.path.realpath(__file__))
16 | fresh_upgrade_file = Path(os.path.join(dir_path, 'upgrade_script.py'))
17 | existing_upgrade_file = Path('./pynsist_helpers/upgrade.py')
18 |
19 | if existing_upgrade_file.exists():
20 | shutil.copyfile(fresh_upgrade_file, existing_upgrade_file)
21 | print(f"Successfully replaced {existing_upgrade_file.absolute()}.")
22 |
--------------------------------------------------------------------------------
/rlbot_gui/upgrade/upgrade_script.py:
--------------------------------------------------------------------------------
1 | # https://stackoverflow.com/a/51704613
2 | try:
3 | from pip import main as pipmain
4 | except ImportError:
5 | from pip._internal import main as pipmain
6 |
7 | DEFAULT_LOGGER = 'rlbot'
8 |
9 |
10 | def upgrade():
11 | package = 'rlbot'
12 |
13 | import importlib
14 | import os
15 | folder = os.path.dirname(os.path.realpath(__file__))
16 |
17 | try:
18 | # https://stackoverflow.com/a/24773951
19 | importlib.import_module(package)
20 |
21 | from rlbot.utils import public_utils, logging_utils
22 |
23 | logger = logging_utils.get_logger(DEFAULT_LOGGER)
24 | if not public_utils.have_internet():
25 | logger.log(logging_utils.logging_level,
26 | 'Skipping upgrade check for now since it looks like you have no internet')
27 | elif public_utils.is_safe_to_upgrade():
28 | # Upgrade only the rlbot-related stuff.
29 | rlbot_requirements = os.path.join(folder, 'rlbot-requirements.txt')
30 | pipmain(['install', '-r', rlbot_requirements, '--upgrade'])
31 |
32 | except (ImportError, ModuleNotFoundError):
33 | # First time installation, install lots of stuff
34 | all_requirements = os.path.join(folder, 'requirements.txt')
35 | pipmain(['install', '-r', all_requirements])
36 |
37 |
38 | if __name__ == '__main__':
39 | upgrade()
40 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from rlbot_gui import gui
5 | from rlbot_gui.upgrade.upgrade_replacer import replace_upgrade_file
6 |
7 | # Insert the pkgs directory into the python path. This is necessary when
8 | # running the pynsist installed version.
9 | scriptdir, script = os.path.split(__file__)
10 | pkgdir = os.path.join(scriptdir, 'pkgs')
11 | sys.path.insert(0, pkgdir)
12 |
13 | if __name__ == '__main__':
14 | replace_upgrade_file()
15 | gui.start()
16 |
--------------------------------------------------------------------------------
/runRLBotInsideEnv.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set "VIRTUAL_ENV=\env"
3 |
4 | REM Don't use () to avoid problems with them in %PATH%
5 | if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME
6 | set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
7 | :ENDIFVHOME
8 |
9 | set PYTHONHOME=
10 |
11 | REM if defined _OLD_VIRTUAL_PATH (
12 | if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1
13 | set "PATH=%_OLD_VIRTUAL_PATH%"
14 | :ENDIFVPATH1
15 | REM ) else (
16 | if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2
17 | set "_OLD_VIRTUAL_PATH=%PATH%"
18 | :ENDIFVPATH2
19 |
20 | set "PATH=%VIRTUAL_ENV%\Scripts;%PATH%"
21 |
22 | .\env\Scripts\python.exe run.py
23 |
--------------------------------------------------------------------------------
/screenshots/RLBotGUI-Home.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/screenshots/RLBotGUI-Home.PNG
--------------------------------------------------------------------------------
/screenshots/rl-story-mode.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/screenshots/rl-story-mode.PNG
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | __version__ = '0.0.162'
4 |
5 | with open("README.md", "r") as readme_file:
6 | long_description = readme_file.read()
7 |
8 | setuptools.setup(
9 | name='rlbot_gui',
10 | packages=setuptools.find_namespace_packages(exclude=['*logos*'], include=["rlbot_gui*"]),
11 | python_requires='>=3.11',
12 | # It actually requires 'gevent', 'eel', 'PyQt5', but that messes up the install for some people and we're
13 | # already bundling those in the pynsist installer.
14 | # We'll go ahead and list some packages needed by bots in the bot pack, though.
15 | install_requires=[
16 | 'numba',
17 | 'scipy',
18 | 'numpy',
19 | 'RLUtilities', # Used by Snek
20 | 'websockets', # Needed for scratch bots
21 | 'selenium', # Needed for scratch bots
22 | 'PyQt5' # Used for settings and file pickers currently.
23 | ],
24 | version=__version__,
25 | description='A streamlined user interface for RLBot.',
26 | long_description=long_description,
27 | long_description_content_type="text/markdown",
28 | author='RLBot Community',
29 | author_email='rlbotofficial@gmail.com',
30 | url='https://github.com/RLBot/RLBotGUI',
31 | keywords=['rocket-league'],
32 | classifiers=[
33 | "Programming Language :: Python :: 3",
34 | "License :: OSI Approved :: MIT License",
35 | "Operating System :: Microsoft :: Windows",
36 | ],
37 | include_package_data=True,
38 | package_data={
39 | 'rlbot_gui': [
40 | '**/*.json',
41 | ]
42 | },
43 | )
44 |
--------------------------------------------------------------------------------
/template.nsi:
--------------------------------------------------------------------------------
1 | !define PRODUCT_NAME "[[ib.appname]]"
2 | !define PRODUCT_VERSION "[[ib.version]]"
3 | !define PY_VERSION "[[ib.py_version]]"
4 | !define PY_MAJOR_VERSION "[[ib.py_major_version]]"
5 | !define BITNESS "[[ib.py_bitness]]"
6 | !define ARCH_TAG "[[arch_tag]]"
7 | !define INSTALLER_NAME "[[ib.installer_name]]"
8 | !define PRODUCT_ICON "[[icon]]"
9 |
10 | ; Marker file to tell the uninstaller that it's a user installation
11 | !define USER_INSTALL_MARKER _user_install_marker
12 |
13 | SetCompressor lzma
14 |
15 | !define MULTIUSER_EXECUTIONLEVEL Standard
16 | !define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER
17 | !define MULTIUSER_MUI
18 | !define MULTIUSER_INSTALLMODE_COMMANDLINE
19 | !define MULTIUSER_INSTALLMODE_INSTDIR "[[ib.appname]]"
20 | [% if ib.py_bitness == 64 %]
21 | !define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files
22 | [% endif %]
23 | !include MultiUser.nsh
24 |
25 | [% block modernui %]
26 | ; Modern UI installer stuff
27 | !include "MUI2.nsh"
28 | !define MUI_ABORTWARNING
29 | !define MUI_ICON "[[icon]]"
30 | !define MUI_UNICON "[[icon]]"
31 |
32 | ; UI pages
33 | [% block ui_pages %]
34 | !insertmacro MUI_PAGE_WELCOME
35 | [% if license_file %]
36 | !insertmacro MUI_PAGE_LICENSE [[license_file]]
37 | [% endif %]
38 | !insertmacro MUI_PAGE_DIRECTORY
39 | !insertmacro MUI_PAGE_INSTFILES
40 | !insertmacro MUI_PAGE_FINISH
41 | [% endblock ui_pages %]
42 | !insertmacro MUI_LANGUAGE "English"
43 | [% endblock modernui %]
44 |
45 | Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
46 | OutFile "${INSTALLER_NAME}"
47 | ShowInstDetails show
48 |
49 | Section -SETTINGS
50 | SetOutPath "$INSTDIR"
51 | SetOverwrite ifnewer
52 | SectionEnd
53 |
54 | [% block sections %]
55 |
56 | Section "!${PRODUCT_NAME}" sec_app
57 | SetRegView [[ib.py_bitness]]
58 | SectionIn RO
59 | File ${PRODUCT_ICON}
60 | SetOutPath "$INSTDIR\pkgs"
61 | File /r "pkgs\*.*"
62 | SetOutPath "$INSTDIR"
63 |
64 | ; Marker file for per-user install
65 | StrCmp $MultiUser.InstallMode CurrentUser 0 +3
66 | FileOpen $0 "$INSTDIR\${USER_INSTALL_MARKER}" w
67 | FileClose $0
68 | SetFileAttributes "$INSTDIR\${USER_INSTALL_MARKER}" HIDDEN
69 |
70 | [% block install_files %]
71 | ; Install files
72 | [% for destination, group in grouped_files %]
73 | SetOutPath "[[destination]]"
74 | [% for file in group %]
75 | File "[[ file ]]"
76 | [% endfor %]
77 | [% endfor %]
78 |
79 | ; Install directories
80 | [% for dir, destination in ib.install_dirs %]
81 | SetOutPath "[[ pjoin(destination, dir) ]]"
82 | File /r "[[dir]]\*.*"
83 | [% endfor %]
84 | [% endblock install_files %]
85 |
86 | [% block install_shortcuts %]
87 | ; Install shortcuts
88 | ; The output path becomes the working directory for shortcuts
89 | SetOutPath "%HOMEDRIVE%\%HOMEPATH%"
90 | [% if single_shortcut %]
91 | [% for scname, sc in ib.shortcuts.items() %]
92 | CreateShortCut "$SMPROGRAMS\[[scname]].lnk" "[[sc['target'] ]]" \
93 | '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]"
94 | [% endfor %]
95 | [% else %]
96 | [# Multiple shortcuts: create a directory for them #]
97 | CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
98 | [% for scname, sc in ib.shortcuts.items() %]
99 | CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\[[scname]].lnk" "[[sc['target'] ]]" \
100 | '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]"
101 | [% endfor %]
102 | [% endif %]
103 | SetOutPath "$INSTDIR"
104 | [% endblock install_shortcuts %]
105 |
106 | [% block install_commands %]
107 | [% if has_commands %]
108 | DetailPrint "Setting up command-line launchers..."
109 | nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" [[ python ]] "$INSTDIR\bin"'
110 |
111 | StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem
112 | ; Add to PATH for current user
113 | nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add_user "$INSTDIR\bin"'
114 | GoTo AddedSysPath
115 | AddSysPathSystem:
116 | ; Add to PATH for all users
117 | nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add "$INSTDIR\bin"'
118 | AddedSysPath:
119 | [% endif %]
120 | [% endblock install_commands %]
121 |
122 | ; Byte-compile Python files.
123 | DetailPrint "Byte-compiling Python modules..."
124 | nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"'
125 | WriteUninstaller $INSTDIR\uninstall.exe
126 | ; Add ourselves to Add/remove programs
127 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
128 | "DisplayName" "${PRODUCT_NAME}"
129 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
130 | "UninstallString" '"$INSTDIR\uninstall.exe"'
131 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
132 | "InstallLocation" "$INSTDIR"
133 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
134 | "DisplayIcon" "$INSTDIR\${PRODUCT_ICON}"
135 | [% if ib.publisher is not none %]
136 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
137 | "Publisher" "[[ib.publisher]]"
138 | [% endif %]
139 | WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
140 | "DisplayVersion" "${PRODUCT_VERSION}"
141 | WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
142 | "NoModify" 1
143 | WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
144 | "NoRepair" 1
145 |
146 | ; Check if we need to reboot
147 | IfRebootFlag 0 noreboot
148 | MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \
149 | /SD IDNO IDNO noreboot
150 | Reboot
151 | noreboot:
152 | SectionEnd
153 |
154 | Section "Uninstall"
155 | SetRegView [[ib.py_bitness]]
156 | SetShellVarContext all
157 | IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3
158 | SetShellVarContext current
159 | Delete "$INSTDIR\${USER_INSTALL_MARKER}"
160 |
161 | Delete $INSTDIR\uninstall.exe
162 | Delete "$INSTDIR\${PRODUCT_ICON}"
163 | RMDir /r "$INSTDIR\pkgs"
164 |
165 | ; Remove ourselves from %PATH%
166 | [% block uninstall_commands %]
167 | [% if has_commands %]
168 | nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" remove "$INSTDIR\bin"'
169 | [% endif %]
170 | [% endblock uninstall_commands %]
171 |
172 | [% block uninstall_files %]
173 | ; Uninstall files
174 | [% for file, destination in ib.install_files %]
175 | Delete "[[pjoin(destination, file)]]"
176 | [% endfor %]
177 | ; Uninstall directories
178 | [% for dir, destination in ib.install_dirs %]
179 | RMDir /r "[[pjoin(destination, dir)]]"
180 | [% endfor %]
181 | [% endblock uninstall_files %]
182 |
183 | [% block uninstall_shortcuts %]
184 | ; Uninstall shortcuts
185 | [% if single_shortcut %]
186 | [% for scname in ib.shortcuts %]
187 | Delete "$SMPROGRAMS\[[scname]].lnk"
188 | [% endfor %]
189 | [% else %]
190 | RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
191 | [% endif %]
192 | [% endblock uninstall_shortcuts %]
193 | RMDir $INSTDIR
194 | DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
195 | SectionEnd
196 |
197 | [% endblock sections %]
198 |
199 | ; Functions
200 |
201 | Function .onMouseOverSection
202 | ; Find which section the mouse is over, and set the corresponding description.
203 | FindWindow $R0 "#32770" "" $HWNDPARENT
204 | GetDlgItem $R0 $R0 1043 ; description item (must be added to the UI)
205 |
206 | [% block mouseover_messages %]
207 | StrCmp $0 ${sec_app} "" +2
208 | SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}"
209 |
210 | [% endblock mouseover_messages %]
211 | FunctionEnd
212 |
213 | Function .onInit
214 | !insertmacro MULTIUSER_INIT
215 | FunctionEnd
216 |
217 | Function un.onInit
218 | !insertmacro MULTIUSER_UNINIT
219 | FunctionEnd
220 |
221 | [% if ib.py_bitness == 64 %]
222 | Function correct_prog_files
223 | ; The multiuser machinery doesn't know about the different Program files
224 | ; folder for 64-bit applications. Override the install dir it set.
225 | StrCmp $MultiUser.InstallMode AllUsers 0 +2
226 | StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}"
227 | FunctionEnd
228 | [% endif %]
229 |
--------------------------------------------------------------------------------