diff --git a/rlbot_gui/bot_management/downloader.py b/rlbot_gui/bot_management/downloader.py
index 00a471c..ededbbd 100644
--- a/rlbot_gui/bot_management/downloader.py
+++ b/rlbot_gui/bot_management/downloader.py
@@ -3,7 +3,10 @@
import urllib
import zipfile
from pathlib import Path
-from shutil import rmtree
+import shutil
+
+from distutils.dir_util import copy_tree
+
def download_and_extract_zip(download_url: str, local_folder_path: Path, clobber: bool = False):
@@ -17,7 +20,7 @@ def download_and_extract_zip(download_url: str, local_folder_path: Path, clobber
urllib.request.urlretrieve(download_url, downloadedZip)
if clobber and os.path.exists(local_folder_path):
- rmtree(local_folder_path)
+ shutil.rmtree(local_folder_path)
with zipfile.ZipFile(downloadedZip, 'r') as zip_ref:
zip_ref.extractall(local_folder_path)
@@ -43,3 +46,19 @@ def download_gitlfs(repo_url: str, checkout_folder: Path, branch_name: str):
file.close()
print('Done downloading git repo.')
+
+def download_botfs(repo_url: str, checkout_folder: Path, branch_name: str, bot_path: str, bot_dir_name: str):
+ print('Starting download of a git repo... this might take a while.')
+
+ shutil.rmtree(checkout_folder, ignore_errors=True)
+
+ # Download the most of the files eg. https://github.com/RLBot/RLBotPack/archive/master.zip
+ download_and_extract_zip(
+ download_url=repo_url + '/archive/' + branch_name + '.zip',
+ local_folder_path=checkout_folder, clobber=True)
+
+ repo_extraction_name = '/' + repo_url.split('/')[-1] + '-' + branch_name + '/'
+
+ shutil.copytree(checkout_folder + repo_extraction_name + '/' + bot_dir_name, bot_path + '/' + bot_dir_name)
+
+ print('Done downloading git repo.')
diff --git a/rlbot_gui/gui.py b/rlbot_gui/gui.py
index 20bb5c2..56fcbf3 100644
--- a/rlbot_gui/gui.py
+++ b/rlbot_gui/gui.py
@@ -1,4 +1,5 @@
import os
+import shutil
import eel
from PyQt5.QtCore import QSettings
@@ -15,15 +16,17 @@
from rlbot_gui.bot_management.bot_creation import bootstrap_python_bot
from rlbot_gui.bot_management.downloader import download_gitlfs
+from rlbot_gui.bot_management.downloader import download_botfs
from rlbot_gui.match_runner.match_runner import hot_reload_bots, shut_down, start_match_helper, do_infinite_loop_content
+import getpass
DEFAULT_BOT_FOLDER = 'default_bot_folder'
-BOTPACK_FOLDER = 'RLBotPackDeletable'
-OLD_BOTPACK_FOLDER = 'RLBotPack'
CREATED_BOTS_FOLDER = 'MyBots'
+HUB_FOLDER = 'repos'
BOT_FOLDER_SETTINGS_KEY = 'bot_folder_settings'
settings = QSettings('rlbotgui', 'preferences')
+
bot_folder_settings = settings.value(BOT_FOLDER_SETTINGS_KEY, type=dict)
if not bot_folder_settings:
@@ -147,6 +150,10 @@ def scan_for_bots():
for bot in bots:
bot_hash[bot['path']] = bot
+ bots = get_bots_from_directory("C:/Users/"+getpass.getuser()+"/AppData/Local/RLBotGUI/repos")
+ for bot in bots:
+ bot_hash[bot['path']] = bot
+
return list(bot_hash.values())
@@ -154,10 +161,12 @@ def get_bots_from_directory(bot_directory):
return [
{
'name': bundle.name,
+ 'safe': 1 if bundle.config_path.split('\\')[7] != 'unferifiedCommunity' and bundle.config_path.split('\\')[7] != 'localRepo' else 0,
'type': 'rlbot',
'skill': 1,
'image': 'imgs/rlbot.png',
'path': bundle.config_path,
+ 'display_path': bundle.config_path.split('\\')[7],
'info': read_info(bundle)
}
for bundle in scan_directory_for_bot_configs(bot_directory)]
@@ -208,24 +217,66 @@ def install_package(package_string):
@eel.expose
-def download_bot_pack():
- # The bot pack in now hosted at https://github.com/RLBot/RLBotPack
- download_gitlfs(
- repo_url="https://github.com/RLBot/RLBotPack",
- checkout_folder=BOTPACK_FOLDER,
- branch_name='master')
+def download_bot(repo_path, repo, bot_dir):
+ print(repo)
+ branch = ""
+ if "tree" in repo:
+ branch = repo.split('/')[-1]
+ repo = repo.split('/tree')[0]
+ else:
+ branch = "master"
- # Configure the folder settings.
- bot_folder_settings['folders'][os.path.abspath(BOTPACK_FOLDER)] = {'visible': True}
+ download_botfs(
+ repo_url=repo,
+ checkout_folder='download',
+ branch_name=branch,
+ bot_path=os.path.abspath(HUB_FOLDER) + '/' + repo_path,
+ bot_dir_name=bot_dir)
+ return 0;
- if os.path.abspath(OLD_BOTPACK_FOLDER) in bot_folder_settings['folders']:
- # Toggle off the old one since it's been replaced.
- bot_folder_settings['folders'][os.path.abspath(OLD_BOTPACK_FOLDER)] = {'visible': False}
- settings.setValue(BOT_FOLDER_SETTINGS_KEY, bot_folder_settings)
- settings.sync()
+@eel.expose
+def delete_bot(repo_path, name):
+ shutil.rmtree(os.path.abspath(HUB_FOLDER + "/" + repo_path + '/' + name))
+ return 0;
+
+
+@eel.expose
+def is_bot_installed(repo_path, bot_name):
+ if os.path.exists(HUB_FOLDER + "/" + repo_path + '/' + bot_name):
+ return True
+ else:
+ return False
+@eel.expose
+def get_bot_packaging(repo_path, bot_name):
+ bot_directory = HUB_FOLDER + "/" + repo_path
+ if os.path.exists(bot_directory+'/'+bot_name):
+ file = open(bot_directory+'/'+bot_name+'/botpackage.json',"r")
+ filestr = file.read()
+ file.close()
+ return filestr
+ else:
+ return False
+
+
+@eel.expose
+def read_local_repofile():
+ if os.path.exists('localRepo.json'):
+ file = open('localRepo.json',"r")
+ filestr = file.read()
+ file.close()
+ return filestr
+ else:
+ return False
+
+@eel.expose
+def write_local_repofile(jsonstr):
+ file = open('localRepo.json',"w")
+ file.write(jsonstr)
+ file.close()
+
@eel.expose
def show_bot_in_explorer(bot_cfg_path):
import subprocess
@@ -261,7 +312,7 @@ def begin_python_bot(bot_name):
def on_websocket_close(page, sockets):
global should_quit
- eel.sleep(3.0) # We might have just refreshed. Give the websocket a moment to reconnect.
+ eel.sleep(10.0) # We might have just refreshed. Give the websocket a moment to reconnect.
if not len(eel._websockets):
# At this point we think the browser window has been closed.
should_quit = True
@@ -296,7 +347,7 @@ def launch_eel(use_chrome):
# installed to pip locally using this technique https://stackoverflow.com/a/49684835
# The suppress_error=True avoids the error "'options' argument deprecated in v1.0.0", we need to keep the
# options argument since a lot of our user base has an older version of eel.
- eel.start('main.html', size=(1000, 800), block=False, callback=on_websocket_close, options=options,
+ eel.start('main.html', size=(1000, 830), block=False, callback=on_websocket_close, options=options,
disable_cache=True, mode=browser_mode, port=port, suppress_error=True)
@@ -314,4 +365,4 @@ def start():
while not should_quit:
do_infinite_loop_content()
- eel.sleep(1.0)
+ eel.sleep(1.0)
\ No newline at end of file
diff --git a/rlbot_gui/gui/botmanager.html b/rlbot_gui/gui/botmanager.html
new file mode 100644
index 0000000..4396f32
--- /dev/null
+++ b/rlbot_gui/gui/botmanager.html
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hub
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{mode.name}}
+
+
+
+
+ {{category.name}}
+
+
+
+
+ {{repo.description}}
+
+
+
+
+ {{repo.repoName}}
+
+
+
+
+
+
+
+
+
+
+ Add bot to local repo
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Begin
+ Close
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rlbot_gui/gui/css/botmanager.css b/rlbot_gui/gui/css/botmanager.css
new file mode 100644
index 0000000..1ecb8b5
--- /dev/null
+++ b/rlbot_gui/gui/css/botmanager.css
@@ -0,0 +1,50 @@
+@media screen and (orientation: landscape) {
+ html {
+ overflow-y: hidden;
+ }
+ aside {
+ width: 20vw;
+ float: left;
+ display: grid;
+ grid-template-rows: max-content auto auto;
+ }
+ aside > div {
+ display: initial;
+ }
+ aside, #main{
+ height: calc(100vh - 64px);
+ }
+ #main {
+ float: right;
+ overflow-y: auto;
+ width: 80vw;
+ }
+ .botinfo-short{
+ width: 10vw;
+ }
+ .botinfo-long{
+ width: 25vw;
+ }
+}
+
+.md-title > small {
+ font-size: small;
+}
+
+.md-layout-item > p > .gamemode:not(:last-of-type)::after, .md-layout-item > p > .category:not(:last-of-type)::after{
+ content: ', ';
+}
+
+.md-layout{
+ width: 100%;
+}
+
+.md-card{
+ display: grid;
+ overflow-y: hidden;
+}
+
+.md-card-content{
+ height: 100%;
+ overflow-y: auto;
+}
\ No newline at end of file
diff --git a/rlbot_gui/gui/css/style.css b/rlbot_gui/gui/css/style.css
index e872e72..c5d8b00 100644
--- a/rlbot_gui/gui/css/style.css
+++ b/rlbot_gui/gui/css/style.css
@@ -9,8 +9,9 @@
--blue-color: #0054a6;
}
-body {
- height: 100%;
+html, body {
+ height: 100vh;
+ overflow: hidden;
}
#app {
@@ -201,4 +202,23 @@ body {
color: #717171;
font-size: 10pt;
font-weight: normal;
+}
+
+.menuActive:not(:hover) {
+ text-decoration: underline solid white !important;
+}
+
+#hub{
+ position: fixed;
+ top: 64px;
+ z-index: 999;
+ left: 100vw;
+ width: 100vw;
+ height: 100vh !important;
+ transition-property: left;
+ transition-duration: 1s;
+}
+
+#menuRLBot{
+ text-decoration: underline solid white;
}
\ No newline at end of file
diff --git a/rlbot_gui/gui/js/botmanager.js b/rlbot_gui/gui/js/botmanager.js
new file mode 100644
index 0000000..5cfd39b
--- /dev/null
+++ b/rlbot_gui/gui/js/botmanager.js
@@ -0,0 +1,276 @@
+Vue.use(VueMaterial.default);
+
+const repolists = ['https://raw.githubusercontent.com/ard1998/RLBot-repos/master/trusted.json',
+ 'https://raw.githubusercontent.com/ard1998/RLBot-repos/master/unverifiedCommunity.json'];
+const newsLink = 'https://raw.githubusercontent.com/ard1998/RLBot-repos/master/news.json';
+var init = true
+
+const app = new Vue({
+ el: '#app',
+ data: {
+ repos: [],
+ newsItems: [],
+ showNewRepoDialog: false,
+ showProgressSpinner: false,
+ init: true,
+ filterChangeCount: 0,
+ highestCardID: -1
+ },
+ created: function () {
+ if (init) {
+ downloadRepos();
+ getNews();
+ init = false;
+ }
+ },
+ methods: {
+ downloadBot: async function (repo, update=false) {
+ if (!repo.safe && !update) {
+ if (!confirm('This download is from an unferified source and may contain unsafe code. Do you really want to download this')) {
+ return false;
+ }
+ }
+ await eel.download_bot(repo.repoName, repo.url, repo.name)(async function() {
+ var botpackage = await eel.get_bot_packaging(repo.repoName, repo.name)()
+
+ repo.is_installed = true;
+ repo.localVersion = JSON.parse(botpackage).version
+
+ app.highestCardID += 1;
+ repo.ID = app.highestCardID;
+
+ repo.push();
+ })
+ },
+ deleteBot: function (repo, update=false) {
+ eel.delete_bot(repo.repoName, repo.name)(function() {
+ if (!update) {
+ repo.is_installed = false;
+
+ app.highestCardID += 1;
+ repo.ID = app.highestCardID;
+
+ if (repo.repoName === 'localRepo') {
+ repo.display = false;
+ repo.deleted = true;
+ app.removeBotFromLocalRepofile(repo.url);
+ }
+
+ app.repo.push();
+ }
+ })
+ },
+ updateBot: function(repo){
+ delete_bot(repo, true)
+ download_bot(repo, true)
+ },
+ readRepofile: async function(){
+ var repostr = await eel.read_local_repofile()();
+ if (repostr === false) {
+ var repostr = '{"note": "Note when enabling the repo", "repos":[]}'
+ }
+ var obj = JSON.parse(repostr);
+ return obj;
+ },
+ addBotToLocalRepofile: async function(url, folder){
+ //todo directly downloading the bot after adding
+
+ var json = await app.readRepofile()
+ var len = json.repos.length
+
+ json.repos[len] = {};
+ json.repos[len].url = url;
+ json.repos[len].name = folder;
+ var jsonstr = JSON.stringify(json);
+ var repo = await downloadBotCard(json.repos[len], 'localRepo');
+
+ if(repo.error){
+ alert('Error fetching ' + repo.url + '/' + repo.name + ': Branch or folder does not exist');
+ }
+ else{
+ await eel.write_local_repofile(jsonstr)();
+ await app.downloadBot(repo, true)
+ app.showNewRepoDialog = false;
+ }
+ },
+ removeBotFromLocalRepofile: async function(url){
+ var json = await app.readRepofile()
+ var len = json.repos.length -1;
+ var found = false;
+
+ do{
+ if (json.repos[len].url === url) {
+ delete json.repos[len];
+ found = true;
+ }
+ --len;
+ }
+ while (len >=0 || !found)
+
+ var jsonstr = JSON.stringify(json);
+ jsonstr = jsonstr.replace("null", "");
+ eel.write_local_repofile(jsonstr);
+ }
+ }
+});
+
+function getNews(){
+ fetch(newsLink)
+ .then(function(response) {
+ return response.json();
+ })
+ .then(async function(myJson) {
+ console.log(myJson)
+ for (var i = 0; i < myJson.length; i++) {
+ app.newsItems.push(myJson[i])
+ }
+ });
+}
+
+async function downloadRepos() {
+ for (var i = 0; i < repolists.length; i++) {
+ await fetch(repolists[i])
+ .then(function(response) {
+ return response.json();
+ })
+ .then(async function(myJson) {
+ await addPackageData(i, myJson);
+ });
+ }
+ json = await app.readRepofile();
+ await addPackageData(repolists.length, json);
+}
+
+async function addPackageData(repoListIndex, json) {
+ repoName = ''
+
+ if (repoListIndex= 0; i--) {
+ var repo = app.repos[i];
+
+ //fill gamemode filters
+ var gamemodeFilters = [];
+ for (var j = document.getElementsByName('gamemode').length - 1; j >= 0; j--) {
+ if (document.getElementsByName('gamemode')[j].checked) {
+ arrLen = gamemodeFilters.length;
+ gamemodeFilters[arrLen] = document.getElementsByName('gamemode')[j].value.toLowerCase();
+ }
+ }
+
+ var gamemodeNotFoundCount = 0;
+ if (gamemodeFilters.length != 0) {
+ //if any active gamemode gamemodeFilters, apply them
+ var gamemodeNotFoundCount = gamemodeFilters.length;
+ for (var gamemodeFilterIndex = repo.gamemodes.length - 1; gamemodeFilterIndex >= 0; gamemodeFilterIndex--) {
+ if (gamemodeFilters.includes(repo.gamemodes[gamemodeFilterIndex].name.toLowerCase())) {
+ --gamemodeNotFoundCount;
+ }
+ }
+ }
+
+ //fill repo filters
+ var repoFilterPass = false;
+ var repoFilter = '';
+ for (var j = document.getElementsByName('source').length - 1; j >= 0; j--) {
+ if (document.getElementsByName('source')[j].checked) {
+ repoFilter = document.getElementsByName('source')[j].value.toLowerCase();
+ }
+ }
+ if (repo.repoName.toLowerCase() === repoFilter || repoFilter === 'all') {
+ repoFilterPass = true;
+ }
+
+ //display or hide cards
+ if (!repo.deleted && repoFilterPass && gamemodeNotFoundCount === 0 && repo.name.toLowerCase().indexOf(document.getElementById('searchBotName').value.toLowerCase())!==-1) {
+ app.repos[i].display=true;
+ }
+ else{
+ app.repos[i].display=false;
+ }
+
+ app.repos.push()
+ }
+}
\ No newline at end of file
diff --git a/rlbot_gui/gui/js/main.js b/rlbot_gui/gui/js/main.js
index 5b3f03e..8818523 100644
--- a/rlbot_gui/gui/js/main.js
+++ b/rlbot_gui/gui/js/main.js
@@ -6,10 +6,10 @@ function PythonPrint(message) {
Vue.use(VueMaterial.default);
const STARTING_BOT_POOL = [
- {'name': 'Human', 'type': 'human', 'image': 'imgs/human.png'},
- {'name': 'Psyonix Allstar', 'type': 'psyonix', 'skill': 1, 'image': 'imgs/psyonix.png'},
- {'name': 'Psyonix Pro', 'type': 'psyonix', 'skill': 0.5, 'image': 'imgs/psyonix.png'},
- {'name': 'Psyonix Rookie', 'type': 'psyonix', 'skill': 0, 'image': 'imgs/psyonix.png'}
+ {'name': 'Human', 'type': 'human', 'image': 'imgs/human.png', 'safe': true},
+ {'name': 'Psyonix Allstar', 'type': 'psyonix', 'skill': 1, 'image': 'imgs/psyonix.png', 'safe': true},
+ {'name': 'Psyonix Pro', 'type': 'psyonix', 'skill': 0.5, 'image': 'imgs/psyonix.png', 'safe': true},
+ {'name': 'Psyonix Rookie', 'type': 'psyonix', 'skill': 0, 'image': 'imgs/psyonix.png', 'safe': true}
];
const app = new Vue({
@@ -104,9 +104,14 @@ const app = new Vue({
this.showProgressSpinner = true;
eel.install_package(this.packageString)(onInstallationComplete);
},
- downloadBotPack: function() {
- this.showProgressSpinner = true;
- eel.download_bot_pack()(botPackDownloaded);
+ downloadBot: function(repo, branch) {
+ eel.download_bot(repo, branch)(downloadBotComplete);
+ },
+ deleteBot: function(name) {
+ eel.delete_bot(name)(deleteBotComplete);
+ },
+ isBotInstalled: function (name) {
+ return eel.isBotInstalled(name)
},
showBotInExplorer: function (botPath) {
eel.show_bot_in_explorer(botPath);
@@ -212,6 +217,14 @@ function onInstallationComplete(result) {
app.showProgressSpinner = false;
}
+function downloadBotComplete(){
+ console.log('test')
+}
+
+function deleteBotComplete(){
+ console.log('test')
+}
+
Vue.component('mutator-field', {
props: ['label', 'options', 'value'],
data: function() {
@@ -234,4 +247,18 @@ Vue.component('mutator-field', {
`
}
-);
\ No newline at end of file
+);
+
+function showHub(){
+ document.getElementById('menuHub').style.textDecoration = "underline solid white";
+ document.getElementById('menuRLBot').style.textDecoration = "none";
+ document.getElementById('hub').style.left = '0vw';
+}
+
+function hideHub(){
+ eel.get_folder_settings()(folderSettingsReceived);
+ eel.scan_for_bots()(botsReceived);
+ document.getElementById('menuHub').style.textDecoration = "none";
+ document.getElementById('menuRLBot').style.textDecoration = "underline solid white";
+ document.getElementById('hub').style.left = '100vw';
+}
\ No newline at end of file
diff --git a/rlbot_gui/gui/main.html b/rlbot_gui/gui/main.html
index b29f89a..a87ea8a 100644
--- a/rlbot_gui/gui/main.html
+++ b/rlbot_gui/gui/main.html
@@ -22,7 +22,8 @@
+