__author__ = 'Droopy' __version__ = 3.0 # ba_meta require api 7 import datetime import json import math import os import pickle import random import time import urllib.request import weakref from threading import Thread from typing import List, Tuple, Sequence, Optional, Dict, Any, cast from hashlib import md5 import _ba import ba import bastd.ui.party from bastd.ui.colorpicker import ColorPickerExact from bastd.ui.confirm import ConfirmWindow from bastd.ui.mainmenu import MainMenuWindow from bastd.ui.popup import PopupMenuWindow, PopupWindow, PopupMenu _ip = '127.0.0.1' _port = 43210 _ping = '-' url = 'http://bombsquadprivatechat.ml' last_msg = None my_directory = _ba.env()['python_directory_user'] + '/UltraPartyWindowFiles/' quick_msg_file = my_directory + 'QuickMessages.txt' cookies_file = my_directory + 'cookies.txt' saved_ids_file = my_directory + 'saved_ids.json' my_location = my_directory def initialize(): config_defaults = {'Party Chat Muted': False, 'Chat Muted': False, 'ping button': True, 'IP button': True, 'copy button': True, 'Direct Send': False, 'Colorful Chat': True, 'Custom Commands': [], 'Message Notification': 'bottom', 'Self Status': 'online', 'Translate Source Language': '', 'Translate Destination Language': 'en', 'Pronunciation': True } config = ba.app.config for key in config_defaults: if key not in config: config[key] = config_defaults[key] if not os.path.exists(my_directory): os.makedirs(my_directory) if not os.path.exists(cookies_file): with open(cookies_file, 'wb') as f: pickle.dump({}, f) if not os.path.exists(saved_ids_file): with open(saved_ids_file, 'w') as f: data = {} json.dump(data, f) def display_error(msg=None): if msg: ba.screenmessage(msg, (1, 0, 0)) else: ba.screenmessage('Failed!', (1, 0, 0)) ba.playsound(ba.getsound('error')) def display_success(msg=None): if msg: ba.screenmessage(msg, (0, 1, 0)) else: ba.screenmessage('Successful!', (0, 1, 0)) class Translate(Thread): def __init__(self, data, callback): super().__init__() self.data = data self._callback = callback def run(self): _ba.pushcall(ba.Call(ba.screenmessage, 'Translating...'), from_other_thread=True) response = messenger._send_request(f'{url}/translate', self.data) if response: _ba.pushcall(ba.Call(self._callback, response), from_other_thread=True) class ColorTracker: def __init__(self): self.saved = {} def _get_safe_color(self, sender): while True: color = (random.random(), random.random(), random.random()) s = 0 background = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) for i, j in zip(color, background): s += (i - j) ** 2 if s > 0.1: self.saved[sender] = color if len(self.saved) > 20: self.saved.pop(list(self.saved.keys())[0]) break time.sleep(0.1) def _get_sender_color(self, sender): if sender not in self.saved: self.thread = Thread(target=self._get_safe_color, args=(sender,)) self.thread.start() return (1, 1, 1) else: return self.saved[sender] class PrivateChatHandler: def __init__(self): self.pvt_msgs = {} self.login_id = None self.last_msg_id = None self.logged_in = False self.cookieProcessor = urllib.request.HTTPCookieProcessor() self.opener = urllib.request.build_opener(self.cookieProcessor) self.filter = 'all' self.pending_messages = [] self.friends_status = {} self.error = '' Thread(target=self._ping).start() def _load_ids(self): with open(saved_ids_file, 'r') as f: saved = json.load(f) if self.myid in saved: self.saved_ids = saved[self.myid] else: self.saved_ids = {'all': ''} def _dump_ids(self): with open(saved_ids_file, 'r') as f: saved = json.load(f) with open(saved_ids_file, 'w') as f: saved[self.myid] = self.saved_ids json.dump(saved, f) def _ping(self): self.server_online = False response = self._send_request(url=f'{url}') if not response: self.error = 'Server offline' elif response: try: self.server_online = True version = float(response.replace('v', '')) if version > __version__: self._update_version() except: self.error = 'Server offline' def _update_version(self): new_file = self._send_request(url=f'{url}/updatepartywindow?get=file') hash = self._send_request(url=f'{url}/updatepartywindow?get=md5') if hash and new_file: file_hash = md5(new_file.encode()).hexdigest() if hash == file_hash: with open(__file__, 'w') as f: f.write(new_file) _ba.pushcall(ba.Call(ba.screenmessage, 'Ultra party window updated\nNeeds restart.'), True) def _signup(self, registration_key): data = dict(pb_id=self.myid, registration_key=registration_key) response = self._send_request(url=f'{url}/signup', data=data) if response: if response == 'successful': display_success('Account Created Successfully') self._login(registration_key=registration_key) return True display_error(response) def _save_cookie(self): with open(cookies_file, 'rb') as f: cookies = pickle.load(f) with open(cookies_file, 'wb') as f: for c in self.cookieProcessor.cookiejar: cookie = pickle.dumps(c) break cookies[self.myid] = cookie pickle.dump(cookies, f) def _cookie_login(self): self.myid = _ba.get_v1_account_misc_read_val_2('resolvedAccountID', '') try: with open(cookies_file, 'rb') as f: cookies = pickle.load(f) except: return False if self.myid in cookies: cookie = pickle.loads(cookies[self.myid]) self.cookieProcessor.cookiejar.set_cookie(cookie) self.opener = urllib.request.build_opener(self.cookieProcessor) response = self._send_request(url=f'{url}/login') if response.startswith('logged in as'): self.logged_in = True self._load_ids() display_success(response) return True def _login(self, registration_key): self.myid = _ba.get_v1_account_misc_read_val_2('resolvedAccountID', '') data = dict(pb_id=self.myid, registration_key=registration_key) response = self._send_request(url=f'{url}/login', data=data) if response == 'successful': self.logged_in = True self._load_ids() self._save_cookie() display_success('Account Logged in Successfully') return True else: display_error(response) def _query(self, pb_id=None): if not pb_id: pb_id = self.myid response = self._send_request(url=f'{url}/query/{pb_id}') if response == 'exists': return True return False def _send_request(self, url, data=None): try: if not data: response = self.opener.open(url) else: response = self.opener.open(url, data=json.dumps(data).encode()) if response.getcode() != 200: display_error(response.read().decode()) return None else: return response.read().decode() except: return None def _save_id(self, account_id, nickname='', verify=True): # display_success(f'Saving {account_id}. Please wait...') if verify: url = 'http://bombsquadgame.com/accountquery?id=' + account_id response = json.loads(urllib.request.urlopen(url).read().decode()) if 'error' in response: display_error('Enter valid account id') return False self.saved_ids[account_id] = {} name = None if nickname == '': name_html = response['name_html'] name = name_html.split('>')[1] nick = name if name else nickname else: nick = nickname self.saved_ids[account_id] = nick self._dump_ids() display_success(f'Account added: {nick}({account_id})') return True def _remove_id(self, account_id): removed = self.saved_ids.pop(account_id) self._dump_ids() ba.screenmessage(f'Removed successfully: {removed}({account_id})', (0, 1, 0)) ba.playsound(ba.getsound('shieldDown')) def _format_message(self, msg): filter = msg['filter'] if filter in self.saved_ids: if self.filter == 'all': message = '[' + self.saved_ids[filter] + ']' + msg['message'] else: message = msg['message'] else: message = '[' + msg['filter'] + ']: ' + 'Message from unsaved id. Save id to view message.' return message def _get_status(self, id, type='status'): info = self.friends_status.get(id, {}) if not info: return '-' if type == 'status': return info['status'] else: last_seen = info["last_seen"] last_seen = _get_local_time(last_seen) ba.screenmessage(f'Last seen on: {last_seen}') def _get_local_time(utctime): d = datetime.datetime.strptime(utctime, '%d-%m-%Y %H:%M:%S') d = d.replace(tzinfo=datetime.timezone.utc) d = d.astimezone() return d.strftime('%B %d,\t\t%H:%M:%S') def update_status(): if messenger.logged_in: if ba.app.config['Self Status'] == 'online': host = _ba.get_connection_to_host_info().get('name', '') if host: my_status = f'Playing in {host}' else: my_status = 'in Lobby' ids_to_check = [i for i in messenger.saved_ids if i != 'all'] response = messenger._send_request(url=f'{url}/updatestatus', data=dict(self_status=my_status, ids=ids_to_check)) if response: messenger.friends_status = json.loads(response) else: messenger.friends_status = {} def messenger_thread(): counter = 0 while True: counter += 1 time.sleep(0.6) check_new_message() if counter > 5: counter = 0 update_status() def check_new_message(): if messenger.logged_in: if messenger.login_id != messenger.myid: response = messenger._send_request(f'{url}/first') if response: messenger.pvt_msgs = json.loads(response) if messenger.pvt_msgs['all']: messenger.last_msg_id = messenger.pvt_msgs['all'][-1]['id'] messenger.login_id = messenger.myid else: response = messenger._send_request(f'{url}/new/{messenger.last_msg_id}') if response: new_msgs = json.loads(response) if new_msgs: for msg in new_msgs['messages']: if msg['id'] > messenger.last_msg_id: messenger.last_msg_id = msg['id'] messenger.pvt_msgs['all'].append( dict(id=msg['id'], filter=msg['filter'], message=msg['message'], sent=msg['sent'])) if len(messenger.pvt_msgs['all']) > 40: messenger.pvt_msgs['all'].pop(0) if msg['filter'] not in messenger.pvt_msgs: messenger.pvt_msgs[msg['filter']] = [ dict(id=msg['id'], filter=msg['filter'], message=msg['message'], sent=msg['sent'])] else: messenger.pvt_msgs[msg['filter']].append( dict(id=msg['id'], filter=msg['filter'], message=msg['message'], sent=msg['sent'])) if len(messenger.pvt_msgs[msg['filter']]) > 20: messenger.pvt_msgs[msg['filter']].pop(0) messenger.pending_messages.append( (messenger._format_message(msg), msg['filter'], msg['sent'])) def display_message(msg, msg_type, filter=None, sent=None): flag = None notification = ba.app.config['Message Notification'] if _ba.app.ui.party_window: if _ba.app.ui.party_window(): if _ba.app.ui.party_window()._private_chat: flag = 1 if msg_type == 'private': if messenger.filter == filter or messenger.filter == 'all': _ba.app.ui.party_window().on_chat_message(msg, sent) else: if notification == 'top': ba.screenmessage(msg, (1, 1, 0), True, ba.gettexture('coin')) else: ba.screenmessage(msg, (1, 1, 0), False) else: ba.screenmessage(msg, (0.2, 1.0, 1.0), True, ba.gettexture('circleShadow')) else: flag = 1 if msg_type == 'private': if notification == 'top': ba.screenmessage(msg, (1, 1, 0), True, ba.gettexture('coin')) else: ba.screenmessage(msg, (1, 1, 0), False) if not flag: if msg_type == 'private': if notification == 'top': ba.screenmessage(msg, (1, 1, 0), True, ba.gettexture('coin')) else: ba.screenmessage(msg, (1, 1, 0), False) else: ba.screenmessage(msg, (0.2, 1.0, 1.0), True, ba.gettexture('circleShadow')) def msg_displayer(): for msg in messenger.pending_messages: display_message(msg[0], 'private', msg[1], msg[2]) messenger.pending_messages.remove(msg) if ba.app.config['Chat Muted'] and not ba.app.config['Party Chat Muted']: global last_msg last = _ba.get_chat_messages() lm = last[-1] if last else None if lm != last_msg: last_msg = lm display_message(lm, 'public') class SortQuickMessages: def __init__(self): uiscale = ba.app.ui.uiscale bg_color = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self._width = 750 if uiscale is ba.UIScale.SMALL else 600 self._height = (300 if uiscale is ba.UIScale.SMALL else 325 if uiscale is ba.UIScale.MEDIUM else 350) self._root_widget = ba.containerwidget( size=(self._width, self._height), transition='in_right', on_outside_click_call=self._save, color=bg_color, parent=_ba.get_special_widget('overlay_stack'), scale=(2.0 if uiscale is ba.UIScale.SMALL else 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -16) if uiscale is ba.UIScale.SMALL else (0, 0)) ba.textwidget(parent=self._root_widget, position=(-10, self._height - 50), size=(self._width, 25), text='Sort Quick Messages', color=ba.app.ui.title_color, scale=1.05, h_align='center', v_align='center', maxwidth=270) b_textcolor = (0.4, 0.75, 0.5) up_button = ba.buttonwidget(parent=self._root_widget, position=(10, 170), size=(75, 75), on_activate_call=self._move_up, label=ba.charstr(ba.SpecialChar.UP_ARROW), button_type='square', color=bg_color, textcolor=b_textcolor, autoselect=True, repeat=True) down_button = ba.buttonwidget(parent=self._root_widget, position=(10, 75), size=(75, 75), on_activate_call=self._move_down, label=ba.charstr(ba.SpecialChar.DOWN_ARROW), button_type='square', color=bg_color, textcolor=b_textcolor, autoselect=True, repeat=True) self._scroll_width = self._width - 150 self._scroll_height = self._height - 110 self._scrollwidget = ba.scrollwidget( parent=self._root_widget, size=(self._scroll_width, self._scroll_height), color=bg_color, position=(100, 40)) self._columnwidget = ba.columnwidget( parent=self._scrollwidget, border=2, margin=0) with open(quick_msg_file, 'r') as f: self.msgs = f.read().split('\n') self._msg_selected = None self._refresh() ba.containerwidget(edit=self._root_widget, on_cancel_call=self._save) def _refresh(self): for child in self._columnwidget.get_children(): child.delete() for msg in enumerate(self.msgs): txt = ba.textwidget( parent=self._columnwidget, size=(self._scroll_width - 10, 30), selectable=True, always_highlight=True, on_select_call=ba.Call(self._on_msg_select, msg), text=msg[1], h_align='left', v_align='center', maxwidth=self._scroll_width) if msg == self._msg_selected: ba.columnwidget(edit=self._columnwidget, selected_child=txt, visible_child=txt) def _on_msg_select(self, msg): self._msg_selected = msg def _move_up(self): index = self._msg_selected[0] msg = self._msg_selected[1] if index: self.msgs.insert((index - 1), self.msgs.pop(index)) self._msg_selected = (index - 1, msg) self._refresh() def _move_down(self): index = self._msg_selected[0] msg = self._msg_selected[1] if index + 1 < len(self.msgs): self.msgs.insert((index + 1), self.msgs.pop(index)) self._msg_selected = (index + 1, msg) self._refresh() def _save(self) -> None: try: with open(quick_msg_file, 'w') as f: f.write('\n'.join(self.msgs)) except: ba.print_exception() ba.screenmessage('Error!', (1, 0, 0)) ba.containerwidget( edit=self._root_widget, transition='out_right') class TranslationSettings: def __init__(self): uiscale = ba.app.ui.uiscale height = (300 if uiscale is ba.UIScale.SMALL else 350 if uiscale is ba.UIScale.MEDIUM else 400) width = (500 if uiscale is ba.UIScale.SMALL else 600 if uiscale is ba.UIScale.MEDIUM else 650) self._transition_out: Optional[str] scale_origin: Optional[Tuple[float, float]] self._transition_out = 'out_scale' scale_origin = 10 transition = 'in_scale' scale_origin = None cancel_is_selected = False cfg = ba.app.config bg_color = cfg.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) LANGUAGES = { '': 'Auto-Detect', 'af': 'afrikaans', 'sq': 'albanian', 'am': 'amharic', 'ar': 'arabic', 'hy': 'armenian', 'az': 'azerbaijani', 'eu': 'basque', 'be': 'belarusian', 'bn': 'bengali', 'bs': 'bosnian', 'bg': 'bulgarian', 'ca': 'catalan', 'ceb': 'cebuano', 'ny': 'chichewa', 'zh-cn': 'chinese (simplified)', 'zh-tw': 'chinese (traditional)', 'co': 'corsican', 'hr': 'croatian', 'cs': 'czech', 'da': 'danish', 'nl': 'dutch', 'en': 'english', 'eo': 'esperanto', 'et': 'estonian', 'tl': 'filipino', 'fi': 'finnish', 'fr': 'french', 'fy': 'frisian', 'gl': 'galician', 'ka': 'georgian', 'de': 'german', 'el': 'greek', 'gu': 'gujarati', 'ht': 'haitian creole', 'ha': 'hausa', 'haw': 'hawaiian', 'iw': 'hebrew', 'he': 'hebrew', 'hi': 'hindi', 'hmn': 'hmong', 'hu': 'hungarian', 'is': 'icelandic', 'ig': 'igbo', 'id': 'indonesian', 'ga': 'irish', 'it': 'italian', 'ja': 'japanese', 'jw': 'javanese', 'kn': 'kannada', 'kk': 'kazakh', 'km': 'khmer', 'ko': 'korean', 'ku': 'kurdish (kurmanji)', 'ky': 'kyrgyz', 'lo': 'lao', 'la': 'latin', 'lv': 'latvian', 'lt': 'lithuanian', 'lb': 'luxembourgish', 'mk': 'macedonian', 'mg': 'malagasy', 'ms': 'malay', 'ml': 'malayalam', 'mt': 'maltese', 'mi': 'maori', 'mr': 'marathi', 'mn': 'mongolian', 'my': 'myanmar (burmese)', 'ne': 'nepali', 'no': 'norwegian', 'or': 'odia', 'ps': 'pashto', 'fa': 'persian', 'pl': 'polish', 'pt': 'portuguese', 'pa': 'punjabi', 'ro': 'romanian', 'ru': 'russian', 'sm': 'samoan', 'gd': 'scots gaelic', 'sr': 'serbian', 'st': 'sesotho', 'sn': 'shona', 'sd': 'sindhi', 'si': 'sinhala', 'sk': 'slovak', 'sl': 'slovenian', 'so': 'somali', 'es': 'spanish', 'su': 'sundanese', 'sw': 'swahili', 'sv': 'swedish', 'tg': 'tajik', 'ta': 'tamil', 'te': 'telugu', 'th': 'thai', 'tr': 'turkish', 'uk': 'ukrainian', 'ur': 'urdu', 'ug': 'uyghur', 'uz': 'uzbek', 'vi': 'vietnamese', 'cy': 'welsh', 'xh': 'xhosa', 'yi': 'yiddish', 'yo': 'yoruba', 'zu': 'zulu'} self.root_widget = ba.containerwidget( size=(width, height), color=bg_color, transition=transition, toolbar_visibility='menu_minimal_no_back', parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self._cancel, scale=(2.1 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), scale_origin_stack_offset=scale_origin) ba.textwidget(parent=self.root_widget, position=(width * 0.5, height - 45), size=(20, 20), h_align='center', v_align='center', text="Text Translation", scale=0.9, color=(5, 5, 5)) cbtn = btn = ba.buttonwidget(parent=self.root_widget, autoselect=True, position=(30, height - 60), size=(30, 30), label=ba.charstr(ba.SpecialChar.BACK), button_type='backSmall', on_activate_call=self._cancel) source_lang_text = ba.textwidget(parent=self.root_widget, position=(40, height - 110), size=(20, 20), h_align='left', v_align='center', text="Source Language : ", scale=0.9, color=(1, 1, 1)) source_lang_menu = PopupMenu( parent=self.root_widget, position=(330 if uiscale is ba.UIScale.SMALL else 400, height - 115), width=200, scale=(2.8 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.2), current_choice=cfg['Translate Source Language'], choices=LANGUAGES.keys(), choices_display=(ba.Lstr(value=i) for i in LANGUAGES.values()), button_size=(130, 35), on_value_change_call=self._change_source) destination_lang_text = ba.textwidget(parent=self.root_widget, position=(40, height - 165), size=(20, 20), h_align='left', v_align='center', text="Destination Language : ", scale=0.9, color=(1, 1, 1)) destination_lang_menu = PopupMenu( parent=self.root_widget, position=(330 if uiscale is ba.UIScale.SMALL else 400, height - 170), width=200, scale=(2.8 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.2), current_choice=cfg['Translate Destination Language'], choices=list(LANGUAGES.keys())[1:], choices_display=list(ba.Lstr(value=i) for i in LANGUAGES.values())[1:], button_size=(130, 35), on_value_change_call=self._change_destination) try: translation_mode_text = ba.textwidget(parent=self.root_widget, position=(40, height - 215), size=(20, 20), h_align='left', v_align='center', text="Translate Mode", scale=0.9, color=(1, 1, 1)) decoration = ba.textwidget(parent=self.root_widget, position=(40, height - 225), size=(20, 20), h_align='left', v_align='center', text="________________", scale=0.9, color=(1, 1, 1)) language_char_text = ba.textwidget(parent=self.root_widget, position=(85, height - 273), size=(20, 20), h_align='left', v_align='center', text='Normal Translation', scale=0.6, color=(1, 1, 1)) pronunciation_text = ba.textwidget(parent=self.root_widget, position=(295, height - 273), size=(20, 20), h_align='left', v_align='center', text="Show Prononciation", scale=0.6, color=(1, 1, 1)) from bastd.ui.radiogroup import make_radio_group cur_val = ba.app.config.get('Pronunciation', True) cb1 = ba.checkboxwidget( parent=self.root_widget, position=(250, height - 275), size=(20, 20), maxwidth=300, scale=1, autoselect=True, text="") cb2 = ba.checkboxwidget( parent=self.root_widget, position=(40, height - 275), size=(20, 20), maxwidth=300, scale=1, autoselect=True, text="") make_radio_group((cb1, cb2), (True, False), cur_val, self._actions_changed) except Exception as e: print(e) pass ba.containerwidget(edit=self.root_widget, cancel_button=btn) def _change_source(self, choice): cfg = ba.app.config cfg['Translate Source Language'] = choice cfg.apply_and_commit() def _change_destination(self, choice): cfg = ba.app.config cfg['Translate Destination Language'] = choice cfg.apply_and_commit() def _actions_changed(self, v: str) -> None: cfg = ba.app.config cfg['Pronunciation'] = v cfg.apply_and_commit() def _cancel(self) -> None: ba.containerwidget(edit=self.root_widget, transition='out_scale') SettingsWindow() class SettingsWindow: def __init__(self): uiscale = ba.app.ui.uiscale height = (300 if uiscale is ba.UIScale.SMALL else 350 if uiscale is ba.UIScale.MEDIUM else 400) width = (500 if uiscale is ba.UIScale.SMALL else 600 if uiscale is ba.UIScale.MEDIUM else 650) scroll_h = (200 if uiscale is ba.UIScale.SMALL else 250 if uiscale is ba.UIScale.MEDIUM else 270) scroll_w = (450 if uiscale is ba.UIScale.SMALL else 550 if uiscale is ba.UIScale.MEDIUM else 600) self._transition_out: Optional[str] scale_origin: Optional[Tuple[float, float]] self._transition_out = 'out_scale' scale_origin = 10 transition = 'in_scale' scale_origin = None cancel_is_selected = False cfg = ba.app.config bg_color = cfg.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self.root_widget = ba.containerwidget( size=(width, height), color=bg_color, transition=transition, toolbar_visibility='menu_minimal_no_back', parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self._cancel, scale=(2.1 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), scale_origin_stack_offset=scale_origin) ba.textwidget(parent=self.root_widget, position=(width * 0.5, height - 45), size=(20, 20), h_align='center', v_align='center', text="Custom Settings", scale=0.9, color=(5, 5, 5)) cbtn = btn = ba.buttonwidget(parent=self.root_widget, autoselect=True, position=(30, height - 60), size=(30, 30), label=ba.charstr(ba.SpecialChar.BACK), button_type='backSmall', on_activate_call=self._cancel) scroll_position = (30 if uiscale is ba.UIScale.SMALL else 40 if uiscale is ba.UIScale.MEDIUM else 50) self._scrollwidget = ba.scrollwidget(parent=self.root_widget, position=(30, scroll_position), simple_culling_v=20.0, highlight=False, size=(scroll_w, scroll_h), selection_loops_to_parent=True) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) self._subcontainer = ba.columnwidget(parent=self._scrollwidget, selection_loops_to_parent=True) ip_button = ba.checkboxwidget( parent=self._subcontainer, size=(300, 30), maxwidth=300, textcolor=((0, 1, 0) if cfg['IP button'] else (0.95, 0.65, 0)), scale=1, value=cfg['IP button'], autoselect=True, text="IP Button", on_value_change_call=self.ip_button) ping_button = ba.checkboxwidget( parent=self._subcontainer, size=(300, 30), maxwidth=300, textcolor=((0, 1, 0) if cfg['ping button'] else (0.95, 0.65, 0)), scale=1, value=cfg['ping button'], autoselect=True, text="Ping Button", on_value_change_call=self.ping_button) copy_button = ba.checkboxwidget( parent=self._subcontainer, size=(300, 30), maxwidth=300, textcolor=((0, 1, 0) if cfg['copy button'] else (0.95, 0.65, 0)), scale=1, value=cfg['copy button'], autoselect=True, text="Copy Text Button", on_value_change_call=self.copy_button) direct_send = ba.checkboxwidget( parent=self._subcontainer, size=(300, 30), maxwidth=300, textcolor=((0, 1, 0) if cfg['Direct Send'] else (0.95, 0.65, 0)), scale=1, value=cfg['Direct Send'], autoselect=True, text="Directly Send Custom Commands", on_value_change_call=self.direct_send) colorfulchat = ba.checkboxwidget( parent=self._subcontainer, size=(300, 30), maxwidth=300, textcolor=((0, 1, 0) if cfg['Colorful Chat'] else (0.95, 0.65, 0)), scale=1, value=cfg['Colorful Chat'], autoselect=True, text="Colorful Chat", on_value_change_call=self.colorful_chat) msg_notification_text = ba.textwidget(parent=self._subcontainer, scale=0.8, color=(1, 1, 1), text='Message Notifcation:', size=(100, 30), h_align='left', v_align='center') msg_notification_widget = PopupMenu( parent=self._subcontainer, position=(100, height - 1200), width=200, scale=(2.8 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.2), choices=['top', 'bottom'], current_choice=ba.app.config['Message Notification'], button_size=(80, 25), on_value_change_call=self._change_notification) self_status_text = ba.textwidget(parent=self._subcontainer, scale=0.8, color=(1, 1, 1), text='Self Status:', size=(100, 30), h_align='left', v_align='center') self_status_widget = PopupMenu( parent=self._subcontainer, position=(50, height - 1000), width=200, scale=(2.8 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.2), choices=['online', 'offline'], current_choice=ba.app.config['Self Status'], button_size=(80, 25), on_value_change_call=self._change_status) ba.containerwidget(edit=self.root_widget, cancel_button=btn) ba.containerwidget(edit=self.root_widget, selected_child=(cbtn if cbtn is not None and cancel_is_selected else None), start_button=None) self._translation_btn = ba.buttonwidget(parent=self._subcontainer, scale=1.2, position=(100, 1200), size=(150, 50), label='Translate Settings', on_activate_call=self._translaton_btn, autoselect=True) def ip_button(self, value: bool): cfg = ba.app.config cfg['IP button'] = value cfg.apply_and_commit() if cfg['IP button']: ba.screenmessage("IP Button is now enabled", color=(0, 1, 0)) else: ba.screenmessage("IP Button is now disabled", color=(1, 0.7, 0)) def ping_button(self, value: bool): cfg = ba.app.config cfg['ping button'] = value cfg.apply_and_commit() if cfg['ping button']: ba.screenmessage("Ping Button is now enabled", color=(0, 1, 0)) else: ba.screenmessage("Ping Button is now disabled", color=(1, 0.7, 0)) def copy_button(self, value: bool): cfg = ba.app.config cfg['copy button'] = value cfg.apply_and_commit() if cfg['copy button']: ba.screenmessage("Copy Text Button is now enabled", color=(0, 1, 0)) else: ba.screenmessage("Copy Text Button is now disabled", color=(1, 0.7, 0)) def direct_send(self, value: bool): cfg = ba.app.config cfg['Direct Send'] = value cfg.apply_and_commit() def colorful_chat(self, value: bool): cfg = ba.app.config cfg['Colorful Chat'] = value cfg.apply_and_commit() def _change_notification(self, choice): cfg = ba.app.config cfg['Message Notification'] = choice cfg.apply_and_commit() def _change_status(self, choice): cfg = ba.app.config cfg['Self Status'] = choice cfg.apply_and_commit() def _translaton_btn(self): try: ba.containerwidget(edit=self.root_widget, transition='out_scale') TranslationSettings() except Exception as e: print(e) pass def _cancel(self) -> None: ba.containerwidget(edit=self.root_widget, transition='out_scale') class PartyWindow(ba.Window): """Party list/chat window.""" def __del__(self) -> None: _ba.set_party_window_open(False) def __init__(self, origin: Sequence[float] = (0, 0)): self._private_chat = False self._firstcall = True self.ping_server() _ba.set_party_window_open(True) self._r = 'partyWindow' 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) self.bg_color = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self.ping_timer = ba.Timer(5, ba.WeakCall(self.ping_server), repeat=True) 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=self.bg_color, icon=ba.gettexture('crossOut'), 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 - 80, self._height - 47), size=(50, 50), label='...', autoselect=True, button_type='square', on_activate_call=ba.WeakCall(self._on_menu_button_press), color=self.bg_color, iconscale=1.2) info = _ba.get_connection_to_host_info() if info.get('name', '') != '': self.title = ba.Lstr(value=info['name']) else: self.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=self.title, size=(0, 0), position=(self._width * 0.47, self._height - 29), maxwidth=self._width * 0.6, 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, 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=self.bg_color) 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=ba.Lstr(resource='chatMutedText')) self._text_field = txt = ba.textwidget( parent=self._root_widget, editable=True, size=(500, 40), position=(54, 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) 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) self._send_button = btn = ba.buttonwidget(parent=self._root_widget, size=(50, 35), label=ba.Lstr(resource=self._r + '.sendText'), button_type='square', autoselect=True, color=self.bg_color, position=(self._width - 90, 35), on_activate_call=self._send_chat_message) ba.textwidget(edit=txt, on_return_press_call=btn.activate) self._previous_button = ba.buttonwidget(parent=self._root_widget, size=(30, 30), label=ba.charstr(ba.SpecialChar.UP_ARROW), button_type='square', autoselect=True, position=(15, 57), color=self.bg_color, scale=0.75, on_activate_call=self._previous_message) self._next_button = ba.buttonwidget(parent=self._root_widget, size=(30, 30), label=ba.charstr(ba.SpecialChar.DOWN_ARROW), button_type='square', autoselect=True, color=self.bg_color, scale=0.75, position=(15, 28), on_activate_call=self._next_message) self._translate_button = ba.buttonwidget(parent=self._root_widget, size=(55, 47), label="Trans", button_type='square', autoselect=True, color=self.bg_color, scale=0.75, position=(self._width - 28, 35), on_activate_call=self._translate) if ba.app.config['copy button']: self._copy_button = ba.buttonwidget(parent=self._root_widget, size=(15, 15), label='©', button_type='backSmall', autoselect=True, color=self.bg_color, position=(self._width - 40, 80), on_activate_call=self._copy_to_clipboard) self._ping_button = None if info.get('name', '') != '': if ba.app.config['ping button']: self._ping_button = ba.buttonwidget( parent=self._root_widget, scale=0.7, position=(self._width - 538, self._height - 57), size=(75, 75), autoselect=True, button_type='square', label=f'{_ping}', on_activate_call=self._send_ping, color=self.bg_color, text_scale=2.3, iconscale=1.2) if ba.app.config['IP button']: self._ip_port_button = ba.buttonwidget(parent=self._root_widget, size=(30, 30), label='IP', button_type='square', autoselect=True, color=self.bg_color, position=(self._width - 530, self._height - 100), on_activate_call=self._ip_port_msg) self._settings_button = ba.buttonwidget(parent=self._root_widget, size=(50, 50), scale=0.5, button_type='square', autoselect=True, color=self.bg_color, position=(self._width - 40, self._height - 47), on_activate_call=self._on_setting_button_press, icon=ba.gettexture('settingsIcon'), iconscale=1.2) self._privatechat_button = ba.buttonwidget(parent=self._root_widget, size=(50, 50), scale=0.5, button_type='square', autoselect=True, color=self.bg_color, position=(self._width - 40, self._height - 80), on_activate_call=self._on_privatechat_button_press, icon=ba.gettexture('ouyaOButton'), iconscale=1.2) self._name_widgets: List[ba.Widget] = [] self._roster: Optional[List[Dict[str, Any]]] = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, timetype=ba.TimeType.REAL) self._update() def on_chat_message(self, msg: str, sent=None) -> None: """Called when a new chat message comes through.""" if ba.app.config['Party Chat Muted'] and not _ba.app.ui.party_window()._private_chat: return if sent: self._add_msg(msg, sent) else: self._add_msg(msg) def _add_msg(self, msg: str, sent=None) -> None: if ba.app.config['Colorful Chat']: sender = msg.split(': ')[0] color = color_tracker._get_sender_color(sender) if sender else (1, 1, 1) else: color = (1, 1, 1) maxwidth = self._scroll_width * 0.94 txt = ba.textwidget(parent=self._columnwidget, text=msg, h_align='left', v_align='center', size=(0, 13), scale=0.55, color=color, maxwidth=maxwidth, shadow=0.3, flatness=1.0) if sent: ba.textwidget(edit=txt, size=(100, 15), selectable=True, click_activate=True, on_activate_call=ba.Call(ba.screenmessage, f'Message sent: {_get_local_time(sent)}')) 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 _on_menu_button_press(self) -> None: is_muted = ba.app.config['Party Chat Muted'] uiscale = ba.app.ui.uiscale choices = ['muteOption', 'modifyColor', 'addQuickReply', 'removeQuickReply', 'credits'] choices_display = ['Mute Option', 'Modify Main Color', 'Add as Quick Reply', 'Remove a Quick Reply', 'Credits'] if hasattr(_ba.get_foreground_host_activity(), '_map'): choices.append('manualCamera') choices_display.append('Manual Camera') PopupMenuWindow( position=self._menu_button.get_screen_space_center(), color=self.bg_color, scale=(2.3 if uiscale is ba.UIScale.SMALL else 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23), choices=choices, choices_display=self._create_baLstr_list(choices_display), current_choice='muteOption', delegate=self) self._popup_type = 'menu' def _update(self) -> None: if not self._private_chat: _ba.set_party_window_open(True) ba.textwidget(edit=self._title_text, text=self.title) if self._firstcall: if hasattr(self, '_status_text'): self._status_text.delete() self._roster = [] self._firstcall = False self._chat_texts: List[ba.Widget] = [] if not ba.app.config['Party Chat Muted']: msgs = _ba.get_chat_messages() for msg in msgs: self._add_msg(msg) # update muted state if ba.app.config['Party 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)) if self._ping_button: ba.buttonwidget(edit=self._ping_button, label=f'{_ping}', textcolor=self._get_ping_color()) # update roster section roster = _ba.get_game_roster() if roster != self._roster or self._firstcall: self._roster = roster # 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)) 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._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] + '...' else: p_str = self._roster[index][ 'display_string'] except Exception: ba.print_exception( 'Error calcing client name str.') p_str = '???' 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) # 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). ba.textwidget(edit=widget, on_activate_call=ba.Call( self._on_party_member_press, self._roster[index]['client_id'], is_host, widget)) 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) 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)) 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)) else: _ba.set_party_window_open(False) for widget in self._name_widgets: widget.delete() self._name_widgets = [] ba.textwidget(edit=self._title_text, text='Private Chat') ba.textwidget(edit=self._empty_str, text='') if self._firstcall: self._firstcall = False if hasattr(self, '_status_text'): self._status_text.delete() try: msgs = messenger.pvt_msgs[messenger.filter] except: msgs = [] if self._chat_texts: while self._chat_texts: first = self._chat_texts.pop() first.delete() uiscale = ba.app.ui.uiscale scroll_height = (165 if uiscale is ba.UIScale.SMALL else 280 if uiscale is ba.UIScale.MEDIUM else 400) ba.scrollwidget(edit=self._scrollwidget, size=(self._width - 50, scroll_height)) for msg in msgs: message = messenger._format_message(msg) self._add_msg(message, msg['sent']) self._filter_text = ba.textwidget(parent=self._root_widget, scale=0.6, color=(0.9, 1.0, 0.9), text='Filter: ', size=(0, 0), position=(self._width * 0.3, self._height - 70), h_align='center', v_align='center') choices = [i for i in messenger.saved_ids] choices_display = [ba.Lstr(value=messenger.saved_ids[i]) for i in messenger.saved_ids] choices.append('add') choices_display.append(ba.Lstr(value='***Add New***')) filter_widget = PopupMenu( parent=self._root_widget, position=(self._width * 0.4, self._height - 80), width=200, scale=(2.8 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.2), choices=choices, choices_display=choices_display, current_choice=messenger.filter, button_size=(120, 30), on_value_change_call=self._change_filter) self._popup_button = filter_widget.get_button() if messenger.filter != 'all': user_status = messenger._get_status(messenger.filter) if user_status == 'Offline': color = (1, 0, 0) elif user_status.startswith(('Playing in', 'in Lobby')): color = (0, 1, 0) else: color = (0.9, 1.0, 0.9) self._status_text = ba.textwidget(parent=self._root_widget, scale=0.5, color=color, text=f'Status:\t{user_status}', size=(200, 30), position=(self._width * 0.3, self._height - 110), h_align='center', v_align='center', autoselect=True, selectable=True, click_activate=True) ba.textwidget(edit=self._status_text, on_activate_call=ba.Call(messenger._get_status, messenger.filter, 'last_seen')) def _change_filter(self, choice): if choice == 'add': self.close() AddNewIdWindow() else: messenger.filter = choice self._firstcall = True self._filter_text.delete() self._popup_button.delete() if self._chat_texts: while self._chat_texts: first = self._chat_texts.pop() first.delete() self._update() def popup_menu_selected_choice(self, popup_window: PopupMenuWindow, choice: str) -> None: """Called when a choice is selected in the popup.""" if self._popup_type == 'partyMemberPress': playerinfo = self._get_player_info(self._popup_party_member_client_id) if choice == 'kick': name = playerinfo['ds'] ConfirmWindow(text=f'Are you sure to kick {name}?', action=self._vote_kick_player, cancel_button=True, cancel_is_selected=True, color=self.bg_color, text_scale=1.0, origin_widget=self.get_root_widget()) elif choice == 'mention': players = playerinfo['players'] choices = [] namelist = [playerinfo['ds']] for player in players: name = player['name_full'] if name not in namelist: namelist.append(name) choices_display = self._create_baLstr_list(namelist) for i in namelist: i = i.replace('"', '\"') i = i.replace("'", "\'") choices.append(f'self._edit_text_msg_box("{i}")') PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), color=self.bg_color, scale=self._get_popup_window_scale(), choices=choices, choices_display=choices_display, current_choice=choices[0], delegate=self) self._popup_type = "executeChoice" elif choice == 'adminkick': name = playerinfo['ds'] ConfirmWindow(text=f'Are you sure to use admin\ncommand to kick {name}', 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 == 'customCommands': choices = [] choices_display = [] playerinfo = self._get_player_info(self._popup_party_member_client_id) account = playerinfo['ds'] try: name = playerinfo['players'][0]['name_full'] except: name = account for i in ba.app.config.get('Custom Commands'): i = i.replace('$c', str(self._popup_party_member_client_id)) i = i.replace('$a', str(account)) i = i.replace('$n', str(name)) if ba.app.config['Direct Send']: choices.append(f'_ba.chatmessage("{i}")') else: choices.append(f'self._edit_text_msg_box("{i}")') choices_display.append(ba.Lstr(value=i)) choices.append('AddNewChoiceWindow()') choices_display.append(ba.Lstr(value='***Add New***')) PopupMenuWindow(position=popup_window.root_widget.get_screen_space_center(), color=self.bg_color, scale=self._get_popup_window_scale(), choices=choices, choices_display=choices_display, current_choice=choices[0], delegate=self) self._popup_type = 'executeChoice' elif choice == 'addNew': AddNewChoiceWindow() elif self._popup_type == 'menu': if choice == 'muteOption': current_choice = self._get_current_mute_type() PopupMenuWindow( position=(self._width - 60, self._height - 47), color=self.bg_color, scale=self._get_popup_window_scale(), choices=['muteInGameOnly', 'mutePartyWindowOnly', 'muteAll', 'unmuteAll'], choices_display=self._create_baLstr_list( ['Mute In Game Messages Only', 'Mute Party Window Messages Only', 'Mute all', 'Unmute All']), current_choice=current_choice, delegate=self ) self._popup_type = 'muteType' elif choice == 'modifyColor': ColorPickerExact(parent=self.get_root_widget(), position=self.get_root_widget().get_screen_space_center(), initial_color=self.bg_color, delegate=self, tag='') elif choice == 'addQuickReply': try: newReply = ba.textwidget(query=self._text_field) oldReplies = self._get_quick_responds() oldReplies.append(newReply) self._write_quick_responds(oldReplies) ba.screenmessage(f'"{newReply}" is added.', (0, 1, 0)) ba.playsound(ba.getsound('dingSmallHigh')) except: ba.print_exception() elif choice == 'removeQuickReply': quick_reply = self._get_quick_responds() PopupMenuWindow(position=self._send_button.get_screen_space_center(), color=self.bg_color, scale=self._get_popup_window_scale(), choices=quick_reply, choices_display=self._create_baLstr_list(quick_reply), current_choice=quick_reply[0], delegate=self) self._popup_type = 'removeQuickReplySelect' elif choice == 'credits': ConfirmWindow( text=u'\ue043Party Window Reloaded V3\ue043\n\nCredits - Droopy#3730\nSpecial Thanks - BoTT-Vishah#4150', action=self.join_discord, width=420, height=230, color=self.bg_color, text_scale=1.0, ok_text="Join Discord", origin_widget=self.get_root_widget()) elif choice == 'manualCamera': ba.containerwidget(edit=self._root_widget, transition='out_scale') Manual_camera_window() elif self._popup_type == 'muteType': self._change_mute_type(choice) elif self._popup_type == 'executeChoice': exec(choice) elif self._popup_type == 'quickMessage': if choice == '*** EDIT ORDER ***': SortQuickMessages() else: self._edit_text_msg_box(choice) elif self._popup_type == 'removeQuickReplySelect': data = self._get_quick_responds() data.remove(choice) self._write_quick_responds(data) ba.screenmessage(f'"{choice}" is removed.', (1, 0, 0)) ba.playsound(ba.getsound('shieldDown')) else: print(f'unhandled popup type: {self._popup_type}') del popup_window # unused def _vote_kick_player(self): if self._popup_party_member_is_host: ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='internal.cantKickHostError'), color=(1, 0, 0)) else: assert self._popup_party_member_client_id is not None # 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)) def _send_admin_kick_command(self): _ba.chatmessage('/kick ' + str(self._popup_party_member_client_id)) def _translate(self): def _apply_translation(translated): if self._text_field.exists(): ba.textwidget(edit=self._text_field, text=translated) msg = ba.textwidget(query=self._text_field) cfg = ba.app.config if msg == '': ba.screenmessage('Nothing to translate.', (1, 0, 0)) ba.playsound(ba.getsound('error')) else: data = dict(message=msg) if cfg['Translate Source Language']: data['src'] = cfg['Translate Source Language'] if cfg['Translate Destination Language']: data['dest'] = cfg['Translate Destination Language'] if cfg['Pronunciation']: data['type'] = 'pronunciation' Translate(data, _apply_translation).start() def _copy_to_clipboard(self): msg = ba.textwidget(query=self._text_field) if msg == '': ba.screenmessage('Nothing to copy.', (1, 0, 0)) ba.playsound(ba.getsound('error')) else: ba.clipboard_set_text(msg) ba.screenmessage(f'"{msg}" is copied to clipboard.', (0, 1, 0)) ba.playsound(ba.getsound('dingSmallHigh')) def _get_current_mute_type(self): cfg = ba.app.config if cfg['Chat Muted'] == True: if cfg['Party Chat Muted'] == True: return 'muteAll' else: return 'muteInGameOnly' else: if cfg['Party Chat Muted'] == True: return 'mutePartyWindowOnly' else: return 'unmuteAll' def _change_mute_type(self, choice): cfg = ba.app.config if choice == 'muteInGameOnly': cfg['Chat Muted'] = True cfg['Party Chat Muted'] = False elif choice == 'mutePartyWindowOnly': cfg['Chat Muted'] = False cfg['Party Chat Muted'] = True elif choice == 'muteAll': cfg['Chat Muted'] = True cfg['Party Chat Muted'] = True else: cfg['Chat Muted'] = False cfg['Party Chat Muted'] = False cfg.apply_and_commit() self._update() def popup_menu_closing(self, popup_window: PopupWindow) -> None: """Called when the popup is closing.""" 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-votes appeared in build 14248 if (_ba.get_connection_to_host_info().get('build_number', 0) < 14248): return kick_str = ba.Lstr(resource='kickVoteText') uiscale = ba.app.ui.uiscale choices = ['kick', 'mention', 'adminkick'] choices_display = [kick_str] + list(self._create_baLstr_list(['Mention this guy', f'Kick ID: {client_id}'])) choices.append('customCommands') choices_display.append(ba.Lstr(value='Custom Commands')) PopupMenuWindow( position=widget.get_screen_space_center(), color=self.bg_color, scale=(2.3 if uiscale is ba.UIScale.SMALL else 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23), choices=choices, choices_display=choices_display, current_choice='mention', delegate=self) self._popup_type = 'partyMemberPress' self._popup_party_member_client_id = client_id self._popup_party_member_is_host = is_host def _send_chat_message(self) -> None: msg = ba.textwidget(query=self._text_field) ba.textwidget(edit=self._text_field, text='') if '\\' in msg: msg = msg.replace('\\d', ('\ue048')) msg = msg.replace('\\c', ('\ue043')) msg = msg.replace('\\h', ('\ue049')) msg = msg.replace('\\s', ('\ue046')) msg = msg.replace('\\n', ('\ue04b')) msg = msg.replace('\\f', ('\ue04f')) msg = msg.replace('\\g', ('\ue027')) msg = msg.replace('\\i', ('\ue03a')) msg = msg.replace('\\m', ('\ue04d')) msg = msg.replace('\\t', ('\ue01f')) msg = msg.replace('\\bs', ('\ue01e')) msg = msg.replace('\\j', ('\ue010')) msg = msg.replace('\\e', ('\ue045')) msg = msg.replace('\\l', ('\ue047')) msg = msg.replace('\\a', ('\ue020')) msg = msg.replace('\\b', ('\ue00c')) if not msg: choices = self._get_quick_responds() choices.append('*** EDIT ORDER ***') PopupMenuWindow(position=self._send_button.get_screen_space_center(), scale=self._get_popup_window_scale(), color=self.bg_color, choices=choices, current_choice=choices[0], delegate=self) self._popup_type = 'quickMessage' return elif msg.startswith('/info '): account = msg.replace('/info ', '') if account: from bastd.ui.account import viewer viewer.AccountViewerWindow( account_id=account) ba.textwidget(edit=self._text_field, text='') return if not self._private_chat: if msg == '/id': myid = _ba.get_v1_account_misc_read_val_2('resolvedAccountID', '') _ba.chatmessage(f"My Unique ID : {myid}") elif msg == '/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}@{_port}'] = { 'addr': _ip, 'port': _port, 'name': title } config.commit() ba.screenmessage("Server Added To Manual", color=(0, 1, 0), transient=True) ba.playsound(ba.getsound('gunCocking')) elif msg != '': _ba.chatmessage(cast(str, msg)) else: receiver = messenger.filter name = _ba.get_v1_account_display_string() if not receiver: display_error('Choose a valid receiver id') return data = {'receiver': receiver, 'message': f'{name}: {msg}'} if msg.startswith('/rename '): if messenger.filter != 'all': nickname = msg.replace('/rename ', '') messenger._save_id(messenger.filter, nickname, verify=False) self._change_filter(messenger.filter) elif msg == '/remove': if messenger.filter != 'all': messenger._remove_id(messenger.filter) self._change_filter('all') else: display_error('Cant delete this') ba.textwidget(edit=self._text_field, text='') return ba.Call(messenger._send_request, url, data) ba.Call(check_new_message) Thread(target=messenger._send_request, args=(url, data)).start() Thread(target=check_new_message).start() ba.textwidget(edit=self._text_field, text='') def _write_quick_responds(self, data): try: with open(quick_msg_file, 'w') as f: f.write('\n'.join(data)) except: ba.print_exception() ba.screenmessage('Error!', (1, 0, 0)) ba.playsound(ba.getsound('error')) def _get_quick_responds(self): if os.path.exists(quick_msg_file): with open(quick_msg_file, 'r') as f: return f.read().split('\n') else: default_replies = ['What the hell?', 'Dude that\'s amazing!'] self._write_quick_responds(default_replies) return default_replies def color_picker_selected_color(self, picker, color) -> None: ba.containerwidget(edit=self._root_widget, color=color) color = tuple(round(i, 2) for i in color) self.bg_color = color ba.app.config['PartyWindow Main Color'] = color def color_picker_closing(self, picker) -> None: ba.app.config.apply_and_commit() def _remove_sender_from_message(self, msg=''): msg_start = msg.find(": ") + 2 return msg[msg_start:] def _previous_message(self): msgs = self._chat_texts if not hasattr(self, 'msg_index'): self.msg_index = len(msgs) - 1 else: if self.msg_index > 0: self.msg_index -= 1 else: del self.msg_index try: msg_widget = msgs[self.msg_index] msg = ba.textwidget(query=msg_widget) msg = self._remove_sender_from_message(msg) if msg in ('', ' '): self._previous_message() return except: msg = '' self._edit_text_msg_box(msg, 'replace') def _next_message(self): msgs = self._chat_texts if not hasattr(self, 'msg_index'): self.msg_index = 0 else: if self.msg_index < len(msgs) - 1: self.msg_index += 1 else: del self.msg_index try: msg_widget = msgs[self.msg_index] msg = ba.textwidget(query=msg_widget) msg = self._remove_sender_from_message(msg) if msg in ('', ' '): self._next_message() return except: msg = '' self._edit_text_msg_box(msg, 'replace') def _ip_port_msg(self): try: msg = f'IP : {_ip} PORT : {_port}' except: msg = '' self._edit_text_msg_box(msg, 'replace') def ping_server(self): info = _ba.get_connection_to_host_info() if info.get('name', '') != '': self.pingThread = PingThread(_ip, _port) self.pingThread.start() def _get_ping_color(self): try: if _ping < 100: return (0, 1, 0) elif _ping < 500: return (1, 1, 0) else: return (1, 0, 0) except: return (0.1, 0.1, 0.1) def _send_ping(self): if isinstance(_ping, int): _ba.chatmessage(f'My ping = {_ping}ms') def close(self) -> None: """Close the window.""" ba.containerwidget(edit=self._root_widget, transition='out_scale') def close_with_sound(self) -> None: """Close the window and make a lovely sound.""" ba.playsound(ba.getsound('swish')) self.close() def _get_popup_window_scale(self) -> float: uiscale = ba.app.ui.uiscale return (2.4 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0) def _create_baLstr_list(self, list1): return (ba.Lstr(value=i) for i in list1) def _get_player_info(self, clientID): info = {} for i in _ba.get_game_roster(): if i['client_id'] == clientID: info['ds'] = i['display_string'] info['players'] = i['players'] info['aid'] = i['account_id'] break return info def _edit_text_msg_box(self, text, action='add'): if isinstance(text, str): if action == 'add': ba.textwidget(edit=self._text_field, text=ba.textwidget(query=self._text_field) + text) elif action == 'replace': ba.textwidget(edit=self._text_field, text=text) def _on_setting_button_press(self): try: SettingsWindow() except Exception as e: ba.print_exception() pass def _on_privatechat_button_press(self): try: if messenger.logged_in: self._firstcall = True if self._chat_texts: while self._chat_texts: first = self._chat_texts.pop() first.delete() if not self._private_chat: self._private_chat = True else: self._filter_text.delete() self._popup_button.delete() self._private_chat = False self._update() else: if messenger.server_online: if not messenger._cookie_login(): if messenger._query(): LoginWindow(wtype='login') else: LoginWindow(wtype='signup') else: display_error(messenger.error) except Exception as e: ba.print_exception() pass def join_discord(self): ba.open_url("https://discord.gg/KvYgpEg2JR") class LoginWindow: def __init__(self, wtype): self.wtype = wtype if self.wtype == 'signup': title = 'Sign Up Window' label = 'Sign Up' else: title = 'Login Window' label = 'Log In' uiscale = ba.app.ui.uiscale bg_color = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self._root_widget = ba.containerwidget(size=(500, 250), transition='in_scale', color=bg_color, toolbar_visibility='menu_minimal_no_back', parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self._close, scale=(2.1 if uiscale is ba.UIScale.SMALL else 1.5 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._title_text = ba.textwidget(parent=self._root_widget, scale=0.8, color=(1, 1, 1), text=title, size=(0, 0), position=(250, 200), h_align='center', v_align='center') self._id = ba.textwidget(parent=self._root_widget, scale=0.5, color=(1, 1, 1), text=f'Account: ' + _ba.get_v1_account_misc_read_val_2('resolvedAccountID', ''), size=(0, 0), position=(220, 170), h_align='center', v_align='center') self._registrationkey_text = ba.textwidget(parent=self._root_widget, scale=0.5, color=(1, 1, 1), text=f'Registration Key:', size=(0, 0), position=(100, 140), h_align='center', v_align='center') self._text_field = ba.textwidget( parent=self._root_widget, editable=True, size=(200, 40), position=(175, 130), text='', maxwidth=410, flatness=1.0, autoselect=True, v_align='center', corner_scale=0.7) self._connect_button = ba.buttonwidget(parent=self._root_widget, size=(150, 30), color=(0, 1, 0), label='Get Registration Key', button_type='square', autoselect=True, position=(150, 80), on_activate_call=self._connect) self._confirm_button = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label=label, button_type='square', autoselect=True, position=(200, 40), on_activate_call=self._confirmcall) ba.textwidget(edit=self._text_field, on_return_press_call=self._confirm_button.activate) def _close(self): ba.containerwidget(edit=self._root_widget, transition=('out_scale')) def _connect(self): try: host = url.split('http://')[1].split(':')[0] import socket address = socket.gethostbyname(host) _ba.disconnect_from_host() _ba.connect_to_party(address, port=11111) except Exception: display_error('Cant get ip from hostname') def _confirmcall(self): if self.wtype == 'signup': key = ba.textwidget(query=self._text_field) answer = messenger._signup(registration_key=key) if key else None if answer: self._close() else: if messenger._login(registration_key=ba.textwidget(query=self._text_field)): self._close() class AddNewIdWindow: def __init__(self): uiscale = ba.app.ui.uiscale bg_color = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self._root_widget = ba.containerwidget(size=(500, 250), transition='in_scale', color=bg_color, toolbar_visibility='menu_minimal_no_back', parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self._close, scale=(2.1 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)) self._title_text = ba.textwidget(parent=self._root_widget, scale=0.8, color=(1, 1, 1), text='Add New ID', size=(0, 0), position=(250, 200), h_align='center', v_align='center') self._accountid_text = ba.textwidget(parent=self._root_widget, scale=0.6, color=(1, 1, 1), text='pb-id: ', size=(0, 0), position=(50, 155), h_align='center', v_align='center') self._accountid_field = ba.textwidget( parent=self._root_widget, editable=True, size=(250, 40), position=(100, 140), text='', maxwidth=410, flatness=1.0, autoselect=True, v_align='center', corner_scale=0.7) self._nickname_text = ba.textwidget(parent=self._root_widget, scale=0.5, color=(1, 1, 1), text='Nickname: ', size=(0, 0), position=(50, 115), h_align='center', v_align='center') self._nickname_field = ba.textwidget( parent=self._root_widget, editable=True, size=(250, 40), position=(100, 100), text='', maxwidth=410, flatness=1.0, autoselect=True, v_align='center', corner_scale=0.7) self._help_text = ba.textwidget(parent=self._root_widget, scale=0.4, color=(0.1, 0.9, 0.9), text='Help:\nEnter pb-id of account you\n want to chat to\nEnter nickname of id to\n recognize id easily\nLeave nickname \n to use their default name', size=(0, 0), position=(325, 120), h_align='left', v_align='center') self._add = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label='Add', button_type='square', autoselect=True, position=(100, 50), on_activate_call=ba.Call(self._relay_function)) ba.textwidget(edit=self._accountid_field, on_return_press_call=self._add.activate) self._remove = ba.buttonwidget(parent=self._root_widget, size=(75, 30), label='Remove', button_type='square', autoselect=True, position=(170, 50), on_activate_call=self._remove_id) ba.containerwidget(edit=self._root_widget, on_cancel_call=self._close) def _relay_function(self): account_id = ba.textwidget(query=self._accountid_field) nickname = ba.textwidget(query=self._nickname_field) try: if messenger._save_id(account_id, nickname): self._close() except: display_error('Enter valid pb-id') def _remove_id(self): uiscale = ba.app.ui.uiscale if len(messenger.saved_ids) > 1: choices = [i for i in messenger.saved_ids] choices.remove('all') choices_display = [ba.Lstr(value=messenger.saved_ids[i]) for i in choices] PopupMenuWindow(position=self._remove.get_screen_space_center(), color=ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)), scale=(2.4 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), choices=choices, choices_display=choices_display, current_choice=choices[0], delegate=self) self._popup_type = 'removeSelectedID' def popup_menu_selected_choice(self, popup_window: PopupMenuWindow, choice: str) -> None: """Called when a choice is selected in the popup.""" if self._popup_type == 'removeSelectedID': messenger._remove_id(choice) self._close() def popup_menu_closing(self, popup_window: PopupWindow) -> None: """Called when the popup is closing.""" def _close(self): ba.containerwidget(edit=self._root_widget, transition=('out_scale')) class AddNewChoiceWindow: def __init__(self): uiscale = ba.app.ui.uiscale bg_color = ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)) self._root_widget = ba.containerwidget(size=(500, 250), transition='in_scale', color=bg_color, toolbar_visibility='menu_minimal_no_back', parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self._close, scale=(2.1 if uiscale is ba.UIScale.SMALL else 1.5 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._title_text = ba.textwidget(parent=self._root_widget, scale=0.8, color=(1, 1, 1), text='Add Custom Command', size=(0, 0), position=(250, 200), h_align='center', v_align='center') self._text_field = ba.textwidget( parent=self._root_widget, editable=True, size=(500, 40), position=(75, 140), text='', maxwidth=410, flatness=1.0, autoselect=True, v_align='center', corner_scale=0.7) self._help_text = ba.textwidget(parent=self._root_widget, scale=0.4, color=(0.2, 0.2, 0.2), text='Use\n$c = client id\n$a = account id\n$n = name', size=(0, 0), position=(70, 75), h_align='left', v_align='center') self._add = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label='Add', button_type='square', autoselect=True, position=(150, 50), on_activate_call=self._add_choice) ba.textwidget(edit=self._text_field, on_return_press_call=self._add.activate) self._remove = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label='Remove', button_type='square', autoselect=True, position=(350, 50), on_activate_call=self._remove_custom_command) ba.containerwidget(edit=self._root_widget, on_cancel_call=self._close) def _add_choice(self): newCommand = ba.textwidget(query=self._text_field) cfg = ba.app.config if any(i in newCommand for i in ('$c', '$a', '$n')): cfg['Custom Commands'].append(newCommand) cfg.apply_and_commit() ba.screenmessage('Added successfully', (0, 1, 0)) ba.playsound(ba.getsound('dingSmallHigh')) self._close() else: ba.screenmessage('Use at least of these ($c, $a, $n)', (1, 0, 0)) ba.playsound(ba.getsound('error')) def _remove_custom_command(self): uiscale = ba.app.ui.uiscale commands = ba.app.config['Custom Commands'] PopupMenuWindow(position=self._remove.get_screen_space_center(), color=ba.app.config.get('PartyWindow Main Color', (0.5, 0.5, 0.5)), scale=(2.4 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), choices=commands, current_choice=commands[0], delegate=self) self._popup_type = 'removeCustomCommandSelect' def popup_menu_selected_choice(self, popup_window: PopupMenuWindow, choice: str) -> None: """Called when a choice is selected in the popup.""" if self._popup_type == 'removeCustomCommandSelect': config = ba.app.config config['Custom Commands'].remove(choice) config.apply_and_commit() ba.screenmessage('Removed successfully', (0, 1, 0)) ba.playsound(ba.getsound('shieldDown')) def popup_menu_closing(self, popup_window: PopupWindow) -> None: """Called when the popup is closing.""" def _close(self): ba.containerwidget(edit=self._root_widget, transition=('out_scale')) class Manual_camera_window: def __init__(self): self._root_widget = ba.containerwidget( on_outside_click_call=None, size=(0, 0)) button_size = (30, 30) self._title_text = ba.textwidget(parent=self._root_widget, scale=0.9, color=(1, 1, 1), text='Manual Camera Setup', size=(0, 0), position=(130, 153), h_align='center', v_align='center') self._xminus = ba.buttonwidget(parent=self._root_widget, size=button_size, label=ba.charstr(ba.SpecialChar.LEFT_ARROW), button_type='square', autoselect=True, position=(1, 60), on_activate_call=ba.Call(self._change_camera_position, 'x-')) self._xplus = ba.buttonwidget(parent=self._root_widget, size=button_size, label=ba.charstr(ba.SpecialChar.RIGHT_ARROW), button_type='square', autoselect=True, position=(60, 60), on_activate_call=ba.Call(self._change_camera_position, 'x')) self._yplus = ba.buttonwidget(parent=self._root_widget, size=button_size, label=ba.charstr(ba.SpecialChar.UP_ARROW), button_type='square', autoselect=True, position=(30, 100), on_activate_call=ba.Call(self._change_camera_position, 'y')) self._yminus = ba.buttonwidget(parent=self._root_widget, size=button_size, label=ba.charstr(ba.SpecialChar.DOWN_ARROW), button_type='square', autoselect=True, position=(30, 20), on_activate_call=ba.Call(self._change_camera_position, 'y-')) self.inwards = ba.buttonwidget(parent=self._root_widget, size=(100, 30), label='INWARDS', button_type='square', autoselect=True, position=(120, 90), on_activate_call=ba.Call(self._change_camera_position, 'z-')) self._outwards = ba.buttonwidget(parent=self._root_widget, size=(100, 30), label='OUTWARDS', button_type='square', autoselect=True, position=(120, 50), on_activate_call=ba.Call(self._change_camera_position, 'z')) self._step_text = ba.textwidget(parent=self._root_widget, scale=0.5, color=(1, 1, 1), text='Step:', size=(0, 0), position=(1, -20), h_align='center', v_align='center') self._text_field = ba.textwidget( parent=self._root_widget, editable=True, size=(100, 40), position=(26, -35), text='', maxwidth=120, flatness=1.0, autoselect=True, v_align='center', corner_scale=0.7) self._reset = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label='Reset', button_type='square', autoselect=True, position=(120, -35), on_activate_call=ba.Call(self._change_camera_position, 'reset')) self._done = ba.buttonwidget(parent=self._root_widget, size=(50, 30), label='Done', button_type='square', autoselect=True, position=(180, -35), on_activate_call=self._close) ba.containerwidget(edit=self._root_widget, cancel_button=self._done) def _close(self): ba.containerwidget(edit=self._root_widget, transition=('out_scale')) def _change_camera_position(self, direction): activity = _ba.get_foreground_host_activity() node = activity.globalsnode aoi = list(node.area_of_interest_bounds) center = [(aoi[0] + aoi[3]) / 2, (aoi[1] + aoi[4]) / 2, (aoi[2] + aoi[5]) / 2] size = (aoi[3] - aoi[0], aoi[4] - aoi[1], aoi[5] - aoi[2]) try: increment = float(ba.textwidget(query=self._text_field)) except: # ba.print_exception() increment = 1 if direction == 'x': center[0] += increment elif direction == 'x-': center[0] -= increment elif direction == 'y': center[1] += increment elif direction == 'y-': center[1] -= increment elif direction == 'z': center[2] += increment elif direction == 'z-': center[2] -= increment elif direction == 'reset': node.area_of_interest_bounds = activity._map.get_def_bound_box('area_of_interest_bounds') return aoi = (center[0] - size[0] / 2, center[1] - size[1] / 2, center[2] - size[2] / 2, center[0] + size[0] / 2, center[1] + size[1] / 2, center[2] + size[2] / 2) node.area_of_interest_bounds = tuple(aoi) def __popup_menu_window_init__(self, position: Tuple[float, float], choices: Sequence[str], current_choice: str, delegate: Any = None, width: float = 230.0, maxwidth: float = None, scale: float = 1.0, color: Tuple[float, float, float] = (0.35, 0.55, 0.15), choices_disabled: Sequence[str] = None, choices_display: Sequence[ba.Lstr] = None): # FIXME: Clean up a bit. # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements if choices_disabled is None: choices_disabled = [] if choices_display is None: choices_display = [] # FIXME: For the moment we base our width on these strings so # we need to flatten them. choices_display_fin: List[str] = [] for choice_display in choices_display: choices_display_fin.append(choice_display.evaluate()) if maxwidth is None: maxwidth = width * 1.5 self._transitioning_out = False self._choices = list(choices) self._choices_display = list(choices_display_fin) self._current_choice = current_choice self._color = color self._choices_disabled = list(choices_disabled) self._done_building = False if not choices: raise TypeError('Must pass at least one choice') self._width = width self._scale = scale if len(choices) > 8: self._height = 280 self._use_scroll = True else: self._height = 20 + len(choices) * 33 self._use_scroll = False self._delegate = None # don't want this stuff called just yet.. # extend width to fit our longest string (or our max-width) for index, choice in enumerate(choices): if len(choices_display_fin) == len(choices): choice_display_name = choices_display_fin[index] else: choice_display_name = choice if self._use_scroll: self._width = max( self._width, min( maxwidth, _ba.get_string_width(choice_display_name, suppress_warning=True)) + 75) else: self._width = max( self._width, min( maxwidth, _ba.get_string_width(choice_display_name, suppress_warning=True)) + 60) # init parent class - this will rescale and reposition things as # needed and create our root widget PopupWindow.__init__(self, position, size=(self._width, self._height), bg_color=self._color, scale=self._scale) if self._use_scroll: self._scrollwidget = ba.scrollwidget(parent=self.root_widget, position=(20, 20), highlight=False, color=(0.35, 0.55, 0.15), size=(self._width - 40, self._height - 40)) self._columnwidget = ba.columnwidget(parent=self._scrollwidget, border=2, margin=0) else: self._offset_widget = ba.containerwidget(parent=self.root_widget, position=(30, 15), size=(self._width - 40, self._height), background=False) self._columnwidget = ba.columnwidget(parent=self._offset_widget, border=2, margin=0) for index, choice in enumerate(choices): if len(choices_display_fin) == len(choices): choice_display_name = choices_display_fin[index] else: choice_display_name = choice inactive = (choice in self._choices_disabled) wdg = ba.textwidget(parent=self._columnwidget, size=(self._width - 40, 28), on_select_call=ba.Call(self._select, index), click_activate=True, color=(0.5, 0.5, 0.5, 0.5) if inactive else ((0.5, 1, 0.5, 1) if choice == self._current_choice else (0.8, 0.8, 0.8, 1.0)), padding=0, maxwidth=maxwidth, text=choice_display_name, on_activate_call=self._activate, v_align='center', selectable=(not inactive)) if choice == self._current_choice: ba.containerwidget(edit=self._columnwidget, selected_child=wdg, visible_child=wdg) # ok from now on our delegate can be called self._delegate = weakref.ref(delegate) self._done_building = True original_connect_to_party = _ba.connect_to_party original_sign_in = _ba.sign_in_v1 def modify_connect_to_party(address: str, port: int = 43210, print_progress: bool = True) -> None: global _ip, _port _ip = address _port = port original_connect_to_party(_ip, _port, print_progress) temptimer = None def modify_sign_in(account_type: str) -> None: original_sign_in(account_type) if messenger.server_online: messenger.logged_in = False global temptimer temptimer = ba.Timer(2, messenger._cookie_login) class PingThread(Thread): """Thread for sending out game pings.""" def __init__(self, address: str, port: int): super().__init__() self._address = address self._port = port def run(self) -> None: sock: Optional[socket.socket] = None try: import socket from ba.internal import get_ip_address_type socket_type = get_ip_address_type(self._address) sock = socket.socket(socket_type, socket.SOCK_DGRAM) sock.connect((self._address, self._port)) 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) global _ping _ping = int((time.time() - starttime) * 1000.0) 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) def _get_store_char_tex(self) -> str: _ba.set_party_icon_always_visible(True) return ('storeCharacterXmas' if _ba.get_v1_account_misc_read_val( 'xmas', False) else 'storeCharacterEaster' if _ba.get_v1_account_misc_read_val( 'easter', False) else 'storeCharacter') # ba_meta export plugin class InitalRun(ba.Plugin): def __init__(self): if _ba.env().get("build_number", 0) >= 20124: global messenger, listener, displayer, color_tracker initialize() messenger = PrivateChatHandler() listener = Thread(target=messenger_thread) listener.start() displayer = ba.Timer(0.4, msg_displayer, True) color_tracker = ColorTracker() bastd.ui.party.PartyWindow = PartyWindow PopupMenuWindow.__init__ = __popup_menu_window_init__ _ba.connect_to_party = modify_connect_to_party _ba.sign_in_v1 = modify_sign_in MainMenuWindow._get_store_char_tex = _get_store_char_tex else: display_error("This Party Window only runs with BombSquad version higer than 1.6.0.")