diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 98ab2eb4..533c9d1d 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -30,8 +30,8 @@
achname
achs
acinstance
- ack'ed
ack
+ ack'ed
acked
acks
acnt
@@ -153,8 +153,8 @@
bacommon
badguy
bafoundation
- ballistica's
ballistica
+ ballistica's
ballisticacore
ballisticacorecb
bamaster
@@ -801,8 +801,8 @@
gamedata
gameinstance
gamemap
- gamepad's
gamepad
+ gamepad's
gamepadadvanced
gamepads
gamepadselect
@@ -1191,8 +1191,8 @@
lsqlite
lssl
lstart
- lstr's
lstr
+ lstr's
lstrs
lsval
ltex
@@ -1828,8 +1828,8 @@
sessionname
sessionplayer
sessionplayers
- sessionteam's
sessionteam
+ sessionteam's
sessionteams
sessiontype
setactivity
@@ -2164,8 +2164,8 @@
txtw
typeargs
typecheck
- typechecker's
typechecker
+ typechecker's
typedval
typeshed
typestr
diff --git a/assets/src/ba_data/python/bastd/ui/gather/publictab.py b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
index 6e2a02ea..3ae73a1c 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/publictab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
@@ -1,12 +1,13 @@
# Released under the MIT License. See LICENSE for details.
#
-# pylint: disable=too-many-lines
"""Defines the public tab in the gather UI."""
from __future__ import annotations
import time
import threading
+from enum import Enum
+from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
import _ba
@@ -18,42 +19,89 @@ if TYPE_CHECKING:
from bastd.ui.gather import GatherWindow
+class SubTabType(Enum):
+ """Available sub-tabs."""
+ JOIN = 'join'
+ HOST = 'host'
+
+
+@dataclass
+class TabState:
+ """State saved/restored only while the app is running."""
+ sub_tab: SubTabType = SubTabType.JOIN
+
+
+@dataclass
+class PartyEntry:
+ """Info about a party."""
+ address: str
+ next_ping_time: float
+ ping: Optional[int]
+ index: int
+ queue: Optional[str] = None
+ port: int = -1
+ name: str = ''
+ size: int = -1
+ size_max: int = -1
+ claimed: bool = False
+ ping_interval: float = -1.0
+ stats_addr: Optional[str] = None
+ name_widget: Optional[ba.Widget] = None
+ ping_widget: Optional[ba.Widget] = None
+ stats_button: Optional[ba.Widget] = None
+ size_widget: Optional[ba.Widget] = None
+
+
+class AddrFetchThread(threading.Thread):
+ """Thread for fetching an address in the bg."""
+
+ def __init__(self, call: Callable[[Any], Any]):
+ super().__init__()
+ self._call = call
+
+ def run(self) -> None:
+ try:
+ # FIXME: Update this to work with IPv6 at some point.
+ import socket
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.connect(('8.8.8.8', 80))
+ val = sock.getsockname()[0]
+ sock.close()
+ ba.pushcall(ba.Call(self._call, val), from_other_thread=True)
+ except Exception as exc:
+ # Ignore expected network errors; log others.
+ import errno
+ if isinstance(exc, OSError) and exc.errno == errno.ENETUNREACH:
+ pass
+ else:
+ ba.print_exception()
+
+
class PublicGatherTab(GatherTab):
"""The public tab in the gather UI"""
def __init__(self, window: GatherWindow) -> None:
super().__init__(window)
self._container: Optional[ba.Widget] = None
- self._internet_join_text: Optional[ba.Widget] = None
- self._internet_host_text: Optional[ba.Widget] = None
- self._internet_local_address: Optional[str] = None
+ self._join_text: Optional[ba.Widget] = None
+ self._host_text: Optional[ba.Widget] = None
+ self._local_address: Optional[str] = None
self._last_public_party_connect_attempt_time: Optional[float] = None
- self._internet_tab: Optional[str] = None
+ self._sub_tab: SubTabType = SubTabType.JOIN
self._public_party_list_selection: Optional[Tuple[str, str]] = None
self._refreshing_public_party_list: Optional[bool] = None
self._update_timer: Optional[ba.Timer] = None
- self._internet_host_scrollwidget: Optional[ba.Widget] = None
- self._internet_host_name_text: Optional[ba.Widget] = None
- self._internet_host_toggle_button: Optional[ba.Widget] = None
- self._internet_join_last_refresh_time = -99999.0
- self._internet_join_party_name_label: Optional[ba.Widget] = None
- self._internet_join_party_language_label: Optional[ba.Widget] = None
- self._internet_join_party_size_label: Optional[ba.Widget] = None
- self._internet_join_party_ping_label: Optional[ba.Widget] = None
- self._internet_host_columnwidget: Optional[ba.Widget] = None
- self._internet_join_status_text: Optional[ba.Widget] = None
- self._internet_host_name_label_text: Optional[ba.Widget] = None
- self._internet_host_max_party_size_label: Optional[ba.Widget] = None
- self._internet_host_max_party_size_value: Optional[ba.Widget] = None
- self._internet_host_max_party_size_minus_button: (
- Optional[ba.Widget]) = None
- self._internet_host_max_party_size_plus_button: (
- Optional[ba.Widget]) = None
- self._internet_host_status_text: Optional[ba.Widget] = None
- self._internet_host_dedicated_server_info_text: (
- Optional[ba.Widget]) = None
- self._internet_lock_icon: Optional[ba.Widget] = None
- self._public_parties: Dict[str, Dict[str, Any]] = {}
+ self._host_scrollwidget: Optional[ba.Widget] = None
+ self._host_name_text: Optional[ba.Widget] = None
+ self._host_toggle_button: Optional[ba.Widget] = None
+ self._join_last_refresh_time = -99999.0
+ self._host_columnwidget: Optional[ba.Widget] = None
+ self._join_status_text: Optional[ba.Widget] = None
+ self._host_max_party_size_value: Optional[ba.Widget] = None
+ self._host_max_party_size_minus_button: (Optional[ba.Widget]) = None
+ self._host_max_party_size_plus_button: (Optional[ba.Widget]) = None
+ self._host_status_text: Optional[ba.Widget] = None
+ self._public_parties: Dict[str, PartyEntry] = {}
self._last_public_party_list_rebuild_time: Optional[float] = None
self._first_public_party_list_rebuild_time: Optional[float] = None
self._next_public_party_entry_index = 0
@@ -77,7 +125,7 @@ class PublicGatherTab(GatherTab):
background=False,
selection_loops_to_parent=True)
v = c_height - 30
- self._internet_join_text = txt = ba.textwidget(
+ self._join_text = ba.textwidget(
parent=self._container,
position=(c_width * 0.5 - 245, v - 13),
color=(0.6, 1.0, 0.6),
@@ -89,16 +137,15 @@ class PublicGatherTab(GatherTab):
click_activate=True,
selectable=True,
autoselect=True,
- on_activate_call=lambda: self._set_internet_tab('join',
- region_width,
- region_height,
- region_left,
- region_bottom,
- playsound=True),
+ on_activate_call=lambda: self._set_sub_tab(SubTabType.JOIN,
+ region_width,
+ region_height,
+ region_left,
+ region_bottom,
+ playsound=True),
text=ba.Lstr(resource='gatherWindow.'
'joinPublicPartyDescriptionText'))
- ba.widget(edit=txt, up_widget=tab_button)
- self._internet_host_text = txt = ba.textwidget(
+ self._host_text = ba.textwidget(
parent=self._container,
position=(c_width * 0.5 + 45, v - 13),
color=(0.6, 1.0, 0.6),
@@ -110,86 +157,60 @@ class PublicGatherTab(GatherTab):
click_activate=True,
selectable=True,
autoselect=True,
- on_activate_call=lambda: self._set_internet_tab('host',
- region_width,
- region_height,
- region_left,
- region_bottom,
- playsound=True),
+ on_activate_call=lambda: self._set_sub_tab(SubTabType.HOST,
+ region_width,
+ region_height,
+ region_left,
+ region_bottom,
+ playsound=True),
text=ba.Lstr(resource='gatherWindow.'
'hostPublicPartyDescriptionText'))
- ba.widget(edit=txt,
- left_widget=self._internet_join_text,
+ ba.widget(edit=self._join_text, up_widget=tab_button)
+ ba.widget(edit=self._host_text,
+ left_widget=self._join_text,
up_widget=tab_button)
- ba.widget(edit=self._internet_join_text, right_widget=txt)
+ ba.widget(edit=self._join_text, right_widget=self._host_text)
# Attempt to fetch our local address so we have it for
# error messages.
- self._internet_local_address = None
+ self._local_address = None
- class AddrFetchThread(threading.Thread):
- """Thread for fetching an address in the bg."""
+ AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start()
- def __init__(self, call: Callable[[Any], Any]):
- super().__init__()
- self._call = call
-
- def run(self) -> None:
- try:
- # FIXME: Update this to work with IPv6 at some point.
- import socket
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.connect(('8.8.8.8', 80))
- val = sock.getsockname()[0]
- sock.close()
- ba.pushcall(ba.Call(self._call, val),
- from_other_thread=True)
- except Exception as exc:
- # Ignore expected network errors; log others.
- import errno
- if (isinstance(exc, OSError)
- and exc.errno == errno.ENETUNREACH):
- pass
- else:
- ba.print_exception()
-
- AddrFetchThread(ba.WeakCall(
- self._internet_fetch_local_addr_cb)).start()
-
- assert self._internet_tab is not None
- self._set_internet_tab(self._internet_tab, region_width, region_height,
- region_left, region_bottom)
+ assert self._sub_tab is not None
+ self._set_sub_tab(self._sub_tab, region_width, region_height,
+ region_left, region_bottom)
self._update_timer = ba.Timer(0.2,
- ba.WeakCall(self._update_internet_tab),
+ ba.WeakCall(self._update_sub_tab),
repeat=True,
timetype=ba.TimeType.REAL)
# Also update it immediately so we don't have to wait for the
# initial query.
- self._update_internet_tab()
+ self._update_sub_tab()
return self._container
def on_deactivate(self) -> None:
self._update_timer = None
def save_state(self) -> None:
- ba.app.ui.window_states[self.__class__.__name__] = {
- 'internet_tab': self._internet_tab
- }
+ ba.app.ui.window_states[self.__class__.__name__] = TabState(
+ sub_tab=self._sub_tab)
def restore_state(self) -> None:
- winstate = ba.app.ui.window_states.get(self.__class__.__name__, {})
- self._internet_tab = winstate.get('internet_tab', 'join')
+ state = ba.app.ui.window_states.get(self.__class__.__name__)
+ if state is None:
+ state = TabState()
+ assert isinstance(state, TabState)
+ self._sub_tab = state.sub_tab
- def _set_internet_tab(self,
- value: str,
- region_width: float,
- region_height: float,
- region_left: float,
- region_bottom: float,
- playsound: bool = False) -> None:
- # pylint: disable=too-many-locals
- # pylint: disable=too-many-statements
+ def _set_sub_tab(self,
+ value: SubTabType,
+ region_width: float,
+ region_height: float,
+ region_left: float,
+ region_bottom: float,
+ playsound: bool = False) -> None:
assert self._container
del region_left, region_bottom # Unused
if playsound:
@@ -198,38 +219,22 @@ class PublicGatherTab(GatherTab):
# If we're switching in from elsewhere, reset our selection.
# (prevents selecting something way down the list if we switched away
# and came back)
- if self._internet_tab != value:
+ if self._sub_tab != value:
self._public_party_list_selection = None
- self._internet_tab = value
+ self._sub_tab = value
active_color = (0.6, 1.0, 0.6)
inactive_color = (0.5, 0.4, 0.5)
ba.textwidget(
- edit=self._internet_join_text,
- color=active_color if value == 'join' else inactive_color)
+ edit=self._join_text,
+ color=active_color if value is SubTabType.JOIN else inactive_color)
ba.textwidget(
- edit=self._internet_host_text,
- color=active_color if value == 'host' else inactive_color)
+ edit=self._host_text,
+ color=active_color if value is SubTabType.HOST else inactive_color)
- # Clear anything in existence.
- for widget in [
- self._internet_host_scrollwidget,
- self._internet_host_name_text,
- self._internet_host_toggle_button,
- self._internet_host_name_label_text,
- self._internet_host_status_text,
- self._internet_join_party_size_label,
- self._internet_join_party_name_label,
- self._internet_join_party_language_label,
- self._internet_join_party_ping_label,
- self._internet_host_max_party_size_label,
- self._internet_host_max_party_size_value,
- self._internet_host_max_party_size_minus_button,
- self._internet_host_max_party_size_plus_button,
- self._internet_join_status_text,
- self._internet_host_dedicated_server_info_text
- ]:
- if widget is not None:
+ # Clear anything in existence in our sub-tabs.
+ for widget in self._container.get_children():
+ if widget and widget not in {self._host_text, self._join_text}:
widget.delete()
c_width = region_width
@@ -238,231 +243,212 @@ class PublicGatherTab(GatherTab):
sub_scroll_width = 830
v = c_height - 35
v -= 25
- is_public_enabled = _ba.get_public_party_enabled()
- if value == 'join':
- # Reset this so we do an immediate refresh query.
- self._internet_join_last_refresh_time = -99999.0
+ if value is SubTabType.JOIN:
+ self._build_join_tab(v, sub_scroll_width, sub_scroll_height,
+ c_width, c_height)
- # Reset our list of public parties.
- self._public_parties = {}
- self._last_public_party_list_rebuild_time = 0
- self._first_public_party_list_rebuild_time = None
- self._internet_join_party_name_label = ba.textwidget(
- text=ba.Lstr(resource='nameText'),
- parent=self._container,
- size=(0, 0),
- position=(90, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.5, 0.5),
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center')
- if bool(False):
- self._internet_join_party_language_label = ba.textwidget(
- text=ba.Lstr(
- resource='settingsWindowAdvanced.languageText'),
- parent=self._container,
- size=(0, 0),
- position=(662, v - 8),
- maxwidth=100,
- scale=0.6,
- color=(0.5, 0.5, 0.5),
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center')
- self._internet_join_party_size_label = ba.textwidget(
- text=ba.Lstr(resource='gatherWindow.'
- 'partySizeText'),
- parent=self._container,
- size=(0, 0),
- position=(755, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.5, 0.5),
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center')
- self._internet_join_party_ping_label = ba.textwidget(
- text=ba.Lstr(resource='gatherWindow.'
- 'pingText'),
- parent=self._container,
- size=(0, 0),
- position=(825, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.5, 0.5),
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center')
- v -= sub_scroll_height + 23
+ if value is SubTabType.HOST:
+ self._build_host_tab(v, sub_scroll_width, sub_scroll_height,
+ c_width, c_height)
- self._internet_host_scrollwidget = scrollw = ba.scrollwidget(
- parent=self._container,
- simple_culling_v=10,
- position=((region_width - sub_scroll_width) * 0.5, v),
- size=(sub_scroll_width, sub_scroll_height))
- ba.widget(edit=scrollw, autoselect=True)
- colw = self._internet_host_columnwidget = ba.containerwidget(
- parent=scrollw, background=False, size=(400, 400))
- ba.containerwidget(edit=scrollw, claims_left_right=True)
- ba.containerwidget(edit=colw, claims_left_right=True)
+ def _build_join_tab(self, v: float, sub_scroll_width: float,
+ sub_scroll_height: float, c_width: float,
+ c_height: float) -> None:
+ # Reset this so we do an immediate refresh query.
+ self._join_last_refresh_time = -99999.0
- self._internet_join_status_text = ba.textwidget(
- parent=self._container,
- text=ba.Lstr(value='${A}...',
- subs=[('${A}',
- ba.Lstr(resource='store.loadingText'))]),
- size=(0, 0),
- scale=0.9,
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='top',
- maxwidth=c_width,
- color=(0.6, 0.6, 0.6),
- position=(c_width * 0.5, c_height * 0.5))
+ # Reset our list of public parties.
+ self._public_parties = {}
+ self._last_public_party_list_rebuild_time = 0
+ self._first_public_party_list_rebuild_time = None
+ ba.textwidget(text=ba.Lstr(resource='nameText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(90, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.5, 0.5),
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='center')
+ ba.textwidget(text=ba.Lstr(resource='gatherWindow.'
+ 'partySizeText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(755, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.5, 0.5),
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='center')
+ ba.textwidget(text=ba.Lstr(resource='gatherWindow.'
+ 'pingText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(825, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.5, 0.5),
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='center')
+ v -= sub_scroll_height + 23
- if value == 'host':
- v -= 30
- party_name_text = ba.Lstr(
- resource='gatherWindow.partyNameText',
- fallback_resource='editGameListWindow.nameText')
- self._internet_host_name_label_text = ba.textwidget(
- parent=self._container,
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=ba.app.ui.infotextcolor,
- position=(210, v - 9),
- text=party_name_text)
- self._internet_host_name_text = ba.textwidget(
- parent=self._container,
- editable=True,
- size=(535, 40),
- position=(230, v - 30),
- text=ba.app.config.get('Public Party Name', ''),
- maxwidth=494,
- shadow=0.3,
- flatness=1.0,
- description=party_name_text,
- autoselect=True,
- v_align='center',
- corner_scale=1.0)
-
- v -= 60
- self._internet_host_max_party_size_label = ba.textwidget(
- parent=self._container,
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=ba.app.ui.infotextcolor,
- position=(210, v - 9),
- text=ba.Lstr(resource='maxPartySizeText',
- fallback_resource='maxConnectionsText'))
- self._internet_host_max_party_size_value = ba.textwidget(
- parent=self._container,
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=1.2,
- color=(1, 1, 1),
- position=(240, v - 9),
- text=str(_ba.get_public_party_max_size()))
- btn1 = self._internet_host_max_party_size_minus_button = (
- ba.buttonwidget(
- parent=self._container,
- size=(40, 40),
- on_activate_call=ba.WeakCall(
- self._on_max_public_party_size_minus_press),
- position=(280, v - 26),
- label='-',
- autoselect=True))
- btn2 = self._internet_host_max_party_size_plus_button = (
- ba.buttonwidget(parent=self._container,
- size=(40, 40),
- on_activate_call=ba.WeakCall(
- self._on_max_public_party_size_plus_press),
- position=(350, v - 26),
- label='+',
- autoselect=True))
- v -= 50
- v -= 70
- if is_public_enabled:
- label = ba.Lstr(
- resource='gatherWindow.makePartyPrivateText',
- fallback_resource='gatherWindow.stopAdvertisingText')
- else:
- label = ba.Lstr(
- resource='gatherWindow.makePartyPublicText',
- fallback_resource='gatherWindow.startAdvertisingText')
- self._internet_host_toggle_button = ba.buttonwidget(
- parent=self._container,
- label=label,
- size=(400, 80),
- on_activate_call=self._on_stop_internet_advertising_press
- if is_public_enabled else
- self._on_start_internet_advertizing_press,
- position=(c_width * 0.5 - 200, v),
- autoselect=True,
- up_widget=btn2)
- ba.widget(edit=self._internet_host_name_text, down_widget=btn2)
- ba.widget(edit=btn2, up_widget=self._internet_host_name_text)
- ba.widget(edit=btn1, up_widget=self._internet_host_name_text)
- ba.widget(edit=self._internet_join_text,
- down_widget=self._internet_host_name_text)
- v -= 10
- self._internet_host_status_text = ba.textwidget(
- parent=self._container,
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusNotPublicText'),
- size=(0, 0),
- scale=0.7,
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='top',
- maxwidth=c_width,
- color=(0.6, 0.6, 0.6),
- position=(c_width * 0.5, v))
- v -= 90
- self._internet_host_dedicated_server_info_text = ba.textwidget(
- parent=self._container,
- text=ba.Lstr(resource='gatherWindow.'
- 'dedicatedServerInfoText'),
- size=(0, 0),
- scale=0.7,
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center',
- maxwidth=c_width * 0.9,
- color=ba.app.ui.infotextcolor,
- position=(c_width * 0.5, v))
-
- # If public sharing is already on,
- # launch a status-check immediately.
- if _ba.get_public_party_enabled():
- self._do_internet_status_check()
-
- # Now add a lock icon overlay for if we don't have pro.
- icon = self._internet_lock_icon
- if icon and self._internet_lock_icon:
- self._internet_lock_icon.delete() # Kill any existing.
- self._internet_lock_icon = ba.imagewidget(
+ self._host_scrollwidget = scrollw = ba.scrollwidget(
parent=self._container,
- position=(c_width * 0.5 - 60, c_height * 0.5 - 50),
- size=(120, 120),
- opacity=0.0 if not self._is_internet_locked() else 0.5,
- texture=ba.gettexture('lock'))
+ simple_culling_v=10,
+ position=((c_width - sub_scroll_width) * 0.5, v),
+ size=(sub_scroll_width, sub_scroll_height))
+ ba.widget(edit=scrollw, autoselect=True)
+ colw = self._host_columnwidget = ba.containerwidget(parent=scrollw,
+ background=False,
+ size=(400, 400))
+ ba.containerwidget(edit=scrollw, claims_left_right=True)
+ ba.containerwidget(edit=colw, claims_left_right=True)
+
+ self._join_status_text = ba.textwidget(
+ parent=self._container,
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='store.loadingText'))],
+ ),
+ size=(0, 0),
+ scale=0.9,
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='top',
+ maxwidth=c_width,
+ color=(0.6, 0.6, 0.6),
+ position=(c_width * 0.5, c_height * 0.5))
+
+ def _build_host_tab(self, v: float, sub_scroll_width: float,
+ sub_scroll_height: float, c_width: float,
+ c_height: float) -> None:
+ del sub_scroll_width, sub_scroll_height, c_height # Unused.
+ is_public_enabled = _ba.get_public_party_enabled()
+ v -= 30
+ party_name_text = ba.Lstr(
+ resource='gatherWindow.partyNameText',
+ fallback_resource='editGameListWindow.nameText')
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=ba.app.ui.infotextcolor,
+ position=(210, v - 9),
+ text=party_name_text)
+ self._host_name_text = ba.textwidget(parent=self._container,
+ editable=True,
+ size=(535, 40),
+ position=(230, v - 30),
+ text=ba.app.config.get(
+ 'Public Party Name', ''),
+ maxwidth=494,
+ shadow=0.3,
+ flatness=1.0,
+ description=party_name_text,
+ autoselect=True,
+ v_align='center',
+ corner_scale=1.0)
+
+ v -= 60
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=ba.app.ui.infotextcolor,
+ position=(210, v - 9),
+ text=ba.Lstr(resource='maxPartySizeText',
+ fallback_resource='maxConnectionsText'))
+ self._host_max_party_size_value = ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=1.2,
+ color=(1, 1, 1),
+ position=(240, v - 9),
+ text=str(_ba.get_public_party_max_size()))
+ btn1 = self._host_max_party_size_minus_button = (ba.buttonwidget(
+ parent=self._container,
+ size=(40, 40),
+ on_activate_call=ba.WeakCall(
+ self._on_max_public_party_size_minus_press),
+ position=(280, v - 26),
+ label='-',
+ autoselect=True))
+ btn2 = self._host_max_party_size_plus_button = (ba.buttonwidget(
+ parent=self._container,
+ size=(40, 40),
+ on_activate_call=ba.WeakCall(
+ self._on_max_public_party_size_plus_press),
+ position=(350, v - 26),
+ label='+',
+ autoselect=True))
+ v -= 50
+ v -= 70
+ if is_public_enabled:
+ label = ba.Lstr(
+ resource='gatherWindow.makePartyPrivateText',
+ fallback_resource='gatherWindow.stopAdvertisingText')
+ else:
+ label = ba.Lstr(
+ resource='gatherWindow.makePartyPublicText',
+ fallback_resource='gatherWindow.startAdvertisingText')
+ self._host_toggle_button = ba.buttonwidget(
+ parent=self._container,
+ label=label,
+ size=(400, 80),
+ on_activate_call=self._on_stop_advertising_press
+ if is_public_enabled else self._on_start_advertizing_press,
+ position=(c_width * 0.5 - 200, v),
+ autoselect=True,
+ up_widget=btn2)
+ ba.widget(edit=self._host_name_text, down_widget=btn2)
+ ba.widget(edit=btn2, up_widget=self._host_name_text)
+ ba.widget(edit=btn1, up_widget=self._host_name_text)
+ ba.widget(edit=self._join_text, down_widget=self._host_name_text)
+ v -= 10
+ self._host_status_text = ba.textwidget(
+ parent=self._container,
+ text=ba.Lstr(resource='gatherWindow.'
+ 'partyStatusNotPublicText'),
+ size=(0, 0),
+ scale=0.7,
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='top',
+ maxwidth=c_width,
+ color=(0.6, 0.6, 0.6),
+ position=(c_width * 0.5, v))
+ v -= 90
+ ba.textwidget(parent=self._container,
+ text=ba.Lstr(resource='gatherWindow.'
+ 'dedicatedServerInfoText'),
+ size=(0, 0),
+ scale=0.7,
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='center',
+ maxwidth=c_width * 0.9,
+ color=ba.app.ui.infotextcolor,
+ position=(c_width * 0.5, v))
+
+ # If public sharing is already on,
+ # launch a status-check immediately.
+ if _ba.get_public_party_enabled():
+ self._do_status_check()
def _rebuild_public_party_list(self) -> None:
# pylint: disable=too-many-branches
@@ -484,7 +470,7 @@ class PublicGatherTab(GatherTab):
# First off, check for the existence of our column widget;
# if we don't have this, we're done.
- columnwidget = self._internet_host_columnwidget
+ columnwidget = self._host_columnwidget
if not columnwidget:
return
@@ -498,9 +484,9 @@ class PublicGatherTab(GatherTab):
ordered_parties = sorted(
list(self._public_parties.values()),
key=lambda p: (
- p['queue'] is None, # Show non-queued last.
- p['ping'] if p['ping'] is not None else 999999,
- p['index']))
+ p.queue is None, # Show non-queued last.
+ p.ping if p.ping is not None else 999999,
+ p.index))
existing_selection = self._public_party_list_selection
first = True
@@ -518,55 +504,43 @@ class PublicGatherTab(GatherTab):
ba.pushcall(refresh_on)
# Janky - allow escaping if there's nothing in us.
- ba.containerwidget(edit=self._internet_host_scrollwidget,
+ ba.containerwidget(edit=self._host_scrollwidget,
claims_up_down=(len(ordered_parties) > 0))
for i, party in enumerate(ordered_parties):
hpos = 20
vpos = sub_scroll_height - lineheight * i - 50
- party['name_widget'] = ba.textwidget(
- text=ba.Lstr(value=party['name']),
+ party.name_widget = ba.textwidget(
+ text=ba.Lstr(value=party.name),
parent=columnwidget,
size=(sub_scroll_width * 0.63, 20),
position=(0 + hpos, 4 + vpos),
selectable=True,
on_select_call=ba.WeakCall(
self._set_public_party_selection,
- (party['address'], 'name')),
+ (party.address, 'name')),
on_activate_call=ba.WeakCall(
self._on_public_party_activate, party),
click_activate=True,
maxwidth=sub_scroll_width * 0.45,
corner_scale=1.4,
autoselect=True,
- color=(1, 1, 1, 0.3 if party['ping'] is None else 1.0),
+ color=(1, 1, 1, 0.3 if party.ping is None else 1.0),
h_align='left',
v_align='center')
- ba.widget(edit=party['name_widget'],
- left_widget=self._internet_join_text,
+ ba.widget(edit=party.name_widget,
+ left_widget=self._join_text,
show_buffer_top=64.0,
show_buffer_bottom=64.0)
- if existing_selection == (party['address'], 'name'):
+ if existing_selection == (party.address, 'name'):
ba.containerwidget(edit=columnwidget,
- selected_child=party['name_widget'])
- if bool(False):
- party['language_widget'] = ba.textwidget(
- text=ba.Lstr(translate=('languages',
- party['language'])),
- parent=columnwidget,
- size=(0, 0),
- position=(sub_scroll_width * 0.73 + hpos, 20 + vpos),
- maxwidth=sub_scroll_width * 0.13,
- scale=0.7,
- color=(0.8, 0.8, 0.8),
- h_align='center',
- v_align='center')
- if party['stats_addr'] != '':
- url = party['stats_addr'].replace(
+ selected_child=party.name_widget)
+ if party.stats_addr:
+ url = party.stats_addr.replace(
'${ACCOUNT}',
_ba.get_account_misc_read_val_2(
'resolvedAccountID', 'UNKNOWN'))
- party['stats_button'] = ba.buttonwidget(
+ party.stats_button = ba.buttonwidget(
color=(0.3, 0.6, 0.94),
textcolor=(1.0, 1.0, 1.0),
label=ba.Lstr(resource='statsText'),
@@ -575,30 +549,29 @@ class PublicGatherTab(GatherTab):
on_activate_call=ba.Call(ba.open_url, url),
on_select_call=ba.WeakCall(
self._set_public_party_selection,
- (party['address'], 'stats_button')),
+ (party.address, 'stats_button')),
size=(120, 40),
position=(sub_scroll_width * 0.66 + hpos, 1 + vpos),
scale=0.9)
- if existing_selection == (party['address'],
- 'stats_button'):
- ba.containerwidget(
- edit=columnwidget,
- selected_child=party['stats_button'])
+ if existing_selection == (party.address, 'stats_button'):
+ ba.containerwidget(edit=columnwidget,
+ selected_child=party.stats_button)
else:
- if 'stats_button' in party:
- del party['stats_button']
+ if party.stats_button:
+ party.stats_button.delete()
+ party.stats_button = None
if first:
- if 'stats_button' in party:
- ba.widget(edit=party['stats_button'],
- up_widget=self._internet_join_text)
- if 'name_widget' in party:
- ba.widget(edit=party['name_widget'],
- up_widget=self._internet_join_text)
+ if party.stats_button:
+ ba.widget(edit=party.stats_button,
+ up_widget=self._join_text)
+ if party.name_widget:
+ ba.widget(edit=party.name_widget,
+ up_widget=self._join_text)
first = False
- party['size_widget'] = ba.textwidget(
- text=str(party['size']) + '/' + str(party['size_max']),
+ party.size_widget = ba.textwidget(
+ text=str(party.size) + '/' + str(party.size_max),
parent=columnwidget,
size=(0, 0),
position=(sub_scroll_width * 0.86 + hpos, 20 + vpos),
@@ -606,38 +579,38 @@ class PublicGatherTab(GatherTab):
color=(0.8, 0.8, 0.8),
h_align='right',
v_align='center')
- party['ping_widget'] = ba.textwidget(
+ party.ping_widget = ba.textwidget(
parent=columnwidget,
size=(0, 0),
position=(sub_scroll_width * 0.94 + hpos, 20 + vpos),
scale=0.7,
h_align='right',
v_align='center')
- if party['ping'] is None:
- ba.textwidget(edit=party['ping_widget'],
+ if party.ping is None:
+ ba.textwidget(edit=party.ping_widget,
text='-',
color=(0.5, 0.5, 0.5))
else:
ping_good = _ba.get_account_misc_read_val('pingGood', 100)
ping_med = _ba.get_account_misc_read_val('pingMed', 500)
- ba.textwidget(edit=party['ping_widget'],
- text=str(party['ping']),
+ ba.textwidget(edit=party.ping_widget,
+ text=str(party.ping),
color=(0, 1,
- 0) if party['ping'] <= ping_good else
- (1, 1, 0) if party['ping'] <= ping_med else
+ 0) if party.ping <= ping_good else
+ (1, 1, 0) if party.ping <= ping_med else
(1, 0, 0))
# So our selection callbacks can start firing..
- def refresh_on2() -> None:
+ def refresh_off() -> None:
self._refreshing_public_party_list = False
- ba.pushcall(refresh_on2)
+ ba.pushcall(refresh_off)
def _on_public_party_query_result(
self, result: Optional[Dict[str, Any]]) -> None:
with ba.Context('ui'):
# Any time we get any result at all, kill our loading status.
- status_text = self._internet_join_status_text
+ status_text = self._join_status_text
if status_text:
# Don't show results if not signed in
# (probably didn't get any anyway).
@@ -657,7 +630,7 @@ class PublicGatherTab(GatherTab):
parties_in = []
for partyval in list(self._public_parties.values()):
- partyval['claimed'] = False
+ partyval.claimed = False
for party_in in parties_in:
# Party is indexed by (ADDR)_(PORT)
@@ -667,39 +640,43 @@ class PublicGatherTab(GatherTab):
# If this party is new to us, init it.
index = self._next_public_party_entry_index
self._next_public_party_entry_index = index + 1
- party = self._public_parties[party_key] = {
- 'address':
- party_in.get('a'),
- 'next_ping_time':
- ba.time(ba.TimeType.REAL) + 0.001 * party_in['pd'],
- 'ping':
- None,
- 'index':
- index,
- }
+ party = self._public_parties[party_key] = PartyEntry(
+ address=party_in.get('a'),
+ next_ping_time=ba.time(ba.TimeType.REAL) +
+ 0.001 * party_in['pd'],
+ ping=None,
+ index=index)
+ assert isinstance(party.address, str)
+ assert isinstance(party.next_ping_time, float)
# Now, new or not, update its values.
- party['queue'] = party_in.get('q')
- party['port'] = party_in.get('p')
- party['name'] = party_in['n']
- party['size'] = party_in['s']
- party['language'] = party_in['l']
- party['size_max'] = party_in['sm']
- party['claimed'] = True
+ party.queue = party_in.get('q')
+ assert isinstance(party.queue, (str, type(None)))
+ party.port = party_in.get('p')
+ assert isinstance(party.port, int)
+ party.name = party_in['n']
+ assert isinstance(party.name, str)
+ party.size = party_in['s']
+ assert isinstance(party.size, int)
+ party.size_max = party_in['sm']
+ assert isinstance(party.size_max, int)
+ party.claimed = True
# (server provides this in milliseconds; we use seconds)
- party['ping_interval'] = 0.001 * party_in['pi']
- party['stats_addr'] = party_in['sa']
+ party.ping_interval = 0.001 * party_in['pi']
+ assert isinstance(party.ping_interval, float)
+ party.stats_addr = party_in['sa']
+ assert isinstance(party.stats_addr, (str, type(None)))
# Prune unclaimed party entries.
self._public_parties = {
key: val
for key, val in list(self._public_parties.items())
- if val['claimed']
+ if val.claimed
}
self._rebuild_public_party_list()
- def _update_internet_tab(self) -> None:
+ def _update_sub_tab(self) -> None:
# pylint: disable=too-many-statements
# Special case: if a party-queue window is up, don't do any of this
@@ -709,25 +686,16 @@ class PublicGatherTab(GatherTab):
# If we've got a party-name text widget, keep its value plugged
# into our public host name.
- text = self._internet_host_name_text
+ text = self._host_name_text
if text:
- name = cast(str,
- ba.textwidget(query=self._internet_host_name_text))
+ name = cast(str, ba.textwidget(query=self._host_name_text))
_ba.set_public_party_name(name)
- # Show/hide the lock icon depending on if we've got pro.
- icon = self._internet_lock_icon
- if icon:
- if self._is_internet_locked():
- ba.imagewidget(edit=icon, opacity=0.5)
- else:
- ba.imagewidget(edit=icon, opacity=0.0)
-
- if self._internet_tab == 'join':
+ if self._sub_tab is SubTabType.JOIN:
now = ba.time(ba.TimeType.REAL)
- if (now - self._internet_join_last_refresh_time > 0.001 *
+ if (now - self._join_last_refresh_time > 0.001 *
_ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)):
- self._internet_join_last_refresh_time = now
+ self._join_last_refresh_time = now
app = ba.app
_ba.add_transaction(
{
@@ -741,20 +709,19 @@ class PublicGatherTab(GatherTab):
# Go through our existing public party entries firing off pings
# for any that have timed out.
for party in list(self._public_parties.values()):
- if (party['next_ping_time'] <= now
+ if (party.next_ping_time <= now
and ba.app.ping_thread_count < 15):
# Make sure to fully catch up and not to multi-ping if
# we're way behind somehow.
- while party['next_ping_time'] <= now:
+ while party.next_ping_time <= now:
# Crank the interval up for high-latency parties to
# save us some work.
mult = 1
- if party['ping'] is not None:
- mult = (10 if party['ping'] > 300 else
- 5 if party['ping'] > 150 else 2)
- party[
- 'next_ping_time'] += party['ping_interval'] * mult
+ if party.ping is not None:
+ mult = (10 if party.ping > 300 else
+ 5 if party.ping > 150 else 2)
+ party.next_ping_time += party.ping_interval * mult
class PingThread(threading.Thread):
"""Thread for sending out pings."""
@@ -842,7 +809,7 @@ class PublicGatherTab(GatherTab):
once=True)
ba.app.ping_thread_count -= 1
- PingThread(party['address'], party['port'],
+ PingThread(party.address, party.port,
ba.WeakCall(self._ping_callback)).start()
def _ping_callback(self, address: str, port: Optional[int],
@@ -853,30 +820,30 @@ class PublicGatherTab(GatherTab):
if party is not None:
# We now smooth ping a bit to reduce jumping around in the list
# (only where pings are relatively good).
- current_ping = party.get('ping')
+ current_ping = party.ping
if (current_ping is not None and result is not None
and result < 150):
smoothing = 0.7
- party['ping'] = int(smoothing * current_ping +
- (1.0 - smoothing) * result)
+ party.ping = int(smoothing * current_ping +
+ (1.0 - smoothing) * result)
else:
- party['ping'] = result
+ party.ping = result
# This can happen if we switch away and then back to the
# client tab while pings are in flight.
- if 'ping_widget' not in party:
- pass
- elif party['ping_widget']:
+ # if 'ping_widget' not in party:
+ # pass
+ if party.ping_widget:
self._rebuild_public_party_list()
- def _internet_fetch_local_addr_cb(self, val: str) -> None:
- self._internet_local_address = str(val)
+ def _fetch_local_addr_cb(self, val: str) -> None:
+ self._local_address = str(val)
def _on_public_party_accessible_response(
self, data: Optional[Dict[str, Any]]) -> None:
# If we've got status text widgets, update them.
- text = self._internet_host_status_text
+ text = self._host_status_text
if text:
if data is None:
ba.textwidget(
@@ -888,13 +855,13 @@ class PublicGatherTab(GatherTab):
else:
if not data.get('accessible', False):
ex_line: Union[str, ba.Lstr]
- if self._internet_local_address is not None:
+ if self._local_address is not None:
ex_line = ba.Lstr(
value='\n${A} ${B}',
subs=[('${A}',
ba.Lstr(resource='gatherWindow.'
'manualYourLocalAddressText')),
- ('${B}', self._internet_local_address)])
+ ('${B}', self._local_address)])
else:
ex_line = ''
ba.textwidget(
@@ -917,9 +884,9 @@ class PublicGatherTab(GatherTab):
'partyStatusJoinableText'),
color=(0, 1, 0))
- def _do_internet_status_check(self) -> None:
+ def _do_status_check(self) -> None:
from ba.internal import master_server_get
- ba.textwidget(edit=self._internet_host_status_text,
+ ba.textwidget(edit=self._host_status_text,
color=(1, 1, 0),
text=ba.Lstr(resource='gatherWindow.'
'partyStatusCheckingText'))
@@ -927,22 +894,13 @@ class PublicGatherTab(GatherTab):
callback=ba.WeakCall(
self._on_public_party_accessible_response))
- def _on_start_internet_advertizing_press(self) -> None:
+ def _on_start_advertizing_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
- from bastd.ui.purchase import PurchaseWindow
if _ba.get_account_state() != 'signed_in':
show_sign_in_prompt()
return
- # Requires sign-in and pro.
- if self._is_internet_locked():
- if _ba.get_account_state() != 'signed_in':
- show_sign_in_prompt()
- else:
- PurchaseWindow(items=['pro'])
- return
-
- name = cast(str, ba.textwidget(query=self._internet_host_name_text))
+ name = cast(str, ba.textwidget(query=self._host_name_text))
if name == '':
ba.screenmessage(ba.Lstr(resource='internal.invalidNameErrorText'),
color=(1, 0, 0))
@@ -959,22 +917,22 @@ class PublicGatherTab(GatherTab):
# public parties.
_ba.set_authenticate_clients(True)
- self._do_internet_status_check()
+ self._do_status_check()
ba.buttonwidget(
- edit=self._internet_host_toggle_button,
+ edit=self._host_toggle_button,
label=ba.Lstr(
resource='gatherWindow.makePartyPrivateText',
fallback_resource='gatherWindow.stopAdvertisingText'),
- on_activate_call=self._on_stop_internet_advertising_press)
+ on_activate_call=self._on_stop_advertising_press)
- def _on_stop_internet_advertising_press(self) -> None:
+ def _on_stop_advertising_press(self) -> None:
_ba.set_public_party_enabled(False)
# In GUI builds we want to authenticate clients only when hosting
# public parties.
_ba.set_authenticate_clients(False)
ba.playsound(ba.getsound('shieldDown'))
- text = self._internet_host_status_text
+ text = self._host_status_text
if text:
ba.textwidget(
edit=text,
@@ -983,33 +941,20 @@ class PublicGatherTab(GatherTab):
color=(0.6, 0.6, 0.6),
)
ba.buttonwidget(
- edit=self._internet_host_toggle_button,
+ edit=self._host_toggle_button,
label=ba.Lstr(
resource='gatherWindow.makePartyPublicText',
fallback_resource='gatherWindow.startAdvertisingText'),
- on_activate_call=self._on_start_internet_advertizing_press)
+ on_activate_call=self._on_start_advertizing_press)
- def _is_internet_locked(self) -> bool:
- if _ba.get_account_misc_read_val('ilck', False):
- return not ba.app.accounts.have_pro()
- return False
-
- def _on_public_party_activate(self, party: Dict[str, Any]) -> None:
- from bastd.ui.purchase import PurchaseWindow
- from bastd.ui.account import show_sign_in_prompt
- if party['queue'] is not None:
+ def _on_public_party_activate(self, party: PartyEntry) -> None:
+ if party.queue is not None:
from bastd.ui.partyqueue import PartyQueueWindow
ba.playsound(ba.getsound('swish'))
- PartyQueueWindow(party['queue'], party['address'], party['port'])
+ PartyQueueWindow(party.queue, party.address, party.port)
else:
- address = party['address']
- port = party['port']
- if self._is_internet_locked():
- if _ba.get_account_state() != 'signed_in':
- show_sign_in_prompt()
- else:
- PurchaseWindow(items=['pro'])
- return
+ address = party.address
+ port = party.port
# Rate limit this a bit.
now = time.time()
@@ -1029,12 +974,10 @@ class PublicGatherTab(GatherTab):
if val < 1:
val = 1
_ba.set_public_party_max_size(val)
- ba.textwidget(edit=self._internet_host_max_party_size_value,
- text=str(val))
+ ba.textwidget(edit=self._host_max_party_size_value, text=str(val))
def _on_max_public_party_size_plus_press(self) -> None:
val = _ba.get_public_party_max_size()
val += 1
_ba.set_public_party_max_size(val)
- ba.textwidget(edit=self._internet_host_max_party_size_value,
- text=str(val))
+ ba.textwidget(edit=self._host_max_party_size_value, text=str(val))