bombsquad-plugin-manager/plugins/utilities/Updated_Ultra_Partywindow.py
2022-10-02 00:28:07 +05:30

2755 lines
120 KiB
Python

__author__ = 'Droopy'
__version__ = 4.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': '<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', ''))
except:
self.error = 'Server offline'
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.internal.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.internal.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='<default>', 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 == '<default>':
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.internal.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.internal.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.internal.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='<default>',
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 <default>\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.internal.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.internal.get_v1_account_misc_read_val(
'xmas', False) else
'storeCharacterEaster' if ba.internal.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.internal.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.")