├── .github └── workflows │ └── publish.yml ├── .gitignore ├── MANIFEST.in ├── README.md ├── alternative-install ├── README.md ├── RLBotGUI.bat ├── python-3.7.9-custom-amd64.zip ├── rlbot.ico └── update_venv.py ├── installer.cfg ├── linux-install ├── README.md └── RLBotGUI.sh ├── publish-to-pypi-prod.bat ├── publish-to-pypi-test.bat ├── pynsist_helpers ├── install-and-run.bat ├── requirements.txt ├── rlbot-requirements.txt ├── rlbot.ico └── upgrade.py ├── requirements.txt ├── rlbot_gui ├── __init__.py ├── bot_management │ ├── __init__.py │ ├── bot_creation.py │ └── downloader.py ├── gui.py ├── gui │ ├── css │ │ ├── appearance-editor.css │ │ ├── bootstrap-vue.min.css │ │ ├── bootstrap.min.css │ │ ├── story.css │ │ └── style.css │ ├── csv │ │ └── items.csv │ ├── imgs │ │ ├── arena_diagram.png │ │ ├── arenas │ │ │ ├── AquaDome.jpg │ │ │ ├── Arctagon.jpg │ │ │ ├── Badlands.jpg │ │ │ ├── Badlands_Night.jpg │ │ │ ├── BeckwithPark.jpg │ │ │ ├── BeckwithPark_GothamNight.jpg │ │ │ ├── BeckwithPark_Midnight.jpg │ │ │ ├── BeckwithPark_Stormy.jpg │ │ │ ├── ChampionsField.jpg │ │ │ ├── ChampionsField_Day.jpg │ │ │ ├── ChampionsField_NFL.jpg │ │ │ ├── Cosmic.jpg │ │ │ ├── DFHStadium.jpg │ │ │ ├── DFHStadium_Circuit.jpg │ │ │ ├── DFHStadium_Day.jpg │ │ │ ├── DFHStadium_Snowy.jpg │ │ │ ├── DFHStadium_Stormy.jpg │ │ │ ├── DeadeyeCanyon.jpg │ │ │ ├── DoubleGoal.jpg │ │ │ ├── DropShot_Core707.jpg │ │ │ ├── Farmstead.jpg │ │ │ ├── Farmstead_Night.jpg │ │ │ ├── ForbiddenTemple.jpg │ │ │ ├── ForbiddenTemple_Day.jpg │ │ │ ├── Hoops_DunkHouse.jpg │ │ │ ├── Mannfield.jpg │ │ │ ├── Mannfield_Night.jpg │ │ │ ├── Mannfield_Snowy.jpg │ │ │ ├── Mannfield_Stormy.jpg │ │ │ ├── NeoTokyo.jpg │ │ │ ├── NeonFields.jpg │ │ │ ├── Octagon.jpg │ │ │ ├── Pillars.jpg │ │ │ ├── RivalsArena.jpg │ │ │ ├── SaltyShores.jpg │ │ │ ├── SaltyShores_Night.jpg │ │ │ ├── StarbaseARC.jpg │ │ │ ├── StarbaseArc_Aftermath.jpg │ │ │ ├── ThrowbackStadium.jpg │ │ │ ├── ThrowbackStadium_Snowy.jpg │ │ │ ├── TokyoUnderpass.jpg │ │ │ ├── Underpass.jpg │ │ │ ├── UrbanCentral.jpg │ │ │ ├── UrbanCentral_Dawn.jpg │ │ │ ├── UrbanCentral_Haunted.jpg │ │ │ ├── UrbanCentral_Night.jpg │ │ │ ├── UtopiaColiseum.jpg │ │ │ ├── UtopiaColiseum_Dusk.jpg │ │ │ ├── UtopiaColiseum_Snowy.jpg │ │ │ ├── UtopiaRetro.jpg │ │ │ ├── Wasteland.jpg │ │ │ └── Wasteland_Night.jpg │ │ ├── epic.png │ │ ├── human.png │ │ ├── psyonix.png │ │ ├── rlbot.png │ │ ├── rlbot_logo.png │ │ ├── steam.png │ │ └── story │ │ │ ├── checkmark-100px.png │ │ │ ├── checkmark-outline-100px.png │ │ │ ├── coin.png │ │ │ ├── exit-160px.png │ │ │ ├── lock-100px.png │ │ │ ├── sources.txt │ │ │ ├── story-mode-map.png │ │ │ └── victory-160px.png │ ├── js │ │ ├── Sortable.min.js │ │ ├── appearance-editor-vue.js │ │ ├── bootstrap-vue-icons.min.js │ │ ├── bootstrap-vue.min.js │ │ ├── bot-card-vue.js │ │ ├── bot-pool-vue.js │ │ ├── categories.js │ │ ├── colorpicker-vue.js │ │ ├── community-events-vue.js │ │ ├── item-field-vue.js │ │ ├── konva.min.js │ │ ├── launcher-preference-vue.js │ │ ├── main-vue.js │ │ ├── main.js │ │ ├── mutator-field-vue.js │ │ ├── runnable-card-vue.js │ │ ├── sandbox-vue.js │ │ ├── script-card-vue.js │ │ ├── script-dependencies-vue.js │ │ ├── story-alter-save-state.js │ │ ├── story-challenges.js │ │ ├── story-mode-start.js │ │ ├── story-mode.js │ │ ├── story-pick-team.js │ │ ├── story-recruit-list.js │ │ ├── story-upgrades.js │ │ ├── team-card-vue.js │ │ ├── velocity-arrow-vue.js │ │ ├── vue-konva.min.js │ │ ├── vue-router.js │ │ ├── vue.min.js │ │ ├── vuedraggable.min.js │ │ └── vuex.min.js │ ├── json │ │ ├── colors.json │ │ └── standard-maps.json │ └── main.html ├── match_runner │ ├── __init__.py │ ├── custom_maps.py │ └── match_runner.py ├── persistence │ ├── __init__.py │ └── settings.py ├── story │ ├── __init__.py │ ├── bots-base.json │ ├── load_story_descriptions.py │ ├── story-default-with-cmaps.json │ ├── story-default.json │ ├── story-easy-with-cmaps.json │ ├── story-easy.json │ ├── story_challenge_setup.py │ └── story_runner.py ├── type_translation │ ├── __init__.py │ ├── packet_translation.py │ └── set_state_translation.py └── upgrade │ ├── __init__.py │ ├── upgrade_replacer.py │ └── upgrade_script.py ├── run.py ├── runRLBotInsideEnv.bat ├── screenshots ├── RLBotGUI-Home.PNG └── rl-story-mode.PNG ├── setup.py └── template.nsi /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build & Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | release-build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: "3.11" 21 | 22 | - name: Install deps 23 | run: python -m pip install wheel build 24 | 25 | - name: Test install 26 | run: python -m pip install . 27 | 28 | - name: Build for distribution 29 | run: python -m build 30 | 31 | - name: Upload distributions 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: release-dists 35 | path: dist/ 36 | 37 | pypi-publish: 38 | runs-on: ubuntu-latest 39 | needs: 40 | - release-build 41 | permissions: 42 | # IMPORTANT: this permission is mandatory for trusted publishing 43 | id-token: write 44 | 45 | # Dedicated environments with protections for publishing are strongly recommended. 46 | # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules 47 | environment: 48 | name: pypi 49 | url: https://pypi.org/p/rlbot-gui 50 | 51 | steps: 52 | - name: Retrieve release distributions 53 | uses: actions/download-artifact@v4 54 | with: 55 | name: release-dists 56 | path: dist/ 57 | 58 | - name: Publish release distributions to PyPI 59 | uses: pypa/gh-action-pypi-publish@release/v1 60 | with: 61 | packages-dir: dist/ 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # Logs 107 | logs 108 | *.log 109 | npm-debug.log* 110 | yarn-debug.log* 111 | yarn-error.log* 112 | 113 | # Runtime data 114 | pids 115 | *.pid 116 | *.seed 117 | *.pid.lock 118 | 119 | # Directory for instrumented libs generated by jscoverage/JSCover 120 | lib-cov 121 | 122 | # Coverage directory used by tools like istanbul 123 | coverage 124 | 125 | # nyc test coverage 126 | .nyc_output 127 | 128 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 129 | .grunt 130 | 131 | # Bower dependency directory (https://bower.io/) 132 | bower_components 133 | 134 | # node-waf configuration 135 | .lock-wscript 136 | 137 | # Compiled binary addons (https://nodejs.org/api/addons.html) 138 | build/Release 139 | 140 | # Dependency directories 141 | node_modules/ 142 | jspm_packages/ 143 | 144 | # TypeScript v1 declaration files 145 | typings/ 146 | 147 | # Optional npm cache directory 148 | .npm 149 | 150 | # Optional eslint cache 151 | .eslintcache 152 | 153 | # Optional REPL history 154 | .node_repl_history 155 | 156 | # Output of 'npm pack' 157 | *.tgz 158 | 159 | # Yarn Integrity file 160 | .yarn-integrity 161 | 162 | # dotenv environment variables file 163 | .env 164 | 165 | # parcel-bundler cache (https://parceljs.org/) 166 | .cache 167 | 168 | # next.js build output 169 | .next 170 | 171 | # nuxt.js build output 172 | .nuxt 173 | 174 | # vuepress build output 175 | .vuepress/dist 176 | 177 | # Serverless directories 178 | .serverless/ 179 | 180 | # FuseBox cache 181 | .fusebox/ 182 | 183 | #DynamoDB Local files 184 | .dynamodb/ 185 | 186 | .idea/ 187 | .vscode/ 188 | 189 | # RLBot stuff that tends to get downloaded 190 | RLBotPack.zip 191 | github-bot-pack.zip 192 | RLBotPack/ 193 | RLBotPackDeletable/ 194 | RLBotMapPackDeletable/ 195 | MyBots/ 196 | rlbot_gui/gui/imgs/logos 197 | 198 | # Hello! If you're adding something here, consider also adding it to MANIFEST.in! 199 | # See the README.md for details. 200 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include rlbot_gui/gui * 2 | prune rlbot_gui/gui/imgs/logos* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RLBotGUI 2 | 3 | [](https://pypi.org/project/rlbot-gui/) 4 | 5 | ## About 6 | 7 | RLBotGUI is a streamlined user interface that helps you run custom 8 | Rocket League bots for offline entertainment. It relies on the RLBot 9 | project to work its magic: https://github.com/RLBot/RLBot 10 | 11 | Works on Windows and Linux 12 | 13 | ## Features 14 | 15 | - Download various bots programmed by the community 16 | - Get started with creating your own bot 17 | - Create matches against bots 18 | - Customize game mode, arena and mutators! 19 | - Try out the Story Mode 20 | 21 | ## Screenshots 22 | 23 | You can download, update or create bots 24 | 25 | ![HomeScreenshot](screenshots/RLBotGUI-Home.PNG) 26 | 27 | 28 | In Story Mode, you can take over cities by beating challenges and can also 29 | recruit teammates and purchase upgrades for your car! 30 | 31 | ![StoryMode](screenshots/rl-story-mode.PNG) 32 | 33 | ## Installation 34 | 35 | If you just want to use this GUI, you can go download the installer from http://www.rlbot.org/ 36 | 37 | It will put "RLBotGUI" in your Windows start menu. 38 | 39 | ## Dev Environment Setup 40 | 41 | ### Prerequisites 42 | 43 | - Python 3.7 44 | 45 | ### Setup 46 | 47 | 1. In a command prompt, run `pip install -r requirements.txt` 48 | 2. Run `python run.py` 49 | 50 | ### Deployment to PyPI 51 | 52 | For normal changes, e.g. things happening inside the rlbot_gui folder, 53 | you should be publishing an update to PyPI. All users will get this change 54 | automatically without needing to reinstall! 55 | 56 | #### Automated 57 | 58 | To deploy: 59 | 1. Look in `setup.py` and increment the version number 60 | 2. Push a commit to the master branch (or open a PR with your changes to the master branch) 61 | - After the commit is made, a new version will automatically be deployed to PyPi. 62 | 63 | #### Manual 64 | 65 | To deploy: 66 | 1. Create a `.pypirc` file like the one described here: 67 | https://github.com/RLBot/RLBot/wiki/Deploying-Changes#first-time-setup 68 | 1. Look in `setup.py` and increment the version number. 69 | 1. Run `publish-to-pypi-prod.bat` 70 | 71 | #### Note 72 | When deploying to pypi, the files which get included are controlled by the MANIFEST.in file. 73 | You may wish to exclude anything which does not belong in the initial install, e.g. 74 | bot logos which get copied in to the GUI folder as you use the program. 75 | 76 | As a rule of thumb, if you add something to .gitignore, it may also belong in MANIFEST.in 77 | as a prune line. 78 | 79 | ### Building the Installer 80 | 81 | You can build an installer executable for users to download. You will rarely need 82 | to do this, because normal updates should be pushed to users by deploying to PyPI. 83 | 84 | You really only need a new installer if you changed something in the pynsist_helpers 85 | folder, run.py, or anything else that gets referenced in installer.cfg. **AVOID THIS** 86 | because you don't want to run around bugging users to reinstall. 87 | 88 | 1. Follow https://pynsist.readthedocs.io/en/latest/index.html to get NSIS installed. 89 | 2. Run `pip install pynsist` 90 | 3. Run `pynsist installer.cfg` 91 | 92 | Find the resulting executable in build\nsis. 93 | 94 | ### How to update items in the appearance editor 95 | 1. Install and run [BakkesMod](http://www.bakkesmod.com/) 96 | 2. In Rocket League, press F6 to open the BakkesMod console, and enter the `dumpitems` command 97 | 3. Find the output `items.csv` in the folder where your `RocketLeague.exe` is, usually `C:/Program Files (x86)/Steam/steamapps/common/rocketleague/Binaries/Win64` 98 | 4. Replace `rlbot_gui/gui/csv/items.csv` with the new file 99 | 5. Change encoding of the new file to UTF-8. Here's how to do that in VS Code: 100 | - use the _Change File Encoding_ command (or click the UTF-8 button in the bottom right) 101 | - select _Reopen with Encoding_, select the one with _Guessed from content_ 102 | - now do that again, but _Save with Encoding_ and _UTF-8_ 103 | 6. Don't forget to bump the version number in `setup.py` 104 | -------------------------------------------------------------------------------- /alternative-install/README.md: -------------------------------------------------------------------------------- 1 | To create an installer from this, bat2exe from https://www.bat2exe.net/ and run it on this directory. 2 | You'll end up with an exe file which people can use to install / run. 3 | -------------------------------------------------------------------------------- /alternative-install/RLBotGUI.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Installing RLBotGUI if necessary, then launching! 4 | 5 | if not exist "%LocalAppData%\RLBotGUIX" mkdir "%LocalAppData%\RLBotGUIX" 6 | pushd "%LocalAppData%\RLBotGUIX" 7 | 8 | if not exist "%LocalAppData%\RLBotGUIX\Python37" ( 9 | echo Looks like we're missing RLBot's Python ^(3.7.9^), installing... 10 | 11 | powershell -command "Expand-Archive '%~dp0python-3.7.9-custom-amd64.zip' '%LocalAppData%\RLBotGUIX\Python37'" 12 | 13 | if exist "%LocalAppData%\RLBotGUIX\venv\pyvenv.cfg" ( 14 | echo Old venv detected, updating Python location so we don't have to reinstall... 15 | rem This is a custom python script that updates the Python location in the venv 16 | "%LocalAppData%\RLBotGUIX\Python37\python.exe" "%~dp0\update_venv.py" 17 | ) 18 | ) 19 | 20 | rem Create a virtual environment which will isolate our package installations from any 21 | rem existing python installation that the user may have. 22 | 23 | if not exist .\venv\Scripts\activate.bat ( 24 | echo Creating Python virtual environment just for RLBot... 25 | "%LocalAppData%\RLBotGUIX\Python37\python.exe" -m venv .\venv 26 | if %ERRORLEVEL% GTR 0 ( 27 | echo Something went wrong with creating Python virtual environment, aborting. 28 | pause 29 | exit 30 | ) 31 | ) 32 | 33 | rem Activate the virtual environment 34 | call .\venv\Scripts\activate.bat 35 | 36 | rem We ping google.com to see if we have an internet connection 37 | rem We then store the output of the command to nul which prevents the command from printing to the console 38 | %WINDIR%\system32\ping -n 1 google.com > nul 39 | if %errorlevel% == 0 ( 40 | echo Installing / upgrading RLBot components... 41 | python -m pip install --upgrade pip 42 | pip install wheel 43 | pip install gevent^<22 44 | pip install eel 45 | pip install --upgrade rlbot_gui rlbot 46 | ) else ( 47 | echo It looks like you're offline, skipping package upgrades. 48 | echo Please note that if this is your first time running RLBotGUI, an internet connection is required to properly install. 49 | ) 50 | 51 | echo Launching RLBotGUI... 52 | 53 | python -c "from rlbot_gui import gui; gui.start()" 54 | 55 | if %ERRORLEVEL% GTR 0 ( 56 | pause 57 | ) 58 | -------------------------------------------------------------------------------- /alternative-install/python-3.7.9-custom-amd64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/alternative-install/python-3.7.9-custom-amd64.zip -------------------------------------------------------------------------------- /alternative-install/rlbot.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/alternative-install/rlbot.ico -------------------------------------------------------------------------------- /alternative-install/update_venv.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | path = os.path.join(os.getenv('LocalAppData'), 'RLBotGUIX\\venv\\pyvenv.cfg') 4 | 5 | with open(path, 'r') as file: 6 | config = file.readlines() 7 | 8 | config[0] = "home = " + os.path.join(os.getenv('LocalAppData'), 'RLBotGUIX\\Python37') + "\n" 9 | 10 | with open(path, 'w') as file: 11 | file.writelines(config) 12 | -------------------------------------------------------------------------------- /installer.cfg: -------------------------------------------------------------------------------- 1 | # https://github.com/takluyver/pynsist 2 | # To make use of this file, you will need to follow the steps at 3 | # https://pynsist.readthedocs.io/en/latest/index.html, including installing NSIS on your computer. 4 | 5 | [Application] 6 | name=RLBotGUI 7 | version=1.0 8 | # How to launch the app - this calls the 'main' function from the 'myapp' package: 9 | target=$WINDIR\system32\cmd.exe 10 | parameters=/c start "RLBotGUI" "$INSTDIR\install-and-run.bat" 11 | icon=pynsist_helpers\rlbot.ico 12 | # It's nice to show the console because bot output / diagnostic info / quit instructions show up there. 13 | console=true 14 | 15 | [Python] 16 | version=3.7.1 17 | 18 | [Include] 19 | # I came up with these lists by uninstalling all my pip stuff then running pip freeze. 20 | # See https://stackoverflow.com/a/40566052 for details. 21 | # I tried putting everything into pypi_wheels first, then moved the ones that didn't work 22 | # down to packages. 23 | 24 | 25 | # Packages from PyPI that the application requires, one per line. These must have wheels on PyPI 26 | pypi_wheels = pip==18.1 27 | bottle==0.12.16 28 | cffi==1.11.5 29 | gevent==1.4.0 30 | gevent-websocket==0.10.1 31 | greenlet==0.4.15 32 | setuptools==40.6.2 33 | whichcraft==0.5.2 34 | 35 | # Packages which don't publish wheels. If you installed the requirements.txt, you'll have these locally and it'll work. 36 | packages = eel 37 | future 38 | bottle_websocket 39 | pycparser 40 | 41 | 42 | # Other files and folders that should be installed 43 | files = run.py 44 | pynsist_helpers/upgrade.py 45 | pynsist_helpers/install-and-run.bat 46 | pynsist_helpers/requirements.txt 47 | pynsist_helpers/rlbot-requirements.txt 48 | 49 | [Build] 50 | nsi_template = template.nsi 51 | -------------------------------------------------------------------------------- /linux-install/README.md: -------------------------------------------------------------------------------- 1 | # RLBotGUI via LISS 2 | 3 | This stands for the Rocket League Bot Graphical User Interface via the Linux Installation Shell Script. 4 | 5 | ## First-time installation with LISS 6 | 7 | 1. Download `RLBotGUI.sh` (Or save [this file](https://raw.githubusercontent.com/RLBot/RLBotGUI/master/linux-install/RLBotGUI.sh)) 8 | 2. `chmod +x path/to/RLBotGUI.sh` 9 | 3. `path/to/RLBotGUI` (`./RLBotGUI` if it's in the current folder) 10 | 4. Do not run the script with sudo - you will be asked to provide your sudo password after it starts, _if needed_. LISS requires sudo in order to run: 11 | 12 | - `sudo apt install software-properties-common` (If needed) 13 | - `sudo add-apt-repository ppa:deadsnakes/ppa` (If needed; this is the Linux Python archive - https://github.com/deadsnakes) 14 | - `sudo apt update` 15 | - `sudo apt install build-essential python3.11-dev python3.11-venv python3.11-distutils` (If needed) 16 | 17 | We won't do anything else with the sudo permission you give us, and is only required for installation. 18 | 19 | 5. Once LISS does its thing, RLBotGUI will open! You can now take `RLBotGUI.sh` and put it wherever in your system you want. 20 | The RLBotGUI has been installed, and you can just keep running it every time you want to use RLBotGUI and it will update itself if needed. 21 | 22 | ## Opening RLBotGUI after installation 23 | 24 | 1. Where ever you've put `RLBotGUI.sh`, run it using `path/to/RLBotGUI.sh` 25 | 2. LISS will now update all dependencies, if you have internet access. (`pip`, `setuptools`, `wheel`, `eel`, `rlbot_gui` and `rlbot`) 26 | 3. RLBotGUI will then launch in Chrome, or your default system browser if Chrome isn't installed. 27 | -------------------------------------------------------------------------------- /linux-install/RLBotGUI.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set +v 4 | 5 | echo "Installing/upgrading RLBotGUI if necessary, then launching!" 6 | echo "" 7 | 8 | if [ ! -d "$HOME/.RLBotGUI" ] 9 | then 10 | mkdir "$HOME/.RLBotGUI" 11 | fi 12 | 13 | pushd "$HOME/.RLBotGUI" 14 | 15 | # If any version of Python 3.11 is not installed, then install it 16 | 17 | python3.11 -V 18 | if [ $? -gt 0 ] 19 | then 20 | echo 21 | echo "Invalid Python install. Installing Python 3.11, and possibly not present dependencies..." 22 | echo 23 | sudo apt install software-properties-common 24 | sudo add-apt-repository ppa:deadsnakes/ppa 25 | sudo apt update 26 | sudo apt install python3.11 27 | 28 | # Instead of waiting to see if these aren't installed, just install them. If they're already installed, then nothing will happen. 29 | echo 30 | echo "Installing build-essential, python3.11-dev, python3.11-venv, python3.11-distutils..." 31 | echo 32 | sudo apt install build-essential python3.11-dev python3.11-venv, python3.11-distutils 33 | fi 34 | 35 | # Check if the virtual environment exists 36 | 37 | if [ ! -e "$HOME/.RLBotGUI/env/bin/activate" ] 38 | then 39 | echo 40 | echo "Creating the Python 3.11 Virtual Environment" 41 | python3.11 -m venv env 42 | fi 43 | 44 | # Enter the virtual environment 45 | source ./env/bin/activate 46 | 47 | # Check for an internet connection 48 | ping -c 1 pypi.org >/dev/null 2>&1 49 | if [ $? -eq 0 ] 50 | then 51 | echo 52 | echo "Checking for updates..." 53 | echo 54 | python -m pip install -U pip 55 | pip install -U setuptools wheel 56 | pip install -U eel rlbot_gui rlbot 57 | else 58 | echo 59 | echo "No internet connection, skipping update check" 60 | echo "NOTE - INTERNET IS REQURIED FOR FIRST TIME LAUNCHES" 61 | fi 62 | 63 | # Launch the GUI 64 | 65 | echo 66 | echo "Launching RLBotGUI" 67 | echo 68 | python -c "from rlbot_gui import gui; gui.start()" 69 | -------------------------------------------------------------------------------- /publish-to-pypi-prod.bat: -------------------------------------------------------------------------------- 1 | py -m pip install twine 2 | py -m pip install wheel 3 | 4 | RD /S /Q dist 5 | 6 | py setup.py sdist bdist_wheel 7 | 8 | @rem This requires you to create a .pypirc file like the one described here: 9 | @rem https://github.com/RLBot/RLBot/wiki/Deploying-Changes#first-time-setup 10 | 11 | twine upload --repository pypi dist/* 12 | -------------------------------------------------------------------------------- /publish-to-pypi-test.bat: -------------------------------------------------------------------------------- 1 | py -m pip install twine 2 | py -m pip install wheel 3 | 4 | RD /S /Q dist 5 | 6 | py setup.py sdist bdist_wheel 7 | 8 | @rem This requires you to create a .pypirc file like the one described here: 9 | @rem https://github.com/RLBot/RLBot/wiki/Deploying-Changes#first-time-setup 10 | 11 | twine upload --repository pypitest dist/* 12 | -------------------------------------------------------------------------------- /pynsist_helpers/install-and-run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | @rem Change the working directory to the location of this file so that relative paths will work 4 | cd /D "%~dp0" 5 | 6 | 7 | .\Python\python.exe upgrade.py || goto :error 8 | .\Python\python.exe run.py || goto :error 9 | exit 10 | 11 | 12 | :error 13 | @rem echo Failed with error #%errorlevel%. 14 | pause 15 | @rem This exit is required to close the cmd that's created in the shortcut. 16 | exit 17 | -------------------------------------------------------------------------------- /pynsist_helpers/requirements.txt: -------------------------------------------------------------------------------- 1 | # Everything in this file will be installed once, the *first* time the GUI runs. 2 | 3 | -r rlbot-requirements.txt 4 | 5 | # This will cause pip to auto-upgrade and stop scaring people with warning messages 6 | pip 7 | 8 | # Packages used by the GUI which are not bundled in the installer 9 | PyQt5 10 | 11 | # Packages commonly used by bots 12 | numpy 13 | websockets 14 | selenium 15 | -------------------------------------------------------------------------------- /pynsist_helpers/rlbot-requirements.txt: -------------------------------------------------------------------------------- 1 | # Everything in this file will be upgraded *every* time the GUI runs 2 | 3 | # Include everything the framework requires 4 | # You will automatically get updates for all versions starting with "1.". 5 | rlbot==1.* 6 | 7 | # Upgrade yourself! 8 | rlbot_gui 9 | 10 | # Chip's utilities - https://github.com/samuelpmish/RLUtilities/tree/master/RLUtilities 11 | RLUtilities 12 | 13 | # VirxERLU's C Library - https://github.com/VirxEC/VirxERLU 14 | VirxERLU-CLib 15 | -------------------------------------------------------------------------------- /pynsist_helpers/rlbot.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/pynsist_helpers/rlbot.ico -------------------------------------------------------------------------------- /pynsist_helpers/upgrade.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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Include everything the framework requires 2 | # You will automatically get updates for all versions starting with "1.". 3 | rlbot==1.* 4 | 5 | # This will cause pip to auto-upgrade and stop scaring people with warning messages 6 | pip 7 | 8 | gevent==24.* 9 | eel>=0.12 10 | PyQt5 11 | -------------------------------------------------------------------------------- /rlbot_gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/__init__.py -------------------------------------------------------------------------------- /rlbot_gui/bot_management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/bot_management/__init__.py -------------------------------------------------------------------------------- /rlbot_gui/bot_management/bot_creation.py: -------------------------------------------------------------------------------- 1 | import fileinput 2 | import os 3 | import random 4 | import re 5 | import string 6 | import sys 7 | import tempfile 8 | from pathlib import Path 9 | from shutil import move 10 | 11 | from rlbot.parsing.directory_scanner import scan_directory_for_bot_configs 12 | 13 | from rlbot_gui.bot_management.downloader import download_and_extract_zip 14 | 15 | 16 | def convert_to_filename(text): 17 | """ 18 | Normalizes string, converts to lowercase, removes non-alphanumeric characters, 19 | and converts spaces to underscores. 20 | """ 21 | import unicodedata 22 | normalized = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode() 23 | valid_chars = f'-_.() {string.ascii_letters}{string.digits}' 24 | filename = ''.join(c for c in normalized if c in valid_chars) 25 | filename = filename.replace(' ', '_') # Replace spaces with underscores 26 | return filename 27 | 28 | 29 | def safe_move(src, dst): 30 | """ https://bugs.python.org/issue32689 """ 31 | move(str(src), str(dst)) 32 | 33 | 34 | def replace_all(file, regex, replacement): 35 | for line in fileinput.input(file, inplace=1): 36 | updated = re.sub(regex, replacement, line) 37 | sys.stdout.write(updated) 38 | 39 | 40 | def bootstrap_python_bot(bot_name, directory): 41 | sanitized_name = convert_to_filename(bot_name) 42 | bot_directory = Path(directory or '.') 43 | top_dir = bot_directory / sanitized_name 44 | if os.path.exists(top_dir): 45 | raise FileExistsError(f'There is already a bot named {sanitized_name}, please choose a different name!') 46 | 47 | with tempfile.TemporaryDirectory() as tmpdirname: 48 | tmpdir = Path(tmpdirname) 49 | print('created temporary directory', tmpdir) 50 | 51 | download_and_extract_zip( 52 | download_url='https://github.com/RLBot/RLBotPythonExample/archive/master.zip', 53 | local_folder_path=tmpdir) 54 | 55 | safe_move(tmpdir / 'RLBotPythonExample-master', top_dir) 56 | 57 | bundle = scan_directory_for_bot_configs(top_dir).pop() 58 | config_file = bundle.config_path 59 | python_file = bundle.python_file 60 | 61 | replace_all(config_file, r'name = .*$', 'name = ' + bot_name) 62 | 63 | # This is intended to open the example python file in the default system editor for .py files. 64 | # Hopefully this will be VS Code or notepad++ or something. If it gets executed as a python script, no harm done. 65 | # This is in a try/except so no error is raised if the user does not have any editor associated with .py files. 66 | try: 67 | os.startfile(python_file) 68 | except OSError: 69 | print(f"You have no default program to open .py files. Your new bot is located at {os.path.abspath(top_dir)}") 70 | 71 | return config_file 72 | 73 | 74 | def bootstrap_scratch_bot(bot_name, directory): 75 | sanitized_name = convert_to_filename(bot_name) 76 | bot_directory = Path(directory or '.') 77 | top_dir = bot_directory / sanitized_name 78 | if os.path.exists(top_dir): 79 | raise FileExistsError(f'There is already a bot named {sanitized_name}, please choose a different name!') 80 | 81 | with tempfile.TemporaryDirectory() as tmpdirname: 82 | tmpdir = Path(tmpdirname) 83 | print('created temporary directory', tmpdir) 84 | 85 | download_and_extract_zip( 86 | download_url='https://github.com/RLBot/RLBotScratchInterface/archive/gui-friendly.zip', 87 | local_folder_path=tmpdir) 88 | 89 | safe_move(tmpdir / 'RLBotScratchInterface-gui-friendly', top_dir) 90 | 91 | # Choose appropriate file names based on the bot name 92 | code_dir = top_dir / sanitized_name 93 | sb3_filename = f'{sanitized_name}.sb3' 94 | sb3_file = code_dir / sb3_filename 95 | config_filename = f'{sanitized_name}.cfg' 96 | config_file = code_dir / config_filename 97 | 98 | replace_all(top_dir / 'rlbot.cfg', r'(participant_config_\d = ).*$', 99 | r'\1' + os.path.join(sanitized_name, config_filename).replace('\\', '\\\\')) 100 | 101 | # We're assuming that the file structure / names in RLBotScratchInterface will not change. 102 | # Semi-safe assumption because we're looking at a gui-specific git branch which ought to be stable. 103 | safe_move(top_dir / 'scratch_bot', code_dir) 104 | safe_move(code_dir / 'my_scratch_bot.sb3', sb3_file) 105 | safe_move(code_dir / 'my_scratch_bot.cfg', config_file) 106 | 107 | replace_all(config_file, r'name = .*$', 'name = ' + bot_name) 108 | replace_all(config_file, r'sb3file = .*$', 'sb3file = ' + sb3_filename) 109 | replace_all(config_file, r'port = .*$', 'port = ' + str(random.randint(20000, 65000))) 110 | 111 | return config_file 112 | 113 | 114 | def bootstrap_python_hivemind(hive_name, directory): 115 | sanitized_name = convert_to_filename(hive_name) 116 | bot_directory = Path(directory or '.') 117 | top_dir = bot_directory / sanitized_name 118 | if os.path.exists(top_dir): 119 | raise FileExistsError(f'There is already a bot named {sanitized_name}, please choose a different name!') 120 | 121 | with tempfile.TemporaryDirectory() as tmpdirname: 122 | tmpdir = Path(tmpdirname) 123 | print('created temporary directory', tmpdir) 124 | 125 | download_and_extract_zip( 126 | download_url='https://github.com/RLBot/RLBotPythonHivemindExample/archive/master.zip', 127 | local_folder_path=tmpdir) 128 | 129 | safe_move(tmpdir / 'RLBotPythonHivemindExample-master', top_dir) 130 | 131 | 132 | config_file = top_dir / 'config.cfg' 133 | drone_file = top_dir / 'src' / 'drone.py' 134 | hive_file = top_dir / 'src' / 'hive.py' 135 | 136 | replace_all(config_file, r'name = .*$', f'name = {hive_name}') 137 | replace_all(drone_file, r'hive_name = .*$', f'hive_name = "{hive_name} Hivemind"') 138 | replace_all(drone_file, r'hive_key = .*$', f'hive_key = "{random.randint(100000, 999999) + hash(hive_name)}"') 139 | replace_all(hive_file, r'class .*\(PythonHivemind\)', f'class {hive_name}Hivemind(PythonHivemind)') 140 | 141 | # This is intended to open the example python file in the default system editor for .py files. 142 | # Hopefully this will be VS Code or notepad++ or something. If it gets executed as a python script, no harm done. 143 | # This is in a try/except so no error is raised if the user does not have any editor associated with .py files. 144 | try: 145 | os.startfile(hive_file) 146 | except OSError: 147 | print(f"You have no default program to open .py files. Your new bot is located at {os.path.abspath(top_dir)}") 148 | 149 | return config_file 150 | 151 | def bootstrap_rust_bot(bot_name, directory): 152 | sanitized_name = convert_to_filename(bot_name) 153 | bot_directory = Path(directory or '.') 154 | top_dir = bot_directory / sanitized_name 155 | if os.path.exists(top_dir): 156 | raise FileExistsError(f'There is already a bot named {sanitized_name}, please choose a different name!') 157 | 158 | with tempfile.TemporaryDirectory() as tmpdirname: 159 | tmpdir = Path(tmpdirname) 160 | print('created temporary directory', tmpdir) 161 | 162 | download_and_extract_zip( 163 | download_url='https://github.com/NicEastvillage/RLBotRustTemplateBot/archive/master.zip', 164 | local_folder_path=tmpdir) 165 | 166 | safe_move(tmpdir / 'RLBotRustTemplateBot-master', top_dir) 167 | 168 | bundle = scan_directory_for_bot_configs(top_dir).pop() 169 | config_file = bundle.config_path 170 | replace_all(config_file, r'name = .*$', f'name = {bot_name}') 171 | replace_all(config_file, r'path = .*$', f'path = ../target/debug/{bot_name}.exe') 172 | 173 | cargo_toml_file = top_dir / 'Cargo.toml' 174 | replace_all(cargo_toml_file, r'name = .*$', f'name = "{bot_name}"') 175 | replace_all(cargo_toml_file, r'authors = .*$', f'authors = []') 176 | 177 | # This is intended to open the main module in the default system editor for .rs files. 178 | # Hopefully this will be VS Code or notepad++ or something. 179 | try: 180 | os.startfile(top_dir / 'src' / 'main.rs') 181 | except OSError: 182 | print(f"You have no default program to open .rs files. Your new bot is located at {os.path.abspath(top_dir)}") 183 | 184 | return config_file -------------------------------------------------------------------------------- /rlbot_gui/gui/css/appearance-editor.css: -------------------------------------------------------------------------------- 1 | .appearance-editor-table .md-field { 2 | margin: 4px 0 -9px; 3 | } 4 | 5 | .appearance-editor-table { 6 | width: 100%; 7 | border-spacing: 10px; 8 | } 9 | 10 | .appearance-editor-table td { 11 | width: 50%; 12 | } 13 | 14 | .appearance-editor-table .md-field { 15 | padding-left: 15px; 16 | border-radius: 3px 3px 0 0; 17 | } 18 | 19 | .paint-color.black { 20 | background-color: #111; 21 | color: #dddddd; 22 | border-color: #dddddd; 23 | } 24 | .paint-color.burntsienna { 25 | background-color: #ffc2b1; 26 | color: #882104; 27 | border-color: #882104; 28 | } 29 | .paint-color.cobalt { 30 | background-color: #ccd3ff; 31 | color: #3f51b5; 32 | border-color: #3f51b5; 33 | } 34 | .paint-color.crimson { 35 | background-color: #ffcece; 36 | color: #d50000; 37 | border-color: #d50000; 38 | } 39 | .paint-color.forestgreen { 40 | background-color: #aae7ac; 41 | color: #199e1e; 42 | border-color: #199e1e; 43 | } 44 | .paint-color.grey { 45 | background-color: #cacaca; 46 | color: #3d3d3d; 47 | border-color: #3d3d3d; 48 | } 49 | .paint-color.lime { 50 | background-color: #f3ffd2; 51 | color: #5ebd00; 52 | border-color: #5ebd00; 53 | } 54 | .paint-color.orange { 55 | background-color: #fff3d3; 56 | color: #ff9d00; 57 | border-color: #ff9d00; 58 | } 59 | .paint-color.pink { 60 | background-color: #ffcdde; 61 | color: #ff4081; 62 | border-color: #ff4081; 63 | } 64 | .paint-color.purple { 65 | background-color: #e2b4eb; 66 | color: #9c27b0; 67 | border-color: #9c27b0; 68 | } 69 | .paint-color.saffron { 70 | background-color: #fffce2; 71 | color: #ffd000; 72 | border-color: #ffd000; 73 | } 74 | .paint-color.skyblue { 75 | background-color: #c4ecff; 76 | color: #03a9f4; 77 | border-color: #03a9f4; 78 | } 79 | .paint-color.titaniumwhite { 80 | background-color: #fff; 81 | color: #929292; 82 | border-color: #929292; 83 | } 84 | 85 | .colorpicker-menu .b-dropdown-text { 86 | padding: 0px; 87 | margin-left: 10px; 88 | margin-right: 10px; 89 | margin-bottom: -5px; 90 | } 91 | 92 | .colorpicker-menu td { 93 | padding: 0; 94 | } 95 | 96 | .colorpicker-color { 97 | width: 25px; 98 | height: 25px; 99 | cursor: pointer; 100 | } 101 | 102 | .colorpicker-color:hover { 103 | border: 2px solid rgba(255, 255, 255, 0.74); 104 | } 105 | 106 | .selected-color { 107 | border: 2px dashed rgba(255, 255, 255, 0.897); 108 | } 109 | 110 | .color-indicator { 111 | border-radius: 12px; 112 | width: 24px; 113 | height: 24px; 114 | display: inline-block; 115 | vertical-align: middle; 116 | } 117 | 118 | #appearance-editor-dialog .form-group { 119 | margin-bottom: 5px; 120 | } 121 | 122 | #appearance-editor-dialog .modal-body { 123 | margin-bottom: 5px; 124 | padding-top: 0; 125 | } 126 | -------------------------------------------------------------------------------- /rlbot_gui/gui/css/story.css: -------------------------------------------------------------------------------- 1 | 2 | .story-card-text { 3 | color: #333333; 4 | } 5 | 6 | .story-map-icon { 7 | position: absolute; 8 | height: 5%; 9 | } 10 | 11 | .story-clicky { 12 | cursor: pointer; 13 | } 14 | 15 | .story-dark-bg { 16 | background-color: #343a40 17 | } 18 | 19 | .completed-challenge { 20 | text-decoration: line-through 21 | } -------------------------------------------------------------------------------- /rlbot_gui/gui/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Exo+2:300,300i,500,500i'); 2 | 3 | :root { 4 | --hover: rgba(200,200,200,1); 5 | --hover-controls: rgba(255,255,255,0.1); 6 | --green-color: #3b7538; 7 | --orange-color: #f26522; 8 | --blue-color: #0054a6; 9 | } 10 | 11 | body, html { 12 | height: 100%; 13 | } 14 | 15 | #app { 16 | font-family: 'Exo 2', sans-serif; 17 | font-weight: 300; 18 | font-size: 16px; 19 | margin: 0; 20 | padding-bottom: 10px; 21 | background-image: url(../imgs/arenas/DFHStadium.jpg); 22 | background-repeat: no-repeat; 23 | background-position: center; 24 | background-size: cover; 25 | transition: background-image 0.5s ease-in-out; 26 | height: 100%; 27 | min-height: 800px; 28 | } 29 | 30 | .noscroll-flex { 31 | display: flex; 32 | flex-direction: column; 33 | overflow: hidden; 34 | } 35 | 36 | .rlbot-main-config .card-body, .settings-card .card-body { 37 | padding: 5px; 38 | } 39 | 40 | .navbar { 41 | background-color: rgb(17, 18, 19); 42 | } 43 | 44 | .control-bar { 45 | background-color: var(--green-color); 46 | height: 2.5rem; 47 | filter: drop-shadow(0 0 0.5rem rgba(0,0,0,1.0)); 48 | margin-bottom: 30px; 49 | } 50 | 51 | .logo { 52 | width: 3rem; 53 | margin-top: -30px; 54 | margin-bottom: -25px; 55 | filter: drop-shadow(0 0 0.5rem rgba(0,0,0,0.6)); 56 | } 57 | 58 | .rlbot-brand { 59 | color: white; 60 | font-size: 1.5rem; 61 | font-weight: bold; 62 | } 63 | 64 | .rlbot-card-header { 65 | font-size: 1.3rem; 66 | margin-right: 20px; 67 | font-weight: bold; 68 | } 69 | 70 | .team-entries { 71 | height: 160px; 72 | overflow-y: auto; 73 | padding: 5px; 74 | margin-bottom: 20px; 75 | } 76 | 77 | .team-card { 78 | position: relative; 79 | } 80 | 81 | .team-card .bot-card { 82 | display: flex; 83 | } 84 | 85 | .team-label { 86 | position: absolute; 87 | bottom: 3px; 88 | color: white; 89 | opacity: 0.5; 90 | } 91 | 92 | .draggable { 93 | cursor: move; 94 | } 95 | 96 | .bot-card { 97 | margin: 2px; 98 | box-shadow: 1px 1px 7px #0000002e; 99 | display: inline-flex; 100 | } 101 | 102 | .bot-card .card-body { 103 | padding: 2px 5px; 104 | display: flex; 105 | width: 100%; 106 | align-items: center; 107 | } 108 | 109 | .unique-bot-identifier { 110 | color: #868686; 111 | } 112 | 113 | .script-card { 114 | background: linear-gradient(90deg, rgb(255, 255, 255) 0%, rgba(255, 255, 255, 0.644) 50%) 115 | } 116 | 117 | .bot-card.disabled { 118 | opacity: 50%; 119 | cursor: default; 120 | } 121 | 122 | .center-flex { 123 | display: flex; 124 | align-items: center; 125 | } 126 | 127 | .bot-name { 128 | flex-grow: 1; 129 | } 130 | 131 | .bot-name:last-child { 132 | padding-right: 10px; 133 | } 134 | 135 | #app .secret-button { 136 | background: none; 137 | color: inherit; 138 | border: none; 139 | padding: 0; 140 | font: inherit; 141 | cursor: inherit; 142 | outline: none; 143 | } 144 | 145 | .secret-button:focus { 146 | background-color: rgba(0, 0, 0, 0.07); 147 | } 148 | 149 | #teamSwitcher .md-switch-thumb { 150 | background-color: var(--blue-color); 151 | } 152 | 153 | #teamSwitcher .md-switch-container { 154 | background-color: rgba(0, 84, 166, 0.5); 155 | } 156 | 157 | #teamSwitcher .orange .md-switch-thumb { 158 | background-color: var(--orange-color); 159 | } 160 | 161 | #teamSwitcher .orange .md-switch-container { 162 | background-color: rgba(242, 101, 34, 0.5); 163 | } 164 | 165 | #teamSwitcher .md-switch-container:focus-within { 166 | background-color: var(--hover) 167 | } 168 | 169 | .card.bot-pool, .card.team-card, .card.settings-card { 170 | margin: 10px; 171 | color: #333333; 172 | } 173 | 174 | #app .card .md-card-header { 175 | padding: 5px; 176 | } 177 | 178 | #app .bot-pool, #app .settings-card { 179 | background-color: #eee; 180 | padding: 2px 5px; 181 | } 182 | 183 | .bot-pool .categories-radio-group { 184 | margin-right: 10px; 185 | background-color: white; 186 | } 187 | 188 | .bot-pool .scripts-header { 189 | margin-top: 5px; 190 | margin-bottom: 0px; 191 | margin-left: 3px; 192 | font-weight: bold; 193 | } 194 | 195 | .bot-card img { 196 | height: 1.5rem; 197 | margin: 3px; 198 | } 199 | 200 | .script-card img { 201 | margin: 0 3px 0 0; 202 | } 203 | 204 | .script-card:not(.disabled) .script-switch * { 205 | cursor: pointer; 206 | } 207 | 208 | .bot-card img.darkened { 209 | filter: brightness(50%); 210 | } 211 | 212 | .org .bot-card img.darkened { 213 | filter: brightness(0%) invert(50%) sepia(25%) saturate(7482%) hue-rotate(350deg) brightness(102%) contrast(90%); 214 | } 215 | 216 | .blu .bot-card img.darkened { 217 | filter: brightness(0%) invert(17%) sepia(78%) saturate(3372%) hue-rotate(198deg) brightness(96%) contrast(101%); 218 | } 219 | 220 | .org .bot-card { 221 | color: var(--orange-color); 222 | } 223 | 224 | .blu .bot-card { 225 | color: var(--blue-color); 226 | } 227 | 228 | #app .org { 229 | color: var(--orange-color); 230 | background-color: var(--orange-color); 231 | } 232 | 233 | #app .blu { 234 | color: var(--blue-color); 235 | background-color: var(--blue-color); 236 | } 237 | 238 | .md-menu-content { 239 | z-index: 10; 240 | } 241 | 242 | .bot-card .bot-hover-reveal { 243 | filter: opacity(20%); 244 | } 245 | 246 | .bot-card:hover .bot-hover-reveal { 247 | filter: opacity(100%); 248 | } 249 | 250 | .bot-info-key { 251 | font-weight: bold; 252 | } 253 | 254 | #app .warning-icon, #app .warning-icon .md-icon { 255 | color: #f5b700; 256 | } 257 | 258 | button.icon-button { 259 | border: none; 260 | margin-left: 6px; 261 | } 262 | 263 | #rlbotgui .bot-pool-adder { 264 | vertical-align: top; 265 | margin-left: 20px; 266 | margin-top: 5px; 267 | } 268 | 269 | .bot-file-path { 270 | color: #939393; 271 | font-weight: normal; 272 | } 273 | 274 | .bot-logo { 275 | float:right; 276 | margin-left: 10px; 277 | margin-top: 15px; 278 | margin-bottom: 10px; 279 | max-width: 250px; 280 | max-height: 250px; 281 | } 282 | 283 | .md-dialog{ 284 | width:80%; 285 | } 286 | 287 | .arena-background { 288 | background: url('/imgs/arena_diagram.png'); 289 | background-size: contain; 290 | background-repeat: no-repeat; 291 | margin: 10px; 292 | } 293 | 294 | .btn.btn-secondary { 295 | background-color: white; 296 | color: black; 297 | border-color: #aeaeae; 298 | box-shadow: 0 2px 0 #989898cf; 299 | } 300 | 301 | .btn.btn-secondary:hover { 302 | color:black; 303 | } 304 | 305 | .btn.btn-primary { 306 | box-shadow: 0 2px 0 rgba(0, 3, 49, 0.8); 307 | } 308 | 309 | .btn.btn-secondary:hover { 310 | background-color: #d9d9d9; 311 | } 312 | 313 | .btn.btn-success { 314 | box-shadow: 0 3px 0 green; 315 | } 316 | 317 | .folder-setting-switch { 318 | max-width: 1000px; 319 | white-space: nowrap; 320 | overflow: hidden; 321 | } 322 | 323 | .start-match-btn { 324 | min-width: 250px; 325 | } 326 | 327 | .navbar-nav { 328 | align-items: center; 329 | } 330 | 331 | .form-group-inline .col { 332 | display: flex; 333 | } 334 | 335 | .form-group-inline .form-control { 336 | display: inline-block; 337 | width: initial; 338 | } 339 | 340 | .platform-icon { 341 | height: 1em; 342 | } 343 | 344 | .btn-dark .platform-icon { 345 | filter: invert() 346 | } 347 | 348 | .highlighted-mutator-field { 349 | font-weight: bold; 350 | color: rgb(27, 27, 27); 351 | } -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arena_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arena_diagram.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/AquaDome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/AquaDome.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Arctagon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Arctagon.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Badlands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Badlands.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Badlands_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Badlands_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/BeckwithPark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/BeckwithPark.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/BeckwithPark_GothamNight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/BeckwithPark_GothamNight.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/BeckwithPark_Midnight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/BeckwithPark_Midnight.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/BeckwithPark_Stormy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/BeckwithPark_Stormy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ChampionsField.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ChampionsField.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ChampionsField_Day.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ChampionsField_Day.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ChampionsField_NFL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ChampionsField_NFL.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Cosmic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Cosmic.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DFHStadium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DFHStadium.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DFHStadium_Circuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DFHStadium_Circuit.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DFHStadium_Day.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DFHStadium_Day.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DFHStadium_Snowy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DFHStadium_Snowy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DFHStadium_Stormy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DFHStadium_Stormy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DeadeyeCanyon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DeadeyeCanyon.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DoubleGoal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DoubleGoal.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/DropShot_Core707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/DropShot_Core707.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Farmstead.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Farmstead.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Farmstead_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Farmstead_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ForbiddenTemple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ForbiddenTemple.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ForbiddenTemple_Day.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ForbiddenTemple_Day.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Hoops_DunkHouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Hoops_DunkHouse.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Mannfield.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Mannfield.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Mannfield_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Mannfield_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Mannfield_Snowy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Mannfield_Snowy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Mannfield_Stormy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Mannfield_Stormy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/NeoTokyo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/NeoTokyo.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/NeonFields.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/NeonFields.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Octagon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Octagon.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Pillars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Pillars.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/RivalsArena.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/RivalsArena.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/SaltyShores.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/SaltyShores.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/SaltyShores_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/SaltyShores_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/StarbaseARC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/StarbaseARC.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/StarbaseArc_Aftermath.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/StarbaseArc_Aftermath.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ThrowbackStadium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ThrowbackStadium.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/ThrowbackStadium_Snowy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/ThrowbackStadium_Snowy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/TokyoUnderpass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/TokyoUnderpass.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Underpass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Underpass.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UrbanCentral.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UrbanCentral.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UrbanCentral_Dawn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UrbanCentral_Dawn.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UrbanCentral_Haunted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UrbanCentral_Haunted.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UrbanCentral_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UrbanCentral_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UtopiaColiseum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UtopiaColiseum.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UtopiaColiseum_Dusk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UtopiaColiseum_Dusk.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UtopiaColiseum_Snowy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UtopiaColiseum_Snowy.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/UtopiaRetro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/UtopiaRetro.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Wasteland.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Wasteland.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/arenas/Wasteland_Night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/arenas/Wasteland_Night.jpg -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/epic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/epic.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/human.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/human.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/psyonix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/psyonix.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/rlbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/rlbot.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/rlbot_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/rlbot_logo.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/steam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/steam.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/checkmark-100px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/checkmark-100px.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/checkmark-outline-100px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/checkmark-outline-100px.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/coin.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/exit-160px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/exit-160px.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/lock-100px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/lock-100px.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/sources.txt: -------------------------------------------------------------------------------- 1 | Sources: 2 | 3 | Lock image: https://commons.wikimedia.org/wiki/File:Lock_font_awesome.svg (cc license) 4 | Checkmark: https://en.wikipedia.org/wiki/File:Checkmark_green.svg (public domain) 5 | 6 | Map: https://azgaar.github.io/Fantasy-Map-Generator/ (MIT license) 7 | 8 | Coin: 2D Game Art Bundle - Casual Game UI - Purchased 9 | https://www.gamedevmarket.net/asset/2d-game-art-bundle-2018/ (royalty free) 10 | 11 | Money, book, crown, cup, diamond, exit, flag_02, gem, gold, medal, prize, rank, skull_03, victory: Gui Icons - Purchased - https://www.gamedevmarket.net/asset/gui-icons-8656/ (royalty free) -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/story-mode-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/story-mode-map.png -------------------------------------------------------------------------------- /rlbot_gui/gui/imgs/story/victory-160px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLBot/RLBotGUI/1b3cdf62848a1ad1427f07711b9c7a3807e3d1a4/rlbot_gui/gui/imgs/story/victory-160px.png -------------------------------------------------------------------------------- /rlbot_gui/gui/js/appearance-editor-vue.js: -------------------------------------------------------------------------------- 1 | import ItemField from './item-field-vue.js'; 2 | import Colorpicker from './colorpicker-vue.js' 3 | 4 | export default { 5 | name: 'appearance-editor', 6 | components: { 7 | 'item-field': ItemField, 8 | 'colorpicker': Colorpicker, 9 | }, 10 | props: ['path', 'activeBot', 'map'], 11 | template: ` 12 | 13 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 | 48 | 49 | View blue car in game 50 | 51 | 52 | 53 | 54 | View orange car in game 55 | 56 | 57 | 58 | 59 | {{ showcaseType.name }} 60 | 61 | 62 | 63 | 64 | 65 | 66 | Save and close 67 | 68 | 69 | 70 | Revert changes 71 | 72 | 73 |
74 |
75 | `, 76 | data () { 77 | return { 78 | appearanceModalActive: false, 79 | config: { 80 | blue: {}, 81 | orange: {}, 82 | }, 83 | items: {}, 84 | itemTypes: [ 85 | {name: 'Body', category: 'Body', itemKey: 'car_id', paintKey: 'car_paint_id'}, 86 | {name: 'Decal', category: 'Skin', itemKey: 'decal_id', paintKey: 'decal_paint_id'}, 87 | {name: 'Wheels', category: 'Wheels', itemKey: 'wheels_id', paintKey: 'wheels_paint_id'}, 88 | {name: 'Boost', category: 'Boost', itemKey: 'boost_id', paintKey: 'boost_paint_id'}, 89 | {name: 'Antenna', category: 'Antenna', itemKey: 'antenna_id', paintKey: 'antenna_paint_id'}, 90 | {name: 'Topper', category: 'Hat', itemKey: 'hat_id', paintKey: 'hat_paint_id'}, 91 | {name: 'Primary Finish', category: 'PaintFinish', itemKey: 'paint_finish_id', paintKey: null}, 92 | {name: 'Accent Finish', category: 'PaintFinish', itemKey: 'custom_finish_id', paintKey: null}, 93 | {name: 'Engine Audio', category: 'EngineAudio', itemKey: 'engine_audio_id', paintKey: null}, 94 | {name: 'Trail', category: 'SupersonicTrail', itemKey: 'trails_id', paintKey: 'trails_paint_id'}, 95 | {name: 'Goal Explosion', category: 'GoalExplosion', itemKey: 'goal_explosion_id', paintKey: 'goal_explosion_paint_id'}, 96 | ], 97 | teams: ['blue', 'orange'], 98 | showcaseTypes: [ 99 | {id: "back-center-kickoff", name: "Static (Back-center kickoff)"}, 100 | {id: "static", name: "Static (Center)"}, 101 | {id: "throttle", name: "Drive around center"}, 102 | {id: "boost", name: "Boost around center"}, 103 | {id: "goal-explosion", name: "Goal explosion"}, 104 | ], 105 | selectedShowcaseType: "boost" 106 | } 107 | }, 108 | 109 | methods: { 110 | getAndParseItems: async function() { 111 | let response = await fetch('csv/items.csv'); 112 | let csv = await response.text(); 113 | let lines = csv.split(/\r?\n/); 114 | 115 | let items = {}; 116 | for (const key in this.itemTypes) { 117 | let category = this.itemTypes[key].category; 118 | items[category] = []; 119 | } 120 | 121 | for (const line of lines) { 122 | let columns = line.split(','); 123 | let category = columns[1]; 124 | 125 | if (items[category]) 126 | items[category].push({id: columns[0], name: columns[3]}); 127 | } 128 | 129 | // rename duplicate item names (append them with (2), (3), ...) 130 | for (const category in items) { 131 | let nameCounts = {}; 132 | for (let item of items[category]) { 133 | if (nameCounts[item.name]) { 134 | nameCounts[item.name]++; 135 | item.name = `${item.name} (${nameCounts[item.name]})`; 136 | } else { 137 | nameCounts[item.name] = 1; 138 | } 139 | } 140 | } 141 | 142 | this.items = items; 143 | }, 144 | saveAppearance: function() { 145 | eel.save_looks(this.config, this.path)(); 146 | this.$bvModal.hide('appearance-editor-dialog'); 147 | }, 148 | spawnCarForViewing: function(team) { 149 | eel.spawn_car_for_viewing(this.config, team, this.selectedShowcaseType, this.map); 150 | }, 151 | loadLooks: async function (path) { 152 | this.config = await eel.get_looks(path)(); 153 | }, 154 | randomizeTeamLoadout: function(team) { 155 | this.config[team].team_color_id = Math.floor(Math.random() * 70); 156 | this.config[team].custom_color_id = Math.floor(Math.random() * 105); 157 | 158 | for (const itemField of this.$refs[team]) { 159 | itemField.selectRandomItem(); 160 | itemField.selectRandomPaintColor(); 161 | } 162 | }, 163 | }, 164 | 165 | created: function() { 166 | this.getAndParseItems(); 167 | }, 168 | 169 | watch: { 170 | appearanceModalActive: { 171 | handler: function(val) { 172 | if (val && this.path) { 173 | this.loadLooks(this.path); 174 | } 175 | } 176 | } 177 | }, 178 | } 179 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/bot-card-vue.js: -------------------------------------------------------------------------------- 1 | import RunnableCard from './runnable-card-vue.js' 2 | 3 | export default { 4 | name: 'bot-card', 5 | components: { 6 | 'runnable-card': RunnableCard, 7 | }, 8 | props: { 9 | bot: Object, 10 | disabled: Boolean, 11 | favorited: Boolean, 12 | draggable: { 13 | type: Boolean, 14 | default: true, 15 | }, 16 | }, 17 | template: /*html*/` 18 | 19 | 20 | 21 | `, 22 | computed: { 23 | draggableModel: function() { 24 | return [this.bot]; 25 | }, 26 | draggableOptions: function() { 27 | return { 28 | group: { 29 | name: 'bots', 30 | pull: 'clone', 31 | put: false, 32 | }, 33 | sort: false, 34 | disabled: !this.draggable || this.disabled, 35 | }; 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/bot-pool-vue.js: -------------------------------------------------------------------------------- 1 | import BotCard from './bot-card-vue.js' 2 | import ScriptCard from './script-card-vue.js' 3 | import ScriptDependencies from './script-dependencies-vue.js' 4 | import categories from './categories.js'; 5 | 6 | export default { 7 | name: 'bot-pool', 8 | components: { 9 | 'bot-card': BotCard, 10 | 'script-card': ScriptCard, 11 | 'script-dependencies': ScriptDependencies, 12 | }, 13 | props: ['bots', 'scripts', 'display-human', 'favorites'], 14 | template: /*html*/` 15 |
16 | 17 | 24 | 32 | 33 | 34 | 35 |
36 | 37 | 38 | 39 | No bots available for this category. 40 | 41 | 42 |
Scripts
43 | 44 | 45 | 46 | 51 |
52 |
53 | `, 54 | data () { 55 | return { 56 | botNameFilter: '', 57 | categories: categories, 58 | primaryCategoryOptions: Object.values(categories).map(ctg => { 59 | ctg.options = ctg.categories.map(sc => ({text: sc.name, value: sc})); 60 | ctg.selected = ctg.categories[0]; 61 | return {text: ctg.name, value: ctg}; 62 | }), 63 | primaryCategorySelected: categories.all, 64 | }; 65 | }, 66 | methods: { 67 | passesFilter: function(runnable) { 68 | let category = this.secondaryCategorySelected; 69 | 70 | // hide runnable when its name doesn't match the search filter 71 | // if the filter is an empty string, anything matches 72 | if (!runnable.name.toLowerCase().includes(this.botNameFilter.toLowerCase())) 73 | return false; 74 | 75 | if (runnable.type === 'human') 76 | return this.displayHuman; 77 | 78 | // show psyonix bots only in specific categories 79 | if (runnable.type === 'psyonix') 80 | return category.includePsyonixBots; 81 | 82 | // if a script is enabled, display it regardless of which category is selected 83 | // except for the script dependencies, where all scripts are displayed already 84 | if (runnable.type === 'script' && !category.displayScriptDependencies && runnable.enabled) 85 | return true; 86 | 87 | if (category && category.favorites) { 88 | return this.favorites.includes(runnable.path) 89 | } 90 | 91 | let allowedTag = runnable.type === 'script' ? category.scripts : category.bots; 92 | if (allowedTag) { 93 | if (allowedTag === '*') { 94 | return true; 95 | } 96 | return runnable.info.tags.includes(allowedTag); 97 | } 98 | return false; 99 | }, 100 | setDefaultCategory: function() { 101 | this.primaryCategorySelected = this.categories.standard; 102 | this.primaryCategorySelected.selected = this.primaryCategorySelected.categories[0]; 103 | }, 104 | }, 105 | computed: { 106 | secondaryCategorySelected: function() { 107 | return this.primaryCategorySelected.selected; 108 | }, 109 | displayedBotsCount: function() { 110 | return this.bots.filter(this.passesFilter).length; 111 | }, 112 | displayedScriptsCount: function() { 113 | return this.scripts.filter(this.passesFilter).length; 114 | }, 115 | }, 116 | } 117 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/categories.js: -------------------------------------------------------------------------------- 1 | export default { 2 | all: { 3 | name: "All bots", 4 | categories: [ 5 | { 6 | bots: "*", 7 | scripts: "*", 8 | includePsyonixBots: true, 9 | }, 10 | ], 11 | }, 12 | standard: { 13 | name: "Standard", 14 | categories: [ 15 | { 16 | name: "Bots for 1v1", 17 | bots: "1v1", 18 | }, 19 | { 20 | name: "Bots with teamplay", 21 | bots: "teamplay", 22 | }, 23 | { 24 | name: "Goalie bots", 25 | bots: "goalie", 26 | }, 27 | ], 28 | }, 29 | extra: { 30 | name: "Extra modes", 31 | categories: [ 32 | { 33 | name: "Hoops", 34 | bots: "hoops", 35 | scripts: "hoops", 36 | }, 37 | { 38 | name: "Dropshot", 39 | bots: "dropshot", 40 | scripts: "dropshot", 41 | }, 42 | { 43 | name: "Snow Day", 44 | bots: "snow-day", 45 | scripts: "snow-day", 46 | }, 47 | { 48 | name: "Rumble", 49 | bots: "rumble", 50 | scripts: "rumble", 51 | }, 52 | { 53 | name: "Spike Rush", 54 | bots: "spike-rush", 55 | scripts: "spike-rush", 56 | }, 57 | { 58 | name: "Heatseeker", 59 | bots: "heatseeker", 60 | scripts: "heatseeker", 61 | }, 62 | ], 63 | }, 64 | special: { 65 | name: "Special bots/scripts", 66 | categories: [ 67 | { 68 | bots: "memebot", 69 | displayScriptDependencies: true, 70 | }, 71 | ], 72 | }, 73 | favorites: { 74 | name: "Favorites", 75 | categories: [ 76 | { 77 | favorites: true, 78 | }, 79 | ], 80 | }, 81 | }; -------------------------------------------------------------------------------- /rlbot_gui/gui/js/colorpicker-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'colorpicker', 3 | props: { 4 | value: String, 5 | primary: Boolean, 6 | team: String, 7 | text: String, 8 | }, 9 | template: ` 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 26 | 27 |
21 |
24 |
25 |
28 |
29 |
30 |
31 | `, 32 | data () { 33 | return { 34 | colors: null, 35 | rows: 7, 36 | columns: this.primary ? 10 : 15 37 | } 38 | }, 39 | 40 | methods: { 41 | getColorStyle: function(colorID) { 42 | let colors = this.primary ? this.colors[this.team] : this.colors.secondary; 43 | let rgb = colors[colorID]; 44 | return {'background-color': `rgb(${rgb ? rgb.toString() : ''})`}; 45 | }, 46 | getColorID(row, column) { 47 | return (row - 1) * this.columns + (column - 1); 48 | }, 49 | }, 50 | 51 | computed: { 52 | indicatorColorStyle: function() { 53 | return this.getColorStyle(this.value); 54 | }, 55 | }, 56 | 57 | beforeMount: async function() { 58 | let response = await fetch('json/colors.json'); 59 | this.colors = await response.json(); 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/community-events-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'community-events', 3 | template: /*html*/` 4 | 5 |
6 |

There are no community events at this time.

7 |
8 |
9 |
10 | 11 |
12 |

{{ event.name }}

13 |

14 | Starts in {{ event.timeUntil }} ({{ event.time }}) 15 |

16 |

17 | Started {{ event.timeUntil }} ago, but you can still join! 18 |

19 |

20 | {{ event.location }} 21 |

22 |

23 | More info 24 |

25 |
26 |
27 |
28 |
29 | `, 30 | data() { 31 | return { 32 | events: [], 33 | eventsNow: 0, 34 | eventsFuture: 0, 35 | } 36 | }, 37 | methods: { 38 | dateTimeCheck: function (today, event) { 39 | const names = event.summary; 40 | const start = event.start.dateTime; 41 | let new_date = new Date(start); 42 | 43 | if (event.recurrence) { 44 | try { 45 | const recurrence = event.recurrence[0].split(";"); 46 | const rec_type = recurrence[0].split("=")[1]; 47 | const interval = recurrence[2].split("=")[1]; 48 | const end_date_type = recurrence[2].split("=")[0]; 49 | const end_date_raw = recurrence[2].split("=")[1]; 50 | let end_date = new Date(new_date); 51 | if (end_date_type == "COUNT") { 52 | if (rec_type == "WEEKLY") { 53 | end_date.setDate(new_date.getDate() + 7 * interval * end_date_raw); 54 | } else if (rec_type == "MONTHLY") { 55 | end_date.setDate(new_date.getDate() + 4 * interval * end_date_raw); 56 | } 57 | } else { 58 | end_date.setDate(end_date_raw); 59 | } 60 | if (rec_type == "WEEKLY") { 61 | while (new_date <= end_date) { 62 | if (new_date > today) { 63 | break; 64 | } 65 | new_date.setDate(new_date.getDate() + 7); 66 | } 67 | } else if (rec_type == "MONTHLY") { 68 | while (new_date <= end_date) { 69 | if (new_date > today) { 70 | break; 71 | } 72 | new_date.setDate(new_date.getDate() + 4); 73 | } 74 | } 75 | } catch (e) { 76 | console.error("Error checking recurrence:" + e); 77 | } 78 | } 79 | 80 | const time_untils = new_date.getTime() - today.getTime(); 81 | return [names, new_date, time_untils]; 82 | }, 83 | formatFromNow: function(milliseconds) { 84 | let days = Math.floor(milliseconds / (1000 * 60 * 60 * 24)); 85 | let hours = Math.floor( 86 | (milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) 87 | ); 88 | let minutes = Math.floor( 89 | (milliseconds % (1000 * 60 * 60)) / (1000 * 60) 90 | ); 91 | let format = ""; 92 | if (days > 0) { 93 | format += days; 94 | if (days > 1) { 95 | format += " days "; 96 | } else { 97 | format += " day "; 98 | } 99 | } 100 | if (hours > 0) { 101 | format += hours; 102 | if (hours > 1) { 103 | format += " hours "; 104 | } else { 105 | format += " hour "; 106 | } 107 | } 108 | if (minutes > 0) { 109 | format += minutes; 110 | if (minutes > 1) { 111 | format += " minutes "; 112 | } else { 113 | format += " minute "; 114 | } 115 | } 116 | return format; 117 | }, 118 | fetchEvents: function () { 119 | const api_key = "AIzaSyBQ40UqlMPexzWxTNd7EYtTrkoFF_DqpqM"; 120 | const to_check = new Date().toISOString(); 121 | const url = `https://www.googleapis.com/calendar/v3/calendars/rlbotofficial@gmail.com/events?maxResults=10&timeMin=${to_check}&key=${api_key}`; 122 | 123 | fetch(url).then((response) => { 124 | response.json().then((data) => { 125 | this.events = []; 126 | 127 | // compute dates and times 128 | for (let event of data.items) { 129 | let [names, new_date, time_until_ms] = this.dateTimeCheck(new Date(), event); 130 | 131 | if (time_until_ms > 0) 132 | this.eventsFuture += 1; 133 | else 134 | this.eventsNow += 1; 135 | 136 | // time_untils is the time until the event in milliseconds 137 | // convert this to something human readable, like "in 2 days" 138 | const format = this.formatFromNow(Math.abs(time_until_ms)); 139 | 140 | let logo_split = event.description ? event.description.split("logo:") : []; 141 | let logo = logo_split.length > 1 142 | ? logo_split[1] 143 | .replace("\n", "") 144 | .split("href=\"")[1] 145 | .split("\"")[0] 146 | : null; 147 | 148 | let description = logo_split.length > 0 && logo_split[0].includes("href=\"") 149 | ? logo_split[0] 150 | .replace("\n", "") 151 | .split("href=\"")[1] 152 | .split("\"")[0] 153 | : event.description; 154 | 155 | this.events.push({ 156 | name: names, 157 | location: event.location, 158 | time: new_date.toLocaleString(), 159 | timeUntil: format, 160 | timeUntilMs: time_until_ms, 161 | moreInfo: description, 162 | logo: logo, 163 | }); 164 | } 165 | 166 | // sort community events by start time 167 | this.events.sort((a, b) => { 168 | return new Date(a.timeUntilMs) - new Date(b.timeUntilMs); 169 | }); 170 | 171 | // only show the first 4 172 | this.events = this.events.slice(0, 4); 173 | }); 174 | }); 175 | }, 176 | }, 177 | mounted() { 178 | this.fetchEvents(); 179 | }, 180 | } 181 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/item-field-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'item-field', 3 | props: ['value', 'items', 'itemType', 'team'], 4 | template: ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ color.name }} 34 | 35 | 36 | 37 | 38 | 39 | `, 40 | data: function() { 41 | return { 42 | itemSelection: null, 43 | validationState: null, 44 | paintColors: [ 45 | {id: 0, class: '', name: 'No Paint'}, 46 | {id: 1, class: 'crimson', name: 'Crimson'}, 47 | {id: 2, class: 'lime', name: 'Lime'}, 48 | {id: 3, class: 'black', name: 'Black'}, 49 | {id: 4, class: 'skyblue', name: 'Sky Blue'}, 50 | {id: 5, class: 'cobalt', name: 'Cobalt'}, 51 | {id: 6, class: 'burntsienna', name: 'Burnt Sienna'}, 52 | {id: 7, class: 'forestgreen', name: 'Forest Green'}, 53 | {id: 8, class: 'purple', name: 'Purple'}, 54 | {id: 9, class: 'pink', name: 'Pink'}, 55 | {id: 10, class: 'orange', name: 'Orange'}, 56 | {id: 11, class: 'grey', name: 'Grey'}, 57 | {id: 12, class: 'titaniumwhite', name: 'Titanium White'}, 58 | {id: 13, class: 'saffron', name: 'Saffron'}, 59 | ] 60 | } 61 | }, 62 | methods: { 63 | loadItemSelection: function() { 64 | let id = this.value[this.itemType.itemKey]; 65 | let item = this.items.find(el => el.id === id); 66 | this.itemSelection = item ? item.name : ''; 67 | }, 68 | selectRandomItem: function() { 69 | let randomIndex = Math.floor(Math.random() * this.items.length); 70 | this.itemSelection = this.items[randomIndex].name; 71 | }, 72 | selectRandomPaintColor: function() { 73 | let randomIndex = Math.floor(Math.random() * this.paintColors.length); 74 | this.selectedPaint = this.paintColors[randomIndex].id; 75 | }, 76 | }, 77 | created: function() { 78 | this.loadItemSelection(); 79 | }, 80 | watch: { 81 | itemSelection: { 82 | handler: function(val) { 83 | let item = this.items.find(el => el.name === val); 84 | this.value[this.itemType.itemKey] = item ? item.id : '0'; 85 | this.validationState = item || val === '' ? null : false; 86 | this.$emit('input', this.value); 87 | } 88 | }, 89 | value: { 90 | handler: function() { 91 | this.loadItemSelection(); 92 | } 93 | } 94 | }, 95 | computed: { 96 | selectedPaint: { 97 | get() { 98 | return this.value[this.itemType.paintKey]; 99 | }, 100 | set(val) { 101 | this.value[this.itemType.paintKey] = val; 102 | this.$emit('input', this.value); 103 | } 104 | }, 105 | selectedPaintColorClass: function() { 106 | let color = this.paintColors.find(el => el.id == this.selectedPaint); 107 | return color ? color.class : ''; 108 | } 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/launcher-preference-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'launcher-preference', 3 | props: ['modalId'], 4 | template: ` 5 |
6 |
7 | 8 | Try both 9 | Steam 10 | Epic Games 11 | 12 | 15 | 21 | 22 | 23 | This needs to be the full path, ending with RocketLeague.exe 24 |
25 | Did you mean:
26 |
27 | {{ correctedExePath }} 28 |
29 | Yes, paste that in 30 |
31 |
32 |
33 |
34 | Save 35 |
36 | `, 37 | data () { 38 | return { 39 | launcherSettings: { 40 | preferred_launcher: 'epic', 41 | use_login_tricks: true, 42 | rocket_league_exe_path: null, 43 | }, 44 | } 45 | }, 46 | computed: { 47 | exePathState: function() { 48 | if (this.launcherSettings.preferred_launcher == 'steam' || !this.launcherSettings.rocket_league_exe_path) return null; 49 | return this.launcherSettings.rocket_league_exe_path.endsWith("RocketLeague.exe"); 50 | }, 51 | correctedExePath: function() { 52 | let path = this.launcherSettings.rocket_league_exe_path || ''; 53 | if (path.endsWith("rocketleague")) { 54 | path += "\\Binaries"; 55 | } 56 | if (path.endsWith("Binaries")) { 57 | path += "\\Win64"; 58 | } 59 | if (path.endsWith("Win64")) { 60 | path += "\\RocketLeague.exe"; 61 | return path; 62 | } 63 | return null; 64 | }, 65 | }, 66 | methods: { 67 | launcherSettingsReceived: function (launcherSettings) { 68 | if (launcherSettings) { 69 | Object.assign(this.launcherSettings, launcherSettings); 70 | } 71 | }, 72 | saveLauncherSettings: function () { 73 | eel.save_launcher_settings(this.launcherSettings); 74 | this.$bvModal.hide(this.modalId); 75 | }, 76 | }, 77 | created: function () { 78 | eel.get_launcher_settings()(this.launcherSettingsReceived); 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/main.js: -------------------------------------------------------------------------------- 1 | eel.expose(PythonPrint); 2 | function PythonPrint(message) { 3 | console.log("Python: "+message); 4 | } 5 | 6 | import Main from './main-vue.js' 7 | import Sandbox from './sandbox-vue.js' 8 | import Story from './story-mode.js' 9 | 10 | // eel does not provide an API for this. Close the browser when the websocket closes. 11 | document.addEventListener("DOMContentLoaded", e => eel._websocket.onclose = window.close.bind(window)); 12 | 13 | const routes = [ 14 | { path: '/', component: Main }, 15 | { path: '/sandbox', component: Sandbox }, 16 | { path: '/story', component: Story } 17 | ]; 18 | 19 | const router = new VueRouter({ 20 | routes: routes 21 | }); 22 | 23 | const store = new Vuex.Store({ 24 | state: { 25 | activeBot: null, 26 | }, 27 | mutations: { 28 | setActiveBot(state, bot) { 29 | state.activeBot = bot; 30 | }, 31 | }, 32 | }); 33 | 34 | const app = new Vue({ 35 | router: router, 36 | store: store, 37 | el: '#app', 38 | data: { 39 | bodyStyle: null 40 | }, 41 | methods: { 42 | changeBackgroundImage: function(bodyStyle) { 43 | this.bodyStyle = bodyStyle; 44 | } 45 | } 46 | }); 47 | 48 | // We loaded this javascript successfully, so get rid of the help message that suggests the new launcher script, 49 | // because they either don't need it or already have it. 50 | document.getElementById("javascript-trouble").remove(); 51 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/mutator-field-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'mutator-field', 3 | props: ['label', 'options', 'value'], 4 | template: ` 5 | 6 | 9 | {{opt}} 10 | 11 | 12 | `, 13 | data: function() { 14 | return { 15 | id: Math.floor(Math.random() * 1000000000).toString(), 16 | model: this.value 17 | } 18 | }, 19 | watch: { 20 | value: function(newVal, oldVal) { 21 | this.model = newVal; 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/runnable-card-vue.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'runnable-card', 3 | props: { 4 | runnable: Object, 5 | disabled: Boolean, 6 | removable: Boolean, 7 | hidewarning: Boolean, 8 | favorited: Boolean, 9 | }, 10 | template: /*html*/` 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ runnable.name }} 18 | 19 |  ({{ runnable.uniquePathSegment }}) 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | `, 42 | } 43 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/script-card-vue.js: -------------------------------------------------------------------------------- 1 | import RunnableCard from './runnable-card-vue.js' 2 | 3 | export default { 4 | name: 'script-card', 5 | props: { 6 | script: Object, 7 | disabled: Boolean, 8 | favorited: Boolean, 9 | }, 10 | components: { 11 | 'runnable-card': RunnableCard, 12 | }, 13 | template: ` 14 | 15 | 16 | 17 | 18 | {{ script.name }} 19 | 20 |  ({{ script.uniquePathSegment }}) 21 | 22 | 23 | 24 | 25 | `, 26 | } 27 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/script-dependencies-vue.js: -------------------------------------------------------------------------------- 1 | import BotCard from './bot-card-vue.js' 2 | import ScriptCard from './script-card-vue.js' 3 | 4 | function prefixFilter(arr, prefix) { 5 | // cut prefix from strings and remove those which don't have the prefix 6 | return arr.filter(str => str.startsWith(prefix)).map(str => str.substring(prefix.length)); 7 | } 8 | 9 | export default { 10 | name: 'script-dependencies', 11 | components: { 12 | 'bot-card': BotCard, 13 | 'script-card': ScriptCard, 14 | }, 15 | props: ['bots', 'scripts', 'nameFilter'], 16 | template: /*html*/` 17 |
18 | 19 |
Scripts
20 |
21 | 22 |
23 | 24 |
Scripts with dependencies
25 |
26 | 27 | 28 | 29 | 36 | 37 |
38 | 43 | 44 | 48 |
49 | 50 |
51 |
52 | `, 53 | methods: { 54 | passesFilter: function(runnable) { 55 | return runnable.name.toLowerCase().includes(this.nameFilter.toLowerCase()); 56 | } 57 | }, 58 | computed: { 59 | dependencies: function() { 60 | // array of objects, which contain a script and bots/scripts that support/require it 61 | return this.scripts.map(script => { 62 | let enableTags = prefixFilter(script.info.tags, "enables-"); 63 | let enableTagFilter = runnable => runnable.info && enableTags.some(tag => 64 | prefixFilter(runnable.info.tags, "supports-").includes(tag) || 65 | prefixFilter(runnable.info.tags, "requires-").includes(tag) 66 | ); 67 | 68 | let supportedBots = this.bots.filter(enableTagFilter); 69 | let supportedScripts = this.scripts.filter(enableTagFilter); 70 | let visible = [script, ...supportedBots, ...supportedScripts].some(this.passesFilter); 71 | 72 | return {script, supportedBots, supportedScripts, visible}; 73 | 74 | }).filter(d => d.supportedScripts.length + d.supportedBots.length > 0); 75 | }, 76 | uninvolvedScripts: function() { 77 | // scripts that don't require another script and arent supported/required by anything else 78 | return this.scripts.filter(script => this.dependencies.every( 79 | d => d.script != script && !script.info.tags.some(tag => tag.startsWith("requires-")) 80 | )); 81 | }, 82 | }, 83 | } 84 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-alter-save-state.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | name: 'alter-save-state', 4 | props: { 'value': Object }, 5 | template: ` 6 |
7 | 8 | Alter State 9 | 10 | 11 | 14 | 15 | Update 16 | 17 |
18 | `, 19 | data: { 20 | "text": '' 21 | }, 22 | methods: { 23 | handleInput: function (event) { 24 | this.text = event.target.value; 25 | }, 26 | sendJSON: function () { 27 | let state = JSON.parse(this.text) 28 | console.log(state); 29 | this.$emit('input', JSON.parse(this.text)); 30 | eel.story_save_fake_state(state) 31 | } 32 | } 33 | }; -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-mode-start.js: -------------------------------------------------------------------------------- 1 | import Colorpicker from './colorpicker-vue.js' 2 | import LauncherPreferenceModal from './launcher-preference-vue.js' 3 | 4 | export default { 5 | name: 'story-start', 6 | components: { 7 | 'colorpicker': Colorpicker, 8 | 'launcher-preference-modal': LauncherPreferenceModal, 9 | }, 10 | template: /*html*/` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | May require large downloads 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Pick File 44 | {{this.form.custom_story.storyPath}} 45 | 46 | 47 | 48 | 49 | 50 | Choose Steam or Epic 51 | 52 | 53 | 54 | Get Started 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | `, 64 | data() { 65 | return { 66 | form: { 67 | teamname: '', 68 | teamcolor: 0, 69 | story_id: 'default', 70 | custom_story: { 71 | storyPath: '' 72 | }, 73 | use_custom_maps: false 74 | }, 75 | storyIdOptions: [ 76 | { value: "easy", text: "Easy"}, 77 | { value: "default", text: "Default"}, 78 | { value: "custom", text: "User Provided Config"} 79 | ] 80 | }; 81 | }, 82 | methods: { 83 | pickFile: async function(event) { 84 | let field = event.target.value; 85 | 86 | let path = await eel.pick_location(false, 'JSON files (*.json)')(); // is_folder=False 87 | if (path) { 88 | this.form.custom_story[field] = path 89 | } 90 | }, 91 | submit: function (event) { 92 | console.log("Submitting story-start"); 93 | event.preventDefault(); 94 | }, 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-mode.js: -------------------------------------------------------------------------------- 1 | 2 | import StoryStart from './story-mode-start.js'; 3 | import StoryChallenges from './story-challenges.js'; 4 | 5 | import AlterSaveState from './story-alter-save-state.js'; 6 | 7 | const UI_STATES = { 8 | 'LOAD_SAVE': 0, 9 | 'START_SCREEN': 1, 10 | 'VALIDATE_PRECONDITIONS': 2, 11 | 'STORY_CHALLENGES': 3 12 | }; 13 | 14 | export default { 15 | name: 'story', 16 | template: /*html*/` 17 |
18 | 19 | 20 | 21 | Story Mode 22 | 23 | 24 | 25 | 26 | 29 | Debug Mode 30 | Delete Save 31 | 32 | 33 | Back 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 |
51 |
52 | {{conf.text}} 53 |
54 |
55 | Download 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | 71 | 72 |
73 |
74 | `, 75 | components: { 76 | 'story-start': StoryStart, 77 | 'story-challenges': StoryChallenges, 78 | 'alter-save-state': AlterSaveState, 79 | }, 80 | data() { 81 | return { 82 | ui_state: UI_STATES.LOAD_SAVE, 83 | saveState: null, 84 | validationState: { 85 | mapPack: { 86 | downloadNeeded: false, 87 | updateNeeded: false 88 | }, 89 | botPack: { 90 | downloadNeeded: false 91 | } 92 | }, 93 | debugMode: false, 94 | debugStateHelper: '', 95 | download_in_progress: false 96 | }; 97 | }, 98 | methods: { 99 | toggleDebugMode() { 100 | this.debugMode = !this.debugMode; 101 | }, 102 | storyStateMachine(targetState) { 103 | console.log(`Going from ${this.ui_state} to ${targetState}`); 104 | this.ui_state = targetState; 105 | }, 106 | startMatch: async function (event) { 107 | console.log("startMatch"); 108 | setTimeout(() => { 109 | console.log("gonna call eel"); 110 | eel.story_story_test(); 111 | }, 0); 112 | }, 113 | startStory: async function (event) { 114 | console.log(event); 115 | let team_settings = { 116 | name: event.teamname, 117 | color: event.teamcolor, 118 | } 119 | let story_settings = { 120 | story_id: event.story_id, 121 | custom_config: event.custom_story, 122 | use_custom_maps: event.use_custom_maps 123 | } 124 | let state = await eel.story_new_save(team_settings, story_settings)(); 125 | this.saveState = state; 126 | 127 | await this.run_validation() 128 | }, 129 | run_validation: async function () { 130 | // check things like map pack and bot pack are downloaded 131 | let settings = await eel.get_story_settings_json(this.saveState.story_config)(); 132 | 133 | // check min map pack version 134 | let key = "min_map_pack_revision" 135 | let min_version = settings[key] 136 | 137 | let cur_version = await eel.get_map_pack_revision()() 138 | let maps_required = (min_version != null) 139 | 140 | let need_maps_download = false 141 | let need_maps_update = false 142 | if (maps_required) { 143 | need_maps_download = (min_version && !cur_version) 144 | need_maps_update = (min_version > cur_version) 145 | } 146 | 147 | // check botpack condition 148 | // we could do version checks with "release tag" but whatever 149 | // just doing existence checks 150 | let commit_id = await eel.get_downloaded_botpack_commit_id()() 151 | let need_bots_download = (commit_id == null) 152 | 153 | this.validationState.mapPack.downloadNeeded = need_maps_download 154 | this.validationState.mapPack.updateNeeded = need_maps_update 155 | this.validationState.botPack.downloadNeeded = need_bots_download 156 | 157 | if (need_maps_download || need_maps_update || need_bots_download) { 158 | this.storyStateMachine(UI_STATES.VALIDATE_PRECONDITIONS); 159 | } 160 | else { 161 | this.storyStateMachine(UI_STATES.STORY_CHALLENGES) 162 | } 163 | }, 164 | validationUIHelper: function() { 165 | let mapPack = this.validationState.mapPack; 166 | let botPack = this.validationState.botPack; 167 | const downloadButtonsHelper = [ 168 | { 169 | "condition": mapPack.downloadNeeded, 170 | "text": "Download Map Pack", 171 | "handler": this.downloadMapPack 172 | }, 173 | { 174 | "condition": !mapPack.downloadNeeded && mapPack.updateNeeded, 175 | "text": "Update Map Pack", 176 | "handler": this.downloadMapPack 177 | }, 178 | { 179 | "condition": botPack.downloadNeeded, 180 | "text": "Download Bot Pack", 181 | "handler": this.downloadBotPack 182 | } 183 | ]; 184 | return downloadButtonsHelper; 185 | }, 186 | downloadBotPack: function() { 187 | this.download_in_progress = true 188 | eel.download_bot_pack()(this.handle_download_updates); 189 | }, 190 | downloadMapPack: function() { 191 | this.download_in_progress = true 192 | eel.update_map_pack()(this.handle_download_updates); 193 | }, 194 | handle_download_updates: function(finished) { 195 | this.download_in_progress = false 196 | this.run_validation() 197 | }, 198 | deleteSave: async function () { 199 | await eel.story_delete_save()(); 200 | this.saveState = null; 201 | this.storyStateMachine(UI_STATES.START_SCREEN); 202 | }, 203 | launchChallenge: function ({ id, pickedTeammates }) { 204 | console.log("Starting match", id); 205 | eel.launch_challenge(id, pickedTeammates); 206 | }, 207 | purchaseUpgrade: function ({ id, currentCurrency, cost }) { 208 | // Send eel a message to add id to purchases and reduce currency 209 | console.log("Will purchase: ", id); 210 | eel.purchase_upgrade(id, currentCurrency, cost); 211 | }, 212 | recruit: function ({ id, currentCurrency }) { 213 | console.log("Will recruit ", id); 214 | eel.recruit(id, currentCurrency); 215 | } 216 | }, 217 | created: async function () { 218 | let state = await eel.story_load_save()(); 219 | console.log(state); 220 | if (!state) { 221 | this.storyStateMachine(UI_STATES.START_SCREEN); 222 | } 223 | else { 224 | this.saveState = state; 225 | this.run_validation() 226 | } 227 | 228 | let self = this; 229 | eel.expose(loadUpdatedSaveState); 230 | function loadUpdatedSaveState(saveState) { 231 | self.saveState = saveState; 232 | console.log(saveState); 233 | } 234 | 235 | }, 236 | }; 237 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-pick-team.js: -------------------------------------------------------------------------------- 1 | 2 | const DEBUG = false; 3 | 4 | export default { 5 | 'name': 'story-pick-team', 6 | 'props': { 7 | 'challenge': Object, 8 | 'teammates': Array, 9 | 'botInfo': Object 10 | }, 11 | 'template': /*html*/` 12 |
13 | 14 | Open Pick Team Modal 15 | 16 | 25 |
26 |
27 |

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

30 | 31 |
32 |
33 | 34 | 39 | {{botInfo[teammate].name}} 40 | 44 | Pick 45 | 46 | 47 | 48 |
49 |
50 |
51 |
52 | `, 53 | 'data': function () { 54 | return { 55 | pickedTeammates: [] 56 | }; 57 | }, 58 | computed: { 59 | 'enoughAvailableTeammates': function () { 60 | return this.teammates.length >= (this.challenge.humanTeamSize - 1); 61 | }, 62 | 'pickedEnough': function () { 63 | return this.pickedTeammates.length == (this.challenge.humanTeamSize - 1); 64 | }, 65 | 'blockOkay': function () { 66 | return (!this.enoughAvailableTeammates || !this.pickedEnough); 67 | } 68 | }, 69 | 'methods': { 70 | ok: function (id) { 71 | // let the popup close first 72 | let event = { 73 | id: this.challenge.id, 74 | pickedTeammates: this.pickedTeammates 75 | }; 76 | setTimeout(() => this.$emit('teamPicked', event), 50); 77 | }, 78 | show: function (challenge) { 79 | // we can remove this if we show other info in this screen 80 | if (challenge.humanTeamSize == 1) { 81 | this.$emit('teamPicked', { id: challenge.id }); 82 | return; 83 | } 84 | this.challenge = challenge; 85 | this.$bvModal.show('pick_team_popup'); 86 | }, 87 | pick: function (id) { 88 | this.pickedTeammates.push(id); 89 | }, 90 | reset: function () { 91 | this.pickedTeammates = []; 92 | } 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-recruit-list.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | name: 'story-recruit-list', 4 | props: { 5 | recruitables: Array, 6 | currency: 0 7 | }, 8 | template: /*html*/ ` 9 | 10 | 14 | {{recruit.name}} 15 | 19 | Recruit 20 | 21 | 22 | 23 | `, 24 | }; -------------------------------------------------------------------------------- /rlbot_gui/gui/js/story-upgrades.js: -------------------------------------------------------------------------------- 1 | 2 | const UPGRADES = [ 3 | { 4 | "id": "boost-33", 5 | "text": "Boost Capacity: 33%", 6 | "cost": 3, 7 | }, 8 | { 9 | "id": "boost-100", 10 | "text": "Boost Capacity: 100%", 11 | "cost": 3, 12 | }, 13 | { 14 | "id": "boost-recharge", 15 | "text": "Auto-Recharge Boost", 16 | "cost": 4, 17 | }, 18 | { 19 | "id": "rumble", 20 | "text": "Rumble Powerups", 21 | "cost": 6 22 | } 23 | ]; 24 | 25 | export default { 26 | name: 'story-upgrades', 27 | props: { upgradeSaveState: Object }, 28 | template: /*html*/` 29 | 30 | 34 | {{upgrade.text}} 35 | 40 | {{upgrade.cost}} 41 | 42 | 43 | 44 | 45 | `, 46 | computed: { 47 | upgrades_ui: function () { 48 | let currency = this.upgradeSaveState.currency; 49 | let result = UPGRADES.map((item) => ({ 50 | id: item.id, 51 | text: item.text, 52 | cost: item.cost, 53 | purchased: Boolean(this.upgradeSaveState[item.id]), 54 | available: currency >= item.cost 55 | })); 56 | 57 | // Screw it, hard coding it is 58 | if (!result[0].purchased) { 59 | // If boost-33 is not purchased, 60 | // boost-100, boost-recharge is disabled 61 | result[1].available = false; 62 | result[2].available = false; 63 | } 64 | return result; 65 | }, 66 | }, 67 | methods: { 68 | purchase: function (item) { 69 | console.log("In purchases", item.id); 70 | this.$emit('purchase_upgrade', { 71 | id: item.id, 72 | currentCurrency: this.upgradeSaveState.currency, 73 | cost: item.cost 74 | }); 75 | } 76 | } 77 | }; -------------------------------------------------------------------------------- /rlbot_gui/gui/js/team-card-vue.js: -------------------------------------------------------------------------------- 1 | import RunnableCard from './runnable-card-vue.js' 2 | 3 | export default { 4 | name: 'team-card', 5 | components: { 6 | 'runnable-card': RunnableCard, 7 | }, 8 | props: ['value', 'team-class'], 9 | template: ` 10 | 11 |
12 | 13 |
14 | 15 | 18 | 19 | 20 |
21 | `, 22 | computed: { 23 | team: { 24 | get() { 25 | return this.value; 26 | }, 27 | set(val) { 28 | this.$emit('input', val); 29 | } 30 | }, 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/velocity-arrow-vue.js: -------------------------------------------------------------------------------- 1 | const VELOCITY_SCALE = 30; 2 | 3 | export default { 4 | name: 'velocity-arrow', 5 | props: ['object', 'color', 'maxradius'], 6 | template: ` 7 | 8 | 9 | 10 | 11 | `, 12 | data: function() { 13 | return { 14 | lineConfig: { 15 | points: [0, 0, 0, 0], 16 | stroke: this.color, 17 | strokeWidth: 5, 18 | lineCap: 'round' 19 | }, 20 | wedgeConfig: { 21 | x: 0, 22 | y: 0, 23 | rotation: 0, 24 | draggable: true, 25 | radius: 15, 26 | angle: 60, 27 | fill: this.color, 28 | offset: { 29 | x: 14, 30 | y: 7.7 31 | } 32 | } 33 | } 34 | }, 35 | methods: { 36 | onDragStart: function(evt) { 37 | this.$emit("dragstart", evt); 38 | }, 39 | onDragEnd: function(evt) { 40 | this.$emit("dragend", this.object); 41 | }, 42 | onDragMove: function(evt) { 43 | let pos = this.dragBounds({x: evt.target.x(), y: evt.target.y()}); 44 | this.wedgeConfig.x = pos.x; 45 | this.wedgeConfig.y = pos.y; 46 | let dx = this.object.x - this.wedgeConfig.x; 47 | let dy = this.object.y - this.wedgeConfig.y; 48 | this.object.vx = dx * VELOCITY_SCALE; 49 | this.object.vy = dy * VELOCITY_SCALE; 50 | this.object.rotation = Math.atan2(dy, dx) * 180/Math.PI; 51 | 52 | this.update(this.object); 53 | }, 54 | dragBounds: function(pos) { 55 | let dx = pos.x - this.object.x; 56 | let dy = pos.y - this.object.y; 57 | let distance = Math.hypot(dx, dy) * VELOCITY_SCALE; 58 | if (distance > this.maxradius) { 59 | return { 60 | x: this.object.x + dx * this.maxradius/distance, 61 | y: this.object.y + dy * this.maxradius/distance 62 | } 63 | } else { 64 | return pos; 65 | } 66 | }, 67 | update: function(object) { 68 | let x = object.x; 69 | let y = object.y; 70 | let vx = object.vx; 71 | let vy = object.vy; 72 | 73 | this.lineConfig.points[0] = x; 74 | this.lineConfig.points[1] = y; 75 | 76 | this.wedgeConfig.x = x - vx/VELOCITY_SCALE; 77 | this.wedgeConfig.y = y - vy/VELOCITY_SCALE; 78 | 79 | this.lineConfig.points[2] = this.wedgeConfig.x; 80 | this.lineConfig.points[3] = this.wedgeConfig.y; 81 | 82 | if (Math.hypot(vx, vy) < 1 && this.object.rotation) { 83 | this.wedgeConfig.rotation = this.object.rotation - 30; 84 | } else { 85 | this.wedgeConfig.rotation = Math.atan2(-vy, -vx) * 180/Math.PI + 150; 86 | } 87 | } 88 | }, 89 | watch: { 90 | object: { 91 | deep: true, 92 | handler: function(newVal) { 93 | this.update(newVal); 94 | } 95 | } 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /rlbot_gui/gui/js/vue-konva.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-konva v2.1.0 - https://github.com/konvajs/vue-konva#readme 3 | * MIT Licensed 4 | */ 5 | !function(n,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue"),require("konva")):"function"==typeof define&&define.amd?define(["vue","konva"],t):"object"==typeof exports?exports.VueKonva=t(require("vue"),require("konva")):n.VueKonva=t(n.Vue,n.Konva)}(this,function(n,t){return function(n){var t={};function e(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return n[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}return e.m=n,e.c=t,e.d=function(n,t,o){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},e.t=function(n,t){if(1&t&&(n=e(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(e.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var r in n)e.d(o,r,function(t){return n[t]}.bind(null,r));return o},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},e.p="",e(e.s=1)}([function(t,e){t.exports=n},function(n,t,e){n.exports=e(3)},function(n,e){n.exports=t},function(n,t,e){"use strict";e.r(t);var o=e(0),r=e.n(o);function i(n){var t=n.getLayer()||n.getStage();t&&t.batchDraw()}var a={key:!0,style:!0,elm:!0,isRootInsert:!0},u=".vue-konva-event";function s(n){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=n._konvaNode,r={},s=!1;for(var c in e)if(!a[c]){var f="on"===c.slice(0,2),d=e[c]!==t[c];if(f&&d){var v=c.substr(2).toLowerCase();"content"===v.substr(0,7)&&(v="content"+v.substr(7,1).toUpperCase()+v.substr(8)),o.off(v+u,e[c])}!t.hasOwnProperty(c)&&o.setAttr(c,void 0)}for(var p in t)if(!a[p]){var l="on"===p.slice(0,2),h=e[p]!==t[p];if(l&&h){var g=p.substr(2).toLowerCase();"content"===g.substr(0,7)&&(g="content"+g.substr(7,1).toUpperCase()+g.substr(8)),t[p]&&(o.off(g+u),o.on(g+u,t[p]))}l||t[p]===e[p]||(s=!0,r[p]=t[p])}s&&(o.setAttrs(r),i(o))}var c="_konvaNode";function f(n){var t={};return Object.keys(n).forEach(function(e){t["on"+e]=n[e]}),t}var d=Object.assign||function(n){for(var t=1;tn-1?n:e[t]},getComponent:function(){return this.$slots["default"][0].componentInstance},resetTransitionData:function(t){if(this.noTransitionOnDrag&&this.transitionMode){var e=this.getChildrenNodes();e[t].data=null;var n=this.getComponent();n.children=[],n.kept=void 0}},onDragStart:function(t){this.context=this.getUnderlyingVm(t.item),t.item._underlying_vm_=this.clone(this.context.element),c=t.item},onDragAdd:function(t){this.updateEvenemt(t);var e=t.item._underlying_vm_;if(void 0!==e){n(t.item);var o=this.getVmIndex(t.newIndex);this.spliceList(o,0,e),this.computeIndexes();var i={element:e,newIndex:o};this.emitChanges({added:i})}},onDragRemove:function(t){if(this.updateEvenemt(t),o(this.rootContainer,t.item,t.oldIndex),this.isCloning)return void n(t.clone);var e=this.context.index;this.spliceList(e,1);var i={element:this.context.element,oldIndex:e};this.resetTransitionData(e),this.emitChanges({removed:i})},onDragUpdate:function(t){this.updateEvenemt(t),n(t.item),o(t.from,t.item,t.oldIndex);var e=this.context.index,i=this.getVmIndex(t.newIndex);this.updatePosition(e,i);var r={element:this.context.element,oldIndex:e,newIndex:i};this.emitChanges({moved:r})},updateEvenemt:function(t){this.updateProperty(t,"newIndex"),this.updateProperty(t,"oldIndex")},updateProperty:function(t,e){t.hasOwnProperty(e)&&(t[e]+=this.headerOffset)},computeFutureIndex:function(t,e){if(!t.element)return 0;var n=[].concat(_toConsumableArray(e.to.children)).filter(function(t){return"none"!==t.style.display}),o=n.indexOf(e.related),i=t.component.getVmIndex(o),r=n.indexOf(c)!=-1;return r||!e.willInsertAfter?i:i+1},onDragMove:function(t,e){var n=this.move;if(!n||!this.realList)return!0;var o=this.getRelatedContextFromMoveEvent(t),i=this.context,r=this.computeFutureIndex(o,t);return _extends(i,{futureIndex:r}),_extends(t,{relatedContext:o,draggedContext:i}),n(t,e)},onDragEnd:function(t){this.computeIndexes(),c=null}}};return h}if(Array.from||(Array.from=function(t){return[].slice.call(t)}),typeof exports == "object"){var n=require("sortablejs");module.exports=e(n)}else if("function"==typeof define&&define.amd)define(["sortablejs"],function(t){return e(t)});else if(window&&window.Vue&&window.Sortable){var o=e(window.Sortable);Vue.component("draggable",o)}}(); -------------------------------------------------------------------------------- /rlbot_gui/gui/json/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "blue": [ 3 | [80, 127, 57], 4 | [57, 127, 63], 5 | [57, 127, 100], 6 | [57, 125, 127], 7 | [57, 107, 127], 8 | [57, 93, 127], 9 | [57, 79, 127], 10 | [57, 66, 127], 11 | [76, 57, 127], 12 | [81, 57, 127], 13 | [101, 178, 62], 14 | [62, 178, 72], 15 | [62, 178, 134], 16 | [62, 174, 178], 17 | [62, 145, 178], 18 | [62, 122, 178], 19 | [62, 99, 178], 20 | [62, 77, 178], 21 | [93, 62, 178], 22 | [103, 62, 178], 23 | [114, 229, 57], 24 | [57, 229, 71], 25 | [57, 229, 163], 26 | [57, 223, 229], 27 | [57, 180, 229], 28 | [57, 146, 229], 29 | [57, 111, 229], 30 | [57, 80, 229], 31 | [103, 57, 229], 32 | [117, 57, 229], 33 | [92, 252, 12], 34 | [12, 252, 32], 35 | [12, 252, 160], 36 | [12, 244, 252], 37 | [12, 184, 252], 38 | [12, 136, 252], 39 | [12, 88, 252], 40 | [12, 44, 252], 41 | [76, 12, 252], 42 | [96, 12, 252], 43 | [74, 204, 10], 44 | [10, 204, 26], 45 | [10, 204, 129], 46 | [10, 197, 204], 47 | [10, 149, 204], 48 | [10, 110, 204], 49 | [10, 71, 204], 50 | [10, 36, 204], 51 | [61, 10, 204], 52 | [78, 10, 204], 53 | [60, 165, 8], 54 | [8, 165, 21], 55 | [8, 165, 105], 56 | [8, 160, 165], 57 | [8, 121, 165], 58 | [8, 89, 165], 59 | [8, 58, 165], 60 | [8, 29, 165], 61 | [50, 8, 165], 62 | [63, 8, 165], 63 | [46, 127, 6], 64 | [6, 127, 16], 65 | [6, 127, 81], 66 | [6, 123, 127], 67 | [6, 93, 127], 68 | [6, 68, 127], 69 | [6, 44, 127], 70 | [6, 22, 127], 71 | [38, 6, 127], 72 | [48, 6, 127] 73 | ], 74 | "orange": [ 75 | [127, 127, 57], 76 | [127, 112, 57], 77 | [127, 99, 57], 78 | [127, 90, 57], 79 | [127, 84, 57], 80 | [127, 78, 57], 81 | [127, 71, 57], 82 | [127, 57, 57], 83 | [127, 57, 81], 84 | [127, 57, 92], 85 | [178, 178, 62], 86 | [178, 153, 62], 87 | [178, 132, 62], 88 | [178, 116, 62], 89 | [178, 106, 62], 90 | [178, 97, 62], 91 | [178, 85, 62], 92 | [178, 62, 62], 93 | [178, 62, 103], 94 | [178, 62, 120], 95 | [229, 229, 57], 96 | [229, 192, 57], 97 | [229, 160, 57], 98 | [229, 137, 57], 99 | [229, 123, 57], 100 | [229, 109, 57], 101 | [229, 91, 57], 102 | [229, 57, 57], 103 | [229, 57, 117], 104 | [229, 57, 143], 105 | [252, 252, 12], 106 | [252, 200, 12], 107 | [252, 156, 12], 108 | [252, 124, 12], 109 | [252, 104, 12], 110 | [252, 84, 12], 111 | [252, 60, 12], 112 | [252, 12, 12], 113 | [252, 12, 96], 114 | [252, 12, 132], 115 | [204, 204, 10], 116 | [204, 162, 10], 117 | [204, 126, 10], 118 | [204, 100, 10], 119 | [204, 84, 10], 120 | [204, 68, 10], 121 | [204, 48, 10], 122 | [204, 10, 10], 123 | [204, 10, 78], 124 | [204, 10, 107], 125 | [165, 165, 8], 126 | [165, 131, 8], 127 | [165, 102, 8], 128 | [165, 81, 8], 129 | [165, 68, 8], 130 | [165, 55, 8], 131 | [165, 39, 8], 132 | [165, 8, 8], 133 | [165, 8, 63], 134 | [165, 8, 87], 135 | [127, 127, 6], 136 | [127, 101, 6], 137 | [127, 79, 6], 138 | [127, 62, 6], 139 | [127, 52, 6], 140 | [127, 42, 6], 141 | [127, 30, 6], 142 | [127, 6, 6], 143 | [127, 6, 48], 144 | [127, 6, 66] 145 | ], 146 | "secondary": [ 147 | [229, 229, 229], 148 | [255, 127, 127], 149 | [255, 159, 127], 150 | [255, 207, 127], 151 | [239, 255, 127], 152 | [175, 255, 127], 153 | [127, 255, 127], 154 | [127, 255, 178], 155 | [127, 233, 255], 156 | [127, 176, 255], 157 | [127, 136, 255], 158 | [174, 127, 255], 159 | [229, 127, 255], 160 | [255, 127, 208], 161 | [255, 127, 148], 162 | [191, 191, 191], 163 | [255, 89, 89], 164 | [255, 130, 89], 165 | [255, 192, 89], 166 | [234, 255, 89], 167 | [151, 255, 89], 168 | [89, 255, 89], 169 | [89, 255, 155], 170 | [89, 227, 255], 171 | [89, 152, 255], 172 | [89, 100, 255], 173 | [150, 89, 255], 174 | [221, 89, 255], 175 | [255, 89, 194], 176 | [255, 89, 116], 177 | [153, 153, 153], 178 | [255, 50, 50], 179 | [255, 101, 50], 180 | [255, 178, 50], 181 | [229, 255, 50], 182 | [127, 255, 50], 183 | [50, 255, 50], 184 | [50, 255, 132], 185 | [50, 220, 255], 186 | [50, 129, 255], 187 | [50, 64, 255], 188 | [125, 50, 255], 189 | [214, 50, 255], 190 | [255, 50, 180], 191 | [255, 50, 85], 192 | [102, 102, 102], 193 | [255, 0, 0], 194 | [255, 63, 0], 195 | [255, 159, 0], 196 | [223, 255, 0], 197 | [95, 255, 0], 198 | [0, 255, 0], 199 | [0, 255, 102], 200 | [0, 212, 255], 201 | [0, 97, 255], 202 | [0, 17, 255], 203 | [93, 0, 255], 204 | [204, 0, 255], 205 | [255, 0, 161], 206 | [255, 0, 42], 207 | [63, 63, 63], 208 | [178, 0, 0], 209 | [178, 44, 0], 210 | [178, 111, 0], 211 | [156, 178, 0], 212 | [66, 178, 0], 213 | [0, 178, 0], 214 | [0, 178, 71], 215 | [0, 148, 178], 216 | [0, 68, 178], 217 | [0, 11, 178], 218 | [65, 0, 178], 219 | [142, 0, 178], 220 | [178, 0, 113], 221 | [178, 0, 29], 222 | [38, 38, 38], 223 | [102, 0, 0], 224 | [102, 25, 0], 225 | [102, 63, 0], 226 | [89, 102, 0], 227 | [38, 102, 0], 228 | [0, 102, 0], 229 | [0, 102, 40], 230 | [0, 84, 102], 231 | [0, 39, 102], 232 | [0, 6, 102], 233 | [37, 0, 102], 234 | [81, 0, 102], 235 | [102, 0, 64], 236 | [102, 0, 17], 237 | [5, 5, 5], 238 | [51, 0, 0], 239 | [51, 12, 0], 240 | [51, 31, 0], 241 | [44, 51, 0], 242 | [19, 51, 0], 243 | [0, 51, 0], 244 | [0, 51, 20], 245 | [0, 42, 51], 246 | [0, 19, 51], 247 | [0, 3, 51], 248 | [18, 0, 51], 249 | [40, 0, 51], 250 | [51, 0, 32], 251 | [51, 0, 8] 252 | ] 253 | } -------------------------------------------------------------------------------- /rlbot_gui/gui/json/standard-maps.json: -------------------------------------------------------------------------------- 1 | [ 2 | "DFHStadium", 3 | "Mannfield", 4 | "ChampionsField", 5 | "UrbanCentral", 6 | "BeckwithPark", 7 | "UtopiaColiseum", 8 | "Wasteland", 9 | "NeoTokyo", 10 | "AquaDome", 11 | "StarbaseArc", 12 | "Farmstead", 13 | "SaltyShores", 14 | "DFHStadium_Stormy", 15 | "DFHStadium_Day", 16 | "Mannfield_Stormy", 17 | "Mannfield_Night", 18 | "ChampionsField_Day", 19 | "BeckwithPark_Stormy", 20 | "UrbanCentral_Night", 21 | "UrbanCentral_Dawn", 22 | "UtopiaColiseum_Dusk", 23 | "DFHStadium_Snowy", 24 | "Mannfield_Snowy", 25 | "UtopiaColiseum_Snowy", 26 | "ForbiddenTemple", 27 | "RivalsArena", 28 | "Farmstead_Night", 29 | "SaltyShores_Night", 30 | "NeonFields", 31 | "DFHStadium_Circuit", 32 | "DeadeyeCanyon", 33 | "StarbaseArc_Aftermath", 34 | "ForbiddenTemple_Day", 35 | "Wasteland_Night" 36 | ] -------------------------------------------------------------------------------- /rlbot_gui/gui/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | RLBot 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |

Loading RLBotGUI...

35 |

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 | --------------------------------------------------------------------------------