diff --git a/plugins/utilities.json b/plugins/utilities.json index 80af302..d134e88 100644 --- a/plugins/utilities.json +++ b/plugins/utilities.json @@ -14,6 +14,7 @@ } ], "versions": { + "1.3.0": null, "1.2.1": { "api_version": 7, "commit_sha": "7753b87", @@ -585,4 +586,4 @@ } } } -} \ No newline at end of file +} diff --git a/plugins/utilities/share_replay.py b/plugins/utilities/share_replay.py index 9909816..f442dcd 100644 --- a/plugins/utilities/share_replay.py +++ b/plugins/utilities/share_replay.py @@ -1,10 +1,24 @@ +""" + Plugin by LoupGarou a.k.a Loup/Soup + Discord →ʟօʊքɢǟʀօʊ#3063 +Share replays easily with your friends or have a backup + +Exported replays are stored in replays folder which is inside mods folder +You can start sharing replays by opening the watch window and going to share replay tab + +Feel free to let me know if you use this plugin,i love to hear that :) + +Message me in discord if you find some bug +Use this code for your experiments or plugin but please dont rename this plugin and distribute with your name,don't do that,its bad' +""" + # ba_meta require api 7 from __future__ import annotations from typing import TYPE_CHECKING, cast if TYPE_CHECKING: from typing import Any, Sequence, Callable, List, Dict, Tuple, Optional, Union -from os import listdir, mkdir, path, sep +from os import listdir, mkdir, path, sep,remove from shutil import copy, copytree import ba @@ -12,12 +26,23 @@ import _ba from enum import Enum from bastd.ui.tabs import TabRow from bastd.ui.confirm import ConfirmWindow -from bastd.ui.watch import WatchWindow as ww +from bastd.ui.watch import WatchWindow from bastd.ui.popup import PopupWindow -# mod by ʟօʊքɢǟʀօʊ -# export replays to mods folder and share with your friends or have a backup +title = "SHARE REPLAY" +internal_dir = _ba.get_replays_dir()+sep +external_dir = path.join(_ba.env()["python_directory_user"], "replays"+sep) +uiscale = ba.app.ui.uiscale + +# colors +pink = (1, 0.2, 0.8) +green = (0.4, 1, 0.4) +red = (1, 0, 0) +blue = (0.26, 0.65, 0.94) +blue_highlight = (0.4, 0.7, 1) +b_color = (0.6, 0.53, 0.63) +b_textcolor = (0.75, 0.7, 0.8) def Print(*args, color=None, top=None): out = "" @@ -35,174 +60,31 @@ def cprint(*args): _ba.chatmessage(out) -title = "SHARE REPLAY" -internal_dir = _ba.get_replays_dir()+sep -external_dir = path.join(_ba.env()["python_directory_user"], "replays"+sep) - -# colors -pink = (1, 0.2, 0.8) -green = (0.4, 1, 0.4) -red = (1, 0, 0) -blue = (0.26, 0.65, 0.94) -blue_highlight = (0.4, 0.7, 1) - if not path.exists(external_dir): mkdir(external_dir) Print("You are ready to share replays", color=pink) -class Help(PopupWindow): - def __init__(self): - uiscale = ba.app.ui.uiscale - self.width = 1000 - self.height = 300 - PopupWindow.__init__(self, - position=(0.0, 0.0), - size=(self.width, self.height), - scale=1.2,) +def override(cls: ClassType) -> Callable[[MethodType], MethodType]: + def decorator(newfunc: MethodType) -> MethodType: + funcname = newfunc.__code__.co_name + if hasattr(cls, funcname): + oldfunc = getattr(cls, funcname) + setattr(cls, f'_old_{funcname}', oldfunc) - ba.containerwidget(edit=self.root_widget, on_outside_click_call=self.close) - ba.textwidget(parent=self.root_widget, position=(0, self.height * 0.6), - text=f">Replays are exported to\n {external_dir}\n>Copy replays to the above folder to be able to import them into the game\n>I would live to hear from you,meet me on discord\n -LoupGarou(author)") + setattr(cls, funcname, newfunc) + return newfunc - def close(self): - ba.playsound(ba.getsound('swish')) - ba.containerwidget(edit=self.root_widget, transition="out_right",) + return decorator -class SettingWindow(): - def __init__(self): - self.draw_ui() - ba.containerwidget(edit=self.root, cancel_button=self.close_button) - self.selected_name = None - # setting tab when window opens - self.on_tab_select(self.TabId.INTERNAL) - self.tab_id = self.TabId.INTERNAL - - class TabId(Enum): - INTERNAL = "internal" - EXTERNAL = "external" +class CommonUtilities: def sync_confirmation(self): ConfirmWindow(text="WARNING:\nreplays with same name in mods folder\n will be overwritten", action=self.sync, cancel_is_selected=True) - def on_select_text(self, widget, name): - existing_widgets = self.scroll2.get_children() - for i in existing_widgets: - ba.textwidget(edit=i, color=(1, 1, 1)) - ba.textwidget(edit=widget, color=(1, 1, 0)) - self.selected_name = name - - def on_tab_select(self, tab_id): - self.tab_id = tab_id - if tab_id == self.TabId.INTERNAL: - dir_list = listdir(internal_dir) - ba.buttonwidget(edit=self.share_button, label="EXPORT", icon=ba.gettexture("upButton"),) - else: - dir_list = listdir(external_dir) - ba.buttonwidget(edit=self.share_button, label="IMPORT", - icon=ba.gettexture("downButton"),) - self.tab_row.update_appearance(tab_id) - dir_list = sorted(dir_list) - existing_widgets = self.scroll2.get_children() - if existing_widgets: - for i in existing_widgets: - i.delete() - height = 900 - # making textwidgets for all replays - for i in dir_list: - height -= 40 - a = i - i = ba.textwidget( - parent=self.scroll2, - size=(500, 50), - text=i.split(".")[0], - position=(10, height), - selectable=True, - max_chars=40, - corner_scale=1.3, - click_activate=True,) - ba.textwidget(edit=i, on_activate_call=ba.Call(self.on_select_text, i, a)) - - def draw_ui(self): - self.uiscale = ba.app.ui.uiscale - self.root = ba.Window(ba.containerwidget( - size=(900, 670), on_outside_click_call=self.close, transition="in_right")).get_root_widget() - - self.close_button = ba.buttonwidget( - parent=self.root, - position=(90, 560), - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK), - scale=1.5, - on_activate_call=self.close) - - ba.textwidget( - parent=self.root, - size=(200, 100), - position=(350, 550), - scale=2, - selectable=False, - h_align="center", - v_align="center", - text=title, - color=green) - - ba.buttonwidget( - parent=self.root, - position=(650, 580), - size=(35, 35), - texture=ba.gettexture("achievementEmpty"), - label="", - on_activate_call=Help) - - tabdefs = [(self.TabId.INTERNAL, 'INTERNAL'), (self.TabId.EXTERNAL, "EXTERNAL")] - self.tab_row = TabRow(self.root, tabdefs, pos=(150, 500-5), - size=(500, 300), on_select_call=self.on_tab_select) - - self.share_button = ba.buttonwidget( - parent=self.root, - position=(720, 400), - size=(110, 50), - scale=1.5, - button_type="square", - label="EXPORT", - text_scale=2, - icon=ba.gettexture("upButton"), - on_activate_call=self.share) - - sync_button = ba.buttonwidget( - parent=self.root, - position=(720, 300), - size=(110, 50), - scale=1.5, - button_type="square", - label="SYNC", - text_scale=2, - icon=ba.gettexture("ouyaYButton"), - on_activate_call=self.sync_confirmation) - - scroll = ba.scrollwidget( - parent=self.root, - size=(600, 400), - position=(100, 100),) - self.scroll2 = ba.columnwidget(parent=scroll, size=( - 500, 900)) - - def share(self): - if self.selected_name is None: - Print("Select a replay", color=red) - return - if self.tab_id == self.TabId.INTERNAL: - self.export() - else: - self.importx() - - # image={"texture":ba.gettexture("bombColor"),"tint_texture":None,"tint_color":None,"tint2_color":None}) - def sync(self): internal_list = listdir(internal_dir) external_list = listdir(external_dir) @@ -215,13 +97,233 @@ class SettingWindow(): copy(external_dir+sep+i, internal_dir+sep+i) Print("Synced all replays", color=pink) - def export(self): - copy(internal_dir+self.selected_name, external_dir+self.selected_name) - Print(self.selected_name[0:-4]+" exported", top=True, color=pink) + def _copy(self, selected_replay,tab_id): + if selected_replay is None: + Print("Select a replay", color=red) + return + elif tab_id==MyTabId.INTERNAL: + copy(internal_dir+selected_replay, external_dir+selected_replay) + Print(selected_replay[0:-4]+" exported", top=True, color=pink) + else: + copy(external_dir+selected_replay, internal_dir+selected_replay) + Print(selected_replay[0:-4]+" imported", top=True, color=green) + + def delete_replay(self,selected_replay,tab_id,cls_inst): + if selected_replay is None: + Print("Select a replay", color=red) + return + def do_it(): + if tab_id==MyTabId.INTERNAL: + remove(internal_dir+selected_replay) + elif tab_id==MyTabId.EXTERNAL: + remove(external_dir+selected_replay) + cls_inst.on_tab_select(tab_id) #updating the tab + Print(selected_replay[0:-4]+" was deleted", top=True, color=red) + ConfirmWindow(text=f"Delete \"{selected_replay.split('.')[0]}\" \nfrom {'internal directory' if tab_id==MyTabId.INTERNAL else 'external directory'}?", + action=do_it, cancel_is_selected=True) + + +CommonUtils = CommonUtilities() - def importx(self): - copy(external_dir+self.selected_name, internal_dir+self.selected_name) - Print(self.selected_name[0:-4]+" imported", top=True, color=green) + +class MyTabId(Enum): + INTERNAL = "internal" + EXTERNAL = "external" + SHARE_REPLAYS = "share_replay" + +class Help(PopupWindow): + def __init__(self): + self.width = 1200 + self.height = 250 + self.root_widget = ba.Window(ba.containerwidget( + size=(self.width, self.height), on_outside_click_call=self.close, transition="in_right")).get_root_widget() + + ba.containerwidget(edit=self.root_widget, on_outside_click_call=self.close) + ba.textwidget(parent=self.root_widget, position=(0, self.height * 0.7),corner_scale=1.2 ,color=green, + text=f"»Replays are exported to\n {external_dir}\n»Copy replays to the above folder to be able to import them into the game\n»I would love to hear from you,meet me on discord\n -LoupGarou(author)") + + def close(self): + ba.playsound(ba.getsound('swish')) + ba.containerwidget(edit=self.root_widget, transition="out_right",) + + +class ShareTabUi(WatchWindow): + def __init__(self, root_widget=None): + self.tab_id = MyTabId.INTERNAL + self.selected_replay = None + + if root_widget is None: + self.root = ba.Window(ba.containerwidget( + size=(1000, 600), on_outside_click_call=self.close, transition="in_right")).get_root_widget() + + else: + self.root = root_widget + + self.draw_ui() + + + def on_select_text(self, widget, name): + existing_widgets = self.scroll2.get_children() + for i in existing_widgets: + ba.textwidget(edit=i, color=(1, 1, 1)) + ba.textwidget(edit=widget, color=(1.0, 1, 0.4)) + self.selected_replay = name + + def on_tab_select(self, tab_id): + self.selected_replay = None + self.tab_id = tab_id + t_scale = 1.6 + + if tab_id == MyTabId.INTERNAL: + dir_list = listdir(internal_dir) + ba.buttonwidget(edit=self.share_button, label="Export\nReplay") + else: + dir_list = listdir(external_dir) + ba.buttonwidget(edit=self.share_button, label="Import\nReplay") + + self.tab_row.update_appearance(tab_id) + dir_list = sorted(dir_list) + existing_widgets = self.scroll2.get_children() + if existing_widgets:# deleting textwidgets from old tab + for i in existing_widgets: + i.delete() + height = 900 + for i in dir_list:# making textwidgets for all replays + height -= 50 + a = i + i = ba.textwidget( + parent=self.scroll2, + size=(self._my_replays_scroll_width/t_scale, 30), + text=i.split(".")[0], + position=(20, height), + selectable=True, + max_chars=40, + corner_scale=t_scale, + click_activate=True, + always_highlight=True,) + ba.textwidget(edit=i, on_activate_call=ba.Call(self.on_select_text, i, a)) + + def draw_ui(self): + self._r = 'watchWindow' + x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 + scroll_buffer_h = 130 + 2 * x_inset + self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 + self._height = ( + 578 + if uiscale is ba.UIScale.SMALL + else 670 + if uiscale is ba.UIScale.MEDIUM + else 800) + self._scroll_width = self._width - scroll_buffer_h + self._scroll_height = self._height - 180 + # + c_width = self._scroll_width + c_height = self._scroll_height - 20 + sub_scroll_height = c_height - 63 + self._my_replays_scroll_width = sub_scroll_width = ( + 680 if uiscale is ba.UIScale.SMALL else 640 + ) + + v = c_height - 30 + b_width = 140 if uiscale is ba.UIScale.SMALL else 178 + b_height = ( + 107 + if uiscale is ba.UIScale.SMALL + else 142 + if uiscale is ba.UIScale.MEDIUM + else 190 + ) + b_space_extra = ( + 0 + if uiscale is ba.UIScale.SMALL + else -2 + if uiscale is ba.UIScale.MEDIUM + else -5 + ) + + b_color = (0.6, 0.53, 0.63) + b_textcolor = (0.75, 0.7, 0.8) + btnv = (c_height- (48 + if uiscale is ba.UIScale.SMALL + else 45 + if uiscale is ba.UIScale.MEDIUM + else 40) - b_height) + btnh = 40 if uiscale is ba.UIScale.SMALL else 40 + smlh = 190 if uiscale is ba.UIScale.SMALL else 225 + tscl = 1.0 if uiscale is ba.UIScale.SMALL else 1.2 + + stab_width=500 + stab_height=300 + stab_h=smlh + + v -= sub_scroll_height + 23 + scroll = ba.scrollwidget( + parent=self.root, + position=(smlh, v), + size=(sub_scroll_width, sub_scroll_height), + ) + + self.scroll2 = ba.columnwidget(parent=scroll, + size=(sub_scroll_width, sub_scroll_height)) + + tabdefs = [(MyTabId.INTERNAL, 'INTERNAL'), (MyTabId.EXTERNAL, "EXTERNAL")] + self.tab_row = TabRow(self.root, tabdefs, pos=(stab_h,sub_scroll_height), + size=(stab_width,stab_height), on_select_call=self.on_tab_select) + + helpbtn_space=20 + helpbtn_v=stab_h+stab_width+helpbtn_space+120 + helpbtn_h=sub_scroll_height+helpbtn_space + + ba.buttonwidget( + parent=self.root, + position=(helpbtn_v ,helpbtn_h ), + size=(35, 35), + button_type="square", + label="?", + text_scale=1.5, + color=b_color, + textcolor=b_textcolor, + on_activate_call=Help) + + call_copy=lambda:CommonUtils._copy(self.selected_replay,self.tab_id) + self.share_button = ba.buttonwidget( + parent=self.root, + size=(b_width, b_height), + position=(btnh, btnv), + button_type="square", + label="Export\nReplay", + text_scale=tscl, + color=b_color, + textcolor=b_textcolor, + on_activate_call=call_copy) + + btnv -= b_height + b_space_extra + sync_button = ba.buttonwidget( + parent=self.root, + size=(b_width, b_height), + position=(btnh, btnv), + button_type="square", + label="Sync\nReplay", + text_scale=tscl, + color=b_color, + textcolor=b_textcolor, + on_activate_call=CommonUtils.sync_confirmation) + + btnv -= b_height + b_space_extra + call_delete = lambda:CommonUtils.delete_replay(self.selected_replay,self.tab_id,self) + delete_replay_button = ba.buttonwidget( + parent=self.root, + size=(b_width, b_height), + position=(btnh, btnv), + button_type="square", + label=ba.Lstr(resource=self._r + '.deleteReplayButtonText'), + text_scale=tscl, + color=b_color, + textcolor=b_textcolor, + on_activate_call=call_delete) + + + self.on_tab_select(MyTabId.INTERNAL) def close(self): ba.playsound(ba.getsound('swish')) @@ -232,33 +334,73 @@ class SettingWindow(): #ba.widget(edit=self.enable_button, up_widget=decrease_button, down_widget=self.lower_text,left_widget=save_button, right_widget=save_button) -# -------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------------------------- -ww.__old_init__ = ww.__init__ +class ShareTab(WatchWindow): + @override(WatchWindow) + def __init__(self, + transition: str | None = 'in_right', + origin_widget: ba.Widget | None = None, + oldmethod=None): + self.my_tab_container = None + self._old___init__(transition, origin_widget) -def new_init(self, transition="in_right", origin_widget=None): - self.__old_init__(transition, origin_widget) - self._share_button = ba.buttonwidget( - parent=self._root_widget, - position=(self._width*0.70, self._height*0.80), - size=(220, 60), - scale=1.0, - color=green, - icon=ba.gettexture('usersButton'), - iconscale=1.5, - label=title, - on_activate_call=SettingWindow) + self._tab_row.tabs[self.TabID.MY_REPLAYS].button.delete() # deleting old tab button + + tabdefs = [(self.TabID.MY_REPLAYS, + ba.Lstr(resource=self._r + '.myReplaysText'),), + (MyTabId.SHARE_REPLAYS, "Share Replays"),] + + uiscale = ba.app.ui.uiscale + x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 + tab_buffer_h = 750 + 2 * x_inset + self._tab_row = TabRow( + self._root_widget, + tabdefs, + pos=((tab_buffer_h / 1.5) * 0.5, self._height - 130), + size=((self._width - tab_buffer_h)*2, 50), + on_select_call=self._set_tab) + + self._tab_row.update_appearance(self.TabID.MY_REPLAYS) + + @override(WatchWindow) + def _set_tab(self, tab_id, oldfunc=None): + self._old__set_tab(tab_id) + if self.my_tab_container: + self.my_tab_container.delete() + if tab_id == MyTabId.SHARE_REPLAYS: + + scroll_left = (self._width - self._scroll_width) * 0.5 + scroll_bottom = self._height - self._scroll_height - 79 - 48 + + c_width = self._scroll_width + c_height = self._scroll_height - 20 + sub_scroll_height = c_height - 63 + self._my_replays_scroll_width = sub_scroll_width = ( + 680 if uiscale is ba.UIScale.SMALL else 640 + ) + + self.my_tab_container = ba.containerwidget( + parent=self._root_widget, + position=(scroll_left, + scroll_bottom + (self._scroll_height - c_height) * 0.5,), + size=(c_width, c_height), + background=False, + selection_loops_to_parent=True, + ) + + ShareTabUi(self.my_tab_container) # ba_meta export plugin class Loup(ba.Plugin): def on_app_running(self): - ww.__init__ = new_init + WatchWindow.__init__ = ShareTab.__init__ def has_settings_ui(self): return True def show_settings_ui(self, button): - SettingWindow() + Print("Open share replay tab in replay window to share your replays",color=blue)