diff --git a/plugins/utilities/advanced_party_window.py b/plugins/utilities/advanced_party_window.py new file mode 100644 index 0000000..889cfd4 --- /dev/null +++ b/plugins/utilities/advanced_party_window.py @@ -0,0 +1,2051 @@ +# -*- coding: utf-8 -*- +# ba_meta require api 7 + +# AdvancedPartyWindow by Mr.Smoothy + +# build on base of plasma's modifypartywindow + +# added many features + +# discord mr.smoothy#5824 + +# https://discord.gg/ucyaesh Join BCS +# Youtube : Hey Smoothy + +# added advanced ID revealer +# live ping support for bcs + +#Made by Mr.Smoothy - Plasma Boson +version_str = "7" + +import os,urllib +import os,sys,re,json,codecs,traceback,base64 +import threading +import time,copy,datetime,shutil + +from _thread import start_new_thread + +import urllib.request + +from typing import TYPE_CHECKING, cast + +import _ba +import ba +import time +import math +import threading + +from dataclasses import dataclass +from bastd.ui.popup import PopupMenuWindow,PopupWindow +from bastd.ui.confirm import ConfirmWindow +from bastd.ui.colorpicker import ColorPickerExact + +from typing import List, Sequence, Optional, Dict, Any, Union + +import bastd.ui.party as bastd_party +cache_chat=[] +connect=_ba.connect_to_party +disconnect=_ba.disconnect_from_host +unmuted_names=[] +smo_mode=3 +f_chat=False +chatlogger=False +screenmsg=True +ip_add="127.0.0.1" +p_port=43210 +p_name="local" +current_ping = 0.0 +enable_typing = False # this will prevent auto ping to update textwidget when user actually typing chat message +import ssl +ssl._create_default_https_context = ssl._create_unverified_context +def newconnect_to_party(address,port=43210,print_progress=False): + global ip_add + global p_port + dd=_ba.get_connection_to_host_info() + if(dd!={} ): + _ba.disconnect_from_host() + + ip_add=address + p_port=port + connect(address,port,print_progress) + else: + ip_add=address + p_port=port + # print(ip_add,p_port) + connect(ip_add,port,print_progress) + + +DEBUG_SERVER_COMMUNICATION = False +DEBUG_PROCESSING = False +class PingThread(threading.Thread): + """Thread for sending out game pings.""" + + def __init__(self): + super().__init__() + + def run(self) -> None: + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + ba.app.ping_thread_count += 1 + sock: Optional[socket.socket] = None + try: + import socket + from ba.internal import get_ip_address_type + socket_type = get_ip_address_type(ip_add) + sock = socket.socket(socket_type, socket.SOCK_DGRAM) + sock.connect((ip_add,p_port)) + + accessible = False + starttime = time.time() + + # Send a few pings and wait a second for + # a response. + sock.settimeout(1) + for _i in range(3): + sock.send(b'\x0b') + result: Optional[bytes] + try: + # 11: BA_PACKET_SIMPLE_PING + result = sock.recv(10) + except Exception: + result = None + if result == b'\x0c': + # 12: BA_PACKET_SIMPLE_PONG + accessible = True + break + time.sleep(1) + ping = (time.time() - starttime) * 1000.0 + global current_ping + current_ping = round(ping,2) + except ConnectionRefusedError: + # Fine, server; sorry we pinged you. Hmph. + pass + except OSError as exc: + import errno + + # Ignore harmless errors. + if exc.errno in { + errno.EHOSTUNREACH, errno.ENETUNREACH, errno.EINVAL, + errno.EPERM, errno.EACCES + }: + pass + elif exc.errno == 10022: + # Windows 'invalid argument' error. + pass + elif exc.errno == 10051: + # Windows 'a socket operation was attempted + # to an unreachable network' error. + pass + elif exc.errno == errno.EADDRNOTAVAIL: + if self._port == 0: + # This has happened. Ignore. + pass + elif ba.do_once(): + print(f'Got EADDRNOTAVAIL on gather ping' + f' for addr {self._address}' + f' port {self._port}.') + else: + ba.print_exception( + f'Error on gather ping ' + f'(errno={exc.errno})', once=True) + except Exception: + ba.print_exception('Error on gather ping', once=True) + finally: + try: + if sock is not None: + sock.close() + except Exception: + ba.print_exception('Error on gather ping cleanup', once=True) + + ba.app.ping_thread_count -= 1 + _ba.pushcall(update_ping, from_other_thread=True) + time.sleep(4) + self.run() + + +try: + import OnlineTranslator + tranTypes = [item for item in dir(OnlineTranslator) if item.startswith("Translator_")] + if "item" in globals():del item +except:tranTypes = []#;ba.print_exception() + +RecordFilesDir = os.path.join(_ba.env()["python_directory_user"],"Configs" + os.sep) +if not os.path.exists(RecordFilesDir):os.makedirs(RecordFilesDir) + +version_str = "3.0.1" + +Current_Lang = None + +SystemEncode = sys.getfilesystemencoding() +if not isinstance(SystemEncode,str): + SystemEncode = "utf-8" + +def update_ping(): + try: + _ba.set_ping_widget_value(current_ping) + except: + with _ba.Context('ui'): + if hasattr(_ba,"ping_widget") and _ba.ping_widget.exists(): + ba.textwidget(edit=_ba.ping_widget,text="Ping:"+str(current_ping)+" ms") + +PingThread().start() + +import datetime +try: + from ba._generated.enums import TimeType +except: + from ba._enums import TimeType +class chatloggThread(): + """Thread for sending out game pings.""" + def __init__(self): + super().__init__() + self.saved_msg=[] + def run(self) -> None: + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + global chatlogger + self.timerr=ba.Timer(5.0,self.chatlogg,repeat=True,timetype=TimeType.REAL) + def chatlogg(self): + global chatlogger + chats=_ba.get_chat_messages() + for msg in chats: + if msg in self.saved_msg: + pass + else: + self.save(msg) + self.saved_msg.append(msg) + if len(self.saved_msg) > 45: + self.saved_msg.pop(0) + if chatlogger: + pass + else: + self.timerr=None + def save(self,msg): + x=str(datetime.datetime.now()) + t=open(os.path.join(_ba.env()["python_directory_user"],"Chat logged.txt"),"a+") + t.write(x+" : "+ msg +"\n") + t.close() +class mututalServerThread(): + def run(self): + self.timer=ba.Timer(10,self.checkPlayers,repeat=True,timetype=TimeType.REAL) + def checkPlayers(self): + if _ba.get_connection_to_host_info()!={}: + server_name=_ba.get_connection_to_host_info()["name"] + players=[] + for ros in _ba.get_game_roster(): + players.append(ros["display_string"]) + start_new_thread(dump_mutual_servers,(players,server_name,)) + +def dump_mutual_servers(players,server_name): + filePath = os.path.join(RecordFilesDir, "players.json") + data={} + if os.path.isfile(filePath): + f=open(filePath,"r") + data=json.load(f) + for player in players: + if player in data: + if server_name not in data[player]: + data[player].insert(0,server_name) + data[player]=data[player][:3] + else: + data[player]=[server_name] + f=open(filePath,"w") + json.dump(data,f) +mututalServerThread().run() + + +class customchatThread(): + """.""" + + def __init__(self): + super().__init__() + global cache_chat + self.saved_msg=[] + chats=_ba.get_chat_messages() + for msg in chats: #fill up old chat , to avoid old msg popup + cache_chat.append(msg) + def run(self) -> None: + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + global chatlogger + self.timerr=ba.Timer(5.0,self.chatcheck,repeat=True,timetype=TimeType.REAL) + + def chatcheck(self): + global unmuted_names + global cache_chat + chats=_ba.get_chat_messages() + for msg in chats: + if msg in cache_chat: + pass + else: + if msg.split(":")[0] in unmuted_names: + ba.screenmessage(msg,color=(0.6,0.9,0.6)) + cache_chat.append(msg) + if len(self.saved_msg) > 45: + cache_chat.pop(0) + if ba.app.config.resolve('Chat Muted'): + pass + else: + self.timerr=None + +def chatloggerstatus(): + global chatlogger + if chatlogger: + return "Turn off Chat Logger" + else: + return "Turn on chat logger" + +def _getTransText(text , isBaLstr = False , same_fb = False): + global Current_Lang + global chatlogger + if Current_Lang != 'English': + Current_Lang = 'English' + global Language_Texts + Language_Texts = { + "Chinese": { + + }, + "English": { + "Add_a_Quick_Reply": "Add a Quick Reply", + "Admin_Command_Kick_Confirm": "Are you sure to use admin\ +command to kick %s?", + "Ban_For_%d_Seconds": "Ban for %d second(s).", + "Ban_Time_Post": "Enter the time you want to ban(Seconds).", + "Credits_for_This": "Credits for This", + "Custom_Action": "Custom Action", + "Debug_for_Host_Info": "Host Info Debug", + "Game_Record_Saved": "Game replay %s is saved.", + "Kick_ID": "Kick ID:%d", + "Mention_this_guy": "Mention this guy", + "Modify_Main_Color": "Modify Main Color", + "No_valid_player_found": "Can't find a valid player.", + "No_valid_player_id_found": "Can't find a valid player ID.", + "Normal_kick_confirm": "Are you sure to kick %s?", + "Remove_a_Quick_Reply": "Remove a Quick Reply", + "Restart_Game_Record": "Save Recording", + "Restart_Game_Record_Confirm": "Are you sure to restart recording game stream?", + "Send_%d_times": "Send for %d times", + "Something_is_added": "'%s' is added.", + "Something_is_removed": "'%s' is removed.", + "Times": "Times", + "Translator": "Translator", + "chatloggeroff":"Turn off Chat Logger", + "chatloggeron":"Turn on Chat Logger", + "screenmsgoff":"Hide ScreenMessage", + "screenmsgon":"Show ScreenMessage", + "unmutethisguy":"unmute this guy", + "mutethisguy":"mute this guy", + "muteall":"Mute all", + "unmuteall":"Unmute all" + + } + } + + Language_Texts = Language_Texts.get(Current_Lang) + try: + from Language_Packs import ModifiedPartyWindow_LanguagePack as ext_lan_pack + if isinstance(ext_lan_pack,dict) and isinstance(ext_lan_pack.get(Current_Lang),dict): + complete_Pack = ext_lan_pack.get(Current_Lang) + for key,item in complete_Pack.items(): + Language_Texts[key] = item + except: + pass + + return(Language_Texts.get(text,"#Unknown Text#" if not same_fb else text) if not isBaLstr else + ba.Lstr(resource = "??Unknown??",fallback_value = Language_Texts.get(text,"#Unknown Text#" if not same_fb else text))) + +def _get_popup_window_scale() -> float: + uiscale = ba.app.ui.uiscale + return(2.3 if uiscale is ba.UIScale.SMALL else + 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + +def _creat_Lstr_list(string_list : list = []) -> list: + return([ba.Lstr(resource = "??Unknown??",fallback_value = item) for item in string_list]) + + + + +customchatThread().run() + +class ModifiedPartyWindow(bastd_party.PartyWindow): + def __init__(self, origin: Sequence[float] = (0, 0)): + _ba.set_party_window_open(True) + self._r = 'partyWindow' + self.msg_user_selected='' + self._popup_type: Optional[str] = None + self._popup_party_member_client_id: Optional[int] = None + self._popup_party_member_is_host: Optional[bool] = None + self._width = 500 + + + uiscale = ba.app.ui.uiscale + self._height = (365 if uiscale is ba.UIScale.SMALL else + 480 if uiscale is ba.UIScale.MEDIUM else 600) + + #Custom color here + self._bg_color = ba.app.config.get("PartyWindow_Main_Color",(0.40, 0.55, 0.20)) if not isinstance(self._getCustomSets().get("Color"),(list,tuple)) else self._getCustomSets().get("Color") + if not isinstance(self._bg_color,(list,tuple)) or not len(self._bg_color) == 3:self._bg_color = (0.40, 0.55, 0.20) + + + + + ba.Window.__init__(self,root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_scale', + color=self._bg_color, + parent=_ba.get_special_widget('overlay_stack'), + on_outside_click_call=self.close_with_sound, + scale_origin_stack_offset=origin, + scale=(2.0 if uiscale is ba.UIScale.SMALL else + 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), + stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else ( + 240, 0) if uiscale is ba.UIScale.MEDIUM else (330, 20))) + + self._cancel_button = ba.buttonwidget(parent=self._root_widget, + scale=0.7, + position=(30, self._height - 47), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2) + self._smoothy_button = ba.buttonwidget(parent=self._root_widget, + scale=0.6, + position=(5, self._height - 47 -40), + size=(50, 50), + label='69', + on_activate_call=self.smoothy_roster_changer, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('replayIcon'), + iconscale=1.2) + ba.containerwidget(edit=self._root_widget, + cancel_button=self._cancel_button) + + self._menu_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.7, + position=(self._width - 60, self._height - 47), + size=(50, 50), + label="\xee\x80\x90", + autoselect=True, + button_type='square', + on_activate_call=ba.WeakCall(self._on_menu_button_press), + color=(0.55, 0.73, 0.25), + icon=ba.gettexture('menuButton'), + iconscale=1.2) + + info = _ba.get_connection_to_host_info() + if info.get('name', '') != '': + title = info['name'] + else: + title = ba.Lstr(resource=self._r + '.titleText') + + self._title_text = ba.textwidget(parent=self._root_widget, + scale=0.9, + color=(0.5, 0.7, 0.5), + text=title, + size=(120, 20), + position=(self._width * 0.5-60, + self._height - 29), + on_select_call=self.title_selected, + selectable=True, + maxwidth=self._width * 0.7, + h_align='center', + v_align='center') + + self._empty_str = ba.textwidget(parent=self._root_widget, + scale=0.75, + size=(0, 0), + position=(self._width * 0.5, + self._height - 65), + maxwidth=self._width * 0.85, + text="no one", + h_align='center', + v_align='center') + + self._scroll_width = self._width - 50 + self._scrollwidget = ba.scrollwidget(parent=self._root_widget, + size=(self._scroll_width, + self._height - 200), + position=(30, 80), + color=(0.4, 0.6, 0.3)) + self._columnwidget = ba.columnwidget(parent=self._scrollwidget, + border=2, + margin=0) + ba.widget(edit=self._menu_button, down_widget=self._columnwidget) + + self._muted_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.5), + size=(0, 0), + h_align='center', + v_align='center', + text="") + self._chat_texts: List[ba.Widget] = [] + self._chat_texts_haxx: List[ba.Widget] = [] + + # add all existing messages if chat is not muted + # print("updates") + if True: #smoothy - always show chat in partywindow + msgs = _ba.get_chat_messages() + for msg in msgs: + self._add_msg(msg) + # print(msg) + # else: + # msgs=_ba.get_chat_messages() + # for msg in msgs: + # print(msg); + # txt = ba.textwidget(parent=self._columnwidget, + # text=msg, + # h_align='left', + # v_align='center', + # size=(0, 13), + # scale=0.55, + # maxwidth=self._scroll_width * 0.94, + # shadow=0.3, + # flatness=1.0) + # self._chat_texts.append(txt) + # if len(self._chat_texts) > 40: + # first = self._chat_texts.pop(0) + # first.delete() + # ba.containerwidget(edit=self._columnwidget, visible_child=txt) + self.ping_widget = txt = ba.textwidget( + parent=self._root_widget, + scale = 0.6, + size=(20, 5), + color=(0.45, 0.63, 0.15), + position=(self._width/2 -20, 50), + text='', + selectable=True, + autoselect=False, + v_align='center') + _ba.ping_widget = self.ping_widget + + def enable_chat_mode(): + pass + + self._text_field = txt = ba.textwidget( + parent=self._root_widget, + editable=True, + size=(530-80, 40), + position=(44+60, 39), + text='', + maxwidth=494, + shadow=0.3, + flatness=1.0, + description=ba.Lstr(resource=self._r + '.chatMessageText'), + autoselect=True, + v_align='center', + corner_scale=0.7) + + + # for m in _ba.get_chat_messages(): + # if m: + # ttchat=ba.textwidget( + # parent=self._columnwidget, + # size=(10,10), + # h_align='left', + # v_align='center', + # text=str(m), + # scale=0.6, + # flatness=0, + # color=(2,2,2), + # shadow=0, + # always_highlight=True + + # ) + ba.widget(edit=self._scrollwidget, + autoselect=True, + left_widget=self._cancel_button, + up_widget=self._cancel_button, + down_widget=self._text_field) + ba.widget(edit=self._columnwidget, + autoselect=True, + up_widget=self._cancel_button, + down_widget=self._text_field) + ba.containerwidget(edit=self._root_widget, selected_child=txt) + btn = ba.buttonwidget(parent=self._root_widget, + size=(50, 35), + label=ba.Lstr(resource=self._r + '.sendText'), + button_type='square', + autoselect=True, + position=(self._width - 70, 35), + on_activate_call=self._send_chat_message) + + + + def _times_button_on_click(): + # self._popup_type = "send_Times_Press" + # allow_range = 100 if _ba.get_foreground_host_session() is not None else 4 + # PopupMenuWindow(position=self._times_button.get_screen_space_center(), + # scale=_get_popup_window_scale(), + # choices=[str(index) for index in range(1,allow_range + 1)], + # choices_display=_creat_Lstr_list([_getTransText("Send_%d_times")%int(index) for index in range(1,allow_range + 1)]), + # current_choice="Share_Server_Info", + # delegate=self) + Quickreply = self._get_quick_responds() + if len(Quickreply) > 0: + PopupMenuWindow(position=self._times_button.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=Quickreply, + choices_display=_creat_Lstr_list(Quickreply), + current_choice=Quickreply[0], + delegate=self) + self._popup_type = "QuickMessageSelect" + + self._send_msg_times = 1 + + self._times_button = ba.buttonwidget(parent=self._root_widget, + size=(50, 35), + label="Quick", + button_type='square', + autoselect=True, + position=(30, 35), + on_activate_call=_times_button_on_click) + + ba.textwidget(edit=txt, on_return_press_call=btn.activate) + self._name_widgets: List[ba.Widget] = [] + self._roster: Optional[List[Dict[str, Any]]] = None + + self.smoothy_mode=1 + self.full_chat_mode=False + self._update_timer = ba.Timer(1.0, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL) + + self._update() + def title_selected(self): + + self.full_chat_mode= self.full_chat_mode ==False + self._update() + def smoothy_roster_changer(self): + + self.smoothy_mode=(self.smoothy_mode+1)%3 + + + + self._update() + + def on_chat_message(self, msg: str) -> None: + """Called when a new chat message comes through.""" + # print("on_chat"+msg) + if True: + self._add_msg(msg) + def _on_chat_press(self,msg,widget): + global unmuted_names + if msg.split(":")[0] in unmuted_names: + + choices=['mute'] + choices_display=[_getTransText("mutethisguy",isBaLstr = True)] + else: + choices=['unmute'] + choices_display=[_getTransText("unmutethisguy",isBaLstr = True)] + PopupMenuWindow(position=widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=choices_display, + current_choice="@ this guy", + delegate=self) + self.msg_user_selected=msg.split(":")[0] + self._popup_type = "chatmessagepress" + + # _ba.chatmessage("pressed") + + + def _add_msg(self, msg: str) -> None: + try: + if ba.app.config.resolve('Chat Muted'): + + + txt = ba.textwidget(parent=self._columnwidget, + text=msg, + h_align='left', + v_align='center', + size=(130, 13), + scale=0.55, + position=(-0.6,0), + selectable=True, + click_activate=True, + maxwidth=self._scroll_width * 0.94, + shadow=0.3, + flatness=1.0) + ba.textwidget(edit=txt, + on_activate_call=ba.Call( + self._on_chat_press, + msg,txt)) + else: + txt = ba.textwidget(parent=self._columnwidget, + text=msg, + h_align='left', + v_align='center', + size=(0, 13), + scale=0.55, + + + + maxwidth=self._scroll_width * 0.94, + shadow=0.3, + flatness=1.0) + + # btn = ba.buttonwidget(parent=self._columnwidget, + # scale=0.7, + # size=(100,20), + # label="smoothy buttin", + # icon=ba.gettexture('replayIcon'), + # texture=None, + # ) + self._chat_texts_haxx.append(txt) + if len(self._chat_texts_haxx) > 40: + first = self._chat_texts_haxx.pop(0) + first.delete() + ba.containerwidget(edit=self._columnwidget, visible_child=txt) + except Exception: + pass + + def _add_msg_when_muted(self, msg: str) -> None: + + txt = ba.textwidget(parent=self._columnwidget, + text=msg, + h_align='left', + v_align='center', + size=(0, 13), + scale=0.55, + maxwidth=self._scroll_width * 0.94, + shadow=0.3, + flatness=1.0) + self._chat_texts.append(txt) + if len(self._chat_texts) > 40: + first = self._chat_texts.pop(0) + first.delete() + ba.containerwidget(edit=self._columnwidget, visible_child=txt) + def color_picker_closing(self, picker) -> None: + ba._appconfig.commit_app_config() + def color_picker_selected_color(self, picker, color) -> None: + #bs.animateArray(self._root_widget,"color",3,{0:self._bg_color,1500:color}) + ba.containerwidget(edit=self._root_widget,color=color) + self._bg_color = color + ba.app.config["PartyWindow_Main_Color"] = color + def _on_nick_rename_press(self,arg) -> None: + + ba.containerwidget(edit=self._root_widget, transition='out_scale') + c_width = 600 + c_height = 250 + uiscale = ba.app.ui.uiscale + self._nick_rename_window = cnt = ba.containerwidget( + + scale=(1.8 if uiscale is ba.UIScale.SMALL else + 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0), + size=(c_width, c_height), + transition='in_scale') + + ba.textwidget(parent=cnt, + size=(0, 0), + h_align='center', + v_align='center', + text='Enter nickname', + maxwidth=c_width * 0.8, + position=(c_width * 0.5, c_height - 60)) + id=self._get_nick(arg) + self._player_nick_text = txt89 = ba.textwidget( + parent=cnt, + size=(c_width * 0.8, 40), + h_align='left', + v_align='center', + text=id, + editable=True, + description='Players nick name', + position=(c_width * 0.1, c_height - 140), + autoselect=True, + maxwidth=c_width * 0.7, + max_chars=200) + cbtn = ba.buttonwidget( + parent=cnt, + label=ba.Lstr(resource='cancelText'), + on_activate_call=ba.Call( + lambda c: ba.containerwidget(edit=c, transition='out_scale'), + cnt), + size=(180, 60), + position=(30, 30), + autoselect=True) + okb = ba.buttonwidget(parent=cnt, + label='Rename', + size=(180, 60), + position=(c_width - 230, 30), + on_activate_call=ba.Call( + self._add_nick,arg), + autoselect=True) + ba.widget(edit=cbtn, right_widget=okb) + ba.widget(edit=okb, left_widget=cbtn) + ba.textwidget(edit=txt89, on_return_press_call=okb.activate) + ba.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) + def _add_nick(self,arg): + config = ba.app.config + new_name_raw = cast(str, ba.textwidget(query=self._player_nick_text)) + if arg: + if not isinstance(config.get('players nick'), dict): + config['players nick'] = {} + config['players nick'][arg] = new_name_raw + config.commit() + ba.containerwidget(edit=self._nick_rename_window, + transition='out_scale') + # ba.containerwidget(edit=self._root_widget,transition='in_scale') + def _get_nick(self,id): + config=ba.app.config + if not isinstance(config.get('players nick'), dict): + return "add nick" + elif id in config['players nick']: + return config['players nick'][id] + else: + return "add nick" + + def _reset_game_record(self) -> None: + try: + dir_path = _ba.get_replays_dir();curFilePath = os.path.join(dir_path+os.sep,"__lastReplay.brp").encode(SystemEncode) + newFileName = str(ba.Lstr(resource="replayNameDefaultText").evaluate()+" (%s)"%(datetime.datetime.strftime(datetime.datetime.now(),"%Y_%m_%d_%H_%M_%S"))+".brp") + newFilePath = os.path.join(dir_path+os.sep,newFileName).encode(SystemEncode) + #print(curFilePath, newFilePath) + #os.rename(curFilePath,newFilePath) + shutil.copyfile(curFilePath, newFilePath) + _ba.reset_game_activity_tracking() + ba.screenmessage(_getTransText("Game_Record_Saved")%newFileName,color = (1,1,1)) + except:ba.print_exception();ba.screenmessage(ba.Lstr(resource="replayWriteErrorText").evaluate()+"\ +"+traceback.format_exc(),color = (1,0,0)) + def _on_menu_button_press(self) -> None: + is_muted = ba.app.config.resolve('Chat Muted') + global chatlogger + choices = ["unmute" if is_muted else "mute","screenmsg","addQuickReply","removeQuickReply","chatlogger","credits"] + DisChoices = [_getTransText("unmuteall",isBaLstr = True) if is_muted else _getTransText("muteall",isBaLstr = True), + _getTransText("screenmsgoff",isBaLstr = True) if screenmsg else _getTransText("screenmsgon",isBaLstr = True), + + _getTransText("Add_a_Quick_Reply",isBaLstr = True), + _getTransText("Remove_a_Quick_Reply",isBaLstr = True), + _getTransText("chatloggeroff",isBaLstr = True) if chatlogger else _getTransText("chatloggeron",isBaLstr = True), + _getTransText("Credits_for_This",isBaLstr = True) + ] + + if len(tranTypes) > 0 : + choices.append("translator");DisChoices.append(_getTransText("Translator",isBaLstr = True)) + + choices.append("resetGameRecord") + DisChoices.append(_getTransText("Restart_Game_Record",isBaLstr = True)) + if self._getCustomSets().get("Enable_HostInfo_Debug",False): + choices.append("hostInfo_Debug");DisChoices.append(_getTransText("Debug_for_Host_Info",isBaLstr = True)) + + PopupMenuWindow( + position=self._menu_button.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=DisChoices, + current_choice="unmute" if is_muted else "mute", delegate=self) + self._popup_type = "menu" + def _on_party_member_press(self, client_id: int, is_host: bool, + widget: ba.Widget) -> None: + # if we"re the host, pop up "kick" options for all non-host members + if _ba.get_foreground_host_session() is not None: + kick_str = ba.Lstr(resource="kickText") + else:kick_str = ba.Lstr(resource="kickVoteText") + choices = ["kick","@ this guy","info","adminkick"] + + choices_display = [kick_str,_getTransText("Mention_this_guy",isBaLstr = True),ba.Lstr(resource="??Unknown??",fallback_value="Info"), + ba.Lstr(resource = "??Unknown??",fallback_value = _getTransText("Kick_ID")%client_id)] + + try: + if len(self._getCustomSets().get("partyMemberPress_Custom") if isinstance(self._getCustomSets().get("partyMemberPress_Custom"),dict) else {}) > 0: + choices.append("customAction");choices_display.append(_getTransText("Custom_Action",isBaLstr = True)) + except:ba.print_exception() + + + + PopupMenuWindow(position=widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=choices_display, + current_choice="@ this guy", + delegate=self) + self._popup_party_member_client_id = client_id + self._popup_party_member_is_host = is_host + self._popup_type = "partyMemberPress" + + def _send_chat_message(self) -> None: + sendtext = ba.textwidget(query=self._text_field) + if sendtext==".ip": + _ba.chatmessage("IP "+ip_add+" PORT "+str(p_port)) + + ba.textwidget(edit=self._text_field,text="") + return + elif sendtext==".info": + if _ba.get_connection_to_host_info() == {}: + s_build=0 + else: + s_build = _ba.get_connection_to_host_info()['build_number'] + s_v="0" + if s_build <=14365: + s_v=" 1.4.148 or below" + elif s_build <=14377: + s_v="1.4.148 < x < = 1.4.155 " + elif s_build>=20001 and s_build < 20308: + s_v ="1.5" + elif s_build >= 20308 and s_build < 20591: + s_v="1.6 " + else: + s_v ="1.7 and above " + _ba.chatmessage("script version "+s_v+"- build "+str(s_build)) + ba.textwidget(edit=self._text_field,text="") + return + elif sendtext==".ping disabled": + PingThread(ip_add, p_port).start() + ba.textwidget(edit=self._text_field,text="") + return + elif sendtext==".save": + info = _ba.get_connection_to_host_info() + config = ba.app.config + if info.get('name', '') != '': + title = info['name'] + if not isinstance(config.get('Saved Servers'), dict): + config['Saved Servers'] = {} + config['Saved Servers'][f'{ip_add}@{p_port}'] = { + 'addr': ip_add, + 'port': p_port, + 'name': title + } + config.commit() + ba.screenmessage("Server saved to manual") + ba.playsound(ba.getsound('gunCocking')) + ba.textwidget(edit=self._text_field,text="") + return + # elif sendtext != "": + # for index in range(getattr(self,"_send_msg_times",1)): + if '\\' in sendtext: + sendtext = sendtext.replace('\\d', ('\ue048')) + sendtext = sendtext.replace('\\c', ('\ue043')) + sendtext = sendtext.replace('\\h', ('\ue049')) + sendtext = sendtext.replace('\\s', ('\ue046')) + sendtext = sendtext.replace('\\n', ('\ue04b')) + sendtext = sendtext.replace('\\f', ('\ue04f')) + sendtext = sendtext.replace('\\g', ('\ue027')) + sendtext = sendtext.replace('\\i', ('\ue03a')) + sendtext = sendtext.replace('\\m', ('\ue04d')) + sendtext = sendtext.replace('\\t', ('\ue01f')) + sendtext = sendtext.replace('\\bs', ('\ue01e')) + sendtext = sendtext.replace('\\j', ('\ue010')) + sendtext = sendtext.replace('\\e', ('\ue045')) + sendtext = sendtext.replace('\\l', ('\ue047')) + sendtext = sendtext.replace('\\a', ('\ue020')) + sendtext = sendtext.replace('\\b', ('\ue00c')) + if sendtext=="": + sendtext=" " + msg=sendtext + msg1=msg.split(" ") + ms2="" + if(len(msg1)>11): + hp=int(len(msg1)/2) + + for m in range (0,hp): + ms2=ms2+" "+msg1[m] + + _ba.chatmessage(ms2) + + ms2="" + for m in range (hp,len(msg1)): + ms2=ms2+" "+msg1[m] + _ba.chatmessage(ms2) + else: + _ba.chatmessage(msg) + + ba.textwidget(edit=self._text_field,text="") + # else: + # Quickreply = self._get_quick_responds() + # if len(Quickreply) > 0: + # PopupMenuWindow(position=self._text_field.get_screen_space_center(), + # scale=_get_popup_window_scale(), + # choices=Quickreply, + # choices_display=_creat_Lstr_list(Quickreply), + # current_choice=Quickreply[0], + # delegate=self) + # self._popup_type = "QuickMessageSelect" + # else: + # _ba.chatmessage(sendtext) + # ba.textwidget(edit=self._text_field,text="") + def _get_quick_responds(self): + if not hasattr(self,"_caches") or not isinstance(self._caches,dict):self._caches = {} + try: + filePath = os.path.join(RecordFilesDir,"Quickmessage.txt") + + if os.path.exists(RecordFilesDir) is not True: + os.makedirs(RecordFilesDir) + + if not os.path.isfile(filePath): + with open(filePath,"wb") as writer: + writer.write(({"Chinese":u"\xe5\x8e\x89\xe5\xae\xb3\xef\xbc\x8c\xe8\xbf\x98\xe6\x9c\x89\xe8\xbf\x99\xe7\xa7\x8d\xe9\xaa\x9a\xe6\x93\x8d\xe4\xbd\x9c!\ +\xe4\xbd\xa0\xe2\x84\xa2\xe8\x83\xbd\xe5\x88\xab\xe6\x89\x93\xe9\x98\x9f\xe5\x8f\x8b\xe5\x90\x97\xef\xbc\x9f\ +\xe5\x8f\xaf\xe4\xbb\xa5\xe5\x95\x8a\xe5\xb1\x85\xe7\x84\xb6\xe8\x83\xbd\xe8\xbf\x99\xe4\xb9\x88\xe7\x8e\xa9\xef\xbc\x9f"}.get(Current_Lang,"What the hell?\nDude that's amazing!")).encode("UTF-8")) + if os.path.getmtime(filePath) != self._caches.get("Vertify_Quickresponse_Text"): + with open(filePath,"rU", encoding = "UTF-8-sig") as Reader: + Text = Reader.read() + if Text.startswith(str(codecs.BOM_UTF8)): + Text = Text[3:] + self._caches["quickReplys"] = (Text).split("\\n") + self._caches["Vertify_Quickresponse_Text"] = os.path.getmtime(filePath) + return(self._caches.get("quickReplys",[])) + except:ba.print_exception();ba.screenmessage(ba.Lstr(resource="errorText"),(1,0,0));ba.playsound(ba.getsound("error")) + def _write_quick_responds(self,data): + try: + with open(os.path.join(RecordFilesDir,"Quickmessage.txt"),"wb") as writer: + writer.write("\\n".join(data).encode("utf-8")) + except:ba.print_exception();ba.screenmessage(ba.Lstr(resource="errorText"),(1,0,0));ba.playsound(ba.getsound("error")) + def _getCustomSets(self): + try: + if not hasattr(self,"_caches") or not isinstance(self._caches,dict):self._caches = {} + try: + from VirtualHost import MainSettings + if MainSettings.get("Custom_PartyWindow_Sets",{}) != self._caches.get("PartyWindow_Sets",{}): + self._caches["PartyWindow_Sets"] = MainSettings.get("Custom_PartyWindow_Sets",{}) + except: + try: + filePath = os.path.join(RecordFilesDir,"Settings.json") + if os.path.isfile(filePath): + if os.path.getmtime(filePath) != self._caches.get("Vertify_MainSettings.json_Text"): + with open(filePath,"rU", encoding = "UTF-8-sig") as Reader: + Text = Reader.read() + if Text.startswith(str(codecs.BOM_UTF8)): + Text = Text[3:] + self._caches["PartyWindow_Sets"] = json.loads(Text.decode("utf-8")).get("Custom_PartyWindow_Sets",{}) + self._caches["Vertify_MainSettings.json_Text"] = os.path.getmtime(filePath) + except:ba.print_exception() + return(self._caches.get("PartyWindow_Sets") if isinstance(self._caches.get("PartyWindow_Sets"),dict) else {}) + + except:ba.print_exception() + def _getObjectByID(self,type = "playerName",ID = None): + if ID is None:ID = self._popup_party_member_client_id + type = type.lower();output = [] + for roster in self._roster: + if type.startswith("all"): + if type in ("roster","fullrecord"):output += [roster] + elif type.find("player") != -1 and roster["players"] != []: + if type.find("namefull") != -1:output += [(i["name_full"]) for i in roster["players"]] + elif type.find("name") != -1:output += [(i["name"]) for i in roster["players"]] + elif type.find("playerid") != -1:output += [i["id"] for i in roster["players"]] + elif type.lower() in ("account","displaystring"):output += [(roster["display_string"])] + elif roster["client_id"] == ID and not type.startswith("all"): + try: + if type in ("roster","fullrecord"):return(roster) + elif type.find("player") != -1 and roster["players"] != []: + if len(roster["players"]) == 1 or type.find("singleplayer") != -1: + if type.find("namefull") != -1:return((roster["players"][0]["name_full"])) + elif type.find("name") != -1:return((roster["players"][0]["name"])) + elif type.find("playerid") != -1:return(roster["players"][0]["id"]) + else: + if type.find("namefull") != -1:return([(i["name_full"]) for i in roster["players"]]) + elif type.find("name") != -1:return([(i["name"]) for i in roster["players"]]) + elif type.find("playerid") != -1:return([i["id"] for i in roster["players"]]) + elif type.lower() in ("account","displaystring"):return((roster["display_string"])) + except:ba.print_exception() + + return(None if len(output) == 0 else output) + def _edit_text_msg_box(self,text,type = "rewrite"): + if not isinstance(type,str) or not isinstance(text,str):return + type = type.lower();text = (text) + if type.find("add") != -1:ba.textwidget(edit=self._text_field,text=ba.textwidget(query=self._text_field)+text) + else:ba.textwidget(edit=self._text_field,text=text) + def _send_admin_kick_command(self):_ba.chatmessage("/kick " + str(self._popup_party_member_client_id)) + def new_input_window_callback(self,got_text, flag, code): + if got_text: + if flag.startswith("Host_Kick_Player:"): + try: + result = _ba.disconnect_client(self._popup_party_member_client_id, ban_time=int(code)) + if not result: + ba.playsound(ba.getsound('error')) + ba.screenmessage( + ba.Lstr(resource='getTicketsWindow.unavailableText'), + color=(1, 0, 0)) + except: + ba.playsound(ba.getsound('error')) + print(traceback.format_exc()) + + + def _kick_selected_player(self): + """ + result = _ba._disconnectClient(self._popup_party_member_client_id,banTime) + if not result: + ba.playsound(ba.getsound("error")) + ba.screenmessage(ba.Lstr(resource="getTicketsWindow.unavailableText"),color=(1,0,0)) + """ + if self._popup_party_member_client_id != -1: + if _ba.get_foreground_host_session() is not None: + self._popup_type = "banTimePress" + choices = [0,30,60,120,300,600,900,1800,3600,7200,99999999] if not (isinstance(self._getCustomSets().get("Ban_Time_List"),list) + and all([isinstance(item,int) for item in self._getCustomSets().get("Ban_Time_List")])) else self._getCustomSets().get("Ban_Time_List") + PopupMenuWindow(position=self.get_root_widget().get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=[str(item) for item in choices], + choices_display=_creat_Lstr_list([_getTransText("Ban_For_%d_Seconds")%item for item in choices]), + current_choice="Share_Server_Info", + delegate=self) + """ + NewInputWindow(origin_widget = self.get_root_widget(), + delegate = self,post_text = _getTransText("Ban_Time_Post"), + default_code = "300",flag = "Host_Kick_Player:"+str(self._popup_party_member_client_id)) + """ + else: + # kick-votes appeared in build 14248 + if (_ba.get_connection_to_host_info().get('build_number', 0) < + 14248): + ba.playsound(ba.getsound('error')) + ba.screenmessage( + ba.Lstr(resource='getTicketsWindow.unavailableText'), + color=(1, 0, 0)) + else: + + # Ban for 5 minutes. + result = _ba.disconnect_client(self._popup_party_member_client_id, ban_time=5 * 60) + if not result: + ba.playsound(ba.getsound('error')) + ba.screenmessage( + ba.Lstr(resource='getTicketsWindow.unavailableText'), + color=(1, 0, 0)) + else: + ba.playsound(ba.getsound('error')) + ba.screenmessage( + ba.Lstr(resource='internal.cantKickHostError'), + color=(1, 0, 0)) + + #NewShareCodeWindow(origin_widget=self.get_root_widget(), delegate=None,code = "300",execText = u"_ba._disconnectClient(%d,{Value})"%self._popup_party_member_client_id) + def joinbombspot(self): + import random + url=['https://discord.gg/CbxhJTrRta','https://discord.gg/ucyaesh'] + ba.open_url(url[random.randint(0,1)]) + def _update(self) -> None: + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + # pylint: disable=too-many-nested-blocks + + # # update muted state + # if ba.app.config.resolve('Chat Muted'): + # ba.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.3)) + # # clear any chat texts we're showing + # if self._chat_texts: + # while self._chat_texts: + # first = self._chat_texts.pop() + # first.delete() + # else: + # ba.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.0)) + + # update roster section + + roster = _ba.get_game_roster() + global f_chat + global smo_mode + if roster != self._roster or smo_mode!=self.smoothy_mode or f_chat!=self.full_chat_mode: + self._roster = roster + smo_mode=self.smoothy_mode + f_chat=self.full_chat_mode + # clear out old + for widget in self._name_widgets: + widget.delete() + self._name_widgets = [] + + if not self._roster : + top_section_height = 60 + ba.textwidget(edit=self._empty_str, + text=ba.Lstr(resource=self._r + '.emptyText')) + ba.scrollwidget(edit=self._scrollwidget, + size=(self._width - 50, + self._height - top_section_height - 110), + position=(30, 80)) + elif self.full_chat_mode: + top_section_height = 60 + ba.scrollwidget(edit=self._scrollwidget, + size=(self._width - 50, + self._height - top_section_height - 75), + position=(30, 80)) + + else: + columns = 1 if len( + self._roster) == 1 else 2 if len(self._roster) == 2 else 3 + rows = int(math.ceil(float(len(self._roster)) / columns)) + c_width = (self._width * 0.9) / max(3, columns) + c_width_total = c_width * columns + c_height = 24 + c_height_total = c_height * rows + for y in range(rows): + for x in range(columns): + index = y * columns + x + if index < len(self._roster): + t_scale = 0.65 + pos = (self._width * 0.53 - c_width_total * 0.5 + + c_width * x - 23, + self._height - 65 - c_height * y - 15) + + # if there are players present for this client, use + # their names as a display string instead of the + # client spec-string + try: + if self.smoothy_mode ==1 and self._roster[index]['players']: + # if there's just one, use the full name; + # otherwise combine short names + if len(self._roster[index] + ['players']) == 1: + p_str = self._roster[index]['players'][ + 0]['name_full'] + else: + p_str = ('/'.join([ + entry['name'] for entry in + self._roster[index]['players'] + ])) + if len(p_str) > 25: + p_str = p_str[:25] + '...' + elif self.smoothy_mode==0 : + p_str = self._roster[index][ + 'display_string'] + p_str= self._get_nick(p_str) + + else: + p_str = self._roster[index][ + 'display_string'] + + + except Exception: + ba.print_exception( + 'Error calcing client name str.') + p_str = '???' + try: + widget = ba.textwidget(parent=self._root_widget, + position=(pos[0], pos[1]), + scale=t_scale, + size=(c_width * 0.85, 30), + maxwidth=c_width * 0.85, + color=(1, 1, + 1) if index == 0 else + (1, 1, 1), + selectable=True, + autoselect=True, + click_activate=True, + text=ba.Lstr(value=p_str), + h_align='left', + v_align='center') + self._name_widgets.append(widget) + except Exception: + pass + # in newer versions client_id will be present and + # we can use that to determine who the host is. + # in older versions we assume the first client is + # host + if self._roster[index]['client_id'] is not None: + is_host = self._roster[index][ + 'client_id'] == -1 + else: + is_host = (index == 0) + + # FIXME: Should pass client_id to these sort of + # calls; not spec-string (perhaps should wait till + # client_id is more readily available though). + try: + ba.textwidget(edit=widget, + on_activate_call=ba.Call( + self._on_party_member_press, + self._roster[index]['client_id'], + is_host, widget)) + except Exception: + pass + pos = (self._width * 0.53 - c_width_total * 0.5 + + c_width * x, + self._height - 65 - c_height * y) + + # Make the assumption that the first roster + # entry is the server. + # FIXME: Shouldn't do this. + if is_host: + twd = min( + c_width * 0.85, + _ba.get_string_width( + p_str, suppress_warning=True) * + t_scale) + try: + self._name_widgets.append( + ba.textwidget( + parent=self._root_widget, + position=(pos[0] + twd + 1, + pos[1] - 0.5), + size=(0, 0), + h_align='left', + v_align='center', + maxwidth=c_width * 0.96 - twd, + color=(0.1, 1, 0.1, 0.5), + text=ba.Lstr(resource=self._r + + '.hostText'), + scale=0.4, + shadow=0.1, + flatness=1.0)) + except Exception: + pass + try: + ba.textwidget(edit=self._empty_str, text='') + ba.scrollwidget(edit=self._scrollwidget, + size=(self._width - 50, + max(100, self._height - 139 - + c_height_total)), + position=(30, 80)) + except Exception: + pass + def hide_screen_msg(self): + file=open('ba_data/data/languages/english.json') + eng=json.loads(file.read()) + file.close() + eng['internal']['playerJoinedPartyText']='' + eng['internal']['playerLeftPartyText']='' + eng['internal']['chatBlockedText']='' + eng['kickVoteStartedText']='' + # eng['kickVoteText']='' + eng['kickWithChatText']='' + eng['kickOccurredText']='' + eng['kickVoteFailedText']='' + eng['votesNeededText']='' + eng['playerDelayedJoinText']='' + eng['playerLeftText']='' + eng['kickQuestionText']='' + file=open('ba_data/data/languages/english.json',"w") + json.dump(eng,file) + file.close() + ba.app.lang.setlanguage(None) + + def restore_screen_msg(self): + file=open('ba_data/data/languages/english.json') + eng=json.loads(file.read()) + file.close() + eng['internal']['playerJoinedPartyText']="${NAME} joined the pawri!" + eng['internal']['playerLeftPartyText']="${NAME} left the pawri." + eng['internal']['chatBlockedText']="${NAME} is chat-blocked for ${TIME} seconds." + eng['kickVoteStartedText']="A kick vote has been started for ${NAME}." + # eng['kickVoteText']='' + eng['kickWithChatText']="Type ${YES} in chat for yes and ${NO} for no." + eng['kickOccurredText']="${NAME} was kicked." + eng['kickVoteFailedText']="Kick-vote failed." + eng['votesNeededText']="${NUMBER} votes needed" + eng['playerDelayedJoinText']="${PLAYER} will enter at the start of the next round." + eng['playerLeftText']="${PLAYER} left the game." + eng['kickQuestionText']="Kick ${NAME}?" + file=open('ba_data/data/languages/english.json',"w") + json.dump(eng,file) + file.close() + ba.app.lang.setlanguage(None) + def popup_menu_selected_choice(self, popup_window: PopupMenuWindow, + choice: str) -> None: + """Called when a choice is selected in the popup.""" + global unmuted_names + if self._popup_type == "banTimePress": + result = _ba.disconnect_client(self._popup_party_member_client_id, ban_time=int(choice)) + if not result: + ba.playsound(ba.getsound('error')) + ba.screenmessage( + ba.Lstr(resource='getTicketsWindow.unavailableText'), + color=(1, 0, 0)) + elif self._popup_type == "send_Times_Press": + self._send_msg_times = int(choice) + ba.buttonwidget(edit = self._times_button,label="%s:%d"%(_getTransText("Times"),getattr(self,"_send_msg_times",1))) + + elif self._popup_type == "chatmessagepress": + if choice=="mute": + + unmuted_names.remove(self.msg_user_selected) + if choice=="unmute": + unmuted_names.append(self.msg_user_selected) + + + elif self._popup_type == "partyMemberPress": + if choice == "kick": + ConfirmWindow(text=_getTransText("Normal_kick_confirm")%self._getObjectByID("account"), + action=self._kick_selected_player, cancel_button=True, cancel_is_selected=True, + color=self._bg_color, text_scale=1.0, + origin_widget=self.get_root_widget()) + elif choice=="info": + account=self._getObjectByID("account") + + self.loading_widget= ConfirmWindow(text="Searching .....", + color=self._bg_color, text_scale=1.0,cancel_button=False, + origin_widget=self.get_root_widget()) + start_new_thread(fetchAccountInfo,(account,self.loading_widget,)) + + elif choice == "adminkick": + ConfirmWindow(text=_getTransText("Admin_Command_Kick_Confirm")%self._getObjectByID("account"), + action=self._send_admin_kick_command, cancel_button=True, cancel_is_selected=True, + color=self._bg_color, text_scale=1.0, + origin_widget=self.get_root_widget()) + + elif choice == "@ this guy": + ChoiceDis = [];NewChoices = [] + account=self._getObjectByID("account") + ChoiceDis.append(account) + temp = self._getObjectByID("playerNameFull") + if temp is not None: + if isinstance(temp,str) and temp not in ChoiceDis:ChoiceDis.append(temp) + elif isinstance(temp,(list,tuple)): + for item in temp: + if isinstance(item,str) and item not in ChoiceDis:ChoiceDis.append(item) + #print("r\\"" + + for item in ChoiceDis: + NewChoices.append(u"self._edit_text_msg_box('%s','add')"%(item.replace("'",r"'").replace('"',r'\\"'))) + + else: + nick=self._get_nick(account) + ChoiceDis.append(nick) + NewChoices.append(u"self._on_nick_rename_press('%s')"%(account)) + p = PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=NewChoices, + choices_display=_creat_Lstr_list(ChoiceDis), + current_choice=NewChoices[0], + delegate=self) + self._popup_type = "Custom_Exec_Choice" + elif choice == "customAction": + customActionSets = self._getCustomSets() + customActionSets = customActionSets.get("partyMemberPress_Custom") if isinstance(customActionSets.get("partyMemberPress_Custom"),dict) else {} + ChoiceDis = [];NewChoices = [] + for key,item in customActionSets.items(): + ChoiceDis.append(key);NewChoices.append(item) + if len(ChoiceDis) > 0: + p = PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=NewChoices, + choices_display=_creat_Lstr_list(ChoiceDis), + current_choice=NewChoices[0], + delegate=self) + self._popup_type = "customAction_partyMemberPress" + else:ba.playsound(ba.getsound("error"));ba.screenmessage(ba.Lstr(resource="getTicketsWindow.unavailableText"),color=(1,0,0)) + elif self._popup_type == "menu": + if choice in ("mute", "unmute"): + cfg = ba.app.config + cfg['Chat Muted'] = (choice == 'mute') + cfg.apply_and_commit() + if cfg['Chat Muted']: + customchatThread().run() + self._update() + elif choice in ("credits",): + ConfirmWindow(text="AdvancePartyWindow by Mr.Smoothy \n extended version of ModifyPartyWindow(Plasma Boson) \n Version 5.3 \n Dont modify or release the source code \n Discord : \n mr.smoothy#5824 Plasma Boson#4104", + action=self.joinbombspot, width=420,height=200, + cancel_button=False, cancel_is_selected=False, + color=self._bg_color, text_scale=1.0, ok_text="More mods >", cancel_text=None, + origin_widget=self.get_root_widget()) + elif choice =="chatlogger": + # ColorPickerExact(parent=self.get_root_widget(), position=self.get_root_widget().get_screen_space_center(), + # initial_color=self._bg_color, delegate=self, tag='') + global chatlogger + if chatlogger: + chatlogger=False + ba.screenmessage("Chat logger turned OFF") + else: + chatlogger=True + chatloggThread().run() + ba.screenmessage("Chat logger turned ON") + elif choice=='screenmsg': + global screenmsg + if screenmsg: + screenmsg=False + self.hide_screen_msg() + else: + screenmsg=True + self.restore_screen_msg() + elif choice == "addQuickReply": + try: + newReply = ba.textwidget(query=self._text_field) + data = self._get_quick_responds() + data.append(newReply) + self._write_quick_responds(data) + ba.screenmessage(_getTransText("Something_is_added")%newReply,color=(0,1,0)) + ba.playsound(ba.getsound("dingSmallHigh")) + except:ba.print_exception() + elif choice == "removeQuickReply": + Quickreply = self._get_quick_responds() + PopupMenuWindow(position=self._text_field.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=Quickreply, + choices_display=_creat_Lstr_list(Quickreply), + current_choice=Quickreply[0], + delegate=self) + self._popup_type = "removeQuickReplySelect" + elif choice in ("hostInfo_Debug",) and isinstance(_ba.get_connection_to_host_info(),dict): + if len(_ba.get_connection_to_host_info()) > 0: + #print(_ba.get_connection_to_host_info(),type(_ba.get_connection_to_host_info())) + + ChoiceDis = list(_ba.get_connection_to_host_info().keys()) + NewChoices = ["ba.screenmessage(str(_ba.get_connection_to_host_info().get('%s')))"%((str(i)).replace("'",r"'").replace('"',r'\\"')) for i in ChoiceDis] + PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=NewChoices, + choices_display=_creat_Lstr_list(ChoiceDis), + current_choice=NewChoices[0], + delegate=self) + + self._popup_type = "Custom_Exec_Choice" + else:ba.playsound(ba.getsound("error"));ba.screenmessage(ba.Lstr(resource="getTicketsWindow.unavailableText"),color=(1,0,0)) + elif choice == "translator": + chats = _ba._getChatMessages() + if len(chats) > 0: + choices = [(item) for item in chats[::-1]] + PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=_creat_Lstr_list(choices), + current_choice=choices[0], + delegate=self) + self._popup_type = "translator_Press" + else:ba.playsound(ba.getsound("error"));ba.screenmessage(ba.Lstr(resource="getTicketsWindow.unavailableText"),color=(1,0,0)) + elif choice == "resetGameRecord": + ConfirmWindow(text=_getTransText("Restart_Game_Record_Confirm"), + action=self._reset_game_record, cancel_button=True, cancel_is_selected=True, + color=self._bg_color, text_scale=1.0, + origin_widget=self.get_root_widget()) + elif self._popup_type == "translator_Press": + + if len(tranTypes) == 1: + exec("start_new_thread(OnlineTranslator.%s,(u'%s',))"%(tranTypes[0],choice.replace("'",r"'").replace('"',r'\\"'))) + elif len(tranTypes) > 1: + choices = ["start_new_thread(OnlineTranslator.%s,(u'%s',))"%(item,choice.replace("'",r"'").replace('"',r'\\"')) for item in tranTypes] + + PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=_creat_Lstr_list(tranTypes), + current_choice=choices[0], + delegate=self) + self._popup_type = "Custom_Exec_Choice" + else:ba.playsound(ba.getsound("error"));ba.screenmessage(ba.Lstr(resource="getTicketsWindow.unavailableText"),color=(1,0,0)) + elif self._popup_type == "customAction_partyMemberPress": + + try: + keyReplaceValue = (r"{$PlayerNameFull}",r"{$PlayerName}",r"{$PlayerID}",r"{$AccountInfo}",r"{$AllPlayerName}",r"{$AllPlayerNameFull}") + pos = None;curKeyWord = None + for keyWord in keyReplaceValue: + CurPos = choice.find(keyWord) + if CurPos != -1 and (pos is None or CurPos < pos): + pos = CurPos;curKeyWord = keyWord + if isinstance(pos,int) and isinstance(curKeyWord,str): + if curKeyWord in (r"{$PlayerNameFull}",r"{$PlayerName}",r"{$AllPlayerName}",r"{$AllPlayerNameFull}"): + #if choice.count(curKeyWord) != 0: + playerName = self._getObjectByID(curKeyWord.replace("{$","").replace("}","")) + if isinstance(playerName,(list,tuple)): + ChoiceDis = [];NewChoices = [] + for i in playerName: + ChoiceDis.append(i) + NewChoices.append(choice.replace(curKeyWord,(i.replace("'",r"'").replace('"',r'\\"')),1)) + p = PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=NewChoices, + choices_display=_creat_Lstr_list(ChoiceDis), + current_choice=NewChoices[0], + delegate=self) + self._popup_type = "customAction_partyMemberPress" + elif isinstance(playerName,str): + self.popup_menu_selected_choice(popup_window,choice.replace(curKeyWord,(playerName.replace("'",r"'").replace('"',r'\\"')),1)) + else:ba.screenmessage(_getTransText("No_valid_player_found"),(1,0,0));ba.playsound(ba.getsound("error")) + elif curKeyWord in (r"{$PlayerID}",) != 0: + playerID = self._getObjectByID("PlayerID") + playerName = self._getObjectByID("PlayerName") + #print(playerID,playerName) + if isinstance(playerID,(list,tuple)) and isinstance(playerName,(list,tuple)) and len(playerName) == len(playerID): + ChoiceDis = [];NewChoices = [] + for i1,i2 in playerName,playerID: + ChoiceDis.append(i1);NewChoices.append(choice.replace(r"{$PlayerID}",str(i2).replace("'",r"'").replace('"',r'\\"')),1) + p = PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=NewChoices, + choices_display=_creat_Lstr_list(ChoiceDis), + current_choice=NewChoices[0], + delegate=self) + self._popup_type = "customAction_partyMemberPress" + elif isinstance(playerID,int): + self.popup_menu_selected_choice(popup_window,choice.replace(r"{$PlayerID}",str(playerID).replace("'",r"'").replace('"',r'\\"'))) + else:ba.screenmessage(_getTransText("No_valid_player_id_found"),(1,0,0));ba.playsound(ba.getsound("error")) + elif curKeyWord in (r"{$AccountInfo}",) != 0: + self.popup_menu_selected_choice(popup_window,choice.replace(r"{$AccountInfo}",(str(self._getObjectByID("roster"))).replace("'",r"'").replace('"',r'\\"'),1)) + else:exec(choice) + except Exception as e:ba.screenmessage(repr(e),(1,0,0)) + elif self._popup_type == "QuickMessageSelect": + #ba.textwidget(edit=self._text_field,text=self._get_quick_responds()[index]) + self._edit_text_msg_box(choice,"add") + elif self._popup_type == "removeQuickReplySelect": + data = self._get_quick_responds() + if len(data) > 0 and choice in data: + data.remove(choice) + self._write_quick_responds(data) + ba.screenmessage(_getTransText("Something_is_removed")%choice,(1,0,0)) + ba.playsound(ba.getsound("shieldDown")) + else:ba.screenmessage(ba.Lstr(resource="errorText"),(1,0,0));ba.playsound(ba.getsound("error")) + elif choice.startswith("custom_Exec_Choice_") or self._popup_type == "Custom_Exec_Choice": + exec(choice[len("custom_Exec_Choice_"):] if choice.startswith("custom_Exec_Choice_") else choice) + else: + print("unhandled popup type: "+str(self._popup_type)) + + +import base64 +from ba._general import Call +def fetchAccountInfo(account,loading_widget): + pbid="" + account_data=[] + servers=[] + try: + filePath = os.path.join(RecordFilesDir, "players.json") + fdata = {} + if os.path.isfile(filePath): + f = open(filePath, "r") + fdata = json.load(f) + if account in fdata: + servers=fdata[account] + data = urllib.request.urlopen(f'https://api.bombsquad.ga/player?key={base64.b64encode(account.encode("utf-8")).decode("utf-8")}&base64=true') + account_data=json.loads(data.read().decode('utf-8'))[0] + pbid=account_data["pbid"] + + except: + pass + # _ba.pushcall(Call(updateAccountWindow,loading_widget,accounts[0]),from_other_thread=True) + _ba.pushcall(Call(CustomAccountViewerWindow,pbid,account_data,servers,loading_widget),from_other_thread =True) + +from bastd.ui.account import viewer + +class CustomAccountViewerWindow(viewer.AccountViewerWindow): + def __init__(self,account_id,custom_data,servers,loading_widget): + super().__init__(account_id) + try: + loading_widget._cancel() + except: + pass + self.custom_data=custom_data + self.pb_id=account_id + self.servers=servers + def _copy_pb(self): + ba.clipboard_set_text(self.pb_id) + ba.screenmessage(ba.Lstr(resource='gatherWindow.copyCodeConfirmText')) + def _on_query_response(self,data): + + if data is None: + ba.textwidget(edit=self._loading_text,text="") + ba.textwidget(parent=self._scrollwidget, + size=(0, 0), + position=(170, 200), + flatness=1.0, + h_align='center', + v_align='center', + scale=0.5, + color=ba.app.ui.infotextcolor, + text="Mutual servers", + maxwidth=300) + v=200-21 + for server in self.servers: + ba.textwidget(parent=self._scrollwidget, + size=(0, 0), + position=(170, v), + h_align='center', + v_align='center', + scale=0.55, + text=server, + maxwidth=300) + v-=23 + else: + for account in self.custom_data["accounts"]: + if account not in data["accountDisplayStrings"]: + data["accountDisplayStrings"].append(account) + try: + self._loading_text.delete() + trophystr = '' + try: + trophystr = data['trophies'] + num = 10 + chunks = [ + trophystr[i:i + num] + for i in range(0, len(trophystr), num) + ] + trophystr = ('\n\n'.join(chunks)) + if trophystr == '': + trophystr = '-' + except Exception: + ba.print_exception('Error displaying trophies.') + account_name_spacing = 15 + tscale = 0.65 + ts_height = _ba.get_string_height(trophystr, + suppress_warning=True) + sub_width = self._width - 80 + sub_height = 500 + ts_height * tscale + \ + account_name_spacing * len(data['accountDisplayStrings']) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(sub_width, sub_height), + background=False) + v = sub_height - 20 + + title_scale = 0.37 + center = 0.3 + maxwidth_scale = 0.45 + showing_character = False + if data['profileDisplayString'] is not None: + tint_color = (1, 1, 1) + try: + if data['profile'] is not None: + profile = data['profile'] + character = ba.app.spaz_appearances.get( + profile['character'], None) + if character is not None: + tint_color = (profile['color'] if 'color' + in profile else (1, 1, 1)) + tint2_color = (profile['highlight'] + if 'highlight' in profile else + (1, 1, 1)) + icon_tex = character.icon_texture + tint_tex = character.icon_mask_texture + mask_texture = ba.gettexture( + 'characterIconMask') + ba.imagewidget( + parent=self._subcontainer, + position=(sub_width * center - 40, v - 80), + size=(80, 80), + color=(1, 1, 1), + mask_texture=mask_texture, + texture=ba.gettexture(icon_tex), + tint_texture=ba.gettexture(tint_tex), + tint_color=tint_color, + tint2_color=tint2_color) + v -= 95 + except Exception: + ba.print_exception('Error displaying character.') + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.9, + color=ba.safecolor(tint_color, 0.7), + shadow=1.0, + text=ba.Lstr(value=data['profileDisplayString']), + maxwidth=sub_width * maxwidth_scale * 0.75) + showing_character = True + v -= 33 + + center = 0.75 if showing_character else 0.5 + maxwidth_scale = 0.45 if showing_character else 0.9 + + v = sub_height - 20 + if len(data['accountDisplayStrings']) <= 1: + account_title = ba.Lstr( + resource='settingsWindow.accountText') + else: + account_title = ba.Lstr( + resource='accountSettingsWindow.accountsText', + fallback_resource='settingsWindow.accountText') + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=account_title, + maxwidth=sub_width * maxwidth_scale) + draw_small = (showing_character + or len(data['accountDisplayStrings']) > 1) + v -= 14 if draw_small else 20 + for account_string in data['accountDisplayStrings']: + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55 if draw_small else 0.8, + text=account_string, + maxwidth=sub_width * maxwidth_scale) + v -= account_name_spacing + + v += account_name_spacing + v -= 25 if showing_character else 29 + # ======================================================================= + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=str(self.pb_id), + maxwidth=sub_width * maxwidth_scale) + self._copy_btn = ba.buttonwidget( + parent=self._subcontainer, + position=(sub_width * center -120, v -9), + size=(60, 30), + scale=0.5, + label='copy', + color=(0.6, 0.5, 0.6), + on_activate_call=self._copy_pb, + autoselect=True) + + v-=24 + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text="Name", + maxwidth=sub_width * maxwidth_scale) + v-=26 + for name in self.custom_data["names"]: + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.51, + text=name, + maxwidth=sub_width * maxwidth_scale) + v-=13 + v-=8 + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text="Created On", + maxwidth=sub_width * maxwidth_scale) + v-=19 + d=self.custom_data["createdOn"] + + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=d[:d.index("T")], + maxwidth=sub_width * maxwidth_scale) + v-=29 + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text="Discord", + maxwidth=sub_width * maxwidth_scale) + v -= 19 + if len(self.custom_data["discord"]) >0: + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=self.custom_data["discord"][0]["username"]+ ","+self.custom_data["discord"][0]["id"], + maxwidth=sub_width * maxwidth_scale) + v -= 26 + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text="Mutual servers", + maxwidth=sub_width * maxwidth_scale) + v=-19 + v=270 + for server in self.servers: + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=server, + maxwidth=sub_width * maxwidth_scale) + v-=13 + + v-=16 + #================================================================== + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=ba.Lstr(resource='rankText'), + maxwidth=sub_width * maxwidth_scale) + v -= 14 + if data['rank'] is None: + rank_str = '-' + suffix_offset = None + else: + str_raw = ba.Lstr( + resource='league.rankInLeagueText').evaluate() + # FIXME: Would be nice to not have to eval this. + rank_str = ba.Lstr( + resource='league.rankInLeagueText', + subs=[('${RANK}', str(data['rank'][2])), + ('${NAME}', + ba.Lstr(translate=('leagueNames', + data['rank'][0]))), + ('${SUFFIX}', '')]).evaluate() + rank_str_width = min( + sub_width * maxwidth_scale, + _ba.get_string_width(rank_str, suppress_warning=True) * + 0.55) + + # Only tack our suffix on if its at the end and only for + # non-diamond leagues. + if (str_raw.endswith('${SUFFIX}') + and data['rank'][0] != 'Diamond'): + suffix_offset = rank_str_width * 0.5 + 2 + else: + suffix_offset = None + + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=rank_str, + maxwidth=sub_width * maxwidth_scale) + if suffix_offset is not None: + assert data['rank'] is not None + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center + suffix_offset, + v + 3), + h_align='left', + v_align='center', + scale=0.29, + flatness=1.0, + text='[' + str(data['rank'][1]) + ']') + v -= 14 + + str_raw = ba.Lstr( + resource='league.rankInLeagueText').evaluate() + old_offs = -50 + prev_ranks_shown = 0 + for prev_rank in data['prevRanks']: + rank_str = ba.Lstr( + value='${S}: ${I}', + subs=[ + ('${S}', + ba.Lstr(resource='league.seasonText', + subs=[('${NUMBER}', str(prev_rank[0]))])), + ('${I}', + ba.Lstr(resource='league.rankInLeagueText', + subs=[('${RANK}', str(prev_rank[3])), + ('${NAME}', + ba.Lstr(translate=('leagueNames', + prev_rank[1]))), + ('${SUFFIX}', '')])) + ]).evaluate() + rank_str_width = min( + sub_width * maxwidth_scale, + _ba.get_string_width(rank_str, suppress_warning=True) * + 0.3) + + # Only tack our suffix on if its at the end and only for + # non-diamond leagues. + if (str_raw.endswith('${SUFFIX}') + and prev_rank[1] != 'Diamond'): + suffix_offset = rank_str_width + 2 + else: + suffix_offset = None + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center + old_offs, v), + h_align='left', + v_align='center', + scale=0.3, + text=rank_str, + flatness=1.0, + maxwidth=sub_width * maxwidth_scale) + if suffix_offset is not None: + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center + old_offs + + suffix_offset, v + 1), + h_align='left', + v_align='center', + scale=0.20, + flatness=1.0, + text='[' + str(prev_rank[2]) + ']') + prev_ranks_shown += 1 + v -= 10 + + v -= 13 + + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=ba.Lstr(resource='achievementsText'), + maxwidth=sub_width * maxwidth_scale) + v -= 14 + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=str(data['achievementsCompleted']) + ' / ' + + str(len(ba.app.ach.achievements)), + maxwidth=sub_width * maxwidth_scale) + v -= 25 + + if prev_ranks_shown == 0 and showing_character: + v -= 20 + elif prev_ranks_shown == 1 and showing_character: + v -= 10 + + center = 0.5 + maxwidth_scale = 0.9 + + ba.textwidget(parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + flatness=1.0, + text=ba.Lstr(resource='trophiesThisSeasonText', + fallback_resource='trophiesText'), + maxwidth=sub_width * maxwidth_scale) + v -= 19 + ba.textwidget(parent=self._subcontainer, + size=(0, ts_height), + position=(sub_width * 0.5, + v - ts_height * tscale), + h_align='center', + v_align='top', + corner_scale=tscale, + text=trophystr) + + except Exception: + ba.print_exception('Error displaying account info.') + +# ba_meta export plugin +class bySmoothy(ba.Plugin): + def __init__(self): + if _ba.env().get("build_number",0) >= 20577: + _ba.connect_to_party=newconnect_to_party + bastd_party.PartyWindow = ModifiedPartyWindow + else:print("AdvancePartyWindow only runs with BombSquad version equal or higher than 1.7") diff --git a/plugins/utilities/server_switch.py b/plugins/utilities/server_switch.py new file mode 100644 index 0000000..6047f29 --- /dev/null +++ b/plugins/utilities/server_switch.py @@ -0,0 +1,561 @@ + +# ba_meta require api 7 + +from __future__ import annotations +import copy +import time +from typing import TYPE_CHECKING + +import _ba +import ba +import time +import threading +from enum import Enum +from dataclasses import dataclass +if TYPE_CHECKING: + from typing import Any, Optional, Dict, List, Tuple,Type + import ba + from bastd.ui.gather import GatherWindow + +from bastd.ui.confirm import ConfirmWindow +# discord @mr.smoothy#5824 + +import bastd.ui.mainmenu as bastd_ui_mainmenu + +connect=_ba.connect_to_party +disconnect=_ba.disconnect_from_host + +server=[] + + +ip_add="private" +p_port=44444 +p_name="nothing here" +def newconnect_to_party(address,port=43210,print_progress=False): + global ip_add + global p_port + dd=_ba.get_connection_to_host_info() + if(dd!={} ): + _ba.disconnect_from_host() + + ip_add=address + p_port=port + connect(address,port,print_progress) + else: + + + ip_add=address + p_port=port + # print(ip_add,p_port) + connect(ip_add,port,print_progress) +def newdisconnect_from_host(): + try: + name=_ba.get_connection_to_host_info()['name'] + global server + global ip_add + global p_port + pojo = {"name":name,"ip":ip_add,"port":p_port} + if pojo not in server: + server.insert(0,pojo) + server = server[:3] + except: + pass + disconnect() + +def printip(): + ba.screenmessage("ip address is"+ip_add) +def new_refresh_in_game( + self, positions: List[Tuple[float, float, + float]]) -> Tuple[float, float, float]: + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-statements + custom_menu_entries: List[Dict[str, Any]] = [] + session = _ba.get_foreground_host_session() + if session is not None: + try: + custom_menu_entries = session.get_custom_menu_entries() + for cme in custom_menu_entries: + if (not isinstance(cme, dict) or 'label' not in cme + or not isinstance(cme['label'], (str, ba.Lstr)) + or 'call' not in cme or not callable(cme['call'])): + raise ValueError('invalid custom menu entry: ' + + str(cme)) + except Exception: + custom_menu_entries = [] + ba.print_exception( + f'Error getting custom menu entries for {session}') + self._width = 250.0 + self._height = 250.0 if self._input_player else 180.0 + if (self._is_demo or self._is_arcade) and self._input_player: + self._height -= 40 + if not self._have_settings_button: + self._height -= 50 + if self._connected_to_remote_player: + # In this case we have a leave *and* a disconnect button. + self._height += 50 + self._height += 50 * (len(custom_menu_entries)) + uiscale = ba.app.ui.uiscale + ba.containerwidget( + edit=self._root_widget, + size=(self._width*2, self._height), + scale=(2.15 if uiscale is ba.UIScale.SMALL else + 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0)) + h = 125.0 + v = (self._height - 80.0 if self._input_player else self._height - 60) + h_offset = 0 + d_h_offset = 0 + v_offset = -50 + for _i in range(6 + len(custom_menu_entries)): + positions.append((h, v, 1.0)) + v += v_offset + h += h_offset + h_offset += d_h_offset + self._start_button = None + ba.app.pause() + h, v, scale = positions[self._p_index] + ba.textwidget( + parent=self._root_widget, + draw_controller=None, + text="IP: "+ip_add+" PORT: "+str(p_port), + position=(h+self._button_width-80,v+60), + h_align='center', + v_align='center', + size=(20, 60), + scale=0.6) + v_h=v + + global server + def con(address,port): + global ip_add + global p_port + if(address==ip_add and port==p_port): + self._resume() + else: + _ba.disconnect_from_host() + _ba.connect_to_party(address,port) + if len(server) ==0: + ba.textwidget( + parent=self._root_widget, + draw_controller=None, + text="Nothing in \n recents", + position=(h +self._button_width *scale ,v-30), + h_align='center', + v_align='center', + size=(20, 60), + scale=1) + for ser in server: + self._server_button = ba.buttonwidget( + color=(0.8, 0, 1), + parent=self._root_widget, + position=(h +self._button_width *scale - 80, v_h), + size=(self._button_width, self._button_height), + scale=scale, + autoselect=self._use_autoselect, + label=ser["name"][0:22], + + on_activate_call=ba.Call(con,ser["ip"],ser["port"])) + v_h=v_h-50 + + # Player name if applicable. + if self._input_player: + player_name = self._input_player.getname() + h, v, scale = positions[self._p_index] + v += 35 + ba.textwidget(parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + color=(1, 1, 1, 0.5), + scale=0.7, + h_align='center', + text=ba.Lstr(value=player_name)) + else: + player_name = '' + h, v, scale = positions[self._p_index] + self._p_index += 1 + btn = ba.buttonwidget(parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + scale=scale, + label=ba.Lstr(resource=self._r + '.resumeText'), + autoselect=self._use_autoselect, + on_activate_call=self._resume) + ba.containerwidget(edit=self._root_widget, cancel_button=btn) + + # Add any custom options defined by the current game. + for entry in custom_menu_entries: + h, v, scale = positions[self._p_index] + self._p_index += 1 + + # Ask the entry whether we should resume when we call + # it (defaults to true). + resume = bool(entry.get('resume_on_call', True)) + + if resume: + call = ba.Call(self._resume_and_call, entry['call']) + else: + call = ba.Call(entry['call'], ba.WeakCall(self._resume)) + + ba.buttonwidget(parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + scale=scale, + on_activate_call=call, + label=entry['label'], + autoselect=self._use_autoselect) + # Add a 'leave' button if the menu-owner has a player. + if ((self._input_player or self._connected_to_remote_player) + and not (self._is_demo or self._is_arcade)): + h, v, scale = positions[self._p_index] + self._p_index += 1 + btn = ba.buttonwidget(parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, + self._button_height), + scale=scale, + on_activate_call=self._leave, + label='', + autoselect=self._use_autoselect) + + if (player_name != '' and player_name[0] != '<' + and player_name[-1] != '>'): + txt = ba.Lstr(resource=self._r + '.justPlayerText', + subs=[('${NAME}', player_name)]) + else: + txt = ba.Lstr(value=player_name) + ba.textwidget(parent=self._root_widget, + position=(h, v + self._button_height * + (0.64 if player_name != '' else 0.5)), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.leaveGameText'), + scale=(0.83 if player_name != '' else 1.0), + color=(0.75, 1.0, 0.7), + h_align='center', + v_align='center', + draw_controller=btn, + maxwidth=self._button_width * 0.9) + ba.textwidget(parent=self._root_widget, + position=(h, v + self._button_height * 0.27), + size=(0, 0), + text=txt, + color=(0.75, 1.0, 0.7), + h_align='center', + v_align='center', + draw_controller=btn, + scale=0.45, + maxwidth=self._button_width * 0.9) + return h, v, scale +def new_refresh(self) -> None: + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-statements + global server + print(server) + from bastd.ui.confirm import QuitWindow + from bastd.ui.store.button import StoreButton + import ba + # Clear everything that was there. + children = self._root_widget.get_children() + for child in children: + child.delete() + + self._tdelay = 0.0 + self._t_delay_inc = 0.0 + self._t_delay_play = 0.0 + self._button_width = 200.0 + self._button_height = 45.0 + + self._r = 'mainMenu' + + app = ba.app + self._have_quit_button = (app.ui.uiscale is ba.UIScale.LARGE + or (app.platform == 'windows' + and app.subplatform == 'oculus')) + + self._have_store_button = not self._in_game + + self._have_settings_button = ( + (not self._in_game or not app.toolbar_test) + and not (self._is_demo or self._is_arcade or self._is_iircade)) + + self._input_device = input_device = _ba.get_ui_input_device() + self._input_player = input_device.player if input_device else None + self._connected_to_remote_player = ( + input_device.is_connected_to_remote_player() + if input_device else False) + + + positions: List[Tuple[float, float, float]] = [] + self._p_index = 0 + + if self._in_game: + h, v, scale = self._refresh_in_game(positions) + print("refreshing in GAME",ip_add) + # btn = ba.buttonwidget(parent=self._root_widget, + # position=(80,270), + # size=(100, 90), + # scale=1.2, + # label=ip_add, + # autoselect=None, + # on_activate_call=printip) + ba.textwidget( + parent=self._root_widget, + draw_controller=None, + text="IP: "+ip_add+" PORT: "+str(p_port), + position=(150,270), + h_align='center', + v_align='center', + size=(20, 60), + scale=1) + self._server_button = ba.buttonwidget( + parent=self._root_widget, + position=(h * 3.2 +20 - self._button_width * scale, v), + size=(self._button_width, self._button_height), + scale=scale, + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.settingsText'), + transition_delay=self._tdelay, + on_activate_call=self._settings) + self._server_button2 = ba.buttonwidget( + parent=self._root_widget, + position=(h * 3.2 +20 - self._button_width * scale, v-50), + size=(self._button_width, self._button_height), + scale=scale, + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.settingsText'), + transition_delay=self._tdelay, + on_activate_call=self._settings) + self._server_button3 = ba.buttonwidget( + parent=self._root_widget, + position=(h * 3.2 +20 - self._button_width * scale, v-100), + size=(self._button_width, self._button_height), + scale=scale, + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.settingsText'), + transition_delay=self._tdelay, + on_activate_call=self._settings) + + else: + h, v, scale = self._refresh_not_in_game(positions) + + if self._have_settings_button: + h, v, scale = positions[self._p_index] + self._p_index += 1 + self._settings_button = ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width * 0.5 * scale, v), + size=(self._button_width, self._button_height), + scale=scale, + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.settingsText'), + transition_delay=self._tdelay, + on_activate_call=self._settings) + + # Scattered eggs on easter. + if _ba.get_account_misc_read_val('easter', + False) and not self._in_game: + icon_size = 34 + ba.imagewidget(parent=self._root_widget, + position=(h - icon_size * 0.5 - 15, + v + self._button_height * scale - + icon_size * 0.24 + 1.5), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg3'), + tilt_scale=0.0) + + self._tdelay += self._t_delay_inc + + if self._in_game: + h, v, scale = positions[self._p_index] + self._p_index += 1 + + # If we're in a replay, we have a 'Leave Replay' button. + if _ba.is_in_replay(): + ba.buttonwidget(parent=self._root_widget, + position=(h - self._button_width * 0.5 * scale, + v), + scale=scale, + size=(self._button_width, self._button_height), + autoselect=self._use_autoselect, + label=ba.Lstr(resource='replayEndText'), + on_activate_call=self._confirm_end_replay) + elif _ba.get_foreground_host_session() is not None: + ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width * 0.5 * scale, v), + scale=scale, + size=(self._button_width, self._button_height), + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.endGameText'), + on_activate_call=self._confirm_end_game) + # Assume we're in a client-session. + else: + ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width * 0.5 * scale, v), + scale=scale, + size=(self._button_width, self._button_height), + autoselect=self._use_autoselect, + label=ba.Lstr(resource=self._r + '.leavePartyText'), + on_activate_call=self._confirm_leave_party) + + self._store_button: Optional[ba.Widget] + if self._have_store_button: + this_b_width = self._button_width + h, v, scale = positions[self._p_index] + self._p_index += 1 + + sbtn = self._store_button_instance = StoreButton( + parent=self._root_widget, + position=(h - this_b_width * 0.5 * scale, v), + size=(this_b_width, self._button_height), + scale=scale, + on_activate_call=ba.WeakCall(self._on_store_pressed), + sale_scale=1.3, + transition_delay=self._tdelay) + self._store_button = store_button = sbtn.get_button() + uiscale = ba.app.ui.uiscale + icon_size = (55 if uiscale is ba.UIScale.SMALL else + 55 if uiscale is ba.UIScale.MEDIUM else 70) + ba.imagewidget( + parent=self._root_widget, + position=(h - icon_size * 0.5, + v + self._button_height * scale - icon_size * 0.23), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture(self._store_char_tex), + tilt_scale=0.0, + draw_controller=store_button) + + self._tdelay += self._t_delay_inc + else: + self._store_button = None + + self._quit_button: Optional[ba.Widget] + if not self._in_game and self._have_quit_button: + h, v, scale = positions[self._p_index] + self._p_index += 1 + self._quit_button = quit_button = ba.buttonwidget( + parent=self._root_widget, + autoselect=self._use_autoselect, + position=(h - self._button_width * 0.5 * scale, v), + size=(self._button_width, self._button_height), + scale=scale, + label=ba.Lstr(resource=self._r + + ('.quitText' if 'Mac' in + ba.app.user_agent_string else '.exitGameText')), + on_activate_call=self._quit, + transition_delay=self._tdelay) + + # Scattered eggs on easter. + if _ba.get_account_misc_read_val('easter', False): + icon_size = 30 + ba.imagewidget(parent=self._root_widget, + position=(h - icon_size * 0.5 + 25, + v + self._button_height * scale - + icon_size * 0.24 + 1.5), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg1'), + tilt_scale=0.0) + + ba.containerwidget(edit=self._root_widget, + cancel_button=quit_button) + self._tdelay += self._t_delay_inc + else: + self._quit_button = None + + # If we're not in-game, have no quit button, and this is android, + # we want back presses to quit our activity. + if (not self._in_game and not self._have_quit_button + and ba.app.platform == 'android'): + + def _do_quit() -> None: + QuitWindow(swish=True, back=True) + + ba.containerwidget(edit=self._root_widget, + on_cancel_call=_do_quit) + + # Add speed-up/slow-down buttons for replays. + # (ideally this should be part of a fading-out playback bar like most + # media players but this works for now). + if _ba.is_in_replay(): + b_size = 50.0 + b_buffer = 10.0 + t_scale = 0.75 + uiscale = ba.app.ui.uiscale + if uiscale is ba.UIScale.SMALL: + b_size *= 0.6 + b_buffer *= 1.0 + v_offs = -40 + t_scale = 0.5 + elif uiscale is ba.UIScale.MEDIUM: + v_offs = -70 + else: + v_offs = -100 + self._replay_speed_text = ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource='watchWindow.playbackSpeedText', + subs=[('${SPEED}', str(1.23))]), + position=(h, v + v_offs + 7 * t_scale), + h_align='center', + v_align='center', + size=(0, 0), + scale=t_scale) + + # Update to current value. + self._change_replay_speed(0) + + # Keep updating in a timer in case it gets changed elsewhere. + self._change_replay_speed_timer = ba.Timer( + 0.25, + ba.WeakCall(self._change_replay_speed, 0), + timetype=ba.TimeType.REAL, + repeat=True) + btn = ba.buttonwidget(parent=self._root_widget, + position=(h - b_size - b_buffer, + v - b_size - b_buffer + v_offs), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=ba.Call( + self._change_replay_speed, -1)) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + text='-', + position=(h - b_size * 0.5 - b_buffer, + v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs), + h_align='center', + v_align='center', + size=(0, 0), + scale=3.0 * t_scale) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(h + b_buffer, v - b_size - b_buffer + v_offs), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=ba.Call(self._change_replay_speed, 1)) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + text='+', + position=(h + b_size * 0.5 + b_buffer, + v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs), + h_align='center', + v_align='center', + size=(0, 0), + scale=3.0 * t_scale) + +# ba_meta export plugin +class bySmoothy(ba.Plugin): + def __init__(self): + if _ba.env().get("build_number",0) >= 20577: + bastd_ui_mainmenu.MainMenuWindow._refresh_in_game= new_refresh_in_game + _ba.connect_to_party=newconnect_to_party + _ba.disconnect_from_host=newdisconnect_from_host + else:print("Server Switch only works on bs 1.7 and above")