diff --git a/dist/ba_data/python/ba/__init__.py b/dist/ba_data/python/ba/__init__.py index 89f8abb..82df942 100644 --- a/dist/ba_data/python/ba/__init__.py +++ b/dist/ba_data/python/ba/__init__.py @@ -24,6 +24,7 @@ from ba._actor import Actor from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation from ba._nodeactor import NodeActor from ba._app import App +from ba._cloud import CloudSubsystem from ba._coopgame import CoopGameActivity from ba._coopsession import CoopSession from ba._dependency import (Dependency, DependencyComponent, DependencySet, @@ -89,7 +90,7 @@ __all__ = [ 'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported', 'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget', 'containerwidget', 'Context', 'ContextCall', 'ContextError', - 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType', + 'CloudSubsystem', 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType', 'DelegateNotFoundError', 'Dependency', 'DependencyComponent', 'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage', 'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam', diff --git a/dist/ba_data/python/ba/_accountv1.py b/dist/ba_data/python/ba/_accountv1.py index 2f72f19..0b07457 100644 --- a/dist/ba_data/python/ba/_accountv1.py +++ b/dist/ba_data/python/ba/_accountv1.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class AccountV1Subsystem: @@ -23,12 +23,12 @@ class AccountV1Subsystem: """ def __init__(self) -> None: - self.account_tournament_list: Optional[tuple[int, list[str]]] = None + self.account_tournament_list: tuple[int, list[str]] | None = None # FIXME: should abstract/structure these. self.tournament_info: dict = {} self.league_rank_cache: dict = {} - self.last_post_purchase_message_time: Optional[float] = None + self.last_post_purchase_message_time: float | None = None # If we try to run promo-codes due to launch-args/etc we might # not be signed in yet; go ahead and queue them up in that case. @@ -73,7 +73,7 @@ class AccountV1Subsystem: return self.league_rank_cache.get('info', None) def get_league_rank_points(self, - data: Optional[dict[str, Any]], + data: dict[str, Any] | None, subset: str = None) -> int: """(internal)""" if data is None: diff --git a/dist/ba_data/python/ba/_accountv2.py b/dist/ba_data/python/ba/_accountv2.py index 36b776e..b5758ff 100644 --- a/dist/ba_data/python/ba/_accountv2.py +++ b/dist/ba_data/python/ba/_accountv2.py @@ -6,8 +6,10 @@ from __future__ import annotations from typing import TYPE_CHECKING +import _ba + if TYPE_CHECKING: - from typing import Optional + pass class AccountV2Subsystem: @@ -18,10 +20,20 @@ class AccountV2Subsystem: Access the single shared instance of this class at 'ba.app.accounts_v2'. """ + def __init__(self) -> None: + + # Whether or not everything related to an initial login + # (or lack thereof) has completed. This includes things like + # workspace syncing. Completion of this is what flips the app + # into 'running' state. + self._initial_login_completed = False + + self._kicked_off_workspace_load = False + def on_app_launch(self) -> None: """Should be called at standard on_app_launch time.""" - def set_primary_credentials(self, credentials: Optional[str]) -> None: + def set_primary_credentials(self, credentials: str | None) -> None: """Set credentials for the primary app account.""" raise RuntimeError('This should be overridden.') @@ -35,17 +47,74 @@ class AccountV2Subsystem: raise RuntimeError('This should be overridden.') @property - def primary(self) -> Optional[AccountV2Handle]: + def primary(self) -> AccountV2Handle | None: """The primary account for the app, or None if not logged in.""" return None - def get_primary(self) -> Optional[AccountV2Handle]: + def do_get_primary(self) -> AccountV2Handle | None: """Internal - should be overridden by subclass.""" return None + def on_primary_account_changed(self, + account: AccountV2Handle | None) -> None: + """Callback run after the primary account changes. + + Will be called with None on log-outs or when new credentials + are set but have not yet been verified. + """ + # Currently don't do anything special on sign-outs. + if account is None: + return + + # If this new account has a workspace, update it and ask to be + # informed when that process completes. + if account.workspaceid is not None: + assert account.workspacename is not None + if (not self._initial_login_completed + and not self._kicked_off_workspace_load): + self._kicked_off_workspace_load = True + _ba.app.workspaces.set_active_workspace( + workspaceid=account.workspaceid, + workspacename=account.workspacename, + on_completed=self._on_set_active_workspace_completed) + else: + # Don't activate workspaces if we've already told the game + # that initial-log-in is done or if we've already kicked + # off a workspace load. + _ba.screenmessage( + f'\'{account.workspacename}\'' + f' will be activated at next app launch.', + color=(1, 1, 0)) + _ba.playsound(_ba.getsound('error')) + return + + # Ok; no workspace to worry about; carry on. + if not self._initial_login_completed: + self._initial_login_completed = True + _ba.app.on_initial_login_completed() + + def on_no_initial_primary_account(self) -> None: + """Callback run if the app has no primary account after launch. + + Either this callback or on_primary_account_changed will be called + within a few seconds of app launch; the app can move forward + with the startup sequence at that point. + """ + if not self._initial_login_completed: + self._initial_login_completed = True + _ba.app.on_initial_login_completed() + + def _on_set_active_workspace_completed(self) -> None: + if not self._initial_login_completed: + self._initial_login_completed = True + _ba.app.on_initial_login_completed() + class AccountV2Handle: """Handle for interacting with a v2 account.""" def __init__(self) -> None: self.tag = '?' + + self.workspacename: str | None = None + self.workspaceid: str | None = None diff --git a/dist/ba_data/python/ba/_achievement.py b/dist/ba_data/python/ba/_achievement.py index 23e5d93..5a81d30 100644 --- a/dist/ba_data/python/ba/_achievement.py +++ b/dist/ba_data/python/ba/_achievement.py @@ -9,7 +9,7 @@ import _ba from ba._error import print_exception if TYPE_CHECKING: - from typing import Any, Sequence, Union, Optional + from typing import Any, Sequence import ba # This could use some cleanup. @@ -73,7 +73,7 @@ class AchievementSubsystem: def __init__(self) -> None: self.achievements: list[Achievement] = [] self.achievements_to_display: (list[tuple[ba.Achievement, bool]]) = [] - self.achievement_display_timer: Optional[_ba.Timer] = None + self.achievement_display_timer: _ba.Timer | None = None self.last_achievement_display_time: float = 0.0 self.achievement_completion_banner_slots: set[int] = set() self._init_achievements() @@ -450,7 +450,7 @@ class Achievement: self._icon_name = icon_name self._icon_color: Sequence[float] = list(icon_color) + [1] self._level_name = level_name - self._completion_banner_slot: Optional[int] = None + self._completion_banner_slot: int | None = None self._award = award self._hard_mode_only = hard_mode_only @@ -534,7 +534,7 @@ class Achievement: def display_name(self) -> ba.Lstr: """Return a ba.Lstr for this Achievement's name.""" from ba._language import Lstr - name: Union[ba.Lstr, str] + name: ba.Lstr | str try: if self._level_name != '': from ba._campaign import getcampaign diff --git a/dist/ba_data/python/ba/_activity.py b/dist/ba_data/python/ba/_activity.py index f2d6034..68d740d 100644 --- a/dist/ba_data/python/ba/_activity.py +++ b/dist/ba_data/python/ba/_activity.py @@ -16,7 +16,7 @@ from ba._general import Call, verify_object_death from ba._messages import UNHANDLED if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any import ba # pylint: disable=invalid-name @@ -139,7 +139,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): assert isinstance(settings, dict) assert _ba.getactivity() is self - self._globalsnode: Optional[ba.Node] = None + self._globalsnode: ba.Node | None = None # Player/Team types should have been specified as type args; # grab those. @@ -148,7 +148,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._setup_player_and_team_types() # FIXME: Relocate or remove the need for this stuff. - self.paused_text: Optional[ba.Actor] = None + self.paused_text: ba.Actor | None = None self._session = weakref.ref(_ba.getsession()) @@ -163,7 +163,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._has_transitioned_in = False self._has_begun = False self._has_ended = False - self._activity_death_check_timer: Optional[ba.Timer] = None + self._activity_death_check_timer: ba.Timer | None = None self._expired = False self._delay_delete_players: list[PlayerType] = [] self._delay_delete_teams: list[TeamType] = [] @@ -177,14 +177,14 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._actor_refs: list[ba.Actor] = [] self._actor_weak_refs: list[weakref.ref[ba.Actor]] = [] self._last_prune_dead_actors_time = _ba.time() - self._prune_dead_actors_timer: Optional[ba.Timer] = None + self._prune_dead_actors_timer: ba.Timer | None = None self.teams = [] self.players = [] self.lobby = None - self._stats: Optional[ba.Stats] = None - self._customdata: Optional[dict] = {} + self._stats: ba.Stats | None = None + self._customdata: dict | None = {} def __del__(self) -> None: @@ -396,7 +396,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """Return whether ba.Activity.on_transition_out() has been called.""" return self._transitioning_out - def transition_in(self, prev_globals: Optional[ba.Node]) -> None: + def transition_in(self, prev_globals: ba.Node | None) -> None: """Called by Session to kick off transition-in. (internal) diff --git a/dist/ba_data/python/ba/_activitytypes.py b/dist/ba_data/python/ba/_activitytypes.py index 43da329..81e7a9f 100644 --- a/dist/ba_data/python/ba/_activitytypes.py +++ b/dist/ba_data/python/ba/_activitytypes.py @@ -14,7 +14,6 @@ from ba._player import EmptyPlayer # pylint: disable=W0611 from ba._team import EmptyTeam # pylint: disable=W0611 if TYPE_CHECKING: - from typing import Optional import ba from ba._lobby import JoinInfo @@ -65,9 +64,9 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]): # In vr mode we don't want stuff moving around. self.use_fixed_vr_overlay = True - self._background: Optional[ba.Actor] = None - self._tips_text: Optional[ba.Actor] = None - self._join_info: Optional[JoinInfo] = None + self._background: ba.Actor | None = None + self._tips_text: ba.Actor | None = None + self._join_info: JoinInfo | None = None def on_transition_in(self) -> None: # pylint: disable=cyclic-import @@ -99,7 +98,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]): def __init__(self, settings: dict): super().__init__(settings) - self._background: Optional[ba.Actor] = None + self._background: ba.Actor | None = None def on_transition_in(self) -> None: # pylint: disable=cyclic-import @@ -127,20 +126,20 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): inherits_vr_camera_offset = True use_fixed_vr_overlay = True - default_music: Optional[MusicType] = MusicType.SCORES + default_music: MusicType | None = MusicType.SCORES def __init__(self, settings: dict): super().__init__(settings) self._birth_time = _ba.time() self._min_view_time = 5.0 self._allow_server_transition = False - self._background: Optional[ba.Actor] = None - self._tips_text: Optional[ba.Actor] = None + self._background: ba.Actor | None = None + self._tips_text: ba.Actor | None = None self._kicked_off_server_shutdown = False self._kicked_off_server_restart = False self._default_show_tips = True - self._custom_continue_message: Optional[ba.Lstr] = None - self._server_transitioning: Optional[bool] = None + self._custom_continue_message: ba.Lstr | None = None + self._server_transitioning: bool | None = None def on_player_join(self, player: EmptyPlayer) -> None: from ba._general import WeakCall diff --git a/dist/ba_data/python/ba/_actor.py b/dist/ba_data/python/ba/_actor.py index d6a7c19..9be0831 100644 --- a/dist/ba_data/python/ba/_actor.py +++ b/dist/ba_data/python/ba/_actor.py @@ -12,7 +12,7 @@ from ba._messages import DieMessage, DeathType, OutOfBoundsMessage, UNHANDLED from ba._error import print_exception, ActivityNotFoundError if TYPE_CHECKING: - from typing import Any, Optional, Literal + from typing import Any, Literal import ba TA = TypeVar('TA', bound='Actor') @@ -187,10 +187,10 @@ class Actor: ... @overload - def getactivity(self, doraise: Literal[False]) -> Optional[ba.Activity]: + def getactivity(self, doraise: Literal[False]) -> ba.Activity | None: ... - def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]: + def getactivity(self, doraise: bool = True) -> ba.Activity | None: """Return the ba.Activity this Actor is associated with. If the Activity no longer exists, raises a ba.ActivityNotFoundError diff --git a/dist/ba_data/python/ba/_ads.py b/dist/ba_data/python/ba/_ads.py index a44133c..fbf5696 100644 --- a/dist/ba_data/python/ba/_ads.py +++ b/dist/ba_data/python/ba/_ads.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Optional, Callable, Any + from typing import Callable, Any class AdsSubsystem: @@ -23,11 +23,11 @@ class AdsSubsystem: def __init__(self) -> None: self.last_ad_network = 'unknown' self.last_ad_network_set_time = time.time() - self.ad_amt: Optional[float] = None + self.ad_amt: float | None = None self.last_ad_purpose = 'invalid' self.attempted_first_ad = False - self.last_in_game_ad_remove_message_show_time: Optional[float] = None - self.last_ad_completion_time: Optional[float] = None + self.last_in_game_ad_remove_message_show_time: float | None = None + self.last_ad_completion_time: float | None = None self.last_ad_was_short = False def do_remove_in_game_ads_message(self) -> None: @@ -89,7 +89,7 @@ class AdsSubsystem: show = False # Never show ads during tournaments. if show: - interval: Optional[float] + interval: float | None launch_count = app.config.get('launchCount', 0) # If we're seeing short ads we may want to space them differently. diff --git a/dist/ba_data/python/ba/_app.py b/dist/ba_data/python/ba/_app.py index b47e225..82216d4 100644 --- a/dist/ba_data/python/ba/_app.py +++ b/dist/ba_data/python/ba/_app.py @@ -19,13 +19,14 @@ from ba._accountv1 import AccountV1Subsystem from ba._meta import MetadataSubsystem from ba._ads import AdsSubsystem from ba._net import NetworkSubsystem +from ba._workspace import WorkspaceSubsystem if TYPE_CHECKING: import asyncio - from typing import Optional, Any, Callable + from typing import Any, Callable import ba - from ba.cloud import CloudSubsystem + from ba._cloud import CloudSubsystem from bastd.actor import spazappearance from ba._accountv2 import AccountV2Subsystem @@ -49,10 +50,21 @@ class App: class State(Enum): """High level state the app can be in.""" + + # Python-level systems being inited but should not interact. LAUNCHING = 0 - RUNNING = 1 - PAUSED = 2 - SHUTTING_DOWN = 3 + + # Initial account logins, workspace & asset downloads, etc. + LOADING = 1 + + # Normal running state. + RUNNING = 2 + + # App is backgrounded or otherwise suspended. + PAUSED = 3 + + # App is shutting down. + SHUTTING_DOWN = 4 @property def aioloop(self) -> asyncio.AbstractEventLoop: @@ -208,7 +220,9 @@ class App: self.state = self.State.LAUNCHING - self._app_launched = False + self._launch_completed = False + self._initial_login_completed = False + self._called_on_app_running = False self._app_paused = False # Config. @@ -219,7 +233,7 @@ class App: # refreshed/etc. self.fg_state = 0 - self._aioloop: Optional[asyncio.AbstractEventLoop] = None + self._aioloop: asyncio.AbstractEventLoop | None = None self._env = _ba.env() self.protocol_version: int = self._env['protocol_version'] @@ -243,12 +257,12 @@ class App: # Misc. self.tips: list[str] = [] - self.stress_test_reset_timer: Optional[ba.Timer] = None + self.stress_test_reset_timer: ba.Timer | None = None self.did_weak_call_warning = False self.log_have_new = False self.log_upload_timer_started = False - self._config: Optional[ba.AppConfig] = None + self._config: ba.AppConfig | None = None self.printed_live_object_warning = False # We include this extra hash with shared input-mapping names so @@ -256,13 +270,13 @@ class App: # systems. For instance, different android devices may give different # key values for the same controller type so we keep their mappings # distinct. - self.input_map_hash: Optional[str] = None + self.input_map_hash: str | None = None # Co-op Campaigns. self.campaigns: dict[str, ba.Campaign] = {} # Server Mode. - self.server: Optional[ba.ServerController] = None + self.server: ba.ServerController | None = None self.meta = MetadataSubsystem() self.accounts_v1 = AccountV1Subsystem() @@ -273,15 +287,16 @@ class App: self.ui = UISubsystem() self.ads = AdsSubsystem() self.net = NetworkSubsystem() + self.workspaces = WorkspaceSubsystem() # Lobby. self.lobby_random_profile_index: int = 1 self.lobby_random_char_index_offset = random.randrange(1000) - self.lobby_account_profile_device_id: Optional[int] = None + self.lobby_account_profile_device_id: int | None = None # Main Menu. self.main_menu_did_initial_transition = False - self.main_menu_last_news_fetch_time: Optional[float] = None + self.main_menu_last_news_fetch_time: float | None = None # Spaz. self.spaz_appearances: dict[str, spazappearance.Appearance] = {} @@ -300,19 +315,19 @@ class App: self.did_menu_intro = False # FIXME: Move to mainmenu class. self.main_menu_window_refresh_check_count = 0 # FIXME: Mv to mainmenu. self.main_menu_resume_callbacks: list = [] # Can probably go away. - self.special_offer: Optional[dict] = None + self.special_offer: dict | None = None self.ping_thread_count = 0 self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any. - self.store_layout: Optional[dict[str, list[dict[str, Any]]]] = None - self.store_items: Optional[dict[str, dict]] = None - self.pro_sale_start_time: Optional[int] = None - self.pro_sale_start_val: Optional[int] = None + self.store_layout: dict[str, list[dict[str, Any]]] | None = None + self.store_items: dict[str, dict] | None = None + self.pro_sale_start_time: int | None = None + self.pro_sale_start_val: int | None = None - self.delegate: Optional[ba.AppDelegate] = None - self._asyncio_timer: Optional[ba.Timer] = None + self.delegate: ba.AppDelegate | None = None + self._asyncio_timer: ba.Timer | None = None def on_app_launch(self) -> None: - """Runs after the app finishes bootstrapping. + """Runs after the app finishes low level bootstrapping. (internal)""" # pylint: disable=cyclic-import @@ -326,8 +341,7 @@ class App: from bastd import maps as stdmaps from bastd.actor import spazappearance from ba._generated.enums import TimeType - import custom_hooks - custom_hooks.on_app_launch() + self._aioloop = _asyncio.setup_asyncio() @@ -400,19 +414,25 @@ class App: if not self.headless_mode: _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL) - self.meta.on_app_launch() self.accounts_v2.on_app_launch() self.accounts_v1.on_app_launch() - self.plugins.on_app_launch() # See note below in on_app_pause. if self.state != self.State.LAUNCHING: logging.error('on_app_launch found state %s; expected LAUNCHING.', self.state) - self._app_launched = True + self._launch_completed = True self._update_state() + def on_app_running(self) -> None: + """Called when initially entering the running state.""" + + self.meta.on_app_running() + self.plugins.on_app_running() + import custom_hooks + custom_hooks.on_app_running() + # from ba._dependency import test_depset # test_depset() if bool(False): @@ -422,8 +442,13 @@ class App: if self._app_paused: self.state = self.State.PAUSED else: - if self._app_launched: + if self._initial_login_completed: self.state = self.State.RUNNING + if not self._called_on_app_running: + self._called_on_app_running = True + self.on_app_running() + elif self._launch_completed: + self.state = self.State.LOADING else: self.state = self.State.LAUNCHING @@ -461,7 +486,7 @@ class App: If there's a foreground host-activity that says it's pausable, tell it to pause ..we now no longer pause if there are connected clients. """ - activity: Optional[ba.Activity] = _ba.get_foreground_host_activity() + activity: ba.Activity | None = _ba.get_foreground_host_activity() if (activity is not None and activity.allow_pausing and not _ba.have_connected_clients()): from ba._language import Lstr @@ -525,7 +550,7 @@ class App: # If we're in a host-session, tell them to end. # This lets them tear themselves down gracefully. - host_session: Optional[ba.Session] = _ba.get_foreground_host_session() + host_session: ba.Session | None = _ba.get_foreground_host_session() if host_session is not None: # Kick off a little transaction so we'll hopefully have all the @@ -611,6 +636,17 @@ class App: _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) + def on_initial_login_completed(self) -> None: + """Callback to be run after initial login process (or lack thereof). + + This period includes things such as syncing account workspaces + or other data so it may take a substantial amount of time. + This should also run after a short amount of time if no login + has occurred. + """ + self._initial_login_completed = True + self._update_state() + def _test_https(self) -> None: """Testing https support. diff --git a/dist/ba_data/python/ba/_appdelegate.py b/dist/ba_data/python/ba/_appdelegate.py index 1262870..9f967ae 100644 --- a/dist/ba_data/python/ba/_appdelegate.py +++ b/dist/ba_data/python/ba/_appdelegate.py @@ -6,7 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Optional, Callable + from typing import Callable import ba @@ -18,8 +18,8 @@ class AppDelegate: def create_default_game_settings_ui( self, gameclass: type[ba.GameActivity], - sessiontype: type[ba.Session], settings: Optional[dict], - completion_call: Callable[[Optional[dict]], None]) -> None: + sessiontype: type[ba.Session], settings: dict | None, + completion_call: Callable[[dict | None], None]) -> None: """Launch a UI to configure the given game config. It should manipulate the contents of config and call completion_call diff --git a/dist/ba_data/python/ba/_asyncio.py b/dist/ba_data/python/ba/_asyncio.py index 40e53c9..e10333b 100644 --- a/dist/ba_data/python/ba/_asyncio.py +++ b/dist/ba_data/python/ba/_asyncio.py @@ -13,12 +13,11 @@ from typing import TYPE_CHECKING import asyncio if TYPE_CHECKING: - from typing import Optional import ba # Our timer and event loop for the ballistica game thread. -_asyncio_timer: Optional[ba.Timer] = None -_asyncio_event_loop: Optional[asyncio.AbstractEventLoop] = None +_asyncio_timer: ba.Timer | None = None +_asyncio_event_loop: asyncio.AbstractEventLoop | None = None def setup_asyncio() -> asyncio.AbstractEventLoop: diff --git a/dist/ba_data/python/ba/_cloud.py b/dist/ba_data/python/ba/_cloud.py new file mode 100644 index 0000000..3e79537 --- /dev/null +++ b/dist/ba_data/python/ba/_cloud.py @@ -0,0 +1,93 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality related to the cloud.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, overload + +import _ba + +if TYPE_CHECKING: + from typing import Callable, Any + + from efro.message import Message, Response + import bacommon.cloud + +# TODO: Should make it possible to define a protocol in bacommon.cloud and +# autogenerate this. That would give us type safety between this and +# internal protocols. + + +class CloudSubsystem: + """Manages communication with cloud components.""" + + def is_connected(self) -> bool: + """Return whether a connection to the cloud is present. + + This is a good indicator (though not for certain) that sending + messages will succeed. + """ + return False # Needs to be overridden + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyRequestMessage, + on_response: Callable[ + [bacommon.cloud.LoginProxyRequestResponse | Exception], None], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyStateQueryMessage, + on_response: Callable[ + [bacommon.cloud.LoginProxyStateQueryResponse | Exception], None], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyCompleteMessage, + on_response: Callable[[None | Exception], None], + ) -> None: + ... + + def send_message_cb( + self, + msg: Message, + on_response: Callable[[Any], None], + ) -> None: + """Asynchronously send a message to the cloud from the logic thread. + + The provided on_response call will be run in the logic thread + and passed either the response or the error that occurred. + """ + from ba._general import Call + del msg # Unused. + + _ba.pushcall( + Call(on_response, + RuntimeError('Cloud functionality is not available.'))) + + @overload + def send_message( + self, msg: bacommon.cloud.WorkspaceFetchMessage + ) -> bacommon.cloud.WorkspaceFetchResponse: + ... + + @overload + def send_message( + self, + msg: bacommon.cloud.TestMessage) -> bacommon.cloud.TestResponse: + ... + + def send_message(self, msg: Message) -> Response | None: + """Synchronously send a message to the cloud. + + Must be called from a background thread. + """ + raise RuntimeError('Cloud functionality is not available.') diff --git a/dist/ba_data/python/ba/_coopgame.py b/dist/ba_data/python/ba/_coopgame.py index c3435f3..2e98781 100644 --- a/dist/ba_data/python/ba/_coopgame.py +++ b/dist/ba_data/python/ba/_coopgame.py @@ -10,7 +10,7 @@ from ba._gameactivity import GameActivity from ba._general import WeakCall if TYPE_CHECKING: - from typing import Any, Sequence, Optional + from typing import Any, Sequence from bastd.actor.playerspaz import PlayerSpaz import ba @@ -40,8 +40,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # Cache these for efficiency. self._achievements_awarded: set[str] = set() - self._life_warning_beep: Optional[ba.Actor] = None - self._life_warning_beep_timer: Optional[ba.Timer] = None + self._life_warning_beep: ba.Actor | None = None + self._life_warning_beep_timer: ba.Timer | None = None self._warn_beeps_sound = _ba.getsound('warnBeeps') def on_begin(self) -> None: diff --git a/dist/ba_data/python/ba/_coopsession.py b/dist/ba_data/python/ba/_coopsession.py index 6e9a1b2..e0698c9 100644 --- a/dist/ba_data/python/ba/_coopsession.py +++ b/dist/ba_data/python/ba/_coopsession.py @@ -9,7 +9,7 @@ import _ba from ba._session import Session if TYPE_CHECKING: - from typing import Any, Optional, Callable, Sequence + from typing import Any, Callable, Sequence import ba TEAM_COLORS = [(0.2, 0.4, 1.6)] @@ -37,7 +37,10 @@ class CoopSession(Session): # Note: even though these are instance vars, we annotate them at the # class level so that docs generation can access their types. - campaign: Optional[ba.Campaign] + + campaign: ba.Campaign | None + """The ba.Campaign instance this Session represents, or None if + there is no associated Campaign.""" def __init__(self) -> None: """Instantiate a co-op mode session.""" @@ -69,21 +72,21 @@ class CoopSession(Session): max_players=max_players) # Tournament-ID if we correspond to a co-op tournament (otherwise None) - self.tournament_id: Optional[str] = ( + self.tournament_id: str | None = ( app.coop_session_args.get('tournament_id')) self.campaign = getcampaign(app.coop_session_args['campaign']) self.campaign_level_name: str = app.coop_session_args['level'] self._ran_tutorial_activity = False - self._tutorial_activity: Optional[ba.Activity] = None + self._tutorial_activity: ba.Activity | None = None self._custom_menu_ui: list[dict[str, Any]] = [] # Start our joining screen. self.setactivity(_ba.newactivity(CoopJoinActivity)) - self._next_game_instance: Optional[ba.GameActivity] = None - self._next_game_level_name: Optional[str] = None + self._next_game_instance: ba.GameActivity | None = None + self._next_game_level_name: str | None = None self._update_on_deck_game_instances() def get_current_game_instance(self) -> ba.GameActivity: @@ -126,7 +129,8 @@ class CoopSession(Session): levels = self.campaign.levels level = self.campaign.getlevel(self.campaign_level_name) - nextlevel: Optional[ba.Level] + + nextlevel: ba.Level | None # if level.index < len(levels) - 1: # nextlevel = levels[level.index + 1] # else: diff --git a/dist/ba_data/python/ba/_dependency.py b/dist/ba_data/python/ba/_dependency.py index c65912f..d3a6d4e 100644 --- a/dist/ba_data/python/ba/_dependency.py +++ b/dist/ba_data/python/ba/_dependency.py @@ -10,7 +10,7 @@ from typing import (Generic, TypeVar, TYPE_CHECKING) import _ba if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any import ba T = TypeVar('T', bound='DependencyComponent') @@ -39,7 +39,7 @@ class Dependency(Generic[T]): """ self.cls: type[T] = cls self.config = config - self._hash: Optional[int] = None + self._hash: int | None = None def get_hash(self) -> int: """Return the dependency's hash, calculating it if necessary.""" @@ -133,7 +133,7 @@ class DependencyEntry: # Arbitrary data for use by dependencies in the resolved set # (the static instance for static-deps, etc). - self.component: Optional[DependencyComponent] = None + self.component: DependencyComponent | None = None # Weakref to the depset that includes us (to avoid ref loop). self.depset = weakref.ref(depset) diff --git a/dist/ba_data/python/ba/_gameactivity.py b/dist/ba_data/python/ba/_gameactivity.py index 419eae0..c432e17 100644 --- a/dist/ba_data/python/ba/_gameactivity.py +++ b/dist/ba_data/python/ba/_gameactivity.py @@ -19,7 +19,7 @@ from ba._player import PlayerInfo from ba import _map if TYPE_CHECKING: - from typing import Optional, Any, Callable, Sequence, Union + from typing import Any, Callable, Sequence from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.bomb import TNTSpawner import ba @@ -38,19 +38,19 @@ class GameActivity(Activity[PlayerType, TeamType]): # pylint: disable=too-many-public-methods # Tips to be presented to the user at the start of the game. - tips: list[Union[str, ba.GameTip]] = [] + tips: list[str | ba.GameTip] = [] # Default getname() will return this if not None. - name: Optional[str] = None + name: str | None = None # Default get_description() will return this if not None. - description: Optional[str] = None + description: str | None = None # Default get_available_settings() will return this if not None. - available_settings: Optional[list[ba.Setting]] = None + available_settings: list[ba.Setting] | None = None # Default getscoreconfig() will return this if not None. - scoreconfig: Optional[ba.ScoreConfig] = None + scoreconfig: ba.ScoreConfig | None = None # Override some defaults. allow_pausing = True @@ -61,14 +61,14 @@ class GameActivity(Activity[PlayerType, TeamType]): # If not None, the music type that should play in on_transition_in() # (unless overridden by the map). - default_music: Optional[ba.MusicType] = None + default_music: ba.MusicType | None = None @classmethod def create_settings_ui( cls, sessiontype: type[ba.Session], - settings: Optional[dict], - completion_call: Callable[[Optional[dict]], None], + settings: dict | None, + completion_call: Callable[[dict | None], None], ) -> None: """Launch an in-game UI to configure settings for a game type. @@ -105,7 +105,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return cls.name if cls.name is not None else 'Untitled Game' @classmethod - def get_display_string(cls, settings: Optional[dict] = None) -> ba.Lstr: + def get_display_string(cls, settings: dict | None = None) -> ba.Lstr: """Return a descriptive name for this game/settings combo. Subclasses should override getname(); not this. @@ -214,28 +214,28 @@ class GameActivity(Activity[PlayerType, TeamType]): # Holds some flattened info about the player set at the point # when on_begin() is called. - self.initialplayerinfos: Optional[list[ba.PlayerInfo]] = None + self.initialplayerinfos: list[ba.PlayerInfo] | None = None # Go ahead and get our map loading. self._map_type = _map.get_map_class(self._calc_map_name(settings)) self._spawn_sound = _ba.getsound('spawn') self._map_type.preload() - self._map: Optional[ba.Map] = None - self._powerup_drop_timer: Optional[ba.Timer] = None - self._tnt_spawners: Optional[dict[int, TNTSpawner]] = None - self._tnt_drop_timer: Optional[ba.Timer] = None - self._game_scoreboard_name_text: Optional[ba.Actor] = None - self._game_scoreboard_description_text: Optional[ba.Actor] = None - self._standard_time_limit_time: Optional[int] = None - self._standard_time_limit_timer: Optional[ba.Timer] = None - self._standard_time_limit_text: Optional[ba.NodeActor] = None - self._standard_time_limit_text_input: Optional[ba.NodeActor] = None - self._tournament_time_limit: Optional[int] = None - self._tournament_time_limit_timer: Optional[ba.Timer] = None - self._tournament_time_limit_title_text: Optional[ba.NodeActor] = None - self._tournament_time_limit_text: Optional[ba.NodeActor] = None - self._tournament_time_limit_text_input: Optional[ba.NodeActor] = None + self._map: ba.Map | None = None + self._powerup_drop_timer: ba.Timer | None = None + self._tnt_spawners: dict[int, TNTSpawner] | None = None + self._tnt_drop_timer: ba.Timer | None = None + self._game_scoreboard_name_text: ba.Actor | None = None + self._game_scoreboard_description_text: ba.Actor | None = None + self._standard_time_limit_time: int | None = None + self._standard_time_limit_timer: ba.Timer | None = None + self._standard_time_limit_text: ba.NodeActor | None = None + self._standard_time_limit_text_input: ba.NodeActor | None = None + self._tournament_time_limit: int | None = None + self._tournament_time_limit_timer: ba.Timer | None = None + self._tournament_time_limit_title_text: ba.NodeActor | None = None + self._tournament_time_limit_text: ba.NodeActor | None = None + self._tournament_time_limit_text_input: ba.NodeActor | None = None self._zoom_message_times: dict[int, float] = {} self._is_waiting_for_continue = False @@ -280,7 +280,7 @@ class GameActivity(Activity[PlayerType, TeamType]): print_error('error getting campaign level name') return self.get_instance_display_string() - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: """Return a description for this game instance, in English. This is shown in the center of the screen below the game name at the @@ -306,7 +306,7 @@ class GameActivity(Activity[PlayerType, TeamType]): """ return self.get_description(type(self.session)) - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: """Return a short description for this game instance in English. This description is used above the game scoreboard in the @@ -461,8 +461,8 @@ class GameActivity(Activity[PlayerType, TeamType]): callback=WeakCall(self._on_tournament_query_response), ) - def _on_tournament_query_response(self, data: Optional[dict[str, - Any]]) -> None: + def _on_tournament_query_response(self, + data: dict[str, Any] | None) -> None: if data is not None: data_t = data['t'] # This used to be the whole payload. @@ -662,8 +662,8 @@ class GameActivity(Activity[PlayerType, TeamType]): tip = self.tips.pop(random.randrange(len(self.tips))) tip_title = Lstr(value='${A}:', subs=[('${A}', Lstr(resource='tipText'))]) - icon: Optional[ba.Texture] = None - sound: Optional[ba.Sound] = None + icon: ba.Texture | None = None + sound: ba.Sound | None = None if isinstance(tip, GameTip): icon = tip.icon sound = tip.sound @@ -781,7 +781,7 @@ class GameActivity(Activity[PlayerType, TeamType]): def respawn_player(self, player: PlayerType, - respawn_time: Optional[float] = None) -> None: + respawn_time: float | None = None) -> None: """ Given a ba.Player, sets up a standard respawn timer, along with the standard counter display, etc. @@ -853,12 +853,16 @@ class GameActivity(Activity[PlayerType, TeamType]): color = player.color highlight = player.highlight + playerspaztype = getattr(player, 'playerspaztype', PlayerSpaz) + if not issubclass(playerspaztype, PlayerSpaz): + playerspaztype = PlayerSpaz + light_color = _math.normalized_color(color) display_color = _ba.safecolor(color, target_intensity=0.75) - spaz = PlayerSpaz(color=color, - highlight=highlight, - character=player.character, - player=player) + spaz = playerspaztype(color=color, + highlight=highlight, + character=player.character, + player=player) player.actor = spaz assert spaz.node diff --git a/dist/ba_data/python/ba/_gameresults.py b/dist/ba_data/python/ba/_gameresults.py index 159a6d5..d580e86 100644 --- a/dist/ba_data/python/ba/_gameresults.py +++ b/dist/ba_data/python/ba/_gameresults.py @@ -12,14 +12,14 @@ from efro.util import asserttype from ba._team import Team, SessionTeam if TYPE_CHECKING: - from typing import Sequence, Optional + from typing import Sequence import ba @dataclass class WinnerGroup: """Entry for a winning team or teams calculated by game-results.""" - score: Optional[int] + score: int | None teams: Sequence[ba.SessionTeam] @@ -36,13 +36,13 @@ class GameResults: def __init__(self) -> None: self._game_set = False self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam], - Optional[int]]] = {} - self._sessionteams: Optional[list[weakref.ref[ba.SessionTeam]]] = None - self._playerinfos: Optional[list[ba.PlayerInfo]] = None - self._lower_is_better: Optional[bool] = None - self._score_label: Optional[str] = None - self._none_is_winner: Optional[bool] = None - self._scoretype: Optional[ba.ScoreType] = None + int | None]] = {} + self._sessionteams: list[weakref.ref[ba.SessionTeam]] | None = None + self._playerinfos: list[ba.PlayerInfo] | None = None + self._lower_is_better: bool | None = None + self._score_label: str | None = None + self._none_is_winner: bool | None = None + self._scoretype: ba.ScoreType | None = None def set_game(self, game: ba.GameActivity) -> None: """Set the game instance these results are applying to.""" @@ -59,7 +59,7 @@ class GameResults: self._none_is_winner = scoreconfig.none_is_winner self._scoretype = scoreconfig.scoretype - def set_team_score(self, team: ba.Team, score: Optional[int]) -> None: + def set_team_score(self, team: ba.Team, score: int | None) -> None: """Set the score for a given team. This can be a number or None. @@ -69,8 +69,7 @@ class GameResults: sessionteam = team.sessionteam self._scores[sessionteam.id] = (weakref.ref(sessionteam), score) - def get_sessionteam_score(self, - sessionteam: ba.SessionTeam) -> Optional[int]: + def get_sessionteam_score(self, sessionteam: ba.SessionTeam) -> int | None: """Return the score for a given ba.SessionTeam.""" assert isinstance(sessionteam, SessionTeam) for score in list(self._scores.values()): @@ -157,7 +156,7 @@ class GameResults: return self._lower_is_better @property - def winning_sessionteam(self) -> Optional[ba.SessionTeam]: + def winning_sessionteam(self) -> ba.SessionTeam | None: """The winning ba.SessionTeam if there is exactly one, or else None.""" if not self._game_set: raise RuntimeError("Can't get winners until game is set.") @@ -184,7 +183,7 @@ class GameResults: team = score[0]() assert team is not None sval.append(team) - results: list[tuple[Optional[int], + results: list[tuple[int | None, list[ba.SessionTeam]]] = list(winners.items()) results.sort(reverse=not self._lower_is_better, key=lambda x: asserttype(x[0], int)) @@ -199,7 +198,7 @@ class GameResults: # Add the Nones to the list (either as winners or losers # depending on the rules). if none_sessionteams: - nones: list[tuple[Optional[int], list[ba.SessionTeam]]] = [ + nones: list[tuple[int | None, list[ba.SessionTeam]]] = [ (None, none_sessionteams) ] if self._none_is_winner: diff --git a/dist/ba_data/python/ba/_gameutils.py b/dist/ba_data/python/ba/_gameutils.py index 0a4c4ee..97cac63 100644 --- a/dist/ba_data/python/ba/_gameutils.py +++ b/dist/ba_data/python/ba/_gameutils.py @@ -12,7 +12,7 @@ from ba._generated.enums import TimeType, TimeFormat, SpecialChar, UIScale from ba._error import ActivityNotFoundError if TYPE_CHECKING: - from typing import Sequence, Optional + from typing import Sequence import ba TROPHY_CHARS = { @@ -32,8 +32,8 @@ class GameTip: Category: **Gameplay Classes** """ text: str - icon: Optional[ba.Texture] = None - sound: Optional[ba.Sound] = None + icon: ba.Texture | None = None + sound: ba.Sound | None = None def get_trophy_string(trophy_id: str) -> str: diff --git a/dist/ba_data/python/ba/_general.py b/dist/ba_data/python/ba/_general.py index e9462ee..090e81a 100644 --- a/dist/ba_data/python/ba/_general.py +++ b/dist/ba_data/python/ba/_general.py @@ -17,7 +17,7 @@ from ba._generated.enums import TimeType if TYPE_CHECKING: from types import FrameType - from typing import Any, Optional + from typing import Any from efro.call import Call as Call # 'as Call' so we re-export. @@ -37,15 +37,15 @@ ExistableType = TypeVar('ExistableType', bound=Existable) T = TypeVar('T') -def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]: +def existing(obj: ExistableType | None) -> ExistableType | None: """Convert invalid references to None for any ba.Existable object. Category: **Gameplay Functions** To best support type checking, it is important that invalid references not be passed around and instead get converted to values of None. - That way the type checker can properly flag attempts to pass dead - objects (Optional[FooType]) into functions expecting only live ones + That way the type checker can properly flag attempts to pass possibly-dead + objects (FooType | None) into functions expecting only live ones (FooType), etc. This call can be used on any 'existable' object (one with an exists() method) and will convert it to a None value if it does not exist. diff --git a/dist/ba_data/python/ba/_hooks.py b/dist/ba_data/python/ba/_hooks.py index a3534cb..58b90d2 100644 --- a/dist/ba_data/python/ba/_hooks.py +++ b/dist/ba_data/python/ba/_hooks.py @@ -18,7 +18,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Sequence, Optional, Any + from typing import Sequence, Any import ba @@ -325,7 +325,8 @@ def party_invite_revoke(invite_id: str) -> None: transition='out_right') import custom_hooks as chooks -def filter_chat_message(msg: str, client_id: int) -> Optional[str]: +def filter_chat_message(msg: str, client_id: int) -> str | None: + """Intercept/filter chat messages. Called for all chat messages while hosting. diff --git a/dist/ba_data/python/ba/_language.py b/dist/ba_data/python/ba/_language.py index e997191..8047a13 100644 --- a/dist/ba_data/python/ba/_language.py +++ b/dist/ba_data/python/ba/_language.py @@ -11,7 +11,7 @@ import _ba if TYPE_CHECKING: import ba - from typing import Any, Optional, Union, Sequence + from typing import Any, Sequence class LanguageSubsystem: @@ -23,8 +23,8 @@ class LanguageSubsystem: """ def __init__(self) -> None: - self.language_target: Optional[AttrDict] = None - self.language_merged: Optional[AttrDict] = None + self.language_target: AttrDict | None = None + self.language_merged: AttrDict | None = None self.default_language = self._get_default_language() def _can_display_language(self, language: str) -> bool: @@ -139,7 +139,7 @@ class LanguageSubsystem: if self._can_display_language(name)) def setlanguage(self, - language: Optional[str], + language: str | None, print_change: bool = True, store_to_config: bool = True) -> None: """Set the active language used for the game. @@ -408,7 +408,7 @@ class Lstr: resource: str, fallback_resource: str = '', fallback_value: str = '', - subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: + subs: Sequence[tuple[str, str | Lstr]] = []) -> None: """Create an Lstr from a string resource.""" # noinspection PyShadowingNames,PyDefaultArgument @@ -416,7 +416,7 @@ class Lstr: def __init__(self, *, translate: tuple[str, str], - subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: + subs: Sequence[tuple[str, str | Lstr]] = []) -> None: """Create an Lstr by translating a string in a category.""" # noinspection PyDefaultArgument @@ -424,7 +424,7 @@ class Lstr: def __init__(self, *, value: str, - subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: + subs: Sequence[tuple[str, str | Lstr]] = []) -> None: """Create an Lstr from a raw string value.""" # pylint: enable=redefined-outer-name, dangerous-default-value diff --git a/dist/ba_data/python/ba/_level.py b/dist/ba_data/python/ba/_level.py index 968610a..b3c0237 100644 --- a/dist/ba_data/python/ba/_level.py +++ b/dist/ba_data/python/ba/_level.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any import ba @@ -31,9 +31,9 @@ class Level: self._settings = settings self._preview_texture_name = preview_texture_name self._displayname = displayname - self._campaign: Optional[weakref.ref[ba.Campaign]] = None - self._index: Optional[int] = None - self._score_version_string: Optional[str] = None + self._campaign: weakref.ref[ba.Campaign] | None = None + self._index: int | None = None + self._score_version_string: str | None = None def __repr__(self) -> str: cls = type(self) @@ -78,7 +78,7 @@ class Level: return self._gametype @property - def campaign(self) -> Optional[ba.Campaign]: + def campaign(self) -> ba.Campaign | None: """The ba.Campaign this Level is associated with, or None.""" return None if self._campaign is None else self._campaign() diff --git a/dist/ba_data/python/ba/_lobby.py b/dist/ba_data/python/ba/_lobby.py index bd953b8..31fcfa5 100644 --- a/dist/ba_data/python/ba/_lobby.py +++ b/dist/ba_data/python/ba/_lobby.py @@ -16,7 +16,7 @@ from ba._generated.enums import SpecialChar, InputType from ba._profile import get_player_profile_colors if TYPE_CHECKING: - from typing import Optional, Any, Sequence, Union + from typing import Any, Sequence import ba MAX_QUICK_CHANGE_COUNT = 30 @@ -32,14 +32,12 @@ class JoinInfo: from ba._nodeactor import NodeActor from ba._general import WeakCall self._state = 0 - self._press_to_punch: Union[str, - ba.Lstr] = ('C' if _ba.app.iircade_mode - else _ba.charstr( - SpecialChar.LEFT_BUTTON)) - self._press_to_bomb: Union[str, - ba.Lstr] = ('B' if _ba.app.iircade_mode else + self._press_to_punch: str | ba.Lstr = ('C' if _ba.app.iircade_mode else _ba.charstr( - SpecialChar.RIGHT_BUTTON)) + SpecialChar.LEFT_BUTTON)) + self._press_to_bomb: str | ba.Lstr = ('B' if _ba.app.iircade_mode else + _ba.charstr( + SpecialChar.RIGHT_BUTTON)) self._joinmsg = Lstr(resource='pressAnyButtonToJoinText') can_switch_teams = (len(lobby.sessionteams) > 1) @@ -150,12 +148,12 @@ class Chooser: self._sessionplayer = sessionplayer self._inited = False self._dead = False - self._text_node: Optional[ba.Node] = None + self._text_node: ba.Node | None = None self._profilename = '' self._profilenames: list[str] = [] self._ready: bool = False self._character_names: list[str] = [] - self._last_change: Sequence[Union[float, int]] = (0, 0) + self._last_change: Sequence[float | int] = (0, 0) self._profiles: dict[str, dict[str, Any]] = {} app = _ba.app @@ -318,7 +316,7 @@ class Chooser: raise NotFoundError('Lobby does not exist.') return lobby - def get_lobby(self) -> Optional[ba.Lobby]: + def get_lobby(self) -> ba.Lobby | None: """Return this chooser's lobby if it still exists; otherwise None.""" return self._lobby() diff --git a/dist/ba_data/python/ba/_map.py b/dist/ba_data/python/ba/_map.py index 6236700..db70c76 100644 --- a/dist/ba_data/python/ba/_map.py +++ b/dist/ba_data/python/ba/_map.py @@ -11,7 +11,7 @@ from ba import _math from ba._actor import Actor if TYPE_CHECKING: - from typing import Optional, Sequence, Any + from typing import Sequence, Any import ba @@ -161,7 +161,7 @@ class Map(Actor): return [] @classmethod - def get_preview_texture_name(cls) -> Optional[str]: + def get_preview_texture_name(cls) -> str | None: """Return the name of the preview texture for this map.""" return None @@ -179,7 +179,7 @@ class Map(Actor): return cls.name @classmethod - def get_music_type(cls) -> Optional[ba.MusicType]: + def get_music_type(cls) -> ba.MusicType | None: """Return a music-type string that should be played on this map. If None is returned, default music will be used. @@ -187,13 +187,13 @@ class Map(Actor): return None def __init__(self, - vr_overlay_offset: Optional[Sequence[float]] = None) -> None: + vr_overlay_offset: Sequence[float] | None = None) -> None: """Instantiate a map.""" super().__init__() # This is expected to always be a ba.Node object (whether valid or not) # should be set to something meaningful by child classes. - self.node: Optional[_ba.Node] = None + self.node: _ba.Node | None = None # Make our class' preload-data available to us # (and instruct the user if we weren't preloaded properly). @@ -209,8 +209,9 @@ class Map(Actor): # Set various globals. gnode = _ba.getactivity().globalsnode import ba - from features import text_on_map - text_on_map.textonmap() + import custom_hooks + custom_hooks.on_map_init() + # Set area-of-interest bounds. aoi_bounds = self.get_def_bound_box('area_of_interest_bounds') @@ -297,8 +298,8 @@ class Map(Actor): return False def get_def_bound_box( - self, name: str - ) -> Optional[tuple[float, float, float, float, float, float]]: + self, name: str + ) -> tuple[float, float, float, float, float, float] | None: """Return a 6 member bounds tuple or None if it is not defined.""" try: box = self.defs.boxes[name] @@ -308,7 +309,7 @@ class Map(Actor): except Exception: return None - def get_def_point(self, name: str) -> Optional[Sequence[float]]: + def get_def_point(self, name: str) -> Sequence[float] | None: """Return a single defined point or a default value in its absence.""" val = self.defs.points.get(name) return (None if val is None else diff --git a/dist/ba_data/python/ba/_messages.py b/dist/ba_data/python/ba/_messages.py index feebceb..2e0a97f 100644 --- a/dist/ba_data/python/ba/_messages.py +++ b/dist/ba_data/python/ba/_messages.py @@ -11,7 +11,7 @@ from enum import Enum import _ba if TYPE_CHECKING: - from typing import Sequence, Optional, Any + from typing import Sequence, Any import ba @@ -84,7 +84,7 @@ class PlayerDiedMessage: """The particular type of death.""" def __init__(self, player: ba.Player, was_killed: bool, - killerplayer: Optional[ba.Player], how: ba.DeathType): + killerplayer: ba.Player | None, how: ba.DeathType): """Instantiate a message with the given values.""" # Invalid refs should never be passed as args. @@ -98,7 +98,7 @@ class PlayerDiedMessage: self.how = how def getkillerplayer(self, - playertype: type[PlayerType]) -> Optional[PlayerType]: + playertype: type[PlayerType]) -> PlayerType | None: """Return the ba.Player responsible for the killing, if any. Pass the Player type being used by the current game. @@ -267,8 +267,8 @@ class HitMessage: self.force_direction = (force_direction if force_direction is not None else velocity) - def get_source_player( - self, playertype: type[PlayerType]) -> Optional[PlayerType]: + def get_source_player(self, + playertype: type[PlayerType]) -> PlayerType | None: """Return the source-player if one exists and is the provided type.""" player: Any = self._source_player diff --git a/dist/ba_data/python/ba/_meta.py b/dist/ba_data/python/ba/_meta.py index 017ff85..45445a5 100644 --- a/dist/ba_data/python/ba/_meta.py +++ b/dist/ba_data/python/ba/_meta.py @@ -6,22 +6,23 @@ from __future__ import annotations import os import time -import pathlib import threading +from pathlib import Path from typing import TYPE_CHECKING from dataclasses import dataclass, field import _ba if TYPE_CHECKING: - from typing import Union, Optional import ba # The meta api version of this build of the game. # Only packages and modules requiring this exact api version # will be considered when scanning directories. # See: https://ballistica.net/wiki/Meta-Tags -CURRENT_API_VERSION = 6 +CURRENT_API_VERSION = 6 #TODO update it to latest +# current API version is 7 , im downgrading it to 6 to support mini games which i cant update to 7 bcoz of encryption +# shouldn't be a issue , I manually updated all plugin on_app_launch to on_app_running and that was the only change btw API 6 and 7 @dataclass @@ -43,10 +44,11 @@ class MetadataSubsystem: """ def __init__(self) -> None: - self.metascan: Optional[ScanResults] = None + self.metascan: ScanResults | None = None + self.extra_scan_dirs: list[str] = [] - def on_app_launch(self) -> None: - """Should be called when the app is done bootstrapping.""" + def on_app_running(self) -> None: + """Should be called when the app enters the running state.""" # Start scanning for things exposed via ba_meta. self.start_scan() @@ -58,7 +60,8 @@ class MetadataSubsystem: app = _ba.app if self.metascan is not None: print('WARNING: meta scan run more than once.') - pythondirs = [app.python_directory_app, app.python_directory_user] + pythondirs = ([app.python_directory_app, app.python_directory_user] + + self.extra_scan_dirs) thread = ScanThread(pythondirs) thread.start() @@ -99,16 +102,10 @@ class MetadataSubsystem: class_path=class_path, available=True)) if class_path not in plugstates: - if _ba.app.headless_mode: - # If we running in headless mode, enable plugin by default - # to allow server admins to get their modified build - # working 'out-of-the-box', without manually updating the - # config. - plugstates[class_path] = {'enabled': True} - else: - # If we running in normal mode, disable plugin by default - # (user can enable it later). - plugstates[class_path] = {'enabled': False} + # Go ahead and enable new plugins by default, but we'll + # inform the user that they need to restart to pick them up. + # they can also disable them in settings so they never load. + plugstates[class_path] = {'enabled': True} config_changed = True found_new = True @@ -223,18 +220,17 @@ class DirectoryScan: """ # Skip non-existent paths completely. - self.paths = [pathlib.Path(p) for p in paths if os.path.isdir(p)] + self.paths = [Path(p) for p in paths if os.path.isdir(p)] self.results = ScanResults() - def _get_path_module_entries( - self, path: pathlib.Path, subpath: Union[str, pathlib.Path], - modules: list[tuple[pathlib.Path, pathlib.Path]]) -> None: + def _get_path_module_entries(self, path: Path, subpath: str | Path, + modules: list[tuple[Path, Path]]) -> None: """Scan provided path and add module entries to provided list.""" try: # Special case: let's save some time and skip the whole 'ba' # package since we know it doesn't contain any meta tags. - fullpath = pathlib.Path(path, subpath) - entries = [(path, pathlib.Path(subpath, name)) + fullpath = Path(path, subpath) + entries = [(path, Path(subpath, name)) for name in os.listdir(fullpath) if name != 'ba'] except PermissionError: # Expected sometimes. @@ -248,13 +244,13 @@ class DirectoryScan: for entry in entries: if entry[1].name.endswith('.py'): modules.append(entry) - elif (pathlib.Path(entry[0], entry[1]).is_dir() and pathlib.Path( - entry[0], entry[1], '__init__.py').is_file()): + elif (Path(entry[0], entry[1]).is_dir() + and Path(entry[0], entry[1], '__init__.py').is_file()): modules.append(entry) def scan(self) -> None: """Scan provided paths.""" - modules: list[tuple[pathlib.Path, pathlib.Path]] = [] + modules: list[tuple[Path, Path]] = [] for path in self.paths: self._get_path_module_entries(path, '', modules) for moduledir, subpath in modules: @@ -269,14 +265,13 @@ class DirectoryScan: self.results.games.sort() self.results.plugins.sort() - def scan_module(self, moduledir: pathlib.Path, - subpath: pathlib.Path) -> None: + def scan_module(self, moduledir: Path, subpath: Path) -> None: """Scan an individual module and add the findings to results.""" if subpath.name.endswith('.py'): - fpath = pathlib.Path(moduledir, subpath) + fpath = Path(moduledir, subpath) ispackage = False else: - fpath = pathlib.Path(moduledir, subpath, '__init__.py') + fpath = Path(moduledir, subpath, '__init__.py') ispackage = True with fpath.open(encoding='utf-8') as infile: flines = infile.readlines() @@ -293,7 +288,7 @@ class DirectoryScan: # If we find a module requiring a different api version, warn # and ignore. - if required_api is not None and required_api != CURRENT_API_VERSION: + if required_api is not None and required_api <= CURRENT_API_VERSION: self.results.warnings += ( f'Warning: {subpath} requires api {required_api} but' f' we are running {CURRENT_API_VERSION}; ignoring module.\n') @@ -305,7 +300,7 @@ class DirectoryScan: # If its a package, recurse into its subpackages. if ispackage: try: - submodules: list[tuple[pathlib.Path, pathlib.Path]] = [] + submodules: list[tuple[Path, Path]] = [] self._get_path_module_entries(moduledir, subpath, submodules) for submodule in submodules: if submodule[1].name != '__init__.py': @@ -315,8 +310,7 @@ class DirectoryScan: self.results.warnings += ( f"Error scanning '{subpath}': {traceback.format_exc()}\n") - def _process_module_meta_tags(self, subpath: pathlib.Path, - flines: list[str], + def _process_module_meta_tags(self, subpath: Path, flines: list[str], meta_lines: dict[int, list[str]]) -> None: """Pull data from a module based on its ba_meta tags.""" for lindex, mline in meta_lines.items(): @@ -360,8 +354,8 @@ class DirectoryScan: ': unrecognized export type "' + exporttype + '" on line ' + str(lindex + 1) + '.\n') - def _get_export_class_name(self, subpath: pathlib.Path, lines: list[str], - lindex: int) -> Optional[str]: + def _get_export_class_name(self, subpath: Path, lines: list[str], + lindex: int) -> str | None: """Given line num of an export tag, returns its operand class name.""" lindexorig = lindex classname = None @@ -386,9 +380,12 @@ class DirectoryScan: str(lindexorig + 1) + '.\n') return classname - def get_api_requirement(self, subpath: pathlib.Path, - meta_lines: dict[int, list[str]], - toplevel: bool) -> Optional[int]: + def get_api_requirement( + self, + subpath: Path, + meta_lines: dict[int, list[str]], + toplevel: bool, + ) -> int | None: """Return an API requirement integer or None if none present. Malformed api requirement strings will be logged as warnings. diff --git a/dist/ba_data/python/ba/_multiteamsession.py b/dist/ba_data/python/ba/_multiteamsession.py index 29b4c1e..efc7a42 100644 --- a/dist/ba_data/python/ba/_multiteamsession.py +++ b/dist/ba_data/python/ba/_multiteamsession.py @@ -12,7 +12,7 @@ from ba._session import Session from ba._error import NotFoundError, print_error if TYPE_CHECKING: - from typing import Optional, Any, Sequence + from typing import Any, Sequence import ba DEFAULT_TEAM_COLORS = ((0.1, 0.25, 1.0), (1.0, 0.25, 0.2)) @@ -62,7 +62,7 @@ class MultiTeamSession(Session): show_tutorial = cfg.get('Show Tutorial', True) - self._tutorial_activity_instance: Optional[ba.Activity] + self._tutorial_activity_instance: ba.Activity | None if show_tutorial: from bastd.tutorial import TutorialActivity @@ -105,7 +105,7 @@ class MultiTeamSession(Session): shuffle=self._playlist_randomize) # Get a game on deck ready to go. - self._current_game_spec: Optional[dict[str, Any]] = None + self._current_game_spec: dict[str, Any] | None = None self._next_game_spec: dict[str, Any] = self._playlist.pull_next() self._next_game: type[ba.GameActivity] = ( self._next_game_spec['resolved_type']) @@ -278,7 +278,7 @@ class ShuffleList: self.source_list = items self.shuffle = shuffle self.shuffle_list: list[dict[str, Any]] = [] - self.last_gotten: Optional[dict[str, Any]] = None + self.last_gotten: dict[str, Any] | None = None def pull_next(self) -> dict[str, Any]: """Pull and return the next item on the shuffle-list.""" diff --git a/dist/ba_data/python/ba/_music.py b/dist/ba_data/python/ba/_music.py index 82f9c78..2944f29 100644 --- a/dist/ba_data/python/ba/_music.py +++ b/dist/ba_data/python/ba/_music.py @@ -11,7 +11,7 @@ from enum import Enum import _ba if TYPE_CHECKING: - from typing import Callable, Any, Optional, Union + from typing import Callable, Any import ba @@ -127,11 +127,11 @@ class MusicSubsystem: def __init__(self) -> None: # pylint: disable=cyclic-import - self._music_node: Optional[_ba.Node] = None + self._music_node: _ba.Node | None = None self._music_mode: MusicPlayMode = MusicPlayMode.REGULAR - self._music_player: Optional[MusicPlayer] = None - self._music_player_type: Optional[type[MusicPlayer]] = None - self.music_types: dict[MusicPlayMode, Optional[MusicType]] = { + self._music_player: MusicPlayer | None = None + self._music_player_type: type[MusicPlayer] | None = None + self.music_types: dict[MusicPlayMode, MusicType | None] = { MusicPlayMode.REGULAR: None, MusicPlayMode.TEST: None } @@ -270,7 +270,7 @@ class MusicSubsystem: self.do_play_music(None) def do_play_music(self, - musictype: Union[MusicType, str, None], + musictype: MusicType | str | None, continuous: bool = False, mode: MusicPlayMode = MusicPlayMode.REGULAR, testsoundtrack: dict[str, Any] = None) -> None: @@ -352,7 +352,7 @@ class MusicSubsystem: # Do the thing. self.get_music_player().play(entry) - def _play_internal_music(self, musictype: Optional[MusicType]) -> None: + def _play_internal_music(self, musictype: MusicType | None) -> None: # Stop any existing music-player playback. if self._music_player is not None: @@ -393,7 +393,7 @@ class MusicPlayer: def __init__(self) -> None: self._have_set_initial_volume = False - self._entry_to_play: Optional[Any] = None + self._entry_to_play: Any = None self._volume = 1.0 self._actually_playing = False @@ -470,8 +470,7 @@ class MusicPlayer: self._actually_playing = False -def setmusic(musictype: Optional[ba.MusicType], - continuous: bool = False) -> None: +def setmusic(musictype: ba.MusicType | None, continuous: bool = False) -> None: """Set the app to play (or stop playing) a certain type of music. category: **Gameplay Functions** diff --git a/dist/ba_data/python/ba/_net.py b/dist/ba_data/python/ba/_net.py index f6c05b9..940b911 100644 --- a/dist/ba_data/python/ba/_net.py +++ b/dist/ba_data/python/ba/_net.py @@ -12,9 +12,9 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Union, Callable, Optional + from typing import Any, Callable import socket - MasterServerCallback = Callable[[Union[None, dict[str, Any]]], None] + MasterServerCallback = Callable[[None | dict[str, Any]], None] # Timeout for standard functions talking to the master-server/etc. DEFAULT_REQUEST_TIMEOUT_SECONDS = 60 @@ -72,8 +72,8 @@ class MasterServerCallThread(threading.Thread): """Thread to communicate with the master-server.""" def __init__(self, request: str, request_type: str, - data: Optional[dict[str, Any]], - callback: Optional[MasterServerCallback], + data: dict[str, Any] | None, + callback: MasterServerCallback | None, response_type: MasterServerResponseType): super().__init__() self._request = request @@ -82,7 +82,7 @@ class MasterServerCallThread(threading.Thread): raise TypeError(f'Invalid response type: {response_type}') self._response_type = response_type self._data = {} if data is None else copy.deepcopy(data) - self._callback: Optional[MasterServerCallback] = callback + self._callback: MasterServerCallback | None = callback self._context = _ba.Context('current') # Save and restore the context we were created from. @@ -90,7 +90,7 @@ class MasterServerCallThread(threading.Thread): self._activity = weakref.ref( activity) if activity is not None else None - def _run_callback(self, arg: Union[None, dict[str, Any]]) -> None: + def _run_callback(self, arg: None | dict[str, Any]) -> None: # If we were created in an activity context and that activity has # since died, do nothing. # FIXME: Should we just be using a ContextCall instead of doing @@ -182,7 +182,7 @@ class MasterServerCallThread(threading.Thread): def master_server_get( request: str, data: dict[str, Any], - callback: Optional[MasterServerCallback] = None, + callback: MasterServerCallback | None = None, response_type: MasterServerResponseType = MasterServerResponseType.JSON ) -> None: """Make a call to the master server via a http GET.""" @@ -193,7 +193,7 @@ def master_server_get( def master_server_post( request: str, data: dict[str, Any], - callback: Optional[MasterServerCallback] = None, + callback: MasterServerCallback | None = None, response_type: MasterServerResponseType = MasterServerResponseType.JSON ) -> None: """Make a call to the master server via a http POST.""" diff --git a/dist/ba_data/python/ba/_player.py b/dist/ba_data/python/ba/_player.py index 965213d..3e56fcf 100644 --- a/dist/ba_data/python/ba/_player.py +++ b/dist/ba_data/python/ba/_player.py @@ -13,7 +13,7 @@ from ba._error import (SessionPlayerNotFoundError, print_exception, from ba._messages import DeathType, DieMessage if TYPE_CHECKING: - from typing import Optional, Sequence, Any, Union, Callable + from typing import Sequence, Any, Callable import ba # pylint: disable=invalid-name @@ -39,7 +39,7 @@ class StandLocation: Category: Gameplay Classes """ position: ba.Vec3 - angle: Optional[float] = None + angle: float | None = None class Player(Generic[TeamType]): @@ -56,7 +56,7 @@ class Player(Generic[TeamType]): # their type annotations are introspectable (for docs generation). character: str - actor: Optional[ba.Actor] + actor: ba.Actor | None """The ba.Actor associated with the player.""" color: Sequence[float] @@ -64,7 +64,7 @@ class Player(Generic[TeamType]): _team: TeamType _sessionplayer: ba.SessionPlayer - _nodeactor: Optional[ba.NodeActor] + _nodeactor: ba.NodeActor | None _expired: bool _postinited: bool _customdata: dict @@ -94,7 +94,7 @@ class Player(Generic[TeamType]): self.actor = None self.character = '' - self._nodeactor: Optional[ba.NodeActor] = None + self._nodeactor: ba.NodeActor | None = None self._sessionplayer = sessionplayer self.character = sessionplayer.character self.color = sessionplayer.color @@ -249,8 +249,7 @@ class Player(Generic[TeamType]): assert not self._expired return self._sessionplayer.get_icon() - def assigninput(self, inputtype: Union[ba.InputType, tuple[ba.InputType, - ...]], + def assigninput(self, inputtype: ba.InputType | tuple[ba.InputType, ...], call: Callable) -> None: """ Set the python callable to be run for one or more types of input. @@ -313,7 +312,7 @@ def playercast(totype: type[PlayerType], player: ba.Player) -> PlayerType: # for the optional variety, but that currently seems to not be working. # See: https://github.com/python/mypy/issues/8800 def playercast_o(totype: type[PlayerType], - player: Optional[ba.Player]) -> Optional[PlayerType]: + player: ba.Player | None) -> PlayerType | None: """A variant of ba.playercast() for use with optional ba.Player values. Category: Gameplay Functions diff --git a/dist/ba_data/python/ba/_plugin.py b/dist/ba_data/python/ba/_plugin.py index 6de548a..d59cb63 100644 --- a/dist/ba_data/python/ba/_plugin.py +++ b/dist/ba_data/python/ba/_plugin.py @@ -25,16 +25,16 @@ class PluginSubsystem: self.potential_plugins: list[ba.PotentialPlugin] = [] self.active_plugins: dict[str, ba.Plugin] = {} - def on_app_launch(self) -> None: - """Should be called at app launch time.""" - # Load up our plugins and go ahead and call their on_app_launch calls. + def on_app_running(self) -> None: + """Should be called when the app reaches the running state.""" + # Load up our plugins and go ahead and call their on_app_running calls. self.load_plugins() for plugin in self.active_plugins.values(): try: - plugin.on_app_launch() + plugin.on_app_running() except Exception: from ba import _error - _error.print_exception('Error in plugin on_app_launch()') + _error.print_exception('Error in plugin on_app_running()') def on_app_pause(self) -> None: """Called when the app goes to a suspended state.""" @@ -80,16 +80,23 @@ class PluginSubsystem: try: cls = getclass(plugkey, Plugin) except Exception as exc: - _ba.log(f"Error loading plugin class '{plugkey}': {exc}", - to_server=False) + _ba.playsound(_ba.getsound('error')) + # TODO: Lstr. + errstr = f"Error loading plugin class '{plugkey}': {exc}" + _ba.screenmessage(errstr, color=(1, 0, 0)) + _ba.log(errstr, to_server=False) continue try: plugin = cls() assert plugkey not in self.active_plugins self.active_plugins[plugkey] = plugin - except Exception: + except Exception as exc: from ba import _error - _error.print_exception(f'Error loading plugin: {plugkey}') + _ba.playsound(_ba.getsound('error')) + # TODO: Lstr. + _ba.screenmessage(f"Error loading plugin: '{plugkey}': {exc}", + color=(1, 0, 0)) + _error.print_exception(f"Error loading plugin: '{plugkey}'.") @dataclass @@ -119,8 +126,8 @@ class Plugin: app is running in order to modify its behavior in some way. """ - def on_app_launch(self) -> None: - """Called when the app is being launched.""" + def on_app_running(self) -> None: + """Called when the app reaches the running state.""" def on_app_pause(self) -> None: """Called after pausing game activity.""" diff --git a/dist/ba_data/python/ba/_powerup.py b/dist/ba_data/python/ba/_powerup.py index 246cf7b..8ca65a0 100644 --- a/dist/ba_data/python/ba/_powerup.py +++ b/dist/ba_data/python/ba/_powerup.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from dataclasses import dataclass if TYPE_CHECKING: - from typing import Sequence, Optional + from typing import Sequence import ba @@ -25,7 +25,7 @@ class PowerupMessage: """The type of powerup to be granted (a string). See ba.Powerup.poweruptype for available type values.""" - sourcenode: Optional[ba.Node] = None + sourcenode: ba.Node | None = None """The node the powerup game from, or None otherwise. If a powerup is accepted, a ba.PowerupAcceptMessage should be sent back to the sourcenode to inform it of the fact. This will generally diff --git a/dist/ba_data/python/ba/_profile.py b/dist/ba_data/python/ba/_profile.py index 6513eb6..be0a831 100644 --- a/dist/ba_data/python/ba/_profile.py +++ b/dist/ba_data/python/ba/_profile.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any # NOTE: player color options are enforced server-side for non-pro accounts # so don't change these or they won't stick... @@ -49,7 +49,7 @@ def get_player_profile_icon(profilename: str) -> str: def get_player_profile_colors( - profilename: Optional[str], + profilename: str | None, profiles: dict[str, dict[str, Any]] = None ) -> tuple[tuple[float, float, float], tuple[float, float, float]]: """Given a profile, return colors for them.""" diff --git a/dist/ba_data/python/ba/_servermode.py b/dist/ba_data/python/ba/_servermode.py index 37fad3c..19aecb5 100644 --- a/dist/ba_data/python/ba/_servermode.py +++ b/dist/ba_data/python/ba/_servermode.py @@ -19,7 +19,7 @@ from ba._dualteamsession import DualTeamSession from ba._coopsession import CoopSession if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any import ba from bacommon.servermanager import ServerConfig @@ -85,10 +85,10 @@ class ServerController: self._config = config self._playlist_name = '__default__' self._ran_access_check = False - self._prep_timer: Optional[ba.Timer] = None + self._prep_timer: ba.Timer | None = None self._next_stuck_login_warn_time = time.time() + 10.0 self._first_run = True - self._shutdown_reason: Optional[ShutdownReason] = None + self._shutdown_reason: ShutdownReason | None = None self._executing_shutdown = False # Make note if they want us to import a playlist; @@ -129,7 +129,7 @@ class ServerController: out += f'\n{clientid:<{col1}} {name:<{col2}} {players}' print(out) - def kick(self, client_id: int, ban_time: Optional[int]) -> None: + def kick(self, client_id: int, ban_time: int | None) -> None: """Kick the provided client id. ban_time is provided in seconds. @@ -198,7 +198,7 @@ class ServerController: callback=self._access_check_response, ) - def _access_check_response(self, data: Optional[dict[str, Any]]) -> None: + def _access_check_response(self, data: dict[str, Any] | None) -> None: import os if data is None: print('error on UDP port access check (internet down?)') @@ -267,7 +267,7 @@ class ServerController: def _on_playlist_fetch_response( self, - result: Optional[dict[str, Any]], + result: dict[str, Any] | None, ) -> None: if result is None: print('Error fetching playlist; aborting.') diff --git a/dist/ba_data/python/ba/_session.py b/dist/ba_data/python/ba/_session.py index d0ee126..f5d9775 100644 --- a/dist/ba_data/python/ba/_session.py +++ b/dist/ba_data/python/ba/_session.py @@ -12,7 +12,7 @@ from ba._language import Lstr from ba._player import Player if TYPE_CHECKING: - from typing import Sequence, Any, Optional + from typing import Sequence, Any import ba @@ -134,7 +134,7 @@ class Session: self._sessiondata = _ba.register_session(self) # Should remove this if possible. - self.tournament_id: Optional[str] = None + self.tournament_id: str | None = None self.sessionteams = [] self.sessionplayers = [] @@ -144,16 +144,16 @@ class Session: self.customdata = {} self._in_set_activity = False self._next_team_id = 0 - self._activity_retained: Optional[ba.Activity] = None - self._launch_end_session_activity_time: Optional[float] = None - self._activity_end_timer: Optional[ba.Timer] = None + self._activity_retained: ba.Activity | None = None + self._launch_end_session_activity_time: float | None = None + self._activity_end_timer: ba.Timer | None = None self._activity_weak = empty_weakref(Activity) - self._next_activity: Optional[ba.Activity] = None + self._next_activity: ba.Activity | None = None self._wants_to_end = False self._ending = False self._activity_should_end_immediately = False - self._activity_should_end_immediately_results: ( - Optional[ba.GameResults]) = None + self._activity_should_end_immediately_results: (ba.GameResults + | None) = None self._activity_should_end_immediately_delay = 0.0 # Create static teams if we're using them. @@ -285,7 +285,7 @@ class Session: self.sessionplayers.remove(sessionplayer) def _remove_player_team(self, sessionteam: ba.SessionTeam, - activity: Optional[ba.Activity]) -> None: + activity: ba.Activity | None) -> None: """Remove the player-specific team in non-teams mode.""" # They should have been the only one on their team. @@ -481,7 +481,7 @@ class Session: timetype=TimeType.REAL) self._in_set_activity = False - def getactivity(self) -> Optional[ba.Activity]: + def getactivity(self) -> ba.Activity | None: """Return the current foreground activity for this session.""" return self._activity_weak() diff --git a/dist/ba_data/python/ba/_stats.py b/dist/ba_data/python/ba/_stats.py index 282545b..62fc662 100644 --- a/dist/ba_data/python/ba/_stats.py +++ b/dist/ba_data/python/ba/_stats.py @@ -14,7 +14,7 @@ from ba._error import (print_exception, print_error, SessionTeamNotFoundError, if TYPE_CHECKING: import ba - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence @dataclass @@ -49,12 +49,12 @@ class PlayerRecord: self.accum_kill_count = 0 self.killed_count = 0 self.accum_killed_count = 0 - self._multi_kill_timer: Optional[ba.Timer] = None + self._multi_kill_timer: ba.Timer | None = None self._multi_kill_count = 0 self._stats = weakref.ref(stats) - self._last_sessionplayer: Optional[ba.SessionPlayer] = None - self._sessionplayer: Optional[ba.SessionPlayer] = None - self._sessionteam: Optional[weakref.ref[ba.SessionTeam]] = None + self._last_sessionplayer: ba.SessionPlayer | None = None + self._sessionplayer: ba.SessionPlayer | None = None + self._sessionteam: weakref.ref[ba.SessionTeam] | None = None self.streak = 0 self.associate_with_sessionplayer(sessionplayer) @@ -96,7 +96,7 @@ class PlayerRecord: """Cancel any multi-kill timer for this player entry.""" self._multi_kill_timer = None - def getactivity(self) -> Optional[ba.Activity]: + def getactivity(self) -> ba.Activity | None: """Return the ba.Activity this instance is currently associated with. Returns None if the activity no longer exists.""" @@ -178,12 +178,12 @@ class PlayerRecord: def _apply(name2: Lstr, score2: int, showpoints2: bool, color2: tuple[float, float, float, float], scale2: float, - sound2: Optional[ba.Sound]) -> None: + sound2: ba.Sound | None) -> None: from bastd.actor.popuptext import PopupText # Only award this if they're still alive and we can get # a current position for them. - our_pos: Optional[ba.Vec3] = None + our_pos: ba.Vec3 | None = None if self._sessionplayer: if self._sessionplayer.activityplayer is not None: try: @@ -233,14 +233,14 @@ class Stats: """ def __init__(self) -> None: - self._activity: Optional[weakref.ref[ba.Activity]] = None + self._activity: weakref.ref[ba.Activity] | None = None self._player_records: dict[str, PlayerRecord] = {} - self.orchestrahitsound1: Optional[ba.Sound] = None - self.orchestrahitsound2: Optional[ba.Sound] = None - self.orchestrahitsound3: Optional[ba.Sound] = None - self.orchestrahitsound4: Optional[ba.Sound] = None + self.orchestrahitsound1: ba.Sound | None = None + self.orchestrahitsound2: ba.Sound | None = None + self.orchestrahitsound3: ba.Sound | None = None + self.orchestrahitsound4: ba.Sound | None = None - def setactivity(self, activity: Optional[ba.Activity]) -> None: + def setactivity(self, activity: ba.Activity | None) -> None: """Set the current activity for this instance.""" self._activity = None if activity is None else weakref.ref(activity) @@ -253,7 +253,7 @@ class Stats: with _ba.Context(activity): self._load_activity_media() - def getactivity(self) -> Optional[ba.Activity]: + def getactivity(self) -> ba.Activity | None: """Get the activity associated with this instance. May return None. @@ -319,7 +319,7 @@ class Stats: victim_player: ba.Player = None, scale: float = 1.0, color: Sequence[float] = None, - title: Union[str, ba.Lstr] = None, + title: str | ba.Lstr | None = None, screenmessage: bool = True, display: bool = True, importance: int = 1, diff --git a/dist/ba_data/python/ba/_store.py b/dist/ba_data/python/ba/_store.py index 5c936bd..ec63803 100644 --- a/dist/ba_data/python/ba/_store.py +++ b/dist/ba_data/python/ba/_store.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any import ba @@ -434,7 +434,7 @@ def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int, return count -def get_available_sale_time(tab: str) -> Optional[int]: +def get_available_sale_time(tab: str) -> int | None: """(internal)""" # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks @@ -443,7 +443,7 @@ def get_available_sale_time(tab: str) -> Optional[int]: import datetime from ba._generated.enums import TimeType, TimeFormat app = _ba.app - sale_times: list[Optional[int]] = [] + sale_times: list[int | None] = [] # Calc time for our pro sale (old special case). if tab == 'extras': @@ -475,7 +475,7 @@ def get_available_sale_time(tab: str) -> Optional[int]: return None assert app.pro_sale_start_val is not None - val: Optional[int] = max( + val: int | None = max( 0, app.pro_sale_start_val - (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) - app.pro_sale_start_time)) diff --git a/dist/ba_data/python/ba/_team.py b/dist/ba_data/python/ba/_team.py index 71c6b66..dac90ae 100644 --- a/dist/ba_data/python/ba/_team.py +++ b/dist/ba_data/python/ba/_team.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, TypeVar, Generic from ba._error import print_exception if TYPE_CHECKING: - from typing import Sequence, Union, Optional + from typing import Sequence import ba @@ -26,7 +26,7 @@ class SessionTeam: # Annotate our attr types at the class level so they're introspectable. - name: Union[ba.Lstr, str] + name: ba.Lstr | str """The team's name.""" color: tuple[float, ...] # FIXME: can't we make this fixed len? @@ -46,7 +46,7 @@ class SessionTeam: def __init__(self, team_id: int = 0, - name: Union[ba.Lstr, str] = '', + name: ba.Lstr | str = '', color: Sequence[float] = (1.0, 1.0, 1.0)): """Instantiate a ba.SessionTeam. @@ -59,7 +59,7 @@ class SessionTeam: self.color = tuple(color) self.players = [] self.customdata = {} - self.activityteam: Optional[Team] = None + self.activityteam: Team | None = None def leave(self) -> None: """(internal)""" @@ -84,7 +84,7 @@ class Team(Generic[PlayerType]): # that types are introspectable (these are still instance attrs). players: list[PlayerType] id: int - name: Union[ba.Lstr, str] + name: ba.Lstr | str color: tuple[float, ...] # FIXME: can't we make this fixed length? _sessionteam: weakref.ref[SessionTeam] _expired: bool @@ -120,7 +120,7 @@ class Team(Generic[PlayerType]): self._expired = False self._postinited = True - def manual_init(self, team_id: int, name: Union[ba.Lstr, str], + def manual_init(self, team_id: int, name: ba.Lstr | str, color: tuple[float, ...]) -> None: """Manually init a team for uses such as bots.""" self.id = team_id diff --git a/dist/ba_data/python/ba/_ui.py b/dist/ba_data/python/ba/_ui.py index 0629801..4bf2b70 100644 --- a/dist/ba_data/python/ba/_ui.py +++ b/dist/ba_data/python/ba/_ui.py @@ -10,7 +10,7 @@ import _ba from ba._generated.enums import UIScale if TYPE_CHECKING: - from typing import Optional, Any, Callable + from typing import Any, Callable from ba.ui import UICleanupCheck import ba @@ -26,10 +26,10 @@ class UISubsystem: def __init__(self) -> None: env = _ba.env() - self.controller: Optional[ba.UIController] = None + self.controller: ba.UIController | None = None - self._main_menu_window: Optional[ba.Widget] = None - self._main_menu_location: Optional[str] = None + self._main_menu_window: ba.Widget | None = None + self._main_menu_location: str | None = None self._uiscale: ba.UIScale @@ -44,13 +44,12 @@ class UISubsystem: raise RuntimeError(f'Invalid UIScale value: {interfacetype}') self.window_states: dict[type, Any] = {} # FIXME: Kill this. - self.main_menu_selection: Optional[str] = None # FIXME: Kill this. + self.main_menu_selection: str | None = None # FIXME: Kill this. self.have_party_queue_window = False self.quit_window: Any = None - self.dismiss_wii_remotes_window_call: (Optional[Callable[[], - Any]]) = None + self.dismiss_wii_remotes_window_call: (Callable[[], Any] | None) = None self.cleanupchecks: list[UICleanupCheck] = [] - self.upkeeptimer: Optional[ba.Timer] = None + self.upkeeptimer: ba.Timer | None = None self.use_toolbars = env.get('toolbar_test', True) self.party_window: Any = None # FIXME: Don't use Any. self.title_color = (0.72, 0.7, 0.75) @@ -162,6 +161,6 @@ class UISubsystem: """Set the location represented by the current main menu window.""" self._main_menu_location = location - def get_main_menu_location(self) -> Optional[str]: + def get_main_menu_location(self) -> str | None: """Return the current named main menu location, if any.""" return self._main_menu_location diff --git a/dist/ba_data/python/ba/_workspace.py b/dist/ba_data/python/ba/_workspace.py new file mode 100644 index 0000000..debbdb1 --- /dev/null +++ b/dist/ba_data/python/ba/_workspace.py @@ -0,0 +1,196 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Workspace related functionality.""" + +from __future__ import annotations + +import os +import sys +import logging +from pathlib import Path +from threading import Thread +from typing import TYPE_CHECKING + +from efro.call import tpartial +from efro.error import CleanError +import _ba +import bacommon.cloud +from bacommon.transfer import DirectoryManifest + +if TYPE_CHECKING: + from typing import Callable + + import ba + + +class WorkspaceSubsystem: + """Subsystem for workspace handling in the app. + + Category: **App Classes** + + Access the single shared instance of this class at `ba.app.workspaces`. + """ + + def __init__(self) -> None: + pass + + def set_active_workspace( + self, + workspaceid: str, + workspacename: str, + on_completed: Callable[[], None], + ) -> None: + """(internal)""" + + # Do our work in a background thread so we don't destroy + # interactivity. + Thread( + target=lambda: self._set_active_workspace_bg( + workspaceid=workspaceid, + workspacename=workspacename, + on_completed=on_completed), + daemon=True, + ).start() + + def _errmsg(self, msg: str | ba.Lstr) -> None: + _ba.screenmessage(msg, color=(1, 0, 0)) + _ba.playsound(_ba.getsound('error')) + + def _successmsg(self, msg: str | ba.Lstr) -> None: + _ba.screenmessage(msg, color=(0, 1, 0)) + _ba.playsound(_ba.getsound('gunCocking')) + + def _set_active_workspace_bg(self, workspaceid: str, workspacename: str, + on_completed: Callable[[], None]) -> None: + # pylint: disable=too-many-branches + + class _SkipSyncError(RuntimeError): + pass + + set_path = True + wspath = Path(_ba.get_volatile_data_directory(), 'workspaces', + workspaceid) + try: + + # If it seems we're offline, don't even attempt a sync, + # but allow using the previous synced state. + # (is this a good idea?) + if not _ba.app.cloud.is_connected(): + raise _SkipSyncError() + + manifest = DirectoryManifest.create_from_disk(wspath) + + # FIXME: Should implement a way to pass account credentials in + # from the logic thread. + state = bacommon.cloud.WorkspaceFetchState(manifest=manifest) + + while True: + response = _ba.app.cloud.send_message( + bacommon.cloud.WorkspaceFetchMessage( + workspaceid=workspaceid, state=state)) + state = response.state + self._handle_deletes(workspace_dir=wspath, + deletes=response.deletes) + self._handle_downloads_inline( + workspace_dir=wspath, + downloads_inline=response.downloads_inline) + if response.done: + # Server only deals in files; let's clean up any + # leftover empty dirs after the dust has cleared. + self._handle_dir_prune_empty(str(wspath)) + break + state.iteration += 1 + + extras: list[str] = [] + # Hmm; let's not show deletes for now since currently lots of + # .pyc files get deleted. + if bool(False): + if state.total_deletes: + extras.append(f'{state.total_deletes} files deleted') + if state.total_downloads: + extras.append(f'{state.total_downloads} files downloaded') + if state.total_up_to_date: + extras.append(f'{state.total_up_to_date} files up-to-date') + # Actually let's try with none of this; seems a bit excessive. + if bool(False) and extras: + extras_s = '\n' + ', '.join(extras) + '.' + else: + extras_s = '' + _ba.pushcall(tpartial(self._successmsg, + f'{workspacename} activated.{extras_s}'), + from_other_thread=True) + + except _SkipSyncError: + _ba.pushcall(tpartial( + self._errmsg, f'Can\'t sync {workspacename}' + f'. Reusing previous synced version.'), + from_other_thread=True) + + except CleanError as exc: + # Avoid reusing existing if we fail in the middle; could + # be in wonky state. + set_path = False + _ba.pushcall(tpartial(self._errmsg, str(exc)), + from_other_thread=True) + except Exception: + # Ditto. + set_path = False + logging.exception('Error syncing workspace.') + # TODO: Lstr. + _ba.pushcall(tpartial( + self._errmsg, 'Error syncing workspace. See log for details.'), + from_other_thread=True) + + if set_path and wspath.is_dir(): + # Add to Python paths and also to list of stuff to be scanned + # for meta tags. + sys.path.insert(0, str(wspath)) + _ba.app.meta.extra_scan_dirs.append(str(wspath)) + + # Job's done! + _ba.pushcall(on_completed, from_other_thread=True) + + def _handle_deletes(self, workspace_dir: Path, deletes: list[str]) -> None: + """Handle file deletes.""" + for fname in deletes: + fname = os.path.join(workspace_dir, fname) + # Server shouldn't be sending us dir paths here. + assert not os.path.isdir(fname) + os.unlink(fname) + + def _handle_downloads_inline( + self, + workspace_dir: Path, + downloads_inline: dict[str, bytes], + ) -> None: + """Handle inline file data to be saved to the client.""" + for fname, fdata in downloads_inline.items(): + fname = os.path.join(workspace_dir, fname) + # If there's a directory where we want our file to go, clear it + # out first. File deletes should have run before this so + # everything under it should be empty and thus killable via rmdir. + if os.path.isdir(fname): + for basename, dirnames, _fn in os.walk(fname, topdown=False): + for dirname in dirnames: + os.rmdir(os.path.join(basename, dirname)) + os.rmdir(fname) + + dirname = os.path.dirname(fname) + if dirname: + os.makedirs(dirname, exist_ok=True) + with open(fname, 'wb') as outfile: + outfile.write(fdata) + + def _handle_dir_prune_empty(self, prunedir: str) -> None: + """Handle pruning empty directories.""" + # Walk the tree bottom-up so we can properly kill recursive empty dirs. + for basename, dirnames, filenames in os.walk(prunedir, topdown=False): + # It seems that child dirs we kill during the walk are still + # listed when the parent dir is visited, so lets make sure + # to only acknowledge still-existing ones. + dirnames = [ + d for d in dirnames + if os.path.exists(os.path.join(basename, d)) + ] + if not dirnames and not filenames and basename != prunedir: + os.rmdir(basename) diff --git a/dist/ba_data/python/ba/macmusicapp.py b/dist/ba_data/python/ba/macmusicapp.py index 598f572..4a75872 100644 --- a/dist/ba_data/python/ba/macmusicapp.py +++ b/dist/ba_data/python/ba/macmusicapp.py @@ -10,7 +10,7 @@ import _ba from ba._music import MusicPlayer if TYPE_CHECKING: - from typing import Optional, Callable, Any + from typing import Callable, Any class MacMusicAppMusicPlayer(MusicPlayer): @@ -62,8 +62,8 @@ class _MacMusicAppThread(threading.Thread): self._commands_available = threading.Event() self._commands: list[list] = [] self._volume = 1.0 - self._current_playlist: Optional[str] = None - self._orig_volume: Optional[int] = None + self._current_playlist: str | None = None + self._orig_volume: int | None = None def run(self) -> None: """Run the Music.app thread.""" @@ -136,7 +136,7 @@ class _MacMusicAppThread(threading.Thread): if old_volume == 0.0: self._play_current_playlist() - def play_playlist(self, musictype: Optional[str]) -> None: + def play_playlist(self, musictype: str | None) -> None: """Play the given playlist.""" self._commands.append(['PLAY', musictype]) self._commands_available.set() @@ -170,7 +170,7 @@ class _MacMusicAppThread(threading.Thread): playlists = [] _ba.pushcall(Call(target, playlists), from_other_thread=True) - def _handle_play_command(self, target: Optional[str]) -> None: + def _handle_play_command(self, target: str | None) -> None: if target is None: if self._current_playlist is not None and self._volume > 0: try: diff --git a/dist/ba_data/python/ba/modutils.py b/dist/ba_data/python/ba/modutils.py index 5984f25..e5d1f74 100644 --- a/dist/ba_data/python/ba/modutils.py +++ b/dist/ba_data/python/ba/modutils.py @@ -9,7 +9,7 @@ import os import _ba if TYPE_CHECKING: - from typing import Optional, Sequence + from typing import Sequence def get_human_readable_user_scripts_path() -> str: @@ -19,7 +19,7 @@ def get_human_readable_user_scripts_path() -> str: """ from ba import _language app = _ba.app - path: Optional[str] = app.python_directory_user + path: str | None = app.python_directory_user if path is None: return '' @@ -27,7 +27,7 @@ def get_human_readable_user_scripts_path() -> str: # only visible to the user's processes and thus not really useful printed # in its entirety; lets print it as /myfilepath. if app.platform == 'android': - ext_storage_path: Optional[str] = ( + ext_storage_path: str | None = ( _ba.android_get_external_storage_path()) if (ext_storage_path is not None and app.python_directory_user.startswith(ext_storage_path)): @@ -70,7 +70,7 @@ def show_user_scripts() -> None: # they can see it. if app.platform == 'android': try: - usd: Optional[str] = app.python_directory_user + usd: str | None = app.python_directory_user if usd is not None and os.path.isdir(usd): file_name = usd + '/about_this_folder.txt' with open(file_name, 'w', encoding='utf-8') as outfile: diff --git a/dist/ba_data/python/ba/osmusic.py b/dist/ba_data/python/ba/osmusic.py index 64f2bd1..624e681 100644 --- a/dist/ba_data/python/ba/osmusic.py +++ b/dist/ba_data/python/ba/osmusic.py @@ -12,7 +12,7 @@ import _ba from ba._music import MusicPlayer if TYPE_CHECKING: - from typing import Callable, Any, Union, Optional + from typing import Callable, Any class OSMusicPlayer(MusicPlayer): @@ -60,8 +60,8 @@ class OSMusicPlayer(MusicPlayer): self._on_play_folder_cb).start() def _on_play_folder_cb(self, - result: Union[str, list[str]], - error: Optional[str] = None) -> None: + result: str | list[str], + error: str | None = None) -> None: from ba import _language if error is not None: rstr = (_language.Lstr( @@ -95,8 +95,7 @@ class OSMusicPlayer(MusicPlayer): class _PickFolderSongThread(threading.Thread): def __init__(self, path: str, valid_extensions: list[str], - callback: Callable[[Union[str, list[str]], Optional[str]], - None]): + callback: Callable[[str | list[str], str | None], None]): super().__init__() self._valid_extensions = valid_extensions self._callback = callback diff --git a/dist/ba_data/python/ba/ui/__init__.py b/dist/ba_data/python/ba/ui/__init__.py index a3b16de..67e5d93 100644 --- a/dist/ba_data/python/ba/ui/__init__.py +++ b/dist/ba_data/python/ba/ui/__init__.py @@ -14,7 +14,7 @@ from ba._generated.enums import TimeType from ba._general import print_active_refs if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any import ba @@ -46,7 +46,7 @@ class UICleanupCheck: """Holds info about a uicleanupcheck target.""" obj: weakref.ref widget: ba.Widget - widget_death_time: Optional[float] + widget_death_time: float | None class UILocation: @@ -76,7 +76,7 @@ class UILocationWindow(UILocation): def __init__(self) -> None: super().__init__() - self._root_widget: Optional[ba.Widget] = None + self._root_widget: ba.Widget | None = None def get_root_widget(self) -> ba.Widget: """Return the root widget for this window.""" @@ -91,7 +91,7 @@ class UIEntry: self._name = name self._state = None self._args = None - self._instance: Optional[UILocation] = None + self._instance: UILocation | None = None self._controller = weakref.ref(controller) def create(self) -> None: @@ -129,7 +129,7 @@ class UIController: self._main_stack_menu: list[UIEntry] = [] # This points at either the game or menu stack. - self._main_stack: Optional[list[UIEntry]] = None + self._main_stack: list[UIEntry] | None = None # There's only one of these since we don't need to preserve its state # between sessions. diff --git a/dist/ba_data/python/bacommon/assets.py b/dist/ba_data/python/bacommon/assets.py index 097b52f..2c4441a 100644 --- a/dist/ba_data/python/bacommon/assets.py +++ b/dist/ba_data/python/bacommon/assets.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Optional, Annotated +from typing import TYPE_CHECKING, Annotated from enum import Enum from efro.dataclassio import ioprepped, IOAttrs @@ -57,4 +57,4 @@ class AssetPackageBuildState: # Build error string. If this is present, it should be presented # to the user and they should required to explicitly restart the build # in some way if desired. - error: Annotated[Optional[str], IOAttrs('e')] = None + error: Annotated[str | None, IOAttrs('e')] = None diff --git a/dist/ba_data/python/bacommon/bacloud.py b/dist/ba_data/python/bacommon/bacloud.py index f22f7fe..99c9be3 100644 --- a/dist/ba_data/python/bacommon/bacloud.py +++ b/dist/ba_data/python/bacommon/bacloud.py @@ -3,18 +3,34 @@ """Functionality related to the bacloud tool.""" from __future__ import annotations -from dataclasses import dataclass -from typing import TYPE_CHECKING, Optional -from efro.dataclassio import ioprepped +from dataclasses import dataclass +from typing import TYPE_CHECKING, Annotated + +from efro.dataclassio import ioprepped, IOAttrs if TYPE_CHECKING: pass +# Version is sent to the master-server with all commands. Can be incremented +# if we need to change behavior server-side to go along with client changes. +BACLOUD_VERSION = 6 + @ioprepped @dataclass -class Response: +class RequestData: + """Request sent to bacloud server.""" + command: Annotated[str, IOAttrs('c')] + token: Annotated[str | None, IOAttrs('t')] + payload: Annotated[dict, IOAttrs('p')] + tzoffset: Annotated[float, IOAttrs('z')] + isatty: Annotated[bool, IOAttrs('y')] + + +@ioprepped +@dataclass +class ResponseData: # noinspection PyUnresolvedReferences """Response sent from the bacloud server to the client. @@ -35,10 +51,10 @@ class Response: uploads_inline: If present, a list of pathnames that should be base64 gzipped and uploaded to an 'uploads_inline' dict in end_command args. This should be limited to relatively small files. + deletes: If present, file paths that should be deleted on the client. downloads_inline: If present, pathnames mapped to base64 gzipped data to be written to the client. This should only be used for relatively small files as they are all included inline as part of the response. - deletes: If present, file paths that should be deleted on the client. dir_prune_empty: If present, all empty dirs under this one should be removed. open_url: If present, url to display to the user. @@ -52,20 +68,29 @@ class Response: end_command: If present, this command is run with these args at the end of response processing. """ - message: Optional[str] = None - message_end: str = '\n' - error: Optional[str] = None - delay_seconds: float = 0.0 - login: Optional[str] = None - logout: bool = False - dir_manifest: Optional[str] = None - uploads: Optional[tuple[list[str], str, dict]] = None - uploads_inline: Optional[list[str]] = None - downloads_inline: Optional[dict[str, str]] = None - deletes: Optional[list[str]] = None - dir_prune_empty: Optional[str] = None - open_url: Optional[str] = None - input_prompt: Optional[tuple[str, bool]] = None - end_message: Optional[str] = None - end_message_end: str = '\n' - end_command: Optional[tuple[str, dict]] = None + message: Annotated[str | None, IOAttrs('m', store_default=False)] = None + message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n' + error: Annotated[str | None, IOAttrs('e', store_default=False)] = None + delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0 + login: Annotated[str | None, IOAttrs('l', store_default=False)] = None + logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False + dir_manifest: Annotated[str | None, + IOAttrs('man', store_default=False)] = None + uploads: Annotated[tuple[list[str], str, dict] | None, + IOAttrs('u', store_default=False)] = None + uploads_inline: Annotated[list[str] | None, + IOAttrs('uinl', store_default=False)] = None + deletes: Annotated[list[str] | None, + IOAttrs('dlt', store_default=False)] = None + downloads_inline: Annotated[dict[str, str] | None, + IOAttrs('dinl', store_default=False)] = None + dir_prune_empty: Annotated[str | None, + IOAttrs('dpe', store_default=False)] = None + open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None + input_prompt: Annotated[tuple[str, bool] | None, + IOAttrs('inp', store_default=False)] = None + end_message: Annotated[str | None, + IOAttrs('em', store_default=False)] = None + end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n' + end_command: Annotated[tuple[str, dict] | None, + IOAttrs('ec', store_default=False)] = None diff --git a/dist/ba_data/python/bacommon/cloud.py b/dist/ba_data/python/bacommon/cloud.py index 0e7b7ce..197a1de 100644 --- a/dist/ba_data/python/bacommon/cloud.py +++ b/dist/ba_data/python/bacommon/cloud.py @@ -3,12 +3,13 @@ """Functionality related to cloud functionality.""" from __future__ import annotations -from dataclasses import dataclass -from typing import TYPE_CHECKING, Annotated, Optional +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Annotated from enum import Enum from efro.message import Message, Response from efro.dataclassio import ioprepped, IOAttrs +from bacommon.transfer import DirectoryManifest if TYPE_CHECKING: pass @@ -65,7 +66,7 @@ class LoginProxyStateQueryResponse(Response): state: Annotated[State, IOAttrs('s')] # On success, these will be filled out. - credentials: Annotated[Optional[str], IOAttrs('tk')] + credentials: Annotated[str | None, IOAttrs('tk')] @ioprepped @@ -77,27 +78,57 @@ class LoginProxyCompleteMessage(Message): @ioprepped @dataclass -class AccountSessionReleaseMessage(Message): - """We're done using this particular session.""" - token: Annotated[str, IOAttrs('tk')] - - -@ioprepped -@dataclass -class CredentialsCheckMessage(Message): - """Are our current credentials valid?""" +class TestMessage(Message): + """Can I get some of that workspace action?""" + testfoo: Annotated[int, IOAttrs('f')] @classmethod def get_response_types(cls) -> list[type[Response]]: - return [CredentialsCheckResponse] + return [TestResponse] @ioprepped @dataclass -class CredentialsCheckResponse(Response): - """Info returned when checking credentials.""" +class TestResponse(Response): + """Here's that workspace you asked for, boss.""" - verified: Annotated[bool, IOAttrs('v')] + testfoo: Annotated[int, IOAttrs('f')] - # Current account tag (good time to check if it has changed). - tag: Annotated[str, IOAttrs('t')] + +@ioprepped +@dataclass +class WorkspaceFetchState: + """Common state data for a workspace fetch.""" + manifest: Annotated[DirectoryManifest, IOAttrs('m')] + iteration: Annotated[int, IOAttrs('i')] = 0 + total_deletes: Annotated[int, IOAttrs('tdels')] = 0 + total_downloads: Annotated[int, IOAttrs('tdlds')] = 0 + total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None + + +@ioprepped +@dataclass +class WorkspaceFetchMessage(Message): + """Can I get some of that workspace action?""" + workspaceid: Annotated[str, IOAttrs('w')] + state: Annotated[WorkspaceFetchState, IOAttrs('s')] + + @classmethod + def get_response_types(cls) -> list[type[Response]]: + return [WorkspaceFetchResponse] + + +@ioprepped +@dataclass +class WorkspaceFetchResponse(Response): + """Here's that workspace you asked for, boss.""" + + state: Annotated[WorkspaceFetchState, IOAttrs('s')] + deletes: Annotated[list[str], + IOAttrs('dlt', store_default=False)] = field( + default_factory=list) + downloads_inline: Annotated[dict[str, bytes], + IOAttrs('dinl', store_default=False)] = field( + default_factory=dict) + + done: Annotated[bool, IOAttrs('d')] = False diff --git a/dist/ba_data/python/bacommon/net.py b/dist/ba_data/python/bacommon/net.py index 5c48443..3409ba6 100644 --- a/dist/ba_data/python/bacommon/net.py +++ b/dist/ba_data/python/bacommon/net.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Any, Annotated +from typing import TYPE_CHECKING, Any, Annotated from dataclasses import dataclass, field from efro.dataclassio import ioprepped, IOAttrs @@ -28,7 +28,7 @@ class ServerNodeQueryResponse: """A response to a query about server-nodes.""" # If present, something went wrong, and this describes it. - error: Annotated[Optional[str], IOAttrs('e', store_default=False)] = None + error: Annotated[str | None, IOAttrs('e', store_default=False)] = None # The set of servernodes. servers: Annotated[list[ServerNodeEntry], @@ -40,11 +40,11 @@ class ServerNodeQueryResponse: @dataclass class PrivateHostingState: """Combined state of whether we're hosting, whether we can, etc.""" - unavailable_error: Optional[str] = None - party_code: Optional[str] = None + unavailable_error: str | None = None + party_code: str | None = None tickets_to_host_now: int = 0 - minutes_until_free_host: Optional[float] = None - free_host_minutes_remaining: Optional[float] = None + minutes_until_free_host: float | None = None + free_host_minutes_remaining: float | None = None @ioprepped @@ -55,10 +55,10 @@ class PrivateHostingConfig: playlist_name: str = 'Unknown' randomize: bool = False tutorial: bool = False - custom_team_names: Optional[tuple[str, str]] = None - custom_team_colors: Optional[tuple[tuple[float, float, float], - tuple[float, float, float]]] = None - playlist: Optional[list[dict[str, Any]]] = None + custom_team_names: tuple[str, str] | None = None + custom_team_colors: tuple[tuple[float, float, float], + tuple[float, float, float]] | None = None + playlist: list[dict[str, Any]] | None = None exit_minutes: float = 120.0 exit_minutes_unclean: float = 180.0 exit_minutes_idle: float = 10.0 @@ -68,7 +68,7 @@ class PrivateHostingConfig: @dataclass class PrivatePartyConnectResult: """Info about a server we get back when connecting.""" - error: Optional[str] = None - addr: Optional[str] = None - port: Optional[int] = None - password: Optional[str] = None + error: str | None = None + addr: str | None = None + port: int | None = None + password: str | None = None diff --git a/dist/ba_data/python/bacommon/servermanager.py b/dist/ba_data/python/bacommon/servermanager.py index 5516dbb..8dd5b1d 100644 --- a/dist/ba_data/python/bacommon/servermanager.py +++ b/dist/ba_data/python/bacommon/servermanager.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import Enum from dataclasses import field, dataclass -from typing import TYPE_CHECKING, Optional, Any +from typing import TYPE_CHECKING, Any from efro.dataclassio import ioprepped @@ -60,11 +60,11 @@ class ServerConfig: # playlist editor in the regular version of the game. # This will give you a numeric code you can enter here to host that # playlist. - playlist_code: Optional[int] = None + playlist_code: int | None = None # Alternately, you can embed playlist data here instead of using codes. # Make sure to set session_type to the correct type for the data here. - playlist_inline: Optional[list[dict[str, Any]]] = None + playlist_inline: list[dict[str, Any]] | None = None # Whether to shuffle the playlist or play its games in designated order. playlist_shuffle: bool = True @@ -105,7 +105,7 @@ class ServerConfig: # currently-signed-in account's id. To fetch info about an account, # your back-end server can use the following url: # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE - stats_url: Optional[str] = None + stats_url: str | None = None # If present, the server subprocess will attempt to gracefully exit after # this amount of time. A graceful exit can occur at the end of a series @@ -113,32 +113,32 @@ class ServerConfig: # default) will then spin up a fresh subprocess. This mechanism can be # useful to clear out any memory leaks or other accumulated bad state # in the server subprocess. - clean_exit_minutes: Optional[float] = None + clean_exit_minutes: float | None = None # If present, the server subprocess will shut down immediately after this # amount of time. This can be useful as a fallback for clean_exit_time. # The server manager will then spin up a fresh server subprocess if # auto-restart is enabled (the default). - unclean_exit_minutes: Optional[float] = None + unclean_exit_minutes: float | None = None # If present, the server subprocess will shut down immediately if this # amount of time passes with no activity from any players. The server # manager will then spin up a fresh server subprocess if auto-restart is # enabled (the default). - idle_exit_minutes: Optional[float] = None + idle_exit_minutes: float | None = None # Should the tutorial be shown at the beginning of games? show_tutorial: bool = False # Team names (teams mode only). - team_names: Optional[tuple[str, str]] = None + team_names: tuple[str, str] | None = None # Team colors (teams mode only). - team_colors: Optional[tuple[tuple[float, float, float], - tuple[float, float, float]]] = None + team_colors: tuple[tuple[float, float, float], tuple[float, float, + float]] | None = None # (internal) stress-testing mode. - stress_test_players: Optional[int] = None + stress_test_players: int | None = None # NOTE: as much as possible, communication from the server-manager to the @@ -171,15 +171,15 @@ class ShutdownCommand(ServerCommand): class ChatMessageCommand(ServerCommand): """Chat message from the server.""" message: str - clients: Optional[list[int]] + clients: list[int] | None @dataclass class ScreenMessageCommand(ServerCommand): """Screen-message from the server.""" message: str - color: Optional[tuple[float, float, float]] - clients: Optional[list[int]] + color: tuple[float, float, float] | None + clients: list[int] | None @dataclass @@ -191,4 +191,4 @@ class ClientListCommand(ServerCommand): class KickCommand(ServerCommand): """Kick a client.""" client_id: int - ban_time: Optional[int] + ban_time: int | None diff --git a/dist/ba_data/python/bacommon/transfer.py b/dist/ba_data/python/bacommon/transfer.py new file mode 100644 index 0000000..03ff6bd --- /dev/null +++ b/dist/ba_data/python/bacommon/transfer.py @@ -0,0 +1,80 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality related to transferring files/data.""" + +from __future__ import annotations + +import os +from dataclasses import dataclass +from typing import TYPE_CHECKING, Annotated + +from efro.dataclassio import ioprepped, IOAttrs + +if TYPE_CHECKING: + from pathlib import Path + + +@ioprepped +@dataclass +class DirectoryManifestFile: + """Describes metadata and hashes for a file in a manifest.""" + filehash: Annotated[str, IOAttrs('h')] + filesize: Annotated[int, IOAttrs('s')] + + +@ioprepped +@dataclass +class DirectoryManifest: + """Contains a summary of files in a directory.""" + files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')] + + _empty_hash: str | None = None + + @classmethod + def create_from_disk(cls, path: Path) -> DirectoryManifest: + """Create a manifest from a directory on disk.""" + import hashlib + from concurrent.futures import ThreadPoolExecutor + + pathstr = str(path) + paths: list[str] = [] + + if path.is_dir(): + # Build the full list of package-relative paths. + for basename, _dirnames, filenames in os.walk(path): + for filename in filenames: + fullname = os.path.join(basename, filename) + assert fullname.startswith(pathstr) + paths.append(fullname[len(pathstr) + 1:]) + elif path.exists(): + # Just return a single file entry if path is not a dir. + paths.append(pathstr) + + def _get_file_info(filepath: str) -> tuple[str, DirectoryManifestFile]: + sha = hashlib.sha256() + fullfilepath = os.path.join(pathstr, filepath) + if not os.path.isfile(fullfilepath): + raise Exception(f'File not found: "{fullfilepath}"') + with open(fullfilepath, 'rb') as infile: + filebytes = infile.read() + filesize = len(filebytes) + sha.update(filebytes) + return (filepath, + DirectoryManifestFile(filehash=sha.hexdigest(), + filesize=filesize)) + + # Now use all procs to hash the files efficiently. + cpus = os.cpu_count() + if cpus is None: + cpus = 4 + with ThreadPoolExecutor(max_workers=cpus) as executor: + return cls(files=dict(executor.map(_get_file_info, paths))) + + @classmethod + def get_empty_hash(cls) -> str: + """Return the hash for an empty file.""" + if cls._empty_hash is None: + import hashlib + sha = hashlib.sha256() + cls._empty_hash = sha.hexdigest() + return cls._empty_hash diff --git a/dist/ba_data/python/bastd/__init__.py b/dist/ba_data/python/bastd/__init__.py index 7a4ff44..b77fdd2 100644 --- a/dist/ba_data/python/bastd/__init__.py +++ b/dist/ba_data/python/bastd/__init__.py @@ -2,4 +2,4 @@ # """Ballistica standard library: games, UI, etc.""" -# ba_meta require api 6 +# ba_meta require api 7 diff --git a/dist/ba_data/python/bastd/activity/coopjoin.py b/dist/ba_data/python/bastd/activity/coopjoin.py index 9c44baa..1d1db17 100644 --- a/dist/ba_data/python/bastd/activity/coopjoin.py +++ b/dist/ba_data/python/bastd/activity/coopjoin.py @@ -11,7 +11,7 @@ import ba from ba.internal import JoinActivity if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class CoopJoinActivity(JoinActivity): @@ -54,7 +54,7 @@ class CoopJoinActivity(JoinActivity): ControlsGuide(delay=1.0).autoretain() def _on_got_scores_to_beat(self, - scores: Optional[list[dict[str, Any]]]) -> None: + scores: list[dict[str, Any]] | None) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements from efro.util import asserttype @@ -87,7 +87,7 @@ class CoopJoinActivity(JoinActivity): delay_inc = 0.1 def _add_t( - text: Union[str, ba.Lstr], + text: str | ba.Lstr, h_offs: float = 0.0, scale: float = 1.0, color: Sequence[float] = (1.0, 1.0, 1.0, 0.46) diff --git a/dist/ba_data/python/bastd/activity/coopscore.py b/dist/ba_data/python/bastd/activity/coopscore.py index f578390..dc7c06c 100644 --- a/dist/ba_data/python/bastd/activity/coopscore.py +++ b/dist/ba_data/python/bastd/activity/coopscore.py @@ -14,7 +14,7 @@ from bastd.actor.text import Text from bastd.actor.zoomtext import ZoomText if TYPE_CHECKING: - from typing import Optional, Any, Sequence + from typing import Any, Sequence from bastd.ui.store.button import StoreButton from bastd.ui.league.rankbutton import LeagueRankButton @@ -56,9 +56,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if _ba.get_v1_account_state() == 'signed_in' else None) - self._game_service_icon_color: Optional[Sequence[float]] - self._game_service_achievements_texture: Optional[ba.Texture] - self._game_service_leaderboards_texture: Optional[ba.Texture] + self._game_service_icon_color: Sequence[float] | None + self._game_service_achievements_texture: ba.Texture | None + self._game_service_leaderboards_texture: ba.Texture | None with ba.Context('ui'): if self._account_type == 'Game Center': @@ -89,53 +89,53 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._cashregistersound = ba.getsound('cashRegister') self._gun_cocking_sound = ba.getsound('gunCocking') self._dingsound = ba.getsound('ding') - self._score_link: Optional[str] = None - self._root_ui: Optional[ba.Widget] = None - self._background: Optional[ba.Actor] = None + self._score_link: str | None = None + self._root_ui: ba.Widget | None = None + self._background: ba.Actor | None = None self._old_best_rank = 0.0 - self._game_name_str: Optional[str] = None - self._game_config_str: Optional[str] = None + self._game_name_str: str | None = None + self._game_config_str: str | None = None # Ui bits. - self._corner_button_offs: Optional[tuple[float, float]] = None - self._league_rank_button: Optional[LeagueRankButton] = None - self._store_button_instance: Optional[StoreButton] = None - self._restart_button: Optional[ba.Widget] = None - self._update_corner_button_positions_timer: Optional[ba.Timer] = None - self._next_level_error: Optional[ba.Actor] = None + self._corner_button_offs: tuple[float, float] | None = None + self._league_rank_button: LeagueRankButton | None = None + self._store_button_instance: StoreButton | None = None + self._restart_button: ba.Widget | None = None + self._update_corner_button_positions_timer: ba.Timer | None = None + self._next_level_error: ba.Actor | None = None # Score/gameplay bits. - self._was_complete: Optional[bool] = None - self._is_complete: Optional[bool] = None - self._newly_complete: Optional[bool] = None - self._is_more_levels: Optional[bool] = None - self._next_level_name: Optional[str] = None - self._show_friend_scores: Optional[bool] = None - self._show_info: Optional[dict[str, Any]] = None - self._name_str: Optional[str] = None - self._friends_loading_status: Optional[ba.Actor] = None - self._score_loading_status: Optional[ba.Actor] = None - self._tournament_time_remaining: Optional[float] = None - self._tournament_time_remaining_text: Optional[Text] = None - self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None + self._was_complete: bool | None = None + self._is_complete: bool | None = None + self._newly_complete: bool | None = None + self._is_more_levels: bool | None = None + self._next_level_name: str | None = None + self._show_friend_scores: bool | None = None + self._show_info: dict[str, Any] | None = None + self._name_str: str | None = None + self._friends_loading_status: ba.Actor | None = None + self._score_loading_status: ba.Actor | None = None + self._tournament_time_remaining: float | None = None + self._tournament_time_remaining_text: Text | None = None + self._tournament_time_remaining_text_timer: ba.Timer | None = None # Stuff for activity skip by pressing button self._birth_time = ba.time() self._min_view_time = 5.0 self._allow_server_transition = False - self._server_transitioning: Optional[bool] = None + self._server_transitioning: bool | None = None self._playerinfos: list[ba.PlayerInfo] = settings['playerinfos'] assert isinstance(self._playerinfos, list) assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos) - self._score: Optional[int] = settings['score'] + self._score: int | None = settings['score'] assert isinstance(self._score, (int, type(None))) - self._fail_message: Optional[ba.Lstr] = settings['fail_message'] + self._fail_message: ba.Lstr | None = settings['fail_message'] assert isinstance(self._fail_message, (ba.Lstr, type(None))) - self._begin_time: Optional[float] = None + self._begin_time: float | None = None self._score_order: str if 'score_order' in settings: @@ -410,7 +410,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): texture=self._replay_icon_texture, opacity=0.8) - next_button: Optional[ba.Widget] = None + next_button: ba.Widget | None = None # Our 'next' button is disabled if we haven't unlocked the next # level yet and invisible if there is none. @@ -702,7 +702,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): str(len(self._playerinfos)) + ' Player', []) if self._score is not None: - our_score: Optional[list] = [ + our_score: list | None = [ self._score, { 'players': [{ 'name': p.name, @@ -931,7 +931,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): 'loop': False })).autoretain() - def _got_friend_score_results(self, results: Optional[list[Any]]) -> None: + def _got_friend_score_results(self, results: list[Any] | None) -> None: # FIXME: tidy this up # pylint: disable=too-many-locals @@ -1046,7 +1046,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): transition=Text.Transition.IN_RIGHT, transition_delay=tdelay2).autoretain() - def _got_score_results(self, results: Optional[dict[str, Any]]) -> None: + def _got_score_results(self, results: dict[str, Any] | None) -> None: # FIXME: tidy this up # pylint: disable=too-many-locals diff --git a/dist/ba_data/python/bastd/activity/freeforallvictory.py b/dist/ba_data/python/bastd/activity/freeforallvictory.py index 0aab714..5161c2b 100644 --- a/dist/ba_data/python/bastd/activity/freeforallvictory.py +++ b/dist/ba_data/python/bastd/activity/freeforallvictory.py @@ -10,7 +10,7 @@ import ba from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -236,7 +236,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transtime2: ts_h_offs + (xval - slide_amt) * scale })) - def _safesetattr(node: Optional[ba.Node], attr: str, + def _safesetattr(node: ba.Node | None, attr: str, value: Any) -> None: if node: setattr(node, attr, value) @@ -259,7 +259,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): self._score_display_sound_small)) v_offs -= spacing - def _safe_animate(self, node: Optional[ba.Node], attr: str, + def _safe_animate(self, node: ba.Node | None, attr: str, keys: dict[float, float]) -> None: """Run an animation on a node if the node still exists.""" if node: diff --git a/dist/ba_data/python/bastd/activity/multiteamjoin.py b/dist/ba_data/python/bastd/activity/multiteamjoin.py index a1a2586..92e75f8 100644 --- a/dist/ba_data/python/bastd/activity/multiteamjoin.py +++ b/dist/ba_data/python/bastd/activity/multiteamjoin.py @@ -11,7 +11,7 @@ from ba.internal import JoinActivity from bastd.actor.text import Text if TYPE_CHECKING: - from typing import Optional + pass class MultiTeamJoinActivity(JoinActivity): @@ -19,7 +19,7 @@ class MultiTeamJoinActivity(JoinActivity): def __init__(self, settings: dict): super().__init__(settings) - self._next_up_text: Optional[Text] = None + self._next_up_text: Text | None = None def on_transition_in(self) -> None: from bastd.actor.controlsguide import ControlsGuide diff --git a/dist/ba_data/python/bastd/activity/multiteamscore.py b/dist/ba_data/python/bastd/activity/multiteamscore.py index 077141a..3eac9b7 100644 --- a/dist/ba_data/python/bastd/activity/multiteamscore.py +++ b/dist/ba_data/python/bastd/activity/multiteamscore.py @@ -11,7 +11,7 @@ from bastd.actor.text import Text from bastd.actor.image import Image if TYPE_CHECKING: - from typing import Optional, Union + pass class MultiTeamScoreScreenActivity(ScoreScreenActivity): @@ -52,7 +52,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): def show_player_scores(self, delay: float = 2.5, - results: Optional[ba.GameResults] = None, + results: ba.GameResults | None = None, scale: float = 1.0, x_offset: float = 0.0, y_offset: float = 0.0) -> None: @@ -67,7 +67,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): is_free_for_all = isinstance(self.session, ba.FreeForAllSession) - def _get_prec_score(p_rec: ba.PlayerRecord) -> Optional[int]: + def _get_prec_score(p_rec: ba.PlayerRecord) -> int | None: if is_free_for_all and results is not None: assert isinstance(results, ba.GameResults) assert p_rec.team.activityteam is not None @@ -75,7 +75,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): return val return p_rec.accumscore - def _get_prec_score_str(p_rec: ba.PlayerRecord) -> Union[str, ba.Lstr]: + def _get_prec_score_str(p_rec: ba.PlayerRecord) -> str | ba.Lstr: if is_free_for_all and results is not None: assert isinstance(results, ba.GameResults) assert p_rec.team.activityteam is not None @@ -96,7 +96,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): # noinspection PyUnresolvedReferences def _get_player_score_set_entry( - player: ba.SessionPlayer) -> Optional[ba.PlayerRecord]: + player: ba.SessionPlayer) -> ba.PlayerRecord | None: for p_rec in valid_players: if p_rec[1].player is player: return p_rec[1] @@ -129,7 +129,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): text: ba.Lstr, h_align: Text.HAlign = Text.HAlign.RIGHT, extrascale: float = 1.0, - maxwidth: Optional[float] = 120.0) -> None: + maxwidth: float | None = 120.0) -> None: Text(text, color=(0.5, 0.5, 0.6, 0.5), position=(ts_h_offs + xoffs * scale, @@ -169,7 +169,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): topkillcount = max(topkillcount, prec.accum_kill_count) topkilledcount = min(topkilledcount, prec.accum_killed_count) - def _scoretxt(text: Union[str, ba.Lstr], + def _scoretxt(text: str | ba.Lstr, x_offs: float, highlight: bool, delay2: float, diff --git a/dist/ba_data/python/bastd/activity/multiteamvictory.py b/dist/ba_data/python/bastd/activity/multiteamvictory.py index 061ad68..8c4a60c 100644 --- a/dist/ba_data/python/bastd/activity/multiteamvictory.py +++ b/dist/ba_data/python/bastd/activity/multiteamvictory.py @@ -10,7 +10,7 @@ import ba from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity if TYPE_CHECKING: - from typing import Optional + pass class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -146,8 +146,8 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): else: v_extra = 0 - mvp: Optional[ba.PlayerRecord] = None - mvp_name: Optional[str] = None + mvp: ba.PlayerRecord | None = None + mvp_name: str | None = None # Show game MVP. if not self._is_ffa: diff --git a/dist/ba_data/python/bastd/actor/bomb.py b/dist/ba_data/python/bastd/actor/bomb.py index 7ab074b..96e5aa7 100644 --- a/dist/ba_data/python/bastd/actor/bomb.py +++ b/dist/ba_data/python/bastd/actor/bomb.py @@ -14,7 +14,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Callable + from typing import Any, Sequence, Callable # pylint: disable=invalid-name PlayerType = TypeVar('PlayerType', bound='ba.Player') @@ -678,7 +678,7 @@ class Bomb(ba.Actor): self._exploded = False self.scale = bomb_scale - self.texture_sequence: Optional[ba.Node] = None + self.texture_sequence: ba.Node | None = None if self.bomb_type == 'sticky': self._last_sticky_sound_time = 0.0 @@ -846,8 +846,8 @@ class Bomb(ba.Actor): 0.26: self.scale }) - def get_source_player( - self, playertype: type[PlayerType]) -> Optional[PlayerType]: + def get_source_player(self, + playertype: type[PlayerType]) -> PlayerType | None: """Return the source-player if one exists and is the provided type.""" player: Any = self._source_player return (player if isinstance(player, playertype) and player.exists() @@ -1071,7 +1071,7 @@ class TNTSpawner: def __init__(self, position: Sequence[float], respawn_time: float = 20.0): """Instantiate with given position and respawn_time (in seconds).""" self._position = position - self._tnt: Optional[Bomb] = None + self._tnt: Bomb | None = None self._respawn_time = random.uniform(0.8, 1.2) * respawn_time self._wait_time = 0.0 self._update() diff --git a/dist/ba_data/python/bastd/actor/controlsguide.py b/dist/ba_data/python/bastd/actor/controlsguide.py index 70279a9..827c260 100644 --- a/dist/ba_data/python/bastd/actor/controlsguide.py +++ b/dist/ba_data/python/bastd/actor/controlsguide.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class ControlsGuide(ba.Actor): @@ -52,13 +52,13 @@ class ControlsGuide(ba.Actor): self._lifespan = lifespan self._dead = False self._bright = bright - self._cancel_timer: Optional[ba.Timer] = None - self._fade_in_timer: Optional[ba.Timer] = None - self._update_timer: Optional[ba.Timer] = None - self._title_text: Optional[ba.Node] + self._cancel_timer: ba.Timer | None = None + self._fade_in_timer: ba.Timer | None = None + self._update_timer: ba.Timer | None = None + self._title_text: ba.Node | None clr: Sequence[float] - extra_pos_1: Optional[tuple[float, float]] - extra_pos_2: Optional[tuple[float, float]] + extra_pos_1: tuple[float, float] | None + extra_pos_2: tuple[float, float] | None if ba.app.iircade_mode: xtweak = 0.2 ytweak = 0.2 @@ -238,7 +238,7 @@ class ControlsGuide(ba.Actor): }) if extra_pos_1 is not None: - self._extra_image_1: Optional[ba.Node] = ba.newnode( + self._extra_image_1: ba.Node | None = ba.newnode( 'image', attrs={ 'texture': ba.gettexture('nub'), @@ -252,7 +252,7 @@ class ControlsGuide(ba.Actor): else: self._extra_image_1 = None if extra_pos_2 is not None: - self._extra_image_2: Optional[ba.Node] = ba.newnode( + self._extra_image_2: ba.Node | None = ba.newnode( 'image', attrs={ 'texture': ba.gettexture('nub'), @@ -317,8 +317,9 @@ class ControlsGuide(ba.Actor): # an input device that is *not* the touchscreen. # (otherwise it is confusing to see the touchscreen buttons right # next to our display buttons) - touchscreen: Optional[ba.InputDevice] = _ba.getinputdevice( - 'TouchScreen', '#1', doraise=False) + touchscreen: ba.InputDevice | None = _ba.getinputdevice('TouchScreen', + '#1', + doraise=False) if touchscreen is not None: # We look at the session's players; not the activity's. @@ -477,7 +478,7 @@ class ControlsGuide(ba.Actor): pickup_button_names.clear() self._run_text.text = run_text - w_text: Union[ba.Lstr, str] + w_text: ba.Lstr | str if only_remote and self._lifespan is None: w_text = ba.Lstr(resource='fireTVRemoteWarningText', subs=[('${REMOTE_APP_NAME}', diff --git a/dist/ba_data/python/bastd/actor/flag.py b/dist/ba_data/python/bastd/actor/flag.py index 6fae9d4..ff5d210 100644 --- a/dist/ba_data/python/bastd/actor/flag.py +++ b/dist/ba_data/python/bastd/actor/flag.py @@ -11,7 +11,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional + from typing import Any, Sequence class FlagFactory: @@ -185,7 +185,7 @@ class Flag(ba.Actor): super().__init__() - self._initial_position: Optional[Sequence[float]] = None + self._initial_position: Sequence[float] | None = None self._has_moved = False shared = SharedObjects.get() factory = FlagFactory.get() @@ -214,7 +214,7 @@ class Flag(ba.Actor): if dropped_timeout is not None: dropped_timeout = int(dropped_timeout) self._dropped_timeout = dropped_timeout - self._counter: Optional[ba.Node] + self._counter: ba.Node | None if self._dropped_timeout is not None: self._count = self._dropped_timeout self._tick_timer = ba.Timer(1.0, @@ -234,8 +234,8 @@ class Flag(ba.Actor): self._counter = None self._held_count = 0 - self._score_text: Optional[ba.Node] = None - self._score_text_hide_timer: Optional[ba.Timer] = None + self._score_text: ba.Node | None = None + self._score_text_hide_timer: ba.Timer | None = None def _tick(self) -> None: if self.node: diff --git a/dist/ba_data/python/bastd/actor/image.py b/dist/ba_data/python/bastd/actor/image.py index 915ea44..383926f 100644 --- a/dist/ba_data/python/bastd/actor/image.py +++ b/dist/ba_data/python/bastd/actor/image.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Sequence, Union, Optional + from typing import Any, Sequence class Image(ba.Actor): @@ -33,9 +33,9 @@ class Image(ba.Actor): BOTTOM_CENTER = 'bottomCenter' def __init__(self, - texture: Union[ba.Texture, dict[str, Any]], + texture: ba.Texture | dict[str, Any], position: tuple[float, float] = (0, 0), - transition: Optional[Transition] = None, + transition: Transition | None = None, transition_delay: float = 0.0, attach: Attach = Attach.CENTER, color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), @@ -53,7 +53,7 @@ class Image(ba.Actor): # If they provided a dict as texture, assume its an icon. # otherwise its just a texture value itself. - mask_texture: Optional[ba.Texture] + mask_texture: ba.Texture | None if isinstance(texture, dict): tint_color = texture['tint_color'] tint2_color = texture['tint2_color'] diff --git a/dist/ba_data/python/bastd/actor/onscreencountdown.py b/dist/ba_data/python/bastd/actor/onscreencountdown.py index a686cdf..309ccb7 100644 --- a/dist/ba_data/python/bastd/actor/onscreencountdown.py +++ b/dist/ba_data/python/bastd/actor/onscreencountdown.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class OnScreenCountdown(ba.Actor): @@ -57,7 +57,7 @@ class OnScreenCountdown(ba.Actor): 2: ba.getsound('announceTwo'), 1: ba.getsound('announceOne') } - self._timer: Optional[ba.Timer] = None + self._timer: ba.Timer | None = None def start(self) -> None: """Start the timer.""" diff --git a/dist/ba_data/python/bastd/actor/onscreentimer.py b/dist/ba_data/python/bastd/actor/onscreentimer.py index c58e87d..bf5e8f1 100644 --- a/dist/ba_data/python/bastd/actor/onscreentimer.py +++ b/dist/ba_data/python/bastd/actor/onscreentimer.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, overload import ba if TYPE_CHECKING: - from typing import Optional, Union, Any, Literal + from typing import Any, Literal class OnScreenTimer(ba.Actor): @@ -21,7 +21,7 @@ class OnScreenTimer(ba.Actor): def __init__(self) -> None: super().__init__() - self._starttime_ms: Optional[int] = None + self._starttime_ms: int | None = None self.node = ba.newnode('text', attrs={ 'v_attach': 'top', @@ -55,7 +55,7 @@ class OnScreenTimer(ba.Actor): return self._starttime_ms is not None def stop(self, - endtime: Union[int, float] = None, + endtime: int | float | None = None, timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None: """End the timer. @@ -96,9 +96,8 @@ class OnScreenTimer(ba.Actor): ... def getstarttime( - self, - timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS - ) -> Union[int, float]: + self, + timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> int | float: """Return the sim-time when start() was called. Time will be returned in seconds if timeformat is SECONDS or diff --git a/dist/ba_data/python/bastd/actor/playerspaz.py b/dist/ba_data/python/bastd/actor/playerspaz.py index 1f3f1b1..4687b57 100644 --- a/dist/ba_data/python/bastd/actor/playerspaz.py +++ b/dist/ba_data/python/bastd/actor/playerspaz.py @@ -10,7 +10,7 @@ import ba from bastd.actor.spaz import Spaz from spazmod import modifyspaz if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Literal + from typing import Any, Sequence, Literal # pylint: disable=invalid-name PlayerType = TypeVar('PlayerType', bound=ba.Player) @@ -64,14 +64,13 @@ class PlayerSpaz(Spaz): source_player=player, start_invincible=True, powerups_expire=powerups_expire) - self.last_player_attacked_by: Optional[ba.Player] = None + self.last_player_attacked_by: ba.Player | None = None self.last_attacked_time = 0.0 - self.last_attacked_type: Optional[tuple[str, str]] = None + self.last_attacked_type: tuple[str, str] | None = None self.held_count = 0 - self.last_player_held_by: Optional[ba.Player] = None + self.last_player_held_by: ba.Player | None = None self._player = player self._drive_player_position() - import custom_hooks custom_hooks.playerspaz_init(self, self.node, self._player) @@ -80,7 +79,7 @@ class PlayerSpaz(Spaz): @overload def getplayer(self, playertype: type[PlayerType], - doraise: Literal[False] = False) -> Optional[PlayerType]: + doraise: Literal[False] = False) -> PlayerType | None: ... @overload @@ -90,7 +89,7 @@ class PlayerSpaz(Spaz): def getplayer(self, playertype: type[PlayerType], - doraise: bool = False) -> Optional[PlayerType]: + doraise: bool = False) -> PlayerType | None: """Get the ba.Player associated with this Spaz. By default this will return None if the Player no longer exists. diff --git a/dist/ba_data/python/bastd/actor/popuptext.py b/dist/ba_data/python/bastd/actor/popuptext.py index 5ce8af9..d078e7e 100644 --- a/dist/ba_data/python/bastd/actor/popuptext.py +++ b/dist/ba_data/python/bastd/actor/popuptext.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Sequence + from typing import Any, Sequence class PopupText(ba.Actor): @@ -20,7 +20,7 @@ class PopupText(ba.Actor): """ def __init__(self, - text: Union[str, ba.Lstr], + text: str | ba.Lstr, position: Sequence[float] = (0.0, 0.0, 0.0), color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), random_offset: float = 0.5, diff --git a/dist/ba_data/python/bastd/actor/powerupbox.py b/dist/ba_data/python/bastd/actor/powerupbox.py index cfd51e1..fe01304 100644 --- a/dist/ba_data/python/bastd/actor/powerupbox.py +++ b/dist/ba_data/python/bastd/actor/powerupbox.py @@ -11,7 +11,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional, Sequence + from typing import Any, Sequence DEFAULT_POWERUP_INTERVAL = 8.0 @@ -88,7 +88,7 @@ class PowerupBoxFactory: """ from ba.internal import get_default_powerup_distribution shared = SharedObjects.get() - self._lastpoweruptype: Optional[str] = None + self._lastpoweruptype: str | None = None self.model = ba.getmodel('powerup') self.model_simple = ba.getmodel('powerupSimple') self.tex_bomb = ba.gettexture('powerupBomb') diff --git a/dist/ba_data/python/bastd/actor/respawnicon.py b/dist/ba_data/python/bastd/actor/respawnicon.py index 66c8bf1..66a5cbc 100644 --- a/dist/ba_data/python/bastd/actor/respawnicon.py +++ b/dist/ba_data/python/bastd/actor/respawnicon.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional + pass class RespawnIcon: @@ -49,7 +49,7 @@ class RespawnIcon: texture = icon['texture'] h_offs = -10 ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs) - self._image: Optional[ba.NodeActor] = ba.NodeActor( + self._image: ba.NodeActor | None = ba.NodeActor( ba.newnode('image', attrs={ 'texture': texture, @@ -68,7 +68,7 @@ class RespawnIcon: ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7}) npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs) - self._name: Optional[ba.NodeActor] = ba.NodeActor( + self._name: ba.NodeActor | None = ba.NodeActor( ba.newnode('text', attrs={ 'v_attach': 'top', @@ -88,7 +88,7 @@ class RespawnIcon: ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs) - self._text: Optional[ba.NodeActor] = ba.NodeActor( + self._text: ba.NodeActor | None = ba.NodeActor( ba.newnode('text', attrs={ 'position': tpos, @@ -107,9 +107,9 @@ class RespawnIcon: self._respawn_time = ba.time() + respawn_time self._update() - self._timer: Optional[ba.Timer] = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True) + self._timer: ba.Timer | None = ba.Timer(1.0, + ba.WeakCall(self._update), + repeat=True) @property def visible(self) -> bool: diff --git a/dist/ba_data/python/bastd/actor/scoreboard.py b/dist/ba_data/python/bastd/actor/scoreboard.py index 8432946..16b996e 100644 --- a/dist/ba_data/python/bastd/actor/scoreboard.py +++ b/dist/ba_data/python/bastd/actor/scoreboard.py @@ -10,13 +10,13 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class _Entry: def __init__(self, scoreboard: Scoreboard, team: ba.Team, do_cover: bool, - scale: float, label: Optional[ba.Lstr], flash_length: float): + scale: float, label: ba.Lstr | None, flash_length: float): # pylint: disable=too-many-statements self._scoreboard = weakref.ref(scoreboard) self._do_cover = do_cover @@ -29,11 +29,11 @@ class _Entry: self._bar_tex = self._backing_tex = ba.gettexture('bar') self._cover_tex = ba.gettexture('uiAtlas') self._model = ba.getmodel('meterTransparent') - self._pos: Optional[Sequence[float]] = None - self._flash_timer: Optional[ba.Timer] = None - self._flash_counter: Optional[int] = None - self._flash_colors: Optional[bool] = None - self._score: Optional[float] = None + self._pos: Sequence[float] | None = None + self._flash_timer: ba.Timer | None = None + self._flash_counter: int | None = None + self._flash_colors: bool | None = None + self._score: float | None = None safe_team_color = ba.safecolor(team.color, target_intensity=1.0) @@ -126,7 +126,7 @@ class _Entry: clr = safe_team_color - team_name_label: Union[str, ba.Lstr] + team_name_label: str | ba.Lstr if label is not None: team_name_label = label else: @@ -207,7 +207,7 @@ class _Entry: def _set_flash_colors(self, flash: bool) -> None: self._flash_colors = flash - def _safesetcolor(node: Optional[ba.Node], val: Any) -> None: + def _safesetcolor(node: ba.Node | None, val: Any) -> None: if node: node.color = val diff --git a/dist/ba_data/python/bastd/actor/spaz.py b/dist/ba_data/python/bastd/actor/spaz.py index 46154a8..12e60b5 100644 --- a/dist/ba_data/python/bastd/actor/spaz.py +++ b/dist/ba_data/python/bastd/actor/spaz.py @@ -15,7 +15,7 @@ from bastd.actor.spazfactory import SpazFactory from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union, Callable + from typing import Any, Sequence, Callable POWERUP_WEAR_OFF_TIME = 20000 BASE_PUNCH_COOLDOWN = 400 @@ -57,7 +57,7 @@ class Spaz(ba.Actor): """The 'spaz' ba.Node.""" points_mult = 1 - curse_time: Optional[float] = 5.0 + curse_time: float | None = 5.0 default_bomb_count = 1 default_bomb_type = 'normal' default_boxing_gloves = False @@ -102,7 +102,7 @@ class Spaz(ba.Actor): self._hockey = False self._punched_nodes: set[ba.Node] = set() self._cursed = False - self._connected_to_player: Optional[ba.Player] = None + self._connected_to_player: ba.Player | None = None materials = [ factory.spaz_material, shared.object_material, shared.player_material @@ -155,11 +155,11 @@ class Spaz(ba.Actor): 'invincible': start_invincible, 'source_player': source_player }) - self.shield: Optional[ba.Node] = None + self.shield: ba.Node | None = None if start_invincible: - def _safesetattr(node: Optional[ba.Node], attr: str, + def _safesetattr(node: ba.Node | None, attr: str, val: Any) -> None: if node: setattr(node, attr, val) @@ -168,15 +168,15 @@ class Spaz(ba.Actor): False)) self.hitpoints = 1000 self.hitpoints_max = 1000 - self.shield_hitpoints: Optional[int] = None + self.shield_hitpoints: int | None = None self.shield_hitpoints_max = 650 self.shield_decay_rate = 0 - self.shield_decay_timer: Optional[ba.Timer] = None - self._boxing_gloves_wear_off_timer: Optional[ba.Timer] = None - self._boxing_gloves_wear_off_flash_timer: Optional[ba.Timer] = None - self._bomb_wear_off_timer: Optional[ba.Timer] = None - self._bomb_wear_off_flash_timer: Optional[ba.Timer] = None - self._multi_bomb_wear_off_timer: Optional[ba.Timer] = None + self.shield_decay_timer: ba.Timer | None = None + self._boxing_gloves_wear_off_timer: ba.Timer | None = None + self._boxing_gloves_wear_off_flash_timer: ba.Timer | None = None + self._bomb_wear_off_timer: ba.Timer | None = None + self._bomb_wear_off_flash_timer: ba.Timer | None = None + self._multi_bomb_wear_off_timer: ba.Timer | None = None self.bomb_count = self.default_bomb_count self._max_bomb_count = self.default_bomb_count self.bomb_type_default = self.default_bomb_type @@ -205,7 +205,7 @@ class Spaz(ba.Actor): self._turbo_filter_counts: dict[str, int] = {} self.frozen = False self.shattered = False - self._last_hit_time: Optional[int] = None + self._last_hit_time: int | None = None self._num_times_hit = 0 self._bomb_held = False if self.default_shields: @@ -213,13 +213,13 @@ class Spaz(ba.Actor): self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], Any]] = [] - self._score_text: Optional[ba.Node] = None - self._score_text_hide_timer: Optional[ba.Timer] = None - self._last_stand_pos: Optional[Sequence[float]] = None + self._score_text: ba.Node | None = None + self._score_text_hide_timer: ba.Timer | None = None + self._last_stand_pos: Sequence[float] | None = None # Deprecated stuff.. should make these into lists. - self.punch_callback: Optional[Callable[[Spaz], Any]] = None - self.pick_up_powerup_callback: Optional[Callable[[Spaz], Any]] = None + self.punch_callback: Callable[[Spaz], Any] | None = None + self.pick_up_powerup_callback: Callable[[Spaz], Any] | None = None def exists(self) -> bool: return bool(self.node) @@ -297,7 +297,7 @@ class Spaz(ba.Actor): self._turbo_filter_counts = {source: 1} def set_score_text(self, - text: Union[str, ba.Lstr], + text: str | ba.Lstr, color: Sequence[float] = (1.0, 1.0, 0.4), flash: bool = False) -> None: """ @@ -1208,7 +1208,7 @@ class Spaz(ba.Actor): return super().handlemessage(msg) return None - def drop_bomb(self) -> Optional[stdbomb.Bomb]: + def drop_bomb(self) -> stdbomb.Bomb | None: """ Tell the spaz to drop one of his bombs, and returns the resulting bomb object. diff --git a/dist/ba_data/python/bastd/actor/spazappearance.py b/dist/ba_data/python/bastd/actor/spazappearance.py index bc22940..bab2a8c 100644 --- a/dist/ba_data/python/bastd/actor/spazappearance.py +++ b/dist/ba_data/python/bastd/actor/spazappearance.py @@ -9,7 +9,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass def get_appearances(include_locked: bool = False) -> list[str]: @@ -111,8 +111,8 @@ class Appearance: self.pickup_sounds: list[str] = [] self.fall_sounds: list[str] = [] self.style = 'spaz' - self.default_color: Optional[tuple[float, float, float]] = None - self.default_highlight: Optional[tuple[float, float, float]] = None + self.default_color: tuple[float, float, float] | None = None + self.default_highlight: tuple[float, float, float] | None = None def register_appearances() -> None: diff --git a/dist/ba_data/python/bastd/actor/spazbot.py b/dist/ba_data/python/bastd/actor/spazbot.py index e9bb4bd..2204216 100644 --- a/dist/ba_data/python/bastd/actor/spazbot.py +++ b/dist/ba_data/python/bastd/actor/spazbot.py @@ -13,7 +13,7 @@ import ba from bastd.actor.spaz import Spaz if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Callable + from typing import Any, Sequence, Callable from bastd.actor.flag import Flag LITE_BOT_COLOR = (1.2, 0.9, 0.2) @@ -51,13 +51,13 @@ class SpazBotDiedMessage: spazbot: SpazBot """The SpazBot that was killed.""" - killerplayer: Optional[ba.Player] + killerplayer: ba.Player | None """The ba.Player that killed it (or None).""" how: ba.DeathType """The particular type of death.""" - def __init__(self, spazbot: SpazBot, killerplayer: Optional[ba.Player], + def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None, how: ba.DeathType): """Instantiate with given values.""" self.spazbot = spazbot @@ -115,17 +115,17 @@ class SpazBot(Spaz): # If you need to add custom behavior to a bot, set this to a callable # which takes one arg (the bot) and returns False if the bot's normal # update should be run and True if not. - self.update_callback: Optional[Callable[[SpazBot], Any]] = None + self.update_callback: Callable[[SpazBot], Any] | None = None activity = self.activity assert isinstance(activity, ba.GameActivity) self._map = weakref.ref(activity.map) - self.last_player_attacked_by: Optional[ba.Player] = None + self.last_player_attacked_by: ba.Player | None = None self.last_attacked_time = 0.0 - self.last_attacked_type: Optional[tuple[str, str]] = None - self.target_point_default: Optional[ba.Vec3] = None + self.last_attacked_type: tuple[str, str] | None = None + self.target_point_default: ba.Vec3 | None = None self.held_count = 0 - self.last_player_held_by: Optional[ba.Player] = None - self.target_flag: Optional[Flag] = None + self.last_player_held_by: ba.Player | None = None + self.target_flag: Flag | None = None self._charge_speed = 0.5 * (self.charge_speed_min + self.charge_speed_max) self._lead_amount = 0.5 @@ -135,9 +135,9 @@ class SpazBot(Spaz): self._running = False self._last_jump_time = 0.0 - self._throw_release_time: Optional[float] = None - self._have_dropped_throw_bomb: Optional[bool] = None - self._player_pts: Optional[list[tuple[ba.Vec3, ba.Vec3]]] = None + self._throw_release_time: float | None = None + self._have_dropped_throw_bomb: bool | None = None + self._player_pts: list[tuple[ba.Vec3, ba.Vec3]] | None = None # These cooldowns didn't exist when these bots were calibrated, # so take them out of the equation. @@ -156,17 +156,16 @@ class SpazBot(Spaz): assert mval is not None return mval - def _get_target_player_pt( - self) -> tuple[Optional[ba.Vec3], Optional[ba.Vec3]]: + def _get_target_player_pt(self) -> tuple[ba.Vec3 | None, ba.Vec3 | None]: """Returns the position and velocity of our target. Both values will be None in the case of no target. """ assert self.node botpt = ba.Vec3(self.node.position) - closest_dist: Optional[float] = None - closest_vel: Optional[ba.Vec3] = None - closest: Optional[ba.Vec3] = None + closest_dist: float | None = None + closest_vel: ba.Vec3 | None = None + closest: ba.Vec3 | None = None assert self._player_pts is not None for plpt, plvel in self._player_pts: dist = (plpt - botpt).length() @@ -206,8 +205,8 @@ class SpazBot(Spaz): our_pos = ba.Vec3(pos[0], 0, pos[2]) can_attack = True - target_pt_raw: Optional[ba.Vec3] - target_vel: Optional[ba.Vec3] + target_pt_raw: ba.Vec3 | None + target_vel: ba.Vec3 | None # If we're a flag-bearer, we're pretty simple-minded - just walk # towards the flag and try to pick it up. @@ -517,7 +516,7 @@ class SpazBot(Spaz): # Report normal deaths for scoring purposes. if not self._dead and not msg.immediate: - killerplayer: Optional[ba.Player] + killerplayer: ba.Player | None # If this guy was being held at the time of death, the # holder is the killer. @@ -883,7 +882,7 @@ class SpazBotSet: ] self._spawn_sound = ba.getsound('spawn') self._spawning_count = 0 - self._bot_update_timer: Optional[ba.Timer] = None + self._bot_update_timer: ba.Timer | None = None self.start_moving() def __del__(self) -> None: @@ -904,7 +903,7 @@ class SpazBotSet: self._spawning_count += 1 def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], - on_spawn_call: Optional[Callable[[SpazBot], Any]]) -> None: + on_spawn_call: Callable[[SpazBot], Any] | None) -> None: spaz = bot_type() ba.playsound(self._spawn_sound, position=pos) assert spaz.node diff --git a/dist/ba_data/python/bastd/actor/text.py b/dist/ba_data/python/bastd/actor/text.py index 7b84d0d..992ddc4 100644 --- a/dist/ba_data/python/bastd/actor/text.py +++ b/dist/ba_data/python/bastd/actor/text.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Sequence, Optional + from typing import Any, Sequence class Text(ba.Actor): @@ -49,12 +49,12 @@ class Text(ba.Actor): TOP = 'top' def __init__(self, - text: Union[str, ba.Lstr], + text: str | ba.Lstr, position: tuple[float, float] = (0.0, 0.0), h_align: HAlign = HAlign.LEFT, v_align: VAlign = VAlign.NONE, color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), - transition: Optional[Transition] = None, + transition: Transition | None = None, transition_delay: float = 0.0, flash: bool = False, v_attach: VAttach = VAttach.CENTER, diff --git a/dist/ba_data/python/bastd/actor/zoomtext.py b/dist/ba_data/python/bastd/actor/zoomtext.py index 5288bb6..c91df93 100644 --- a/dist/ba_data/python/bastd/actor/zoomtext.py +++ b/dist/ba_data/python/bastd/actor/zoomtext.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Sequence + from typing import Any, Sequence class ZoomText(ba.Actor): @@ -22,7 +22,7 @@ class ZoomText(ba.Actor): """ def __init__(self, - text: Union[str, ba.Lstr], + text: str | ba.Lstr, position: tuple[float, float] = (0.0, 0.0), shiftposition: tuple[float, float] = None, shiftdelay: float = None, diff --git a/dist/ba_data/python/bastd/appdelegate.py b/dist/ba_data/python/bastd/appdelegate.py index 6a7d277..464117c 100644 --- a/dist/ba_data/python/bastd/appdelegate.py +++ b/dist/ba_data/python/bastd/appdelegate.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class AppDelegate(ba.AppDelegate): @@ -16,8 +16,8 @@ class AppDelegate(ba.AppDelegate): def create_default_game_settings_ui( self, gameclass: type[ba.GameActivity], - sessiontype: type[ba.Session], settings: Optional[dict], - completion_call: Callable[[Optional[dict]], Any]) -> None: + sessiontype: type[ba.Session], settings: dict | None, + completion_call: Callable[[dict | None], Any]) -> None: """(internal)""" # Replace the main window once we come up successfully. diff --git a/dist/ba_data/python/bastd/game/assault.py b/dist/ba_data/python/bastd/game/assault.py index 0a56a07..e9f4ff1 100644 --- a/dist/ba_data/python/bastd/game/assault.py +++ b/dist/ba_data/python/bastd/game/assault.py @@ -2,7 +2,7 @@ # """Defines assault minigame.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -17,7 +17,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Union + from typing import Any, Sequence class Player(ba.Player['Team']): @@ -94,12 +94,12 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Touch the enemy flag.' return 'Touch the enemy flag ${ARG1} times.', self._score_to_win - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'touch 1 flag' return 'touch ${ARG1} flags', self._score_to_win diff --git a/dist/ba_data/python/bastd/game/capturetheflag.py b/dist/ba_data/python/bastd/game/capturetheflag.py index 881d918..781ce8c 100644 --- a/dist/ba_data/python/bastd/game/capturetheflag.py +++ b/dist/ba_data/python/bastd/game/capturetheflag.py @@ -2,7 +2,7 @@ # """Defines a capture-the-flag game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -16,7 +16,7 @@ from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, FlagDroppedMessage, FlagDiedMessage) if TYPE_CHECKING: - from typing import Any, Sequence, Union, Optional + from typing import Any, Sequence class CTFFlag(Flag): @@ -39,9 +39,9 @@ class CTFFlag(Flag): 'h_align': 'center' }) self.reset_return_times() - self.last_player_to_hold: Optional[Player] = None - self.time_out_respawn_time: Optional[int] = None - self.touch_return_time: Optional[float] = None + self.last_player_to_hold: Player | None = None + self.time_out_respawn_time: int | None = None + self.touch_return_time: float | None = None def reset_return_times(self) -> None: """Clear flag related times in the activity.""" @@ -78,11 +78,11 @@ class Team(ba.Team[Player]): self.score = 0 self.flag_return_touches = 0 self.home_flag_at_base = True - self.touch_return_timer: Optional[ba.Timer] = None + self.touch_return_timer: ba.Timer | None = None self.enemy_flag_at_base = False - self.flag: Optional[CTFFlag] = None - self.last_flag_leave_time: Optional[float] = None - self.touch_return_timer_ticking: Optional[ba.NodeActor] = None + self.flag: CTFFlag | None = None + self.last_flag_leave_time: float | None = None + self.touch_return_timer_ticking: ba.NodeActor | None = None # ba_meta export game @@ -161,12 +161,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FLAG_CATCHER) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Steal the enemy flag.' return 'Steal the enemy flag ${ARG1} times.', self._score_to_win - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'return 1 flag' return 'return ${ARG1} flags', self._score_to_win @@ -438,7 +438,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): We keep track of when each player is touching their own flag so we can award points when returned. """ - player: Optional[Player] + player: Player | None try: spaz = ba.getcollision().sourcenode.getdelegate(PlayerSpaz, True) except ba.NotFoundError: diff --git a/dist/ba_data/python/bastd/game/chosenone.py b/dist/ba_data/python/bastd/game/chosenone.py index 9f5e331..ba99d35 100644 --- a/dist/ba_data/python/bastd/game/chosenone.py +++ b/dist/ba_data/python/bastd/game/chosenone.py @@ -2,7 +2,7 @@ # """Provides the chosen-one mini-game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -16,14 +16,14 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class Player(ba.Player['Team']): """Our player type for this game.""" def __init__(self) -> None: - self.chosen_light: Optional[ba.NodeActor] = None + self.chosen_light: ba.NodeActor | None = None class Team(ba.Team[Player]): @@ -87,7 +87,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: dict): super().__init__(settings) self._scoreboard = Scoreboard() - self._chosen_one_player: Optional[Player] = None + self._chosen_one_player: Player | None = None self._swipsound = ba.getsound('swip') self._countdownsounds: dict[int, ba.Sound] = { 10: ba.getsound('announceTen'), @@ -101,10 +101,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): 2: ba.getsound('announceTwo'), 1: ba.getsound('announceOne') } - self._flag_spawn_pos: Optional[Sequence[float]] = None - self._reset_region_material: Optional[ba.Material] = None - self._flag: Optional[Flag] = None - self._reset_region: Optional[ba.Node] = None + self._flag_spawn_pos: Sequence[float] | None = None + self._reset_region_material: ba.Material | None = None + self._flag: Flag | None = None + self._reset_region: ba.Node | None = None self._epic_mode = bool(settings['Epic Mode']) self._chosen_one_time = int(settings['Chosen One Time']) self._time_limit = float(settings['Time Limit']) @@ -116,7 +116,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.CHOSEN_ONE) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'There can be only one.' def create_team(self, sessionteam: ba.SessionTeam) -> Team: @@ -165,7 +165,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): 'materials': [mat] }) - def _get_chosen_one_player(self) -> Optional[Player]: + def _get_chosen_one_player(self) -> Player | None: # Should never return invalid references; return None in that case. if self._chosen_one_player: return self._chosen_one_player @@ -251,7 +251,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): self._chosen_one_time - team.time_remaining) self.end(results=results, announce_delay=0) - def _set_chosen_one_player(self, player: Optional[Player]) -> None: + def _set_chosen_one_player(self, player: Player | None) -> None: existing = self._get_chosen_one_player() if existing: existing.chosen_light = None diff --git a/dist/ba_data/python/bastd/game/conquest.py b/dist/ba_data/python/bastd/game/conquest.py index 93bb279..88650c0 100644 --- a/dist/ba_data/python/bastd/game/conquest.py +++ b/dist/ba_data/python/bastd/game/conquest.py @@ -2,7 +2,7 @@ # """Provides the Conquest game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -17,7 +17,7 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence from bastd.actor.respawnicon import RespawnIcon @@ -26,11 +26,11 @@ class ConquestFlag(Flag): def __init__(self, *args: Any, **keywds: Any): super().__init__(*args, **keywds) - self._team: Optional[Team] = None - self.light: Optional[ba.Node] = None + self._team: Team | None = None + self.light: ba.Node | None = None @property - def team(self) -> Optional[Team]: + def team(self) -> Team | None: """The team that owns this flag.""" return self._team @@ -46,21 +46,21 @@ class Player(ba.Player['Team']): # FIXME: We shouldn't be using customdata here # (but need to update respawn funcs accordingly first). @property - def respawn_timer(self) -> Optional[ba.Timer]: + def respawn_timer(self) -> ba.Timer | None: """Type safe access to standard respawn timer.""" return self.customdata.get('respawn_timer', None) @respawn_timer.setter - def respawn_timer(self, value: Optional[ba.Timer]) -> None: + def respawn_timer(self, value: ba.Timer | None) -> None: self.customdata['respawn_timer'] = value @property - def respawn_icon(self) -> Optional[RespawnIcon]: + def respawn_icon(self) -> RespawnIcon | None: """Type safe access to standard respawn icon.""" return self.customdata.get('respawn_icon', None) @respawn_icon.setter - def respawn_icon(self, value: Optional[RespawnIcon]) -> None: + def respawn_icon(self, value: RespawnIcon | None) -> None: self.customdata['respawn_icon'] = value @@ -136,10 +136,10 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): ('call', 'at_connect', self._handle_flag_player_collide), )) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'Secure all ${ARG1} flags.', len(self.map.flag_points) - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: return 'secure all ${ARG1} flags', len(self.map.flag_points) def on_team_join(self, team: Team) -> None: diff --git a/dist/ba_data/python/bastd/game/deathmatch.py b/dist/ba_data/python/bastd/game/deathmatch.py index ddfa0d4..b1ef3dc 100644 --- a/dist/ba_data/python/bastd/game/deathmatch.py +++ b/dist/ba_data/python/bastd/game/deathmatch.py @@ -2,7 +2,7 @@ # """DeathMatch game and support classes.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -14,7 +14,7 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: - from typing import Any, Union, Sequence, Optional + from typing import Any, Sequence class Player(ba.Player['Team']): @@ -97,7 +97,7 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: dict): super().__init__(settings) self._scoreboard = Scoreboard() - self._score_to_win: Optional[int] = None + self._score_to_win: int | None = None self._dingsound = ba.getsound('dingSmall') self._epic_mode = bool(settings['Epic Mode']) self._kills_to_win_per_player = int( @@ -111,10 +111,10 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.TO_THE_DEATH) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'Crush ${ARG1} of your enemies.', self._score_to_win - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: return 'kill ${ARG1} enemies', self._score_to_win def on_team_join(self, team: Team) -> None: diff --git a/dist/ba_data/python/bastd/game/easteregghunt.py b/dist/ba_data/python/bastd/game/easteregghunt.py index d7f3655..3372522 100644 --- a/dist/ba_data/python/bastd/game/easteregghunt.py +++ b/dist/ba_data/python/bastd/game/easteregghunt.py @@ -2,7 +2,7 @@ # """Provides an easter egg hunt game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -20,15 +20,15 @@ from bastd.actor.respawnicon import RespawnIcon from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class Player(ba.Player['Team']): """Our player type for this game.""" def __init__(self) -> None: - self.respawn_timer: Optional[ba.Timer] = None - self.respawn_icon: Optional[RespawnIcon] = None + self.respawn_timer: ba.Timer | None = None + self.respawn_icon: RespawnIcon | None = None class Team(ba.Team[Player]): @@ -76,9 +76,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): conditions=('they_have_material', shared.player_material), actions=(('call', 'at_connect', self._on_egg_player_collide), )) self._eggs: list[Egg] = [] - self._update_timer: Optional[ba.Timer] = None - self._countdown: Optional[OnScreenCountdown] = None - self._bots: Optional[SpazBotSet] = None + self._update_timer: ba.Timer | None = None + self._countdown: OnScreenCountdown | None = None + self._bots: SpazBotSet | None = None # Base class overrides self.default_music = ba.MusicType.FORWARD_MARCH diff --git a/dist/ba_data/python/bastd/game/elimination.py b/dist/ba_data/python/bastd/game/elimination.py index a0df770..e3d91e4 100644 --- a/dist/ba_data/python/bastd/game/elimination.py +++ b/dist/ba_data/python/bastd/game/elimination.py @@ -2,7 +2,7 @@ # """Elimination mini-game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -14,7 +14,7 @@ from bastd.actor.spazfactory import SpazFactory from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union + from typing import Any, Sequence class Icon(ba.Actor): @@ -162,7 +162,7 @@ class Team(ba.Team[Player]): """Our team type for this game.""" def __init__(self) -> None: - self.survival_seconds: Optional[int] = None + self.survival_seconds: int | None = None self.spawn_order: list[Player] = [] @@ -234,9 +234,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: dict): super().__init__(settings) self._scoreboard = Scoreboard() - self._start_time: Optional[float] = None - self._vs_text: Optional[ba.Actor] = None - self._round_end_timer: Optional[ba.Timer] = None + self._start_time: float | None = None + self._vs_text: ba.Actor | None = None + self._round_end_timer: ba.Timer | None = None self._epic_mode = bool(settings['Epic Mode']) self._lives_per_player = int(settings['Lives Per Player']) self._time_limit = float(settings['Time Limit']) @@ -249,11 +249,11 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'Last team standing wins.' if isinstance( self.session, ba.DualTeamSession) else 'Last one standing wins.' - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: return 'last team standing wins' if isinstance( self.session, ba.DualTeamSession) else 'last one standing wins' @@ -401,7 +401,7 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): icon.update_for_lives() xval += x_offs - def _get_spawn_point(self, player: Player) -> Optional[ba.Vec3]: + def _get_spawn_point(self, player: Player) -> ba.Vec3 | None: del player # Unused. # In solo-mode, if there's an existing live player on the map, spawn at diff --git a/dist/ba_data/python/bastd/game/football.py b/dist/ba_data/python/bastd/game/football.py index 9de4d4f..ff1b735 100644 --- a/dist/ba_data/python/bastd/game/football.py +++ b/dist/ba_data/python/bastd/game/football.py @@ -2,7 +2,7 @@ # """Implements football games (both co-op and teams varieties).""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -26,7 +26,7 @@ from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage, StickyBot, ExplodeyBot) if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union + from typing import Any, Sequence from bastd.actor.spaz import Spaz from bastd.actor.spazbot import SpazBot @@ -39,9 +39,9 @@ class FootballFlag(Flag): dropped_timeout=20, color=(1.0, 1.0, 0.3)) assert self.node - self.last_holding_player: Optional[ba.Player] = None + self.last_holding_player: ba.Player | None = None self.node.is_area_of_interest = True - self.respawn_timer: Optional[ba.Timer] = None + self.respawn_timer: ba.Timer | None = None self.scored = False self.held_count = 0 self.light = ba.newnode('light', @@ -59,8 +59,8 @@ class Player(ba.Player['Team']): """Our player type for this game.""" def __init__(self) -> None: - self.respawn_timer: Optional[ba.Timer] = None - self.respawn_icon: Optional[RespawnIcon] = None + self.respawn_timer: ba.Timer | None = None + self.respawn_icon: RespawnIcon | None = None class Team(ba.Team[Player]): @@ -120,7 +120,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: dict): super().__init__(settings) - self._scoreboard: Optional[Scoreboard] = Scoreboard() + self._scoreboard: Scoreboard | None = Scoreboard() # Load some media we need. self._cheer_sound = ba.getsound('cheer') @@ -136,15 +136,15 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score), )) - self._flag_spawn_pos: Optional[Sequence[float]] = None + self._flag_spawn_pos: Sequence[float] | None = None self._score_regions: list[ba.NodeActor] = [] - self._flag: Optional[FootballFlag] = None - self._flag_respawn_timer: Optional[ba.Timer] = None - self._flag_respawn_light: Optional[ba.NodeActor] = None + self._flag: FootballFlag | None = None + self._flag_respawn_timer: ba.Timer | None = None + self._flag_respawn_light: ba.NodeActor | None = None self._score_to_win = int(settings['Score to Win']) self._time_limit = float(settings['Time Limit']) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: touchdowns = self._score_to_win / 7 # NOTE: if use just touchdowns = self._score_to_win // 7 @@ -155,7 +155,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): return 'Score ${ARG1} touchdowns.', touchdowns return 'Score a touchdown.' - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) if touchdowns > 1: @@ -336,14 +336,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): def get_score_type(self) -> str: return 'time' - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) if touchdowns > 1: return 'Score ${ARG1} touchdowns.', touchdowns return 'Score a touchdown.' - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) if touchdowns > 1: @@ -375,27 +375,27 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._powerup_spread = (10, 5.5) self._player_has_dropped_bomb = False self._player_has_punched = False - self._scoreboard: Optional[Scoreboard] = None - self._flag_spawn_pos: Optional[Sequence[float]] = None + self._scoreboard: Scoreboard | None = None + self._flag_spawn_pos: Sequence[float] | None = None self._score_regions: list[ba.NodeActor] = [] self._exclude_powerups: list[str] = [] self._have_tnt = False - self._bot_types_initial: Optional[list[type[SpazBot]]] = None - self._bot_types_7: Optional[list[type[SpazBot]]] = None - self._bot_types_14: Optional[list[type[SpazBot]]] = None - self._bot_team: Optional[Team] = None - self._starttime_ms: Optional[int] = None - self._time_text: Optional[ba.NodeActor] = None - self._time_text_input: Optional[ba.NodeActor] = None - self._tntspawner: Optional[TNTSpawner] = None + self._bot_types_initial: list[type[SpazBot]] | None = None + self._bot_types_7: list[type[SpazBot]] | None = None + self._bot_types_14: list[type[SpazBot]] | None = None + self._bot_team: Team | None = None + self._starttime_ms: int | None = None + self._time_text: ba.NodeActor | None = None + self._time_text_input: ba.NodeActor | None = None + self._tntspawner: TNTSpawner | None = None self._bots = SpazBotSet() - self._bot_spawn_timer: Optional[ba.Timer] = None - self._powerup_drop_timer: Optional[ba.Timer] = None - self._scoring_team: Optional[Team] = None - self._final_time_ms: Optional[int] = None - self._time_text_timer: Optional[ba.Timer] = None - self._flag_respawn_light: Optional[ba.Actor] = None - self._flag: Optional[FootballFlag] = None + self._bot_spawn_timer: ba.Timer | None = None + self._powerup_drop_timer: ba.Timer | None = None + self._scoring_team: Team | None = None + self._final_time_ms: int | None = None + self._time_text_timer: ba.Timer | None = None + self._flag_respawn_light: ba.Actor | None = None + self._flag: FootballFlag | None = None def on_transition_in(self) -> None: super().on_transition_in() @@ -581,7 +581,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): return flagpos = ba.Vec3(self._flag.node.position) - closest_bot: Optional[SpazBot] = None + closest_bot: SpazBot | None = None closest_dist = 0.0 # Always gets assigned first time through. for bot in bots: # If a bot is picked up, he should forget about the flag. diff --git a/dist/ba_data/python/bastd/game/hockey.py b/dist/ba_data/python/bastd/game/hockey.py index 7f47bf7..97213a6 100644 --- a/dist/ba_data/python/bastd/game/hockey.py +++ b/dist/ba_data/python/bastd/game/hockey.py @@ -2,7 +2,7 @@ # """Hockey game and support classes.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -16,7 +16,7 @@ from bastd.actor.powerupbox import PowerupBoxFactory from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union + from typing import Any, Sequence class PuckDiedMessage: @@ -198,18 +198,18 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): actions=(('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score))) - self._puck_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: Optional[list[ba.NodeActor]] = None - self._puck: Optional[Puck] = None + self._puck_spawn_pos: Sequence[float] | None = None + self._score_regions: list[ba.NodeActor] | None = None + self._puck: Puck | None = None self._score_to_win = int(settings['Score to Win']) self._time_limit = float(settings['Time Limit']) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Score a goal.' return 'Score ${ARG1} goals.', self._score_to_win - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'score a goal' return 'score ${ARG1} goals', self._score_to_win diff --git a/dist/ba_data/python/bastd/game/keepaway.py b/dist/ba_data/python/bastd/game/keepaway.py index 263326a..0dd355e 100644 --- a/dist/ba_data/python/bastd/game/keepaway.py +++ b/dist/ba_data/python/bastd/game/keepaway.py @@ -2,7 +2,7 @@ # """Defines a keep-away game type.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -17,7 +17,7 @@ from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage, FlagPickedUpMessage) if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class FlagState(Enum): @@ -106,20 +106,20 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): 2: ba.getsound('announceTwo'), 1: ba.getsound('announceOne') } - self._flag_spawn_pos: Optional[Sequence[float]] = None - self._update_timer: Optional[ba.Timer] = None + self._flag_spawn_pos: Sequence[float] | None = None + self._update_timer: ba.Timer | None = None self._holding_players: list[Player] = [] - self._flag_state: Optional[FlagState] = None - self._flag_light: Optional[ba.Node] = None - self._scoring_team: Optional[Team] = None - self._flag: Optional[Flag] = None + self._flag_state: FlagState | None = None + self._flag_light: ba.Node | None = None + self._scoring_team: Team | None = None + self._flag: Flag | None = None self._hold_time = int(settings['Hold Time']) self._time_limit = float(settings['Time Limit']) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'Carry the flag for ${ARG1} seconds.', self._hold_time - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: return 'carry the flag for ${ARG1} seconds', self._hold_time def create_team(self, sessionteam: ba.SessionTeam) -> Team: diff --git a/dist/ba_data/python/bastd/game/kingofthehill.py b/dist/ba_data/python/bastd/game/kingofthehill.py index 1ea3e9a..becdc8b 100644 --- a/dist/ba_data/python/bastd/game/kingofthehill.py +++ b/dist/ba_data/python/bastd/game/kingofthehill.py @@ -2,7 +2,7 @@ # """Defines the King of the Hill game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -18,7 +18,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Union + from typing import Any, Sequence class FlagState(Enum): @@ -108,11 +108,11 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): 2: ba.getsound('announceTwo'), 1: ba.getsound('announceOne') } - self._flag_pos: Optional[Sequence[float]] = None - self._flag_state: Optional[FlagState] = None - self._flag: Optional[Flag] = None - self._flag_light: Optional[ba.Node] = None - self._scoring_team: Optional[weakref.ref[Team]] = None + self._flag_pos: Sequence[float] | None = None + self._flag_state: FlagState | None = None + self._flag: Flag | None = None + self._flag_light: ba.Node | None = None + self._scoring_team: weakref.ref[Team] | None = None self._hold_time = int(settings['Hold Time']) self._time_limit = float(settings['Time Limit']) self._flag_region_material = ba.Material() @@ -130,10 +130,10 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): # Base class overrides. self.default_music = ba.MusicType.SCARY - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: return 'Secure the flag for ${ARG1} seconds.', self._hold_time - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: return 'secure the flag for ${ARG1} seconds', self._hold_time def create_team(self, sessionteam: ba.SessionTeam) -> Team: @@ -239,7 +239,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): def _handle_player_flag_region_collide(self, colliding: bool) -> None: try: - spaz = ba.getcollision().sourcenode.getdelegate(PlayerSpaz, True) + spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True) except ba.NotFoundError: return diff --git a/dist/ba_data/python/bastd/game/meteorshower.py b/dist/ba_data/python/bastd/game/meteorshower.py index e346160..2ca2012 100644 --- a/dist/ba_data/python/bastd/game/meteorshower.py +++ b/dist/ba_data/python/bastd/game/meteorshower.py @@ -2,7 +2,7 @@ # """Defines a bomb-dodging mini-game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -15,7 +15,7 @@ from bastd.actor.bomb import Bomb from bastd.actor.onscreentimer import OnScreenTimer if TYPE_CHECKING: - from typing import Any, Sequence, Optional + from typing import Any, Sequence class Player(ba.Player['Team']): @@ -23,7 +23,7 @@ class Player(ba.Player['Team']): def __init__(self) -> None: super().__init__() - self.death_time: Optional[float] = None + self.death_time: float | None = None class Team(ba.Team[Player]): @@ -64,9 +64,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): super().__init__(settings) self._epic_mode = settings.get('Epic Mode', False) - self._last_player_death_time: Optional[float] = None + self._last_player_death_time: float | None = None self._meteor_time = 2.0 - self._timer: Optional[OnScreenTimer] = None + self._timer: OnScreenTimer | None = None # Some base class overrides: self.default_music = (ba.MusicType.EPIC @@ -188,9 +188,10 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Drop them somewhere within our bounds with velocity pointing # toward the opposite side. pos = (-7.3 + 15.3 * random.random(), 11, - -5.5 + 2.1 * random.random()) + -5.57 + 2.1 * random.random()) dropdir = (-1.0 if pos[0] > 0 else 1.0) - vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0) + vel = ((-5.0 + random.random() * 30.0) * dropdir, + random.uniform(-3.066, -4.12), 0) ba.timer(delay, ba.Call(self._drop_bomb, pos, vel)) delay += 0.1 self._set_meteor_timer() diff --git a/dist/ba_data/python/bastd/game/ninjafight.py b/dist/ba_data/python/bastd/game/ninjafight.py index 13b6341..3540263 100644 --- a/dist/ba_data/python/bastd/game/ninjafight.py +++ b/dist/ba_data/python/bastd/game/ninjafight.py @@ -2,7 +2,7 @@ # """Provides Ninja Fight mini-game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -15,7 +15,7 @@ from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage from bastd.actor.onscreentimer import OnScreenTimer if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class Player(ba.Player['Team']): @@ -58,7 +58,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): super().__init__(settings) self._winsound = ba.getsound('score') self._won = False - self._timer: Optional[OnScreenTimer] = None + self._timer: OnScreenTimer | None = None self._bots = SpazBotSet() self._preset = str(settings['preset']) diff --git a/dist/ba_data/python/bastd/game/onslaught.py b/dist/ba_data/python/bastd/game/onslaught.py index 403e7b0..2664858 100644 --- a/dist/ba_data/python/bastd/game/onslaught.py +++ b/dist/ba_data/python/bastd/game/onslaught.py @@ -28,22 +28,22 @@ from bastd.actor.spazbot import ( TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded) if TYPE_CHECKING: - from typing import Any, Optional, Union, Sequence + from typing import Any, Sequence from bastd.actor.spazbot import SpazBot @dataclass class Wave: """A wave of enemies.""" - entries: list[Union[Spawn, Spacing, Delay, None]] + entries: list[Spawn | Spacing | Delay | None] base_angle: float = 0.0 @dataclass class Spawn: """A bot spawn event in a wave.""" - bottype: Union[type[SpazBot], str] - point: Optional[Point] = None + bottype: type[SpazBot] | str + point: Point | None = None spacing: float = 5.0 @@ -123,7 +123,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): name = 'Onslaught' description = 'Defeat all enemies.' - tips: list[Union[str, ba.GameTip]] = [ + tips: list[str | ba.GameTip] = [ 'Hold any button to run.' ' (Trigger buttons work well if you have them)', 'Try tricking enemies into killing eachother or running off cliffs.', @@ -169,26 +169,26 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._powerup_spread = (4.6, 2.7) else: raise Exception('Unsupported map: ' + str(settings['map'])) - self._scoreboard: Optional[Scoreboard] = None + self._scoreboard: Scoreboard | None = None self._game_over = False self._wavenum = 0 self._can_end_wave = True self._score = 0 self._time_bonus = 0 - self._spawn_info_text: Optional[ba.NodeActor] = None + self._spawn_info_text: ba.NodeActor | None = None self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._have_tnt = False - self._excluded_powerups: Optional[list[str]] = None + self._excluded_powerups: list[str] | None = None self._waves: list[Wave] = [] - self._tntspawner: Optional[TNTSpawner] = None - self._bots: Optional[SpazBotSet] = None - self._powerup_drop_timer: Optional[ba.Timer] = None - self._time_bonus_timer: Optional[ba.Timer] = None - self._time_bonus_text: Optional[ba.NodeActor] = None - self._flawless_bonus: Optional[int] = None - self._wave_text: Optional[ba.NodeActor] = None - self._wave_update_timer: Optional[ba.Timer] = None + self._tntspawner: TNTSpawner | None = None + self._bots: SpazBotSet | None = None + self._powerup_drop_timer: ba.Timer | None = None + self._time_bonus_timer: ba.Timer | None = None + self._time_bonus_text: ba.NodeActor | None = None + self._flawless_bonus: int | None = None + self._wave_text: ba.NodeActor | None = None + self._wave_update_timer: ba.Timer | None = None self._throw_off_kills = 0 self._land_mine_kills = 0 self._tnt_kills = 0 @@ -729,7 +729,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): """End the game with the specified outcome.""" if outcome == 'defeat': self.fade_to_red() - score: Optional[int] + score: int | None if self._wavenum >= 2: score = self._score fail_message = None @@ -878,7 +878,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if not any(player.is_alive() for player in self.teams[0].players): self._spawn_info_text.node.text = '' else: - text: Union[str, ba.Lstr] = '' + text: str | ba.Lstr = '' for player in self.players: if (not player.is_alive() and (self._preset @@ -1070,8 +1070,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def _add_entries_for_distribution_group( self, group: list[tuple[int, int]], bot_levels: list[list[type[SpazBot]]], - all_entries: list[Union[Spawn, Spacing, Delay, None]]) -> None: - entries: list[Union[Spawn, Spacing, Delay, None]] = [] + all_entries: list[Spawn | Spacing | Delay | None]) -> None: + entries: list[Spawn | Spacing | Delay | None] = [] for entry in group: bot_level = bot_levels[entry[0] - 1] bot_type = bot_level[random.randrange(len(bot_level))] @@ -1106,7 +1106,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): distribution = self._get_distribution(target_points, min_dudes, max_dudes, group_count, max_level) - all_entries: list[Union[Spawn, Spacing, Delay, None]] = [] + all_entries: list[Spawn | Spacing | Delay | None] = [] for group in distribution: self._add_entries_for_distribution_group(group, bot_levels, all_entries) @@ -1206,7 +1206,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): pts, importance = msg.spazbot.get_death_points(msg.how) if msg.killerplayer is not None: self._handle_kill_achievements(msg) - target: Optional[Sequence[float]] + target: Sequence[float] | None if msg.spazbot.node: target = msg.spazbot.node.position else: diff --git a/dist/ba_data/python/bastd/game/race.py b/dist/ba_data/python/bastd/game/race.py index 1aa3254..e21fbe5 100644 --- a/dist/ba_data/python/bastd/game/race.py +++ b/dist/ba_data/python/bastd/game/race.py @@ -2,7 +2,7 @@ # """Defines Race mini-game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -18,7 +18,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union + from typing import Any, Sequence from bastd.actor.onscreentimer import OnScreenTimer @@ -26,7 +26,7 @@ if TYPE_CHECKING: class RaceMine: """Holds info about a mine on the track.""" point: Sequence[float] - mine: Optional[Bomb] + mine: Bomb | None class RaceRegion(ba.Actor): @@ -53,19 +53,19 @@ class Player(ba.Player['Team']): """Our player type for this game.""" def __init__(self) -> None: - self.distance_txt: Optional[ba.Node] = None + self.distance_txt: ba.Node | None = None self.last_region = 0 self.lap = 0 self.distance = 0.0 self.finished = False - self.rank: Optional[int] = None + self.rank: int | None = None class Team(ba.Team[Player]): """Our team type for this game.""" def __init__(self) -> None: - self.time: Optional[float] = None + self.time: float | None = None self.lap = 0 self.finished = False @@ -141,22 +141,22 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self._scoreboard = Scoreboard() self._score_sound = ba.getsound('score') self._swipsound = ba.getsound('swip') - self._last_team_time: Optional[float] = None - self._front_race_region: Optional[int] = None + self._last_team_time: float | None = None + self._front_race_region: int | None = None self._nub_tex = ba.gettexture('nub') self._beep_1_sound = ba.getsound('raceBeep1') self._beep_2_sound = ba.getsound('raceBeep2') - self.race_region_material: Optional[ba.Material] = None + self.race_region_material: ba.Material | None = None self._regions: list[RaceRegion] = [] - self._team_finish_pts: Optional[int] = None - self._time_text: Optional[ba.Actor] = None - self._timer: Optional[OnScreenTimer] = None - self._race_mines: Optional[list[RaceMine]] = None - self._race_mine_timer: Optional[ba.Timer] = None - self._scoreboard_timer: Optional[ba.Timer] = None - self._player_order_update_timer: Optional[ba.Timer] = None - self._start_lights: Optional[list[ba.Node]] = None - self._bomb_spawn_timer: Optional[ba.Timer] = None + self._team_finish_pts: int | None = None + self._time_text: ba.Actor | None = None + self._timer: OnScreenTimer | None = None + self._race_mines: list[RaceMine] | None = None + self._race_mine_timer: ba.Timer | None = None + self._scoreboard_timer: ba.Timer | None = None + self._player_order_update_timer: ba.Timer | None = None + self._start_lights: list[ba.Node] | None = None + self._bomb_spawn_timer: ba.Timer | None = None self._laps = int(settings['Laps']) self._entire_team_must_finish = bool( settings.get('Entire Team Must Finish', False)) @@ -170,7 +170,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self.default_music = (ba.MusicType.EPIC_RACE if self._epic_mode else ba.MusicType.RACE) - def get_instance_description(self) -> Union[str, Sequence]: + def get_instance_description(self) -> str | Sequence: if (isinstance(self.session, ba.DualTeamSession) and self._entire_team_must_finish): t_str = ' Your entire team has to finish.' @@ -181,7 +181,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): return 'Run ${ARG1} laps.' + t_str, self._laps return 'Run 1 lap.' + t_str - def get_instance_description_short(self) -> Union[str, Sequence]: + def get_instance_description_short(self) -> str | Sequence: if self._laps > 1: return 'run ${ARG1} laps', self._laps return 'run 1 lap' @@ -522,7 +522,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Calc all player distances. for player in self.players: - pos: Optional[ba.Vec3] + pos: ba.Vec3 | None try: pos = player.position except ba.NotFoundError: diff --git a/dist/ba_data/python/bastd/game/runaround.py b/dist/ba_data/python/bastd/game/runaround.py index 29779b2..58ae628 100644 --- a/dist/ba_data/python/bastd/game/runaround.py +++ b/dist/ba_data/python/bastd/game/runaround.py @@ -26,7 +26,7 @@ from bastd.actor.spazbot import ( BomberBotPro, BrawlerBotPro) if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Union + from typing import Any, Sequence class Preset(Enum): @@ -54,7 +54,7 @@ class Spawn: # noinspection PyUnresolvedReferences type: type[SpazBot] path: int = 0 - point: Optional[Point] = None + point: Point | None = None @dataclass @@ -66,15 +66,15 @@ class Spacing: @dataclass class Wave: """Defines a wave of enemies.""" - entries: list[Union[Spawn, Spacing, None]] + entries: list[Spawn | Spacing | None] class Player(ba.Player['Team']): """Our player type for this game.""" def __init__(self) -> None: - self.respawn_timer: Optional[ba.Timer] = None - self.respawn_icon: Optional[RespawnIcon] = None + self.respawn_timer: ba.Timer | None = None + self.respawn_icon: RespawnIcon | None = None class Team(ba.Team[Player]): @@ -144,31 +144,31 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._last_wave_end_time = ba.time() self._player_has_picked_up_powerup = False - self._scoreboard: Optional[Scoreboard] = None + self._scoreboard: Scoreboard | None = None self._game_over = False self._wavenum = 0 self._can_end_wave = True self._score = 0 self._time_bonus = 0 - self._score_region: Optional[ba.Actor] = None + self._score_region: ba.Actor | None = None self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') - self._exclude_powerups: Optional[list[str]] = None - self._have_tnt: Optional[bool] = None - self._waves: Optional[list[Wave]] = None + self._exclude_powerups: list[str] | None = None + self._have_tnt: bool | None = None + self._waves: list[Wave] | None = None self._bots = SpazBotSet() - self._tntspawner: Optional[TNTSpawner] = None - self._lives_bg: Optional[ba.NodeActor] = None + self._tntspawner: TNTSpawner | None = None + self._lives_bg: ba.NodeActor | None = None self._start_lives = 10 self._lives = self._start_lives - self._lives_text: Optional[ba.NodeActor] = None + self._lives_text: ba.NodeActor | None = None self._flawless = True - self._time_bonus_timer: Optional[ba.Timer] = None - self._time_bonus_text: Optional[ba.NodeActor] = None - self._time_bonus_mult: Optional[float] = None - self._wave_text: Optional[ba.NodeActor] = None - self._flawless_bonus: Optional[int] = None - self._wave_update_timer: Optional[ba.Timer] = None + self._time_bonus_timer: ba.Timer | None = None + self._time_bonus_text: ba.NodeActor | None = None + self._time_bonus_mult: float | None = None + self._wave_text: ba.NodeActor | None = None + self._flawless_bonus: int | None = None + self._wave_update_timer: ba.Timer | None = None def on_transition_in(self) -> None: super().on_transition_in() @@ -556,7 +556,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): else: delay = 0 - score: Optional[int] + score: int | None if self._wavenum >= 2: score = self._score fail_message = None @@ -723,13 +723,13 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): t_sec = 0.0 base_delay = 0.5 delay = 0.0 - bot_types: list[Union[Spawn, Spacing, None]] = [] + bot_types: list[Spawn | Spacing | None] = [] if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: level = self._wavenum target_points = (level + 1) * 8.0 group_count = random.randint(1, 3) - entries: list[Union[Spawn, Spacing, None]] = [] + entries: list[Spawn | Spacing | None] = [] spaz_types: list[tuple[type[SpazBot], float]] = [] if level < 6: spaz_types += [(BomberBot, 5.0)] @@ -1121,7 +1121,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): return None pts, importance = msg.spazbot.get_death_points(msg.how) if msg.killerplayer is not None: - target: Optional[Sequence[float]] + target: Sequence[float] | None try: assert msg.spazbot is not None assert msg.spazbot.node diff --git a/dist/ba_data/python/bastd/game/targetpractice.py b/dist/ba_data/python/bastd/game/targetpractice.py index c621306..6437aa5 100644 --- a/dist/ba_data/python/bastd/game/targetpractice.py +++ b/dist/ba_data/python/bastd/game/targetpractice.py @@ -2,7 +2,7 @@ # """Implements Target Practice game.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations @@ -17,7 +17,7 @@ from bastd.actor.bomb import Bomb from bastd.actor.popuptext import PopupText if TYPE_CHECKING: - from typing import Any, Optional, Sequence + from typing import Any, Sequence from bastd.actor.bomb import Blast @@ -62,8 +62,8 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): super().__init__(settings) self._scoreboard = Scoreboard() self._targets: list[Target] = [] - self._update_timer: Optional[ba.Timer] = None - self._countdown: Optional[OnScreenCountdown] = None + self._update_timer: ba.Timer | None = None + self._countdown: OnScreenCountdown | None = None self._target_count = int(settings['Target Count']) self._enable_impact_bombs = bool(settings['Enable Impact Bombs']) self._enable_triple_bombs = bool(settings['Enable Triple Bombs']) diff --git a/dist/ba_data/python/bastd/game/thelaststand.py b/dist/ba_data/python/bastd/game/thelaststand.py index b423591..0d40c0b 100644 --- a/dist/ba_data/python/bastd/game/thelaststand.py +++ b/dist/ba_data/python/bastd/game/thelaststand.py @@ -21,7 +21,7 @@ from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot, ChargerBot, StickyBot, ExplodeyBot) if TYPE_CHECKING: - from typing import Any, Optional, Sequence + from typing import Any, Sequence from bastd.actor.spazbot import SpazBot @@ -71,14 +71,14 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): self._powerup_spread = (7, 2) self._preset = str(settings.get('preset', 'default')) self._excludepowerups: list[str] = [] - self._scoreboard: Optional[Scoreboard] = None + self._scoreboard: Scoreboard | None = None self._score = 0 self._bots = SpazBotSet() self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') - self._tntspawner: Optional[TNTSpawner] = None - self._bot_update_interval: Optional[float] = None - self._bot_update_timer: Optional[ba.Timer] = None + self._tntspawner: TNTSpawner | None = None + self._bot_update_interval: float | None = None + self._bot_update_timer: ba.Timer | None = None self._powerup_drop_timer = None # For each bot type: [spawnrate, increase, d_increase] @@ -220,7 +220,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): # Now go back through and see where this value falls. total = 0 - bottype: Optional[type[SpazBot]] = None + bottype: type[SpazBot] | None = None for spawntype, spawninfo in self._bot_spawn_types.items(): total += spawninfo.spawnrate if randval <= total: @@ -262,7 +262,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): elif isinstance(msg, SpazBotDiedMessage): pts, importance = msg.spazbot.get_death_points(msg.how) - target: Optional[Sequence[float]] + target: Sequence[float] | None if msg.killerplayer: assert msg.spazbot.node target = msg.spazbot.node.position diff --git a/dist/ba_data/python/bastd/gameutils.py b/dist/ba_data/python/bastd/gameutils.py index 89d7ed4..1d297d7 100644 --- a/dist/ba_data/python/bastd/gameutils.py +++ b/dist/ba_data/python/bastd/gameutils.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional + pass class SharedObjects: @@ -29,14 +29,14 @@ class SharedObjects: if self._STORENAME in activity.customdata: raise RuntimeError('Use SharedObjects.get() to fetch the' ' shared instance for this activity.') - self._object_material: Optional[ba.Material] = None - self._player_material: Optional[ba.Material] = None - self._pickup_material: Optional[ba.Material] = None - self._footing_material: Optional[ba.Material] = None - self._attack_material: Optional[ba.Material] = None - self._death_material: Optional[ba.Material] = None - self._region_material: Optional[ba.Material] = None - self._railing_material: Optional[ba.Material] = None + self._object_material: ba.Material | None = None + self._player_material: ba.Material | None = None + self._pickup_material: ba.Material | None = None + self._footing_material: ba.Material | None = None + self._attack_material: ba.Material | None = None + self._death_material: ba.Material | None = None + self._region_material: ba.Material | None = None + self._railing_material: ba.Material | None = None @classmethod def get(cls) -> SharedObjects: diff --git a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py index 8b8ff49..47162c5 100644 --- a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py +++ b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py @@ -2,7 +2,7 @@ # """Defines a default keyboards.""" -# ba_meta require api 6 +# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/dist/ba_data/python/bastd/mainmenu.py b/dist/ba_data/python/bastd/mainmenu.py index b32a937..d883688 100644 --- a/dist/ba_data/python/bastd/mainmenu.py +++ b/dist/ba_data/python/bastd/mainmenu.py @@ -13,7 +13,7 @@ import ba import _ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any # FIXME: Clean this up if I ever revisit it. # pylint: disable=attribute-defined-outside-init @@ -32,8 +32,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): def on_transition_in(self) -> None: super().on_transition_in() random.seed(123) - self._logo_node: Optional[ba.Node] = None - self._custom_logo_tex_name: Optional[str] = None + self._logo_node: ba.Node | None = None + self._custom_logo_tex_name: str | None = None self._word_actors: list[ba.Actor] = [] app = ba.app @@ -246,7 +246,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): self._ts = 0.86 - self._language: Optional[str] = None + self._language: str | None = None self._update_timer = ba.Timer(1.0, self._update, repeat=True) self._update() @@ -263,12 +263,12 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): self._valid = True self._message_duration = 10.0 self._message_spacing = 2.0 - self._text: Optional[ba.NodeActor] = None + self._text: ba.NodeActor | None = None self._activity = weakref.ref(activity) # If we're signed in, fetch news immediately. # Otherwise wait until we are signed in. - self._fetch_timer: Optional[ba.Timer] = ba.Timer( + self._fetch_timer: ba.Timer | None = ba.Timer( 1.0, ba.WeakCall(self._try_fetching_news), repeat=True) self._try_fetching_news() @@ -694,8 +694,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): # (unless we're in VR mode in which case its best to # leave things still). if not ba.app.vr_mode: - cmb: Optional[ba.Node] - cmb2: Optional[ba.Node] + cmb: ba.Node | None + cmb2: ba.Node | None if not shadow: cmb = ba.newnode('combine', owner=word_obj.node, @@ -756,7 +756,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): delay + 0.2: scale }) - def _get_custom_logo_tex_name(self) -> Optional[str]: + def _get_custom_logo_tex_name(self) -> str | None: if _ba.get_v1_account_misc_read_val('easter', False): return 'logoEaster' return None diff --git a/dist/ba_data/python/bastd/stdmap.py b/dist/ba_data/python/bastd/stdmap.py index 4522eac..82101d1 100644 --- a/dist/ba_data/python/bastd/stdmap.py +++ b/dist/ba_data/python/bastd/stdmap.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any def _get_map_data(name: str) -> dict[str, Any]: @@ -26,7 +26,7 @@ class StdMap(ba.Map): """A map completely defined by asset data. """ - _data: Optional[dict[str, Any]] = None + _data: dict[str, Any] | None = None @classmethod def _getdata(cls) -> dict[str, Any]: diff --git a/dist/ba_data/python/bastd/tutorial.py b/dist/ba_data/python/bastd/tutorial.py index 6eb7816..4d424bb 100644 --- a/dist/ba_data/python/bastd/tutorial.py +++ b/dist/ba_data/python/bastd/tutorial.py @@ -23,10 +23,10 @@ import ba from bastd.actor import spaz as basespaz if TYPE_CHECKING: - from typing import Any, Optional, Callable, Sequence, Union + from typing import Any, Callable, Sequence -def _safesetattr(node: Optional[ba.Node], attr: str, value: Any) -> None: +def _safesetattr(node: ba.Node | None, attr: str, value: Any) -> None: if node: setattr(node, attr, value) @@ -46,9 +46,9 @@ class ButtonPress: def run(self, a: TutorialActivity) -> None: s = a.current_spaz assert s is not None - img: Optional[ba.Node] - release_call: Optional[Callable] - color: Optional[Sequence[float]] + img: ba.Node | None + release_call: Callable[[], None] | None + color: Sequence[float] | None if self._button == 'punch': call = s.on_punch_press release_call = s.on_punch_release @@ -124,9 +124,9 @@ class ButtonRelease: def run(self, a: TutorialActivity) -> None: s = a.current_spaz assert s is not None - call: Optional[Callable] - img: Optional[ba.Node] - color: Optional[Sequence[float]] + call: Callable[[], None] | None + img: ba.Node | None + color: Sequence[float] | None if self._button == 'punch': call = s.on_punch_release img = a.punch_image @@ -183,9 +183,9 @@ class TutorialActivity(ba.Activity[Player, Team]): if settings is None: settings = {} super().__init__(settings) - self.current_spaz: Optional[basespaz.Spaz] = None + self.current_spaz: basespaz.Spaz | None = None self._benchmark_type = getattr(ba.getsession(), 'benchmark_type', None) - self.last_start_time: Optional[int] = None + self.last_start_time: int | None = None self.cycle_times: list[int] = [] self.allow_pausing = True self.allow_kick_idle_players = False @@ -200,31 +200,31 @@ class TutorialActivity(ba.Activity[Player, Team]): self._have_skipped = False self.stick_image_position_x = self.stick_image_position_y = 0.0 self.spawn_sound = ba.getsound('spawn') - self.map: Optional[ba.Map] = None - self.text: Optional[ba.Node] = None - self._skip_text: Optional[ba.Node] = None - self._skip_count_text: Optional[ba.Node] = None - self._scale: Optional[float] = None + self.map: ba.Map | None = None + self.text: ba.Node | None = None + self._skip_text: ba.Node | None = None + self._skip_count_text: ba.Node | None = None + self._scale: float | None = None self._stick_base_position: tuple[float, float] = (0.0, 0.0) self._stick_nub_position: tuple[float, float] = (0.0, 0.0) self._stick_base_image_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0) self._stick_nub_image_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0) self._time: int = -1 self.punch_image_color = (1.0, 1.0, 1.0) - self.punch_image: Optional[ba.Node] = None - self.bomb_image: Optional[ba.Node] = None - self.jump_image: Optional[ba.Node] = None - self.pickup_image: Optional[ba.Node] = None - self._stick_base_image: Optional[ba.Node] = None - self._stick_nub_image: Optional[ba.Node] = None + self.punch_image: ba.Node | None = None + self.bomb_image: ba.Node | None = None + self.jump_image: ba.Node | None = None + self.pickup_image: ba.Node | None = None + self._stick_base_image: ba.Node | None = None + self._stick_nub_image: ba.Node | None = None self.bomb_image_color = (1.0, 1.0, 1.0) self.pickup_image_color = (1.0, 1.0, 1.0) self.control_ui_nodes: list[ba.Node] = [] self.spazzes: dict[int, basespaz.Spaz] = {} self.jump_image_color = (1.0, 1.0, 1.0) self._entries: list[Any] = [] - self._read_entries_timer: Optional[ba.Timer] = None - self._entry_timer: Optional[ba.Timer] = None + self._read_entries_timer: ba.Timer | None = None + self._entry_timer: ba.Timer | None = None def on_transition_in(self) -> None: super().on_transition_in() @@ -493,7 +493,7 @@ class TutorialActivity(ba.Activity[Player, Team]): color: Sequence[float] = (1.0, 1.0, 1.0), make_current: bool = False, relative_to: int = None, - name: Union[str, ba.Lstr] = '', + name: str | ba.Lstr = '', flash: bool = True, angle: float = 0.0): self._num = num @@ -752,7 +752,7 @@ class TutorialActivity(ba.Activity[Player, Team]): class Text: - def __init__(self, text: Union[str, ba.Lstr]): + def __init__(self, text: str | ba.Lstr): self.text = text def run(self, a: TutorialActivity) -> None: diff --git a/dist/ba_data/python/bastd/ui/account/link.py b/dist/ba_data/python/bastd/ui/account/link.py index 1e0b2b7..8c026a0 100644 --- a/dist/ba_data/python/bastd/ui/account/link.py +++ b/dist/ba_data/python/bastd/ui/account/link.py @@ -12,14 +12,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class AccountLinkWindow(ba.Window): """Window for linking accounts.""" def __init__(self, origin_widget: ba.Widget = None): - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/account/settings.py b/dist/ba_data/python/bastd/ui/account/settings.py index 901e0c8..f5b180f 100644 --- a/dist/ba_data/python/bastd/ui/account/settings.py +++ b/dist/ba_data/python/bastd/ui/account/settings.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Union + pass class AccountSettingsWindow(ba.Window): @@ -25,15 +25,15 @@ class AccountSettingsWindow(ba.Window): close_once_signed_in: bool = False): # pylint: disable=too-many-statements - self._sign_in_game_circle_button: Optional[ba.Widget] = None - self._sign_in_v2_button: Optional[ba.Widget] = None - self._sign_in_device_button: Optional[ba.Widget] = None + self._sign_in_game_circle_button: ba.Widget | None = None + self._sign_in_v2_button: ba.Widget | None = None + self._sign_in_device_button: ba.Widget | None = None self._close_once_signed_in = close_once_signed_in ba.set_analytics_screen('Account Window') # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -56,7 +56,7 @@ class AccountSettingsWindow(ba.Window): repeat=True) # Currently we can only reset achievements on game-center. - account_type: Optional[str] + account_type: str | None if self._signed_in: account_type = _ba.get_v1_account_type() else: @@ -145,7 +145,7 @@ class AccountSettingsWindow(ba.Window): claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None self._refresh() self._restore_state() @@ -344,7 +344,7 @@ class AccountSettingsWindow(ba.Window): v_align='center') v -= local_signed_in_as_space * 0.4 - self._account_name_text: Optional[ba.Widget] + self._account_name_text: ba.Widget | None if show_signed_in_as: v -= signed_in_as_space * 0.2 txt = ba.Lstr( @@ -383,7 +383,7 @@ class AccountSettingsWindow(ba.Window): if show_sign_in_benefits: v -= sign_in_benefits_space app = ba.app - extra: Optional[Union[str, ba.Lstr]] + extra: str | ba.Lstr | None if (app.platform in ['mac', 'ios'] and app.subplatform == 'appstore'): extra = ba.Lstr( @@ -616,7 +616,7 @@ class AccountSettingsWindow(ba.Window): else: self.game_service_button = None - self._achievements_text: Optional[ba.Widget] + self._achievements_text: ba.Widget | None if show_achievements_text: v -= achievements_text_space * 0.5 self._achievements_text = ba.textwidget( @@ -632,7 +632,7 @@ class AccountSettingsWindow(ba.Window): else: self._achievements_text = None - self._achievements_button: Optional[ba.Widget] + self._achievements_button: ba.Widget | None if show_achievements_button: button_width = 300 v -= achievements_button_space * 0.85 @@ -661,7 +661,7 @@ class AccountSettingsWindow(ba.Window): if show_achievements_text or show_achievements_button: self._refresh_achievements() - self._leaderboards_button: Optional[ba.Widget] + self._leaderboards_button: ba.Widget | None if show_leaderboards_button: button_width = 300 v -= leaderboards_button_space * 0.85 @@ -686,7 +686,7 @@ class AccountSettingsWindow(ba.Window): else: self._leaderboards_button = None - self._campaign_progress_text: Optional[ba.Widget] + self._campaign_progress_text: ba.Widget | None if show_campaign_progress: v -= campaign_progress_space * 0.5 self._campaign_progress_text = ba.textwidget( @@ -703,7 +703,7 @@ class AccountSettingsWindow(ba.Window): else: self._campaign_progress_text = None - self._tickets_text: Optional[ba.Widget] + self._tickets_text: ba.Widget | None if show_tickets: v -= tickets_space * 0.5 self._tickets_text = ba.textwidget(parent=self._subcontainer, @@ -753,7 +753,7 @@ class AccountSettingsWindow(ba.Window): right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) - self._linked_accounts_text: Optional[ba.Widget] + self._linked_accounts_text: ba.Widget | None if show_linked_accounts_text: v -= linked_accounts_text_space * 0.8 self._linked_accounts_text = ba.textwidget( @@ -808,7 +808,7 @@ class AccountSettingsWindow(ba.Window): right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) - self._unlink_accounts_button: Optional[ba.Widget] + self._unlink_accounts_button: ba.Widget | None if show_unlink_accounts_button: v -= unlink_accounts_button_space self._unlink_accounts_button = btn = ba.buttonwidget( @@ -957,7 +957,7 @@ class AccountSettingsWindow(ba.Window): from ba.internal import getcampaign if self._campaign_progress_text is None: return - p_str: Union[str, ba.Lstr] + p_str: str | ba.Lstr try: campaign = getcampaign('Default') levels = campaign.levels diff --git a/dist/ba_data/python/bastd/ui/account/unlink.py b/dist/ba_data/python/bastd/ui/account/unlink.py index 83e00dd..8e9955b 100644 --- a/dist/ba_data/python/bastd/ui/account/unlink.py +++ b/dist/ba_data/python/bastd/ui/account/unlink.py @@ -11,14 +11,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class AccountUnlinkWindow(ba.Window): """A window to kick off account unlinks.""" def __init__(self, origin_widget: ba.Widget = None): - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/account/v2.py b/dist/ba_data/python/bastd/ui/account/v2.py index e807050..1f9fea3 100644 --- a/dist/ba_data/python/bastd/ui/account/v2.py +++ b/dist/ba_data/python/bastd/ui/account/v2.py @@ -14,7 +14,7 @@ from efro.error import CommunicationError import bacommon.cloud if TYPE_CHECKING: - from typing import Union, Optional + pass STATUS_CHECK_INTERVAL_SECONDS = 2.0 @@ -25,8 +25,8 @@ class V2SignInWindow(ba.Window): def __init__(self, origin_widget: ba.Widget): self._width = 600 self._height = 550 - self._proxyid: Optional[str] = None - self._proxykey: Optional[str] = None + self._proxyid: str | None = None + self._proxykey: str | None = None uiscale = ba.app.ui.uiscale super().__init__(root_widget=ba.containerwidget( @@ -60,16 +60,15 @@ class V2SignInWindow(ba.Window): ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) - self._update_timer: Optional[ba.Timer] = None + self._update_timer: ba.Timer | None = None # Ask the cloud for a proxy login id. - ba.app.cloud.send_message(bacommon.cloud.LoginProxyRequestMessage(), - on_response=ba.WeakCall( - self._on_proxy_request_response)) + ba.app.cloud.send_message_cb(bacommon.cloud.LoginProxyRequestMessage(), + on_response=ba.WeakCall( + self._on_proxy_request_response)) def _on_proxy_request_response( - self, response: Union[bacommon.cloud.LoginProxyRequestResponse, - Exception] + self, response: bacommon.cloud.LoginProxyRequestResponse | Exception ) -> None: from ba.internal import is_browser_likely_available @@ -135,13 +134,13 @@ class V2SignInWindow(ba.Window): def _ask_for_status(self) -> None: assert self._proxyid is not None assert self._proxykey is not None - ba.app.cloud.send_message(bacommon.cloud.LoginProxyStateQueryMessage( - proxyid=self._proxyid, proxykey=self._proxykey), - on_response=ba.WeakCall(self._got_status)) + ba.app.cloud.send_message_cb( + bacommon.cloud.LoginProxyStateQueryMessage( + proxyid=self._proxyid, proxykey=self._proxykey), + on_response=ba.WeakCall(self._got_status)) def _got_status( - self, response: Union[bacommon.cloud.LoginProxyStateQueryResponse, - Exception] + self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception ) -> None: # For now, if anything goes wrong on the server-side, just abort @@ -163,7 +162,7 @@ class V2SignInWindow(ba.Window): # so it can clean up (not a huge deal if this fails) assert self._proxyid is not None try: - ba.app.cloud.send_message( + ba.app.cloud.send_message_cb( bacommon.cloud.LoginProxyCompleteMessage( proxyid=self._proxyid), on_response=ba.WeakCall(self._proxy_complete_response)) @@ -183,8 +182,7 @@ class V2SignInWindow(ba.Window): ba.timer(STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)) - def _proxy_complete_response(self, response: Union[None, - Exception]) -> None: + def _proxy_complete_response(self, response: None | Exception) -> None: del response # Not used. # We could do something smart like retry on exceptions here, but # this isn't critical so we'll just let anything slide. diff --git a/dist/ba_data/python/bastd/ui/account/viewer.py b/dist/ba_data/python/bastd/ui/account/viewer.py index 6464da2..549207e 100644 --- a/dist/ba_data/python/bastd/ui/account/viewer.py +++ b/dist/ba_data/python/bastd/ui/account/viewer.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class AccountViewerWindow(popup.PopupWindow): @@ -37,7 +37,7 @@ class AccountViewerWindow(popup.PopupWindow): self._width = 400 self._height = (300 if uiscale is ba.UIScale.SMALL else 400 if uiscale is ba.UIScale.MEDIUM else 450) - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None bg_color = (0.5, 0.4, 0.6) @@ -169,7 +169,7 @@ class AccountViewerWindow(popup.PopupWindow): ba.open_url(_ba.get_master_server_address() + '/highscores?profile=' + self._account_id) - def _on_query_response(self, data: Optional[dict[str, Any]]) -> None: + def _on_query_response(self, data: dict[str, Any] | None) -> None: # FIXME: Tidy this up. # pylint: disable=too-many-locals # pylint: disable=too-many-branches diff --git a/dist/ba_data/python/bastd/ui/appinvite.py b/dist/ba_data/python/bastd/ui/appinvite.py index 262a809..98ba818 100644 --- a/dist/ba_data/python/bastd/ui/appinvite.py +++ b/dist/ba_data/python/bastd/ui/appinvite.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class AppInviteWindow(ba.Window): @@ -20,7 +20,7 @@ class AppInviteWindow(ba.Window): def __init__(self) -> None: ba.set_analytics_screen('AppInviteWindow') - self._data: Optional[dict[str, Any]] = None + self._data: dict[str, Any] | None = None self._width = 650 self._height = 400 @@ -113,7 +113,7 @@ class AppInviteWindow(ba.Window): callback=ba.WeakCall(self._on_code_result)) _ba.run_transactions() - def _on_code_result(self, result: Optional[dict[str, Any]]) -> None: + def _on_code_result(self, result: dict[str, Any] | None) -> None: if result is not None: self._data = result @@ -202,7 +202,7 @@ class ShowFriendCodeWindow(ba.Window): text=data['code'], maxwidth=self._width * 0.85) - award_str: Optional[Union[str, ba.Lstr]] + award_str: str | ba.Lstr | None if self._data['awardTickets'] != 0: award_str = ba.Lstr( resource='gatherWindow.friendPromoCodeAwardText', @@ -317,7 +317,7 @@ def handle_app_invites_press(force_code: bool = False) -> None: ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), color=(0, 1, 0)) - def handle_result(result: Optional[dict[str, Any]]) -> None: + def handle_result(result: dict[str, Any] | None) -> None: with ba.Context('ui'): if result is None: ba.screenmessage(ba.Lstr(resource='errorText'), diff --git a/dist/ba_data/python/bastd/ui/colorpicker.py b/dist/ba_data/python/bastd/ui/colorpicker.py index 3f12ed0..1d7faa0 100644 --- a/dist/ba_data/python/bastd/ui/colorpicker.py +++ b/dist/ba_data/python/bastd/ui/colorpicker.py @@ -10,7 +10,7 @@ import ba from bastd.ui.popup import PopupWindow if TYPE_CHECKING: - from typing import Any, Sequence, Optional + from typing import Any, Sequence class ColorPicker(PopupWindow): @@ -182,8 +182,8 @@ class ColorPickerExact(PopupWindow): self._color = list(initial_color) self._last_press_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - self._last_press_color_name: Optional[str] = None - self._last_press_increasing: Optional[bool] = None + self._last_press_color_name: str | None = None + self._last_press_increasing: bool | None = None self._change_speed = 1.0 width = 180.0 height = 240.0 diff --git a/dist/ba_data/python/bastd/ui/config.py b/dist/ba_data/python/bastd/ui/config.py index d8567e5..fbd7720 100644 --- a/dist/ba_data/python/bastd/ui/config.py +++ b/dist/ba_data/python/bastd/ui/config.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Callable + from typing import Any, Callable class ConfigCheckBox: @@ -27,7 +27,7 @@ class ConfigCheckBox: configkey: str, position: tuple[float, float], size: tuple[float, float], - displayname: Union[str, ba.Lstr] = None, + displayname: str | ba.Lstr | None = None, scale: float = None, maxwidth: float = None, autoselect: bool = True, @@ -86,7 +86,7 @@ class ConfigNumberEdit: increment: float = 1.0, callback: Callable[[float], Any] = None, xoffset: float = 0.0, - displayname: Union[str, ba.Lstr] = None, + displayname: str | ba.Lstr | None = None, changesound: bool = True, textscale: float = 1.0): if displayname is None: diff --git a/dist/ba_data/python/bastd/ui/confirm.py b/dist/ba_data/python/bastd/ui/confirm.py index 37ed99a..3136a7c 100644 --- a/dist/ba_data/python/bastd/ui/confirm.py +++ b/dist/ba_data/python/bastd/ui/confirm.py @@ -10,14 +10,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Union, Callable, Optional + from typing import Any, Callable class ConfirmWindow: """Window for answering simple yes/no questions.""" def __init__(self, - text: Union[str, ba.Lstr] = 'Are you sure?', + text: str | ba.Lstr = 'Are you sure?', action: Callable[[], Any] = None, width: float = 360.0, height: float = 100.0, @@ -25,8 +25,8 @@ class ConfirmWindow: cancel_is_selected: bool = False, color: tuple[float, float, float] = (1, 1, 1), text_scale: float = 1.0, - ok_text: Union[str, ba.Lstr] = None, - cancel_text: Union[str, ba.Lstr] = None, + ok_text: str | ba.Lstr | None = None, + cancel_text: str | ba.Lstr | None = None, origin_widget: ba.Widget = None): # pylint: disable=too-many-locals if ok_text is None: @@ -38,8 +38,8 @@ class ConfirmWindow: self._action = action # if they provided an origin-widget, scale up from that - self._transition_out: Optional[str] - scale_origin: Optional[tuple[float, float]] + self._transition_out: str | None + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -70,7 +70,7 @@ class ConfirmWindow: maxwidth=width * 0.9, max_height=height - 75) - cbtn: Optional[ba.Widget] + cbtn: ba.Widget | None if cancel_button: cbtn = btn = ba.buttonwidget(parent=self.root_widget, autoselect=True, diff --git a/dist/ba_data/python/bastd/ui/continues.py b/dist/ba_data/python/bastd/ui/continues.py index 2635d90..861c860 100644 --- a/dist/ba_data/python/bastd/ui/continues.py +++ b/dist/ba_data/python/bastd/ui/continues.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class ContinuesWindow(ba.Window): @@ -74,8 +74,8 @@ class ContinuesWindow(ba.Window): t_left_width + t_price_width + 5, self._height - 30)) - self._tickets_text_base: Optional[str] - self._tickets_text: Optional[ba.Widget] + self._tickets_text_base: str | None + self._tickets_text: ba.Widget | None if not ba.app.ui.use_toolbars: self._tickets_text_base = ba.Lstr( resource='getTicketsWindow.youHaveShortText', diff --git a/dist/ba_data/python/bastd/ui/coop/browser.py b/dist/ba_data/python/bastd/ui/coop/browser.py index d118b50..aeabeaf 100644 --- a/dist/ba_data/python/bastd/ui/coop/browser.py +++ b/dist/ba_data/python/bastd/ui/coop/browser.py @@ -16,7 +16,7 @@ from bastd.ui.league.rankbutton import LeagueRankButton from bastd.ui.store.browser import StoreBrowserWindow if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class CoopBrowserWindow(ba.Window): @@ -36,7 +36,7 @@ class CoopBrowserWindow(ba.Window): (4 if uiscale is ba.UIScale.SMALL else 0))) def __init__(self, - transition: Optional[str] = 'in_right', + transition: str | None = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=cyclic-import @@ -62,7 +62,7 @@ class CoopBrowserWindow(ba.Window): timetype=ba.TimeType.REAL) # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -76,10 +76,10 @@ class CoopBrowserWindow(ba.Window): self._tournament_button_count = app.config.get('Tournament Rows', 0) assert isinstance(self._tournament_button_count, int) - self._easy_button: Optional[ba.Widget] = None - self._hard_button: Optional[ba.Widget] = None - self._hard_button_lock_image: Optional[ba.Widget] = None - self._campaign_percent_text: Optional[ba.Widget] = None + self._easy_button: ba.Widget | None = None + self._hard_button: ba.Widget | None = None + self._hard_button_lock_image: ba.Widget | None = None + self._campaign_percent_text: ba.Widget | None = None uiscale = ba.app.ui.uiscale self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120 @@ -118,10 +118,10 @@ class CoopBrowserWindow(ba.Window): label=ba.Lstr(resource='backText'), button_type='back') - self._league_rank_button: Optional[LeagueRankButton] - self._store_button: Optional[StoreButton] - self._store_button_widget: Optional[ba.Widget] - self._league_rank_button_widget: Optional[ba.Widget] + self._league_rank_button: LeagueRankButton | None + self._store_button: StoreButton | None + self._store_button_widget: ba.Widget | None + self._league_rank_button_widget: ba.Widget | None if not app.ui.use_toolbars: prb = self._league_rank_button = LeagueRankButton( @@ -167,8 +167,8 @@ class CoopBrowserWindow(ba.Window): repeat=True, timetype=ba.TimeType.REAL) - self._last_tournament_query_time: Optional[float] = None - self._last_tournament_query_response_time: Optional[float] = None + self._last_tournament_query_time: float | None = None + self._last_tournament_query_response_time: float | None = None self._doing_tournament_query = False self._selected_campaign_level = (cfg.get( @@ -232,7 +232,7 @@ class CoopBrowserWindow(ba.Window): claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None # Take note of our account state; we'll refresh later if this changes. self._account_state_num = _ba.get_v1_account_state_num() @@ -362,7 +362,7 @@ class CoopBrowserWindow(ba.Window): except Exception: ba.print_exception('Error updating campaign lock.') - def _update_for_data(self, data: Optional[list[dict[str, Any]]]) -> None: + def _update_for_data(self, data: list[dict[str, Any]] | None) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=too-many-branches @@ -444,7 +444,7 @@ class CoopBrowserWindow(ba.Window): 93 - 90 + prize_y_offs)) leader_name = '-' - leader_score: Union[str, ba.Lstr] = '-' + leader_score: str | ba.Lstr = '-' if entry['scores']: score = tbtn['leader'] = copy.deepcopy(entry['scores'][0]) leader_name = score[1] @@ -464,7 +464,7 @@ class CoopBrowserWindow(ba.Window): text=leader_score) ba.buttonwidget(edit=tbtn['more_scores_button'], label=ba.Lstr(resource=self._r + '.seeMoreText')) - out_of_time_text: Union[str, ba.Lstr] = ( + out_of_time_text: str | ba.Lstr = ( '-' if 'totalTime' not in entry else ba.Lstr( resource=self._r + '.ofTotalTimeText', subs=[('${TOTAL}', @@ -524,11 +524,11 @@ class CoopBrowserWindow(ba.Window): tbtn['allow_ads'] = allow_ads = entry['allowAds'] - final_fee: Optional[int] = (None if fee_var is None else - _ba.get_v1_account_misc_read_val( - fee_var, '?')) + final_fee: int | None = (None if fee_var is None else + _ba.get_v1_account_misc_read_val( + fee_var, '?')) - final_fee_str: Union[str, ba.Lstr] + final_fee_str: str | ba.Lstr if fee_var is None: final_fee_str = '' else: @@ -590,8 +590,8 @@ class CoopBrowserWindow(ba.Window): ('' + str(free_tries_remaining))), color=(0.6, 0.6, 0.6, 1)) - def _on_tournament_query_response(self, data: Optional[dict[str, - Any]]) -> None: + def _on_tournament_query_response(self, + data: dict[str, Any] | None) -> None: accounts = ba.app.accounts_v1 if data is not None: tournament_data = data['t'] # This used to be the whole payload. @@ -1361,8 +1361,8 @@ class CoopBrowserWindow(ba.Window): def _switch_to_score( self, - show_tab: Optional[ - StoreBrowserWindow.TabID] = StoreBrowserWindow.TabID.EXTRAS + show_tab: StoreBrowserWindow.TabID + | None = StoreBrowserWindow.TabID.EXTRAS ) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt @@ -1414,7 +1414,7 @@ class CoopBrowserWindow(ba.Window): return self._tourney_data_up_to_date def run(self, - game: Optional[str], + game: str | None, tournament_button: dict[str, Any] = None) -> None: """Run the provided game.""" # pylint: disable=too-many-branches @@ -1491,7 +1491,7 @@ class CoopBrowserWindow(ba.Window): PurchaseWindow(items=['pro']) return - required_purchase: Optional[str] + required_purchase: str | None if game in ['Challenges:Meteor Shower']: required_purchase = 'games.meteor_shower' elif game in [ diff --git a/dist/ba_data/python/bastd/ui/coop/gamebutton.py b/dist/ba_data/python/bastd/ui/coop/gamebutton.py index 026f4ce..40ff1cf 100644 --- a/dist/ba_data/python/bastd/ui/coop/gamebutton.py +++ b/dist/ba_data/python/bastd/ui/coop/gamebutton.py @@ -11,7 +11,6 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional from bastd.ui.coop.browser import CoopBrowserWindow @@ -35,7 +34,7 @@ class GameButton: if game == 'Easy:The Last Stand': campaignname = 'Default' - rating: Optional[float] + rating: float | None campaign = getcampaign(campaignname) rating = campaign.getlevel(levelname).rating diff --git a/dist/ba_data/python/bastd/ui/creditslist.py b/dist/ba_data/python/bastd/ui/creditslist.py index d6a37c8..722fd7a 100644 --- a/dist/ba_data/python/bastd/ui/creditslist.py +++ b/dist/ba_data/python/bastd/ui/creditslist.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Sequence + from typing import Sequence class CreditsListWindow(ba.Window): @@ -23,7 +23,7 @@ class CreditsListWindow(ba.Window): ba.set_analytics_screen('Credits Window') # if they provided an origin-widget, scale up from that - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/feedback.py b/dist/ba_data/python/bastd/ui/feedback.py index 7d4db7c..6999cca 100644 --- a/dist/ba_data/python/bastd/ui/feedback.py +++ b/dist/ba_data/python/bastd/ui/feedback.py @@ -9,10 +9,10 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional + pass -def ask_for_rating() -> Optional[ba.Widget]: +def ask_for_rating() -> ba.Widget | None: """(internal)""" app = ba.app platform = app.platform diff --git a/dist/ba_data/python/bastd/ui/fileselector.py b/dist/ba_data/python/bastd/ui/fileselector.py index e48b456..8af14da 100644 --- a/dist/ba_data/python/bastd/ui/fileselector.py +++ b/dist/ba_data/python/bastd/ui/fileselector.py @@ -13,7 +13,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Sequence, Optional + from typing import Any, Callable, Sequence class FileSelectorWindow(ba.Window): @@ -21,7 +21,7 @@ class FileSelectorWindow(ba.Window): def __init__(self, path: str, - callback: Callable[[Optional[str]], Any] = None, + callback: Callable[[str | None], Any] = None, show_base_path: bool = True, valid_file_extensions: Sequence[str] = None, allow_folders: bool = False): @@ -33,15 +33,15 @@ class FileSelectorWindow(ba.Window): self._height = 365 if uiscale is ba.UIScale.SMALL else 418 self._callback = callback self._base_path = path - self._path: Optional[str] = None + self._path: str | None = None self._recent_paths: list[str] = [] self._show_base_path = show_base_path self._valid_file_extensions = [ '.' + ext for ext in valid_file_extensions ] self._allow_folders = allow_folders - self._subcontainer: Optional[ba.Widget] = None - self._subcontainerheight: Optional[float] = None + self._subcontainer: ba.Widget | None = None + self._subcontainerheight: float | None = None self._scroll_width = self._width - (80 + 2 * x_inset) self._scroll_height = self._height - 170 self._r = 'fileSelectorWindow' @@ -92,7 +92,7 @@ class FileSelectorWindow(ba.Window): self._folder_color = (1.1, 0.8, 0.2) self._file_tex = ba.gettexture('file') self._file_color = (1, 1, 1) - self._use_folder_button: Optional[ba.Widget] = None + self._use_folder_button: ba.Widget | None = None self._folder_center = self._width * 0.5 + 15 self._folder_icon = ba.imagewidget(parent=self._root_widget, size=(40, 40), @@ -108,7 +108,7 @@ class FileSelectorWindow(ba.Window): v_align='center', text=self._path, maxwidth=self._width * 0.9) - self._scrollwidget: Optional[ba.Widget] = None + self._scrollwidget: ba.Widget | None = None ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) self._set_path(path) @@ -173,7 +173,7 @@ class FileSelectorWindow(ba.Window): class _RefreshThread(threading.Thread): def __init__(self, path: str, - callback: Callable[[list[str], Optional[str]], Any]): + callback: Callable[[list[str], str | None], Any]): super().__init__() self._callback = callback self._path = path @@ -205,7 +205,7 @@ class FileSelectorWindow(ba.Window): self._recent_paths.append(path) self._RefreshThread(path, self._refresh).start() - def _refresh(self, file_names: list[str], error: Optional[str]) -> None: + def _refresh(self, file_names: list[str], error: str | None) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals diff --git a/dist/ba_data/python/bastd/ui/gather/__init__.py b/dist/ba_data/python/bastd/ui/gather/__init__.py index c8de2ce..2eaa49c 100644 --- a/dist/ba_data/python/bastd/ui/gather/__init__.py +++ b/dist/ba_data/python/bastd/ui/gather/__init__.py @@ -13,7 +13,7 @@ import ba from bastd.ui.tabs import TabRow if TYPE_CHECKING: - from typing import Optional + pass class GatherTab: @@ -67,7 +67,7 @@ class GatherWindow(ba.Window): MANUAL = 'manual' def __init__(self, - transition: Optional[str] = 'in_right', + transition: str | None = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals @@ -79,7 +79,7 @@ class GatherWindow(ba.Window): from bastd.ui.gather.nearbytab import NearbyGatherTab ba.set_analytics_screen('Gather Window') - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -94,7 +94,7 @@ class GatherWindow(ba.Window): x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (582 if uiscale is ba.UIScale.SMALL else 680 if uiscale is ba.UIScale.MEDIUM else 800) - self._current_tab: Optional[GatherWindow.TabID] = None + self._current_tab: GatherWindow.TabID | None = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 self._r = 'gatherWindow' @@ -209,7 +209,7 @@ class GatherWindow(ba.Window): self._scroll_height + 2 * buffer_v), texture=ba.gettexture('scrollWidget'), model_transparent=ba.getmodel('softEdgeOutside')) - self._tab_container: Optional[ba.Widget] = None + self._tab_container: ba.Widget | None = None self._restore_state() @@ -291,7 +291,7 @@ class GatherWindow(ba.Window): for tab in self._tabs.values(): tab.restore_state() - sel: Optional[ba.Widget] + sel: ba.Widget | None winstate = ba.app.ui.window_states.get(type(self), {}) sel_name = winstate.get('sel_name', None) assert isinstance(sel_name, (str, type(None))) diff --git a/dist/ba_data/python/bastd/ui/gather/abouttab.py b/dist/ba_data/python/bastd/ui/gather/abouttab.py index 87e0937..a782e6c 100644 --- a/dist/ba_data/python/bastd/ui/gather/abouttab.py +++ b/dist/ba_data/python/bastd/ui/gather/abouttab.py @@ -11,7 +11,6 @@ import _ba from bastd.ui.gather import GatherTab if TYPE_CHECKING: - from typing import Optional from bastd.ui.gather import GatherWindow @@ -20,7 +19,7 @@ class AboutGatherTab(GatherTab): def __init__(self, window: GatherWindow) -> None: super().__init__(window) - self._container: Optional[ba.Widget] = None + self._container: ba.Widget | None = None def on_activate( self, diff --git a/dist/ba_data/python/bastd/ui/gather/manualtab.py b/dist/ba_data/python/bastd/ui/gather/manualtab.py index fe0a706..0f2053a 100644 --- a/dist/ba_data/python/bastd/ui/gather/manualtab.py +++ b/dist/ba_data/python/bastd/ui/gather/manualtab.py @@ -15,12 +15,12 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Union, Callable + from typing import Any, Callable from bastd.ui.gather import GatherWindow -def _safe_set_text(txt: Optional[ba.Widget], - val: Union[str, ba.Lstr], +def _safe_set_text(txt: ba.Widget | None, + val: str | ba.Lstr, success: bool = True) -> None: if txt: ba.textwidget(edit=txt, @@ -31,15 +31,15 @@ def _safe_set_text(txt: Optional[ba.Widget], class _HostLookupThread(threading.Thread): """Thread to fetch an addr.""" - def __init__(self, name: str, port: int, - call: Callable[[Optional[str], int], Any]): + def __init__(self, name: str, port: int, call: Callable[[str | None, int], + Any]): super().__init__() self._name = name self._port = port self._call = call def run(self) -> None: - result: Optional[str] + result: str | None try: import socket result = socket.gethostbyname(self._name) @@ -66,31 +66,31 @@ class ManualGatherTab(GatherTab): def __init__(self, window: GatherWindow) -> None: super().__init__(window) - self._check_button: Optional[ba.Widget] = None - self._doing_access_check: Optional[bool] = None - self._access_check_count: Optional[int] = None + self._check_button: ba.Widget | None = None + self._doing_access_check: bool | None = None + self._access_check_count: int | None = None self._sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS - self._t_addr: Optional[ba.Widget] = None - self._t_accessible: Optional[ba.Widget] = None - self._t_accessible_extra: Optional[ba.Widget] = None - self._access_check_timer: Optional[ba.Timer] = None - self._checking_state_text: Optional[ba.Widget] = None - self._container: Optional[ba.Widget] = None - self._join_by_address_text: Optional[ba.Widget] = None - self._favorites_text: Optional[ba.Widget] = None - self._width: Optional[int] = None - self._height: Optional[int] = None - self._scroll_width: Optional[int] = None - self._scroll_height: Optional[int] = None - self._favorites_scroll_width: Optional[int] = None - self._favorites_connect_button: Optional[ba.Widget] = None - self._scrollwidget: Optional[ba.Widget] = None - self._columnwidget: Optional[ba.Widget] = None - self._favorite_selected: Optional[str] = None - self._favorite_edit_window: Optional[ba.Widget] = None - self._party_edit_name_text: Optional[ba.Widget] = None - self._party_edit_addr_text: Optional[ba.Widget] = None - self._party_edit_port_text: Optional[ba.Widget] = None + self._t_addr: ba.Widget | None = None + self._t_accessible: ba.Widget | None = None + self._t_accessible_extra: ba.Widget | None = None + self._access_check_timer: ba.Timer | None = None + self._checking_state_text: ba.Widget | None = None + self._container: ba.Widget | None = None + self._join_by_address_text: ba.Widget | None = None + self._favorites_text: ba.Widget | None = None + self._width: int | None = None + self._height: int | None = None + self._scroll_width: int | None = None + self._scroll_height: int | None = None + self._favorites_scroll_width: int | None = None + self._favorites_connect_button: ba.Widget | None = None + self._scrollwidget: ba.Widget | None = None + self._columnwidget: ba.Widget | None = None + self._favorite_selected: str | None = None + self._favorite_edit_window: ba.Widget | None = None + self._party_edit_name_text: ba.Widget | None = None + self._party_edit_addr_text: ba.Widget | None = None + self._party_edit_port_text: ba.Widget | None = None def on_activate( self, @@ -674,7 +674,7 @@ class ManualGatherTab(GatherTab): ba.screenmessage('Invalid Address', color=(1, 0, 0)) ba.playsound(ba.getsound('error')) - def _host_lookup_result(self, resolved_address: Optional[str], + def _host_lookup_result(self, resolved_address: str | None, port: int) -> None: if resolved_address is None: ba.screenmessage( @@ -723,7 +723,7 @@ class ManualGatherTab(GatherTab): from_other_thread=True) def _on_show_my_address_button_press(self, v2: float, - container: Optional[ba.Widget], + container: ba.Widget | None, c_width: float) -> None: if not container: return @@ -849,7 +849,7 @@ class ManualGatherTab(GatherTab): callback=ba.WeakCall( self._on_accessible_response)) - def _on_accessible_response(self, data: Optional[dict[str, Any]]) -> None: + def _on_accessible_response(self, data: dict[str, Any] | None) -> None: t_addr = self._t_addr t_accessible = self._t_accessible t_accessible_extra = self._t_accessible_extra diff --git a/dist/ba_data/python/bastd/ui/gather/nearbytab.py b/dist/ba_data/python/bastd/ui/gather/nearbytab.py index 03561cb..4622d5a 100644 --- a/dist/ba_data/python/bastd/ui/gather/nearbytab.py +++ b/dist/ba_data/python/bastd/ui/gather/nearbytab.py @@ -12,7 +12,7 @@ import _ba from bastd.ui.gather import GatherTab if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any from bastd.ui.gather import GatherWindow @@ -30,7 +30,7 @@ class NetScanner: left_border=10) ba.widget(edit=self._columnwidget, up_widget=tab_button) self._width = width - self._last_selected_host: Optional[dict[str, Any]] = None + self._last_selected_host: dict[str, Any] | None = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self.update), @@ -93,8 +93,8 @@ class NearbyGatherTab(GatherTab): def __init__(self, window: GatherWindow) -> None: super().__init__(window) - self._net_scanner: Optional[NetScanner] = None - self._container: Optional[ba.Widget] = None + self._net_scanner: NetScanner | None = None + self._container: ba.Widget | None = None def on_activate( self, diff --git a/dist/ba_data/python/bastd/ui/gather/privatetab.py b/dist/ba_data/python/bastd/ui/gather/privatetab.py index b1d047f..4eac79e 100644 --- a/dist/ba_data/python/bastd/ui/gather/privatetab.py +++ b/dist/ba_data/python/bastd/ui/gather/privatetab.py @@ -20,7 +20,7 @@ from bastd.ui.gather import GatherTab from bastd.ui import getcurrency if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any from bastd.ui.gather import GatherWindow # Print a bit of info about queries, etc. @@ -44,28 +44,28 @@ class PrivateGatherTab(GatherTab): def __init__(self, window: GatherWindow) -> None: super().__init__(window) - self._container: Optional[ba.Widget] = None + self._container: ba.Widget | None = None self._state: State = State() self._hostingstate = PrivateHostingState() - self._join_sub_tab_text: Optional[ba.Widget] = None - self._host_sub_tab_text: Optional[ba.Widget] = None - self._update_timer: Optional[ba.Timer] = None - self._join_party_code_text: Optional[ba.Widget] = None + self._join_sub_tab_text: ba.Widget | None = None + self._host_sub_tab_text: ba.Widget | None = None + self._update_timer: ba.Timer | None = None + self._join_party_code_text: ba.Widget | None = None self._c_width: float = 0.0 self._c_height: float = 0.0 - self._last_hosting_state_query_time: Optional[float] = None + self._last_hosting_state_query_time: float | None = None self._waiting_for_initial_state = True self._waiting_for_start_stop_response = True - self._host_playlist_button: Optional[ba.Widget] = None - self._host_copy_button: Optional[ba.Widget] = None - self._host_connect_button: Optional[ba.Widget] = None - self._host_start_stop_button: Optional[ba.Widget] = None - self._get_tickets_button: Optional[ba.Widget] = None - self._ticket_count_text: Optional[ba.Widget] = None + self._host_playlist_button: ba.Widget | None = None + self._host_copy_button: ba.Widget | None = None + self._host_connect_button: ba.Widget | None = None + self._host_start_stop_button: ba.Widget | None = None + self._get_tickets_button: ba.Widget | None = None + self._ticket_count_text: ba.Widget | None = None self._showing_not_signed_in_screen = False self._create_time = time.time() - self._last_action_send_time: Optional[float] = None - self._connect_press_time: Optional[float] = None + self._last_action_send_time: float | None = None + self._connect_press_time: float | None = None try: self._hostingconfig = self._build_hosting_config() except Exception: @@ -177,7 +177,7 @@ class PrivateGatherTab(GatherTab): if playlist_name == '__default__' else playlist_name) - playlist: Optional[list[dict[str, Any]]] = None + playlist: list[dict[str, Any]] | None = None if playlist_name != '__default__': playlist = (cfg.get(f'{pvars.config_name} Playlists', {}).get(playlist_name)) @@ -197,7 +197,7 @@ class PrivateGatherTab(GatherTab): hcfg.tutorial = tutorial if hcfg.session_type == 'teams': - ctn: Optional[list[str]] = cfg.get('Custom Team Names') + ctn: list[str] | None = cfg.get('Custom Team Names') if ctn is not None: if (isinstance(ctn, (list, tuple)) and len(ctn) == 2 and all(isinstance(x, str) for x in ctn)): @@ -205,7 +205,7 @@ class PrivateGatherTab(GatherTab): else: print(f'Found invalid custom-team-names data: {ctn}') - ctc: Optional[list[list[float]]] = cfg.get('Custom Team Colors') + ctc: list[list[float]] | None = cfg.get('Custom Team Colors') if ctc is not None: if (isinstance(ctc, (list, tuple)) and len(ctc) == 2 and all(isinstance(x, (list, tuple)) for x in ctc) @@ -269,7 +269,7 @@ class PrivateGatherTab(GatherTab): self._last_hosting_state_query_time = now def _hosting_state_idle_response(self, - result: Optional[dict[str, Any]]) -> None: + result: dict[str, Any] | None) -> None: # This simply passes through to our standard response handler. # The one exception is if we've recently sent an action to the @@ -284,15 +284,14 @@ class PrivateGatherTab(GatherTab): return self._hosting_state_response(result) - def _hosting_state_response(self, result: Optional[dict[str, - Any]]) -> None: + def _hosting_state_response(self, result: dict[str, Any] | None) -> None: # Its possible for this to come back to us after our UI is dead; # ignore in that case. if not self._container: return - state: Optional[PrivateHostingState] = None + state: PrivateHostingState | None = None if result is not None: self._debug_server_comm('got private party state response') try: @@ -344,7 +343,7 @@ class PrivateGatherTab(GatherTab): # Kick off an update to get any needed messages sent/etc. ba.pushcall(self._update) - def _selwidgets(self) -> list[Optional[ba.Widget]]: + def _selwidgets(self) -> list[ba.Widget | None]: """An indexed list of widgets we can use for saving/restoring sel.""" return [ self._host_playlist_button, self._host_copy_button, @@ -357,7 +356,7 @@ class PrivateGatherTab(GatherTab): # Store an index for our current selection so we can # reselect the equivalent recreated widget if possible. - selindex: Optional[int] = None + selindex: int | None = None selchild = self._container.get_selected_child() if selchild is not None: try: @@ -793,7 +792,7 @@ class PrivateGatherTab(GatherTab): # If there's a ticket cost, make sure we have enough tickets. if self._hostingstate.tickets_to_host_now > 0: - ticket_count: Optional[int] + ticket_count: int | None try: ticket_count = _ba.get_v1_account_ticket_count() except Exception: @@ -832,7 +831,7 @@ class PrivateGatherTab(GatherTab): def _join_connect_press(self) -> None: # Error immediately if its an empty code. - code: Optional[str] = None + code: str | None = None if self._join_party_code_text: code = cast(str, ba.textwidget(query=self._join_party_code_text)) if not code: @@ -844,7 +843,7 @@ class PrivateGatherTab(GatherTab): self._connect_to_party_code(code) - def _connect_response(self, result: Optional[dict[str, Any]]) -> None: + def _connect_response(self, result: dict[str, Any] | None) -> None: try: self._connect_press_time = None if result is None: diff --git a/dist/ba_data/python/bastd/ui/gather/publictab.py b/dist/ba_data/python/bastd/ui/gather/publictab.py index 0261cef..5eaa282 100644 --- a/dist/ba_data/python/bastd/ui/gather/publictab.py +++ b/dist/ba_data/python/bastd/ui/gather/publictab.py @@ -17,7 +17,7 @@ import ba from bastd.ui.gather import GatherTab if TYPE_CHECKING: - from typing import Callable, Any, Optional, Union + from typing import Callable, Any from bastd.ui.gather import GatherWindow # Print a bit of info about pings, queries, etc. @@ -36,19 +36,19 @@ class PartyEntry: """Info about a public party.""" address: str index: int - queue: Optional[str] = None + queue: str | None = None port: int = -1 name: str = '' size: int = -1 size_max: int = -1 claimed: bool = False - ping: Optional[float] = None + ping: float | None = None ping_interval: float = -1.0 next_ping_time: float = -1.0 ping_attempts: int = 0 ping_responses: int = 0 - stats_addr: Optional[str] = None - clean_display_index: Optional[int] = None + stats_addr: str | None = None + clean_display_index: int | None = None def get_key(self) -> str: """Return the key used to store this party.""" @@ -59,10 +59,10 @@ class UIRow: """Wrangles UI for a row in the party list.""" def __init__(self) -> None: - self._name_widget: Optional[ba.Widget] = None - self._size_widget: Optional[ba.Widget] = None - self._ping_widget: Optional[ba.Widget] = None - self._stats_button: Optional[ba.Widget] = None + self._name_widget: ba.Widget | None = None + self._size_widget: ba.Widget | None = None + self._ping_widget: ba.Widget | None = None + self._stats_button: ba.Widget | None = None def __del__(self) -> None: self._clear() @@ -78,7 +78,7 @@ class UIRow: def update(self, index: int, party: PartyEntry, sub_scroll_width: float, sub_scroll_height: float, lineheight: float, columnwidget: ba.Widget, join_text: ba.Widget, - filter_text: ba.Widget, existing_selection: Optional[Selection], + filter_text: ba.Widget, existing_selection: Selection | None, tab: PublicGatherTab) -> None: """Update for the given data.""" # pylint: disable=too-many-locals @@ -182,7 +182,7 @@ class UIRow: class State: """State saved/restored only while the app is running.""" sub_tab: SubTabType = SubTabType.JOIN - parties: Optional[list[tuple[str, PartyEntry]]] = None + parties: list[tuple[str, PartyEntry]] | None = None next_entry_index: int = 0 filter_value: str = '' have_server_list_response: bool = False @@ -231,7 +231,7 @@ class PingThread(threading.Thread): """Thread for sending out game pings.""" def __init__(self, address: str, port: int, - call: Callable[[str, int, Optional[float]], Optional[int]]): + call: Callable[[str, int, float | None], int | None]): super().__init__() self._address = address self._port = port @@ -239,7 +239,7 @@ class PingThread(threading.Thread): def run(self) -> None: ba.app.ping_thread_count += 1 - sock: Optional[socket.socket] = None + sock: socket.socket | None = None try: import socket from ba.internal import get_ip_address_type @@ -255,7 +255,7 @@ class PingThread(threading.Thread): sock.settimeout(1) for _i in range(3): sock.send(b'\x0b') - result: Optional[bytes] + result: bytes | None try: # 11: BA_PACKET_SIMPLE_PING result = sock.recv(10) @@ -291,31 +291,31 @@ class PublicGatherTab(GatherTab): def __init__(self, window: GatherWindow) -> None: super().__init__(window) - self._container: Optional[ba.Widget] = None - self._join_text: Optional[ba.Widget] = None - self._host_text: Optional[ba.Widget] = None - self._filter_text: Optional[ba.Widget] = None - self._local_address: Optional[str] = None - self._last_connect_attempt_time: Optional[float] = None + self._container: ba.Widget | None = None + self._join_text: ba.Widget | None = None + self._host_text: ba.Widget | None = None + self._filter_text: ba.Widget | None = None + self._local_address: str | None = None + self._last_connect_attempt_time: float | None = None self._sub_tab: SubTabType = SubTabType.JOIN - self._selection: Optional[Selection] = None + self._selection: Selection | None = None self._refreshing_list = False - self._update_timer: Optional[ba.Timer] = None - self._host_scrollwidget: Optional[ba.Widget] = None - self._host_name_text: Optional[ba.Widget] = None - self._host_toggle_button: Optional[ba.Widget] = None - self._last_server_list_query_time: Optional[float] = None - self._join_list_column: 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._update_timer: ba.Timer | None = None + self._host_scrollwidget: ba.Widget | None = None + self._host_name_text: ba.Widget | None = None + self._host_toggle_button: ba.Widget | None = None + self._last_server_list_query_time: float | None = None + self._join_list_column: ba.Widget | None = None + self._join_status_text: ba.Widget | None = None + self._host_max_party_size_value: ba.Widget | None = None + self._host_max_party_size_minus_button: (ba.Widget | None) = None + self._host_max_party_size_plus_button: (ba.Widget | None) = None + self._host_status_text: ba.Widget | None = None self._signed_in = False self._ui_rows: list[UIRow] = [] self._refresh_ui_row = 0 self._have_user_selected_row = False - self._first_valid_server_list_time: Optional[float] = None + self._first_valid_server_list_time: float | None = None # Parties indexed by id: self._parties: dict[str, PartyEntry] = {} @@ -714,8 +714,8 @@ class PublicGatherTab(GatherTab): if _ba.get_public_party_enabled(): self._do_status_check() - def _on_public_party_query_result( - self, result: Optional[dict[str, Any]]) -> None: + def _on_public_party_query_result(self, + result: dict[str, Any] | None) -> None: starttime = time.time() self._have_server_list_response = True @@ -1071,8 +1071,8 @@ class PublicGatherTab(GatherTab): PingThread(party.address, party.port, ba.WeakCall(self._ping_callback)).start() - def _ping_callback(self, address: str, port: Optional[int], - result: Optional[float]) -> None: + def _ping_callback(self, address: str, port: int | None, + result: float | None) -> None: # Look for a widget corresponding to this target. # If we find one, update our list. party_key = f'{address}_{port}' @@ -1100,7 +1100,7 @@ class PublicGatherTab(GatherTab): self._local_address = str(val) def _on_public_party_accessible_response( - self, data: Optional[dict[str, Any]]) -> None: + self, data: dict[str, Any] | None) -> None: # If we've got status text widgets, update them. text = self._host_status_text @@ -1114,7 +1114,7 @@ class PublicGatherTab(GatherTab): ) else: if not data.get('accessible', False): - ex_line: Union[str, ba.Lstr] + ex_line: str | ba.Lstr if self._local_address is not None: ex_line = ba.Lstr( value='\n${A} ${B}', diff --git a/dist/ba_data/python/bastd/ui/getcurrency.py b/dist/ba_data/python/bastd/ui/getcurrency.py index 040d1c1..e0ef860 100644 --- a/dist/ba_data/python/bastd/ui/getcurrency.py +++ b/dist/ba_data/python/bastd/ui/getcurrency.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class GetCurrencyWindow(ba.Window): @@ -31,14 +31,14 @@ class GetCurrencyWindow(ba.Window): self._store_back_location = store_back_location # ew. self._ad_button_greyed = False - self._smooth_update_timer: Optional[ba.Timer] = None + self._smooth_update_timer: ba.Timer | None = None self._ad_button = None self._ad_label = None self._ad_image = None self._ad_time_text = None # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -372,8 +372,8 @@ class GetCurrencyWindow(ba.Window): scale=0.8) # update count now and once per second going forward.. - self._ticking_node: Optional[ba.Node] = None - self._smooth_ticket_count: Optional[float] = None + self._ticking_node: ba.Node | None = None + self._smooth_ticket_count: float | None = None self._ticket_count = 0 self._update() self._update_timer = ba.Timer(1.0, @@ -460,7 +460,7 @@ class GetCurrencyWindow(ba.Window): ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2)) ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2)) ba.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25) - sval: Union[str, ba.Lstr] + sval: str | ba.Lstr if (next_reward_ad_time is not None and next_reward_ad_time > now): sval = ba.timestring( @@ -533,7 +533,7 @@ class GetCurrencyWindow(ba.Window): item)) def _purchase_check_result(self, item: str, - result: Optional[dict[str, Any]]) -> None: + result: dict[str, Any] | None) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( diff --git a/dist/ba_data/python/bastd/ui/helpui.py b/dist/ba_data/python/bastd/ui/helpui.py index 77594b0..09bc4d5 100644 --- a/dist/ba_data/python/bastd/ui/helpui.py +++ b/dist/ba_data/python/bastd/ui/helpui.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class HelpWindow(ba.Window): @@ -25,7 +25,7 @@ class HelpWindow(ba.Window): ba.set_analytics_screen('Help Window') # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/kiosk.py b/dist/ba_data/python/bastd/ui/kiosk.py index 09f92aa..c748b60 100644 --- a/dist/ba_data/python/bastd/ui/kiosk.py +++ b/dist/ba_data/python/bastd/ui/kiosk.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class KioskWindow(ba.Window): @@ -182,9 +182,9 @@ class KioskWindow(ba.Window): if not ba.app.did_menu_intro: ba.app.did_menu_intro = True - self._b4: Optional[ba.Widget] - self._b5: Optional[ba.Widget] - self._b6: Optional[ba.Widget] + self._b4: ba.Widget | None + self._b5: ba.Widget | None + self._b6: ba.Widget | None if bool(False): ba.textwidget( @@ -293,7 +293,7 @@ class KioskWindow(ba.Window): else: self._b4 = self._b5 = self._b6 = None - self._b7: Optional[ba.Widget] + self._b7: ba.Widget | None if ba.app.arcade_mode: self._b7 = ba.buttonwidget( parent=self._root_widget, @@ -317,7 +317,7 @@ class KioskWindow(ba.Window): def _restore_state(self) -> None: sel_name = ba.app.ui.window_states.get(type(self)) - sel: Optional[ba.Widget] + sel: ba.Widget | None if sel_name == 'b1': sel = self._b1 elif sel_name == 'b2': diff --git a/dist/ba_data/python/bastd/ui/league/rankbutton.py b/dist/ba_data/python/bastd/ui/league/rankbutton.py index 3a6ffa5..9b62fc1 100644 --- a/dist/ba_data/python/bastd/ui/league/rankbutton.py +++ b/dist/ba_data/python/bastd/ui/league/rankbutton.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Callable, Union + from typing import Any, Callable class LeagueRankButton: @@ -82,20 +82,20 @@ class LeagueRankButton: transition_delay=transition_delay, color=textcolor) - self._smooth_percent: Optional[float] = None - self._percent: Optional[int] = None - self._smooth_rank: Optional[float] = None - self._rank: Optional[int] = None - self._ticking_node: Optional[ba.Node] = None + self._smooth_percent: float | None = None + self._percent: int | None = None + self._smooth_rank: float | None = None + self._rank: int | None = None + self._ticking_node: ba.Node | None = None self._smooth_increase_speed = 1.0 - self._league: Optional[str] = None - self._improvement_text: Optional[str] = None + self._league: str | None = None + self._improvement_text: str | None = None - self._smooth_update_timer: Optional[ba.Timer] = None + self._smooth_update_timer: ba.Timer | None = None # Take note of our account state; we'll refresh later if this changes. self._account_state_num = _ba.get_v1_account_state_num() - self._last_power_ranking_query_time: Optional[float] = None + self._last_power_ranking_query_time: float | None = None self._doing_power_ranking_query = False self.set_position(position) self._bg_flash = False @@ -191,7 +191,7 @@ class LeagueRankButton: ba.timer(2.0, ba.Call(safe_delete, diff_text), timetype=ba.TimeType.REAL) - status_text: Union[str, ba.Lstr] + status_text: str | ba.Lstr if self._rank is not None: assert self._smooth_rank is not None status_text = ba.Lstr(resource='numberText', @@ -211,8 +211,8 @@ class LeagueRankButton: ba.print_exception('Error doing smooth update.') self._smooth_update_timer = None - def _update_for_league_rank_data(self, data: Optional[dict[str, - Any]]) -> None: + def _update_for_league_rank_data(self, + data: dict[str, Any] | None) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -220,7 +220,7 @@ class LeagueRankButton: if not self._button: return - status_text: Union[str, ba.Lstr] + status_text: str | ba.Lstr in_top = data is not None and data['rank'] is not None do_percent = False @@ -325,8 +325,8 @@ class LeagueRankButton: ba.textwidget(edit=self._title_text, text=txt, color=t_color) ba.textwidget(edit=self._value_text, text=status_text) - def _on_power_ranking_query_response( - self, data: Optional[dict[str, Any]]) -> None: + def _on_power_ranking_query_response(self, + data: dict[str, Any] | None) -> None: self._doing_power_ranking_query = False ba.app.accounts_v1.cache_league_rank_data(data) self._update_for_league_rank_data(data) diff --git a/dist/ba_data/python/bastd/ui/league/rankwindow.py b/dist/ba_data/python/bastd/ui/league/rankwindow.py index e1828ad..3a23f89 100644 --- a/dist/ba_data/python/bastd/ui/league/rankwindow.py +++ b/dist/ba_data/python/bastd/ui/league/rankwindow.py @@ -12,7 +12,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class LeagueRankWindow(ba.Window): @@ -24,11 +24,11 @@ class LeagueRankWindow(ba.Window): origin_widget: ba.Widget = None): ba.set_analytics_screen('League Rank Window') - self._league_rank_data: Optional[dict[str, Any]] = None + self._league_rank_data: dict[str, Any] | None = None self._modal = modal # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -105,17 +105,17 @@ class LeagueRankWindow(ba.Window): cancel_button=self._back_button, selected_child=self._back_button) - self._last_power_ranking_query_time: Optional[float] = None + self._last_power_ranking_query_time: float | None = None self._doing_power_ranking_query = False - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None self._subcontainerwidth = 800 self._subcontainerheight = 483 self._power_ranking_score_widgets: list[ba.Widget] = [] - self._season_popup_menu: Optional[popup_ui.PopupMenu] = None - self._requested_season: Optional[str] = None - self._season: Optional[str] = None + self._season_popup_menu: popup_ui.PopupMenu | None = None + self._requested_season: str | None = None + self._season: str | None = None # take note of our account state; we'll refresh later if this changes self._account_state = _ba.get_v1_account_state() @@ -190,8 +190,8 @@ class LeagueRankWindow(ba.Window): else: ba.playsound(ba.getsound('error')) - def _on_power_ranking_query_response( - self, data: Optional[dict[str, Any]]) -> None: + def _on_power_ranking_query_response(self, + data: dict[str, Any] | None) -> None: self._doing_power_ranking_query = False # important: *only* cache this if we requested the current season.. if data is not None and data.get('s', None) is None: @@ -351,7 +351,7 @@ class LeagueRankWindow(ba.Window): color=(1, 1, 1, 0.3), maxwidth=200) - self._activity_mult_button: Optional[ba.Widget] + self._activity_mult_button: ba.Widget | None if _ba.get_v1_account_misc_read_val('act', False): self._activity_mult_button = ba.buttonwidget( parent=w_parent, @@ -586,8 +586,8 @@ class LeagueRankWindow(ba.Window): '/highscores?list=powerRankings&v=2' + league_str + season_str + '&player=' + our_login_id) - def _update_for_league_rank_data(self, data: Optional[dict[str, - Any]]) -> None: + def _update_for_league_rank_data(self, + data: dict[str, Any] | None) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -714,7 +714,7 @@ class LeagueRankWindow(ba.Window): self._league_url_arg = (data['l']['n'] + '_' + str(data['l']['i'])).lower() - to_end_string: Union[ba.Lstr, str] + to_end_string: ba.Lstr | str if data is None or self._season == 'a' or data['se'] is None: to_end_string = '' show_season_end = False diff --git a/dist/ba_data/python/bastd/ui/mainmenu.py b/dist/ba_data/python/bastd/ui/mainmenu.py index 155188b..4e5c8c5 100644 --- a/dist/ba_data/python/bastd/ui/mainmenu.py +++ b/dist/ba_data/python/bastd/ui/mainmenu.py @@ -11,13 +11,13 @@ import ba import _ba if TYPE_CHECKING: - from typing import Any, Callable, Optional, Union + from typing import Any, Callable class MainMenuWindow(ba.Window): """The main menu window, both in-game and in the main menu session.""" - def __init__(self, transition: Optional[str] = 'in_right'): + def __init__(self, transition: str | None = 'in_right'): # pylint: disable=cyclic-import import threading from bastd.mainmenu import MainMenuSession @@ -52,14 +52,14 @@ class MainMenuWindow(ba.Window): self._button_height = 45.0 self._width = 100.0 self._height = 100.0 - self._demo_menu_button: Optional[ba.Widget] = None - self._gather_button: Optional[ba.Widget] = None - self._start_button: Optional[ba.Widget] = None - self._watch_button: Optional[ba.Widget] = None - self._gc_button: Optional[ba.Widget] = None - self._how_to_play_button: Optional[ba.Widget] = None - self._credits_button: Optional[ba.Widget] = None - self._settings_button: Optional[ba.Widget] = None + self._demo_menu_button: ba.Widget | None = None + self._gather_button: ba.Widget | None = None + self._start_button: ba.Widget | None = None + self._watch_button: ba.Widget | None = None + self._gc_button: ba.Widget | None = None + self._how_to_play_button: ba.Widget | None = None + self._credits_button: ba.Widget | None = None + self._settings_button: ba.Widget | None = None self._store_char_tex = self._get_store_char_tex() @@ -150,7 +150,7 @@ class MainMenuWindow(ba.Window): self._refresh() self._restore_state() - def get_play_button(self) -> Optional[ba.Widget]: + def get_play_button(self) -> ba.Widget | None: """Return the play button.""" return self._start_button @@ -261,7 +261,7 @@ class MainMenuWindow(ba.Window): label=ba.Lstr(resource=self._r + '.leavePartyText'), on_activate_call=self._confirm_leave_party) - self._store_button: Optional[ba.Widget] + self._store_button: ba.Widget | None if self._have_store_button: this_b_width = self._button_width h, v, scale = positions[self._p_index] @@ -293,7 +293,7 @@ class MainMenuWindow(ba.Window): else: self._store_button = None - self._quit_button: Optional[ba.Widget] + self._quit_button: ba.Widget | None if not self._in_game and self._have_quit_button: h, v, scale = positions[self._p_index] self._p_index += 1 @@ -426,7 +426,7 @@ class MainMenuWindow(ba.Window): self._width = 400.0 self._height = 200.0 enable_account_button = True - account_type_name: Union[str, ba.Lstr] + account_type_name: str | ba.Lstr if _ba.get_v1_account_state() == 'signed_in': account_type_name = _ba.get_v1_account_display_string() account_type_icon = None @@ -981,7 +981,7 @@ class MainMenuWindow(ba.Window): if self._in_game: return sel_name = ba.app.ui.main_menu_selection - sel: Optional[ba.Widget] + sel: ba.Widget | None if sel_name is None: sel_name = 'Start' if sel_name == 'HowToPlay': diff --git a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py index 4a446a3..c55917f 100644 --- a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -12,7 +12,7 @@ from ba import charstr from ba import SpecialChar as SpCh if TYPE_CHECKING: - from typing import Optional + pass class OnScreenKeyboardWindow(ba.Window): @@ -72,12 +72,12 @@ class OnScreenKeyboardWindow(ba.Window): self._key_color = (0.69, 0.6, 0.74) self._key_color_dark = (0.55, 0.55, 0.71) - self._shift_button: Optional[ba.Widget] = None - self._backspace_button: Optional[ba.Widget] = None - self._space_button: Optional[ba.Widget] = None + self._shift_button: ba.Widget | None = None + self._backspace_button: ba.Widget | None = None + self._space_button: ba.Widget | None = None self._double_press_shift = False - self._num_mode_button: Optional[ba.Widget] = None - self._emoji_button: Optional[ba.Widget] = None + self._num_mode_button: ba.Widget | None = None + self._emoji_button: ba.Widget | None = None self._char_keys: list[ba.Widget] = [] self._keyboard_index = 0 self._last_space_press = 0.0 @@ -244,7 +244,7 @@ class OnScreenKeyboardWindow(ba.Window): return kbclass() def _refresh(self) -> None: - chars: Optional[list[str]] = None + chars: list[str] | None = None if self._mode in ['normal', 'caps']: chars = list(self._chars) if self._mode == 'caps': diff --git a/dist/ba_data/python/bastd/ui/party.py b/dist/ba_data/python/bastd/ui/party.py index f0b65d3..283b7a0 100644 --- a/dist/ba_data/python/bastd/ui/party.py +++ b/dist/ba_data/python/bastd/ui/party.py @@ -12,7 +12,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Sequence, Optional, Any + from typing import Sequence, Any class PartyWindow(ba.Window): @@ -24,9 +24,9 @@ class PartyWindow(ba.Window): def __init__(self, origin: Sequence[float] = (0, 0)): _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._popup_type: str | None = None + self._popup_party_member_client_id: int | None = None + self._popup_party_member_is_host: bool | None = None self._width = 500 uiscale = ba.app.ui.uiscale self._height = (365 if uiscale is ba.UIScale.SMALL else @@ -153,7 +153,7 @@ class PartyWindow(ba.Window): on_activate_call=self._send_chat_message) ba.textwidget(edit=txt, on_return_press_call=btn.activate) self._name_widgets: list[ba.Widget] = [] - self._roster: Optional[list[dict[str, Any]]] = None + self._roster: list[dict[str, Any]] | None = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, diff --git a/dist/ba_data/python/bastd/ui/partyqueue.py b/dist/ba_data/python/bastd/ui/partyqueue.py index a44f92c..c68878b 100644 --- a/dist/ba_data/python/bastd/ui/partyqueue.py +++ b/dist/ba_data/python/bastd/ui/partyqueue.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Sequence + from typing import Any, Sequence class PartyQueueWindow(ba.Window): @@ -77,8 +77,8 @@ class PartyQueueWindow(ba.Window): self._update_image() # DEBUG: vis target pos.. - self._body_image_target: Optional[ba.Widget] - self._eyes_image_target: Optional[ba.Widget] + self._body_image_target: ba.Widget | None + self._eyes_image_target: ba.Widget | None if self._debug: self._body_image_target = ba.imagewidget( parent=parent.get_root_widget(), @@ -105,7 +105,7 @@ class PartyQueueWindow(ba.Window): # need to push a deferred call to kill these as necessary instead. # (should bulletproof internal widget code to give a clean error # in this case) - def kill_widgets(widgets: Sequence[Optional[ba.Widget]]) -> None: + def kill_widgets(widgets: Sequence[ba.Widget | None]) -> None: for widget in widgets: if widget: widget.delete() @@ -175,11 +175,11 @@ class PartyQueueWindow(ba.Window): self._queue_id = queue_id self._width = 800 self._height = 400 - self._last_connect_attempt_time: Optional[float] = None - self._last_transaction_time: Optional[float] = None - self._boost_button: Optional[ba.Widget] = None - self._boost_price: Optional[ba.Widget] = None - self._boost_label: Optional[ba.Widget] = None + self._last_connect_attempt_time: float | None = None + self._last_transaction_time: float | None = None + self._boost_button: ba.Widget | None = None + self._boost_price: ba.Widget | None = None + self._boost_label: ba.Widget | None = None self._field_shown = False self._dudes: list[PartyQueueWindow.Dude] = [] self._dudes_by_id: dict[int, PartyQueueWindow.Dude] = {} @@ -193,7 +193,7 @@ class PartyQueueWindow(ba.Window): self._boost_strength = 0.0 self._angry_computer_transparent_model = ba.getmodel( 'angryComputerTransparent') - self._angry_computer_image: Optional[ba.Widget] = None + self._angry_computer_image: ba.Widget | None = None self.lineup_1_transparent_model = ba.getmodel( 'playerLineup1Transparent') self._lineup_2_transparent_model = ba.getmodel( @@ -202,7 +202,7 @@ class PartyQueueWindow(ba.Window): 'playerLineup3Transparent') self._lineup_4_transparent_model = ba.getmodel( 'playerLineup4Transparent') - self._line_image: Optional[ba.Widget] = None + self._line_image: ba.Widget | None = None self.eyes_model = ba.getmodel('plasticEyesTransparent') self._white_tex = ba.gettexture('white') uiscale = ba.app.ui.uiscale @@ -277,7 +277,7 @@ class PartyQueueWindow(ba.Window): """(internal)""" return self._line_bottom - def on_account_press(self, account_id: Optional[str], + def on_account_press(self, account_id: str | None, origin_widget: ba.Widget) -> None: """A dude was clicked so we should show his account info.""" from bastd.ui.account import viewer @@ -357,7 +357,7 @@ class PartyQueueWindow(ba.Window): self._dudes = [] self._dudes_by_id = {} - def on_update_response(self, response: Optional[dict[str, Any]]) -> None: + def on_update_response(self, response: dict[str, Any] | None) -> None: """We've received a response from an update to the server.""" # pylint: disable=too-many-branches if not self._root_widget: diff --git a/dist/ba_data/python/bastd/ui/play.py b/dist/ba_data/python/bastd/ui/play.py index 5fefb3e..62809b0 100644 --- a/dist/ba_data/python/bastd/ui/play.py +++ b/dist/ba_data/python/bastd/ui/play.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class PlayWindow(ba.Window): @@ -37,7 +37,7 @@ class PlayWindow(ba.Window): height = 550 button_width = 400 - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -112,7 +112,7 @@ class PlayWindow(ba.Window): 'playerLineup4Transparent') self._eyes_model = ba.getmodel('plasticEyesTransparent') - self._coop_button: Optional[ba.Widget] = None + self._coop_button: ba.Widget | None = None # Only show coop button in main-menu variant. if self._is_main_menu: diff --git a/dist/ba_data/python/bastd/ui/playlist/addgame.py b/dist/ba_data/python/bastd/ui/playlist/addgame.py index 607226d..8ca5c6c 100644 --- a/dist/ba_data/python/bastd/ui/playlist/addgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/addgame.py @@ -10,7 +10,6 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional from bastd.ui.playlist.editcontroller import PlaylistEditController @@ -106,13 +105,13 @@ class PlaylistAddGameWindow(ba.Window): up_widget=self._back_button, left_widget=self._back_button, right_widget=select_button) - self._column: Optional[ba.Widget] = None + self._column: ba.Widget | None = None v -= 35 ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button, start_button=select_button) - self._selected_game_type: Optional[type[ba.GameActivity]] = None + self._selected_game_type: type[ba.GameActivity] | None = None ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) diff --git a/dist/ba_data/python/bastd/ui/playlist/browser.py b/dist/ba_data/python/bastd/ui/playlist/browser.py index 7ca0b33..9dcab82 100644 --- a/dist/ba_data/python/bastd/ui/playlist/browser.py +++ b/dist/ba_data/python/bastd/ui/playlist/browser.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Union + pass class PlaylistBrowserWindow(ba.Window): @@ -20,14 +20,14 @@ class PlaylistBrowserWindow(ba.Window): def __init__(self, sessiontype: type[ba.Session], - transition: Optional[str] = 'in_right', + transition: str | None = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui.playlist import PlaylistTypeVars # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -49,9 +49,9 @@ class PlaylistBrowserWindow(ba.Window): self._sessiontype = sessiontype - self._customize_button: Optional[ba.Widget] = None - self._sub_width: Optional[float] = None - self._sub_height: Optional[float] = None + self._customize_button: ba.Widget | None = None + self._sub_width: float | None = None + self._sub_height: float | None = None self._ensure_standard_playlists_exist() @@ -76,7 +76,7 @@ class PlaylistBrowserWindow(ba.Window): 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9), stack_offset=(0, -26) if uiscale is ba.UIScale.SMALL else (0, 0))) - self._back_button: Optional[ba.Widget] = ba.buttonwidget( + self._back_button: ba.Widget | None = ba.buttonwidget( parent=self._root_widget, position=(59 + x_inset, self._height - 70), size=(120, 60), @@ -125,7 +125,7 @@ class PlaylistBrowserWindow(ba.Window): position=((self._width - self._scroll_width) * 0.5, 65 + scroll_offs)) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None self._config_name_full = self._pvars.config_name + ' Playlists' self._last_config = None @@ -393,7 +393,7 @@ class PlaylistBrowserWindow(ba.Window): if x == 0: ba.widget(edit=btn, left_widget=self._back_button) - print_name: Optional[Union[str, ba.Lstr]] + print_name: str | ba.Lstr | None if name == '__default__': print_name = self._pvars.default_list_name else: @@ -432,7 +432,7 @@ class PlaylistBrowserWindow(ba.Window): mark_unowned=True) for entry in playlist: mapname = entry['settings']['map'] - maptype: Optional[type[ba.Map]] + maptype: type[ba.Map] | None try: maptype = get_map_class(mapname) except ba.NotFoundError: diff --git a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py index e802521..c786aea 100644 --- a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py +++ b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class PlaylistCustomizeBrowserWindow(ba.Window): @@ -28,7 +28,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui import playlist - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -234,8 +234,8 @@ class PlaylistCustomizeBrowserWindow(ba.Window): if self._config_name_full not in ba.app.config: ba.app.config[self._config_name_full] = {} - self._selected_playlist_name: Optional[str] = None - self._selected_playlist_index: Optional[int] = None + self._selected_playlist_name: str | None = None + self._selected_playlist_index: int | None = None self._playlist_widgets: list[ba.Widget] = [] self._refresh(select_playlist=select_playlist) @@ -539,7 +539,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): return if self._selected_playlist_name is None: return - plst: Optional[list[dict[str, Any]]] + plst: list[dict[str, Any]] | None if self._selected_playlist_name == '__default__': plst = self._pvars.get_default_list_call() else: diff --git a/dist/ba_data/python/bastd/ui/playlist/edit.py b/dist/ba_data/python/bastd/ui/playlist/edit.py index 2b9e4c4..42846d0 100644 --- a/dist/ba_data/python/bastd/ui/playlist/edit.py +++ b/dist/ba_data/python/bastd/ui/playlist/edit.py @@ -10,7 +10,6 @@ import ba import _ba if TYPE_CHECKING: - from typing import Optional from bastd.ui.playlist.editcontroller import PlaylistEditController @@ -22,7 +21,7 @@ class PlaylistEditWindow(ba.Window): transition: str = 'in_right'): # pylint: disable=too-many-statements # pylint: disable=too-many-locals - prev_selection: Optional[str] + prev_selection: str | None self._editcontroller = editcontroller self._r = 'editGameListWindow' prev_selection = self._editcontroller.get_edit_ui_selection() diff --git a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py index bf3e832..a2a1c0b 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py +++ b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class PlaylistEditController: @@ -34,7 +34,7 @@ class PlaylistEditController: self._sessiontype = sessiontype self._editing_game = False - self._editing_game_type: Optional[type[ba.GameActivity]] = None + self._editing_game_type: type[ba.GameActivity] | None = None self._pvars = PlaylistTypeVars(sessiontype) self._existing_playlist_name = existing_playlist_name self._config_name_full = self._pvars.config_name + ' Playlists' @@ -86,11 +86,11 @@ class PlaylistEditController: """(internal)""" return self._pvars.config_name - def get_existing_playlist_name(self) -> Optional[str]: + def get_existing_playlist_name(self) -> str | None: """(internal)""" return self._existing_playlist_name - def get_edit_ui_selection(self) -> Optional[str]: + def get_edit_ui_selection(self) -> str | None: """(internal)""" return self._edit_ui_selection @@ -156,7 +156,7 @@ class PlaylistEditController: transition='in_left').get_root_widget()) def _show_edit_ui(self, gametype: type[ba.GameActivity], - settings: Optional[dict[str, Any]]) -> None: + settings: dict[str, Any] | None) -> None: self._editing_game = (settings is not None) self._editing_game_type = gametype assert self._sessiontype is not None @@ -167,7 +167,7 @@ class PlaylistEditController: """(internal)""" self._show_edit_ui(gametype=gametype, settings=None) - def _edit_game_done(self, config: Optional[dict[str, Any]]) -> None: + def _edit_game_done(self, config: dict[str, Any] | None) -> None: from bastd.ui.playlist.edit import PlaylistEditWindow from bastd.ui.playlist.addgame import PlaylistAddGameWindow from ba.internal import get_type_name diff --git a/dist/ba_data/python/bastd/ui/playlist/editgame.py b/dist/ba_data/python/bastd/ui/playlist/editgame.py index 9844108..a4c0007 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/editgame.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional, Union + from typing import Any, Callable class PlaylistEditGameWindow(ba.Window): @@ -21,8 +21,8 @@ class PlaylistEditGameWindow(ba.Window): def __init__(self, gametype: type[ba.GameActivity], sessiontype: type[ba.Session], - config: Optional[dict[str, Any]], - completion_call: Callable[[Optional[dict[str, Any]]], Any], + config: dict[str, Any] | None, + completion_call: Callable[[dict[str, Any] | None], Any], default_selection: str = None, transition: str = 'in_right', edit_info: dict[str, Any] = None): @@ -392,7 +392,7 @@ class PlaylistEditGameWindow(ba.Window): # Ok now wire up the column. try: # pylint: disable=unsubscriptable-object - prev_widgets: Optional[list[ba.Widget]] = None + prev_widgets: list[ba.Widget] | None = None for cwdg in widget_column: if prev_widgets is not None: # Wire our rightmost to their rightmost. @@ -466,9 +466,9 @@ class PlaylistEditGameWindow(ba.Window): def _add(self) -> None: self._completion_call(copy.deepcopy(self._getconfig())) - def _inc(self, ctrl: ba.Widget, min_val: Union[int, float], - max_val: Union[int, float], increment: Union[int, float], - setting_type: type, setting_name: str) -> None: + def _inc(self, ctrl: ba.Widget, min_val: int | float, max_val: int | float, + increment: int | float, setting_type: type, + setting_name: str) -> None: if setting_type == float: val = float(cast(str, ba.textwidget(query=ctrl))) else: diff --git a/dist/ba_data/python/bastd/ui/playlist/mapselect.py b/dist/ba_data/python/bastd/ui/playlist/mapselect.py index 2464e0f..eecbd1a 100644 --- a/dist/ba_data/python/bastd/ui/playlist/mapselect.py +++ b/dist/ba_data/python/bastd/ui/playlist/mapselect.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class PlaylistMapSelectWindow(ba.Window): @@ -22,7 +22,7 @@ class PlaylistMapSelectWindow(ba.Window): sessiontype: type[ba.Session], config: dict[str, Any], edit_info: dict[str, Any], - completion_call: Callable[[Optional[dict[str, Any]]], Any], + completion_call: Callable[[dict[str, Any] | None], Any], transition: str = 'in_right'): from ba.internal import get_filtered_map_name self._gametype = gametype @@ -86,7 +86,7 @@ class PlaylistMapSelectWindow(ba.Window): selected_child=self._scrollwidget) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None self._refresh() def _refresh(self, select_get_more_maps_button: bool = False) -> None: diff --git a/dist/ba_data/python/bastd/ui/playlist/share.py b/dist/ba_data/python/bastd/ui/playlist/share.py index 8355c39..f2987ca 100644 --- a/dist/ba_data/python/bastd/ui/playlist/share.py +++ b/dist/ba_data/python/bastd/ui/playlist/share.py @@ -12,7 +12,7 @@ import ba from bastd.ui import promocode if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class SharePlaylistImportWindow(promocode.PromoCodeWindow): @@ -26,7 +26,7 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow): origin_widget=origin_widget) self._on_success_callback = on_success_callback - def _on_import_response(self, response: Optional[dict[str, Any]]) -> None: + def _on_import_response(self, response: dict[str, Any] | None) -> None: if response is None: ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) diff --git a/dist/ba_data/python/bastd/ui/playoptions.py b/dist/ba_data/python/bastd/ui/playoptions.py index 2d98d5d..b200aec 100644 --- a/dist/ba_data/python/bastd/ui/playoptions.py +++ b/dist/ba_data/python/bastd/ui/playoptions.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class PlayOptionsWindow(popup.PopupWindow): @@ -90,7 +90,7 @@ class PlayOptionsWindow(popup.PopupWindow): game_count = len(plst) for entry in plst: mapname = entry['settings']['map'] - maptype: Optional[type[ba.Map]] + maptype: type[ba.Map] | None try: maptype = get_map_class(mapname) except ba.NotFoundError: @@ -132,9 +132,9 @@ class PlayOptionsWindow(popup.PopupWindow): size=(self._width, self._height), scale=scale) - playlist_name: Union[str, ba.Lstr] = (self._pvars.default_list_name - if playlist == '__default__' else - playlist) + playlist_name: str | ba.Lstr = (self._pvars.default_list_name + if playlist == '__default__' else + playlist) self._title_text = ba.textwidget(parent=self.root_widget, position=(self._width * 0.5, self._height - 89 + 51), @@ -239,7 +239,7 @@ class PlayOptionsWindow(popup.PopupWindow): texture=ba.gettexture('lock')) # Team names/colors. - self._custom_colors_names_button: Optional[ba.Widget] + self._custom_colors_names_button: ba.Widget | None if self._sessiontype is ba.DualTeamSession: y_offs = 50 if show_shuffle_check_box else 0 self._custom_colors_names_button = ba.buttonwidget( diff --git a/dist/ba_data/python/bastd/ui/popup.py b/dist/ba_data/python/bastd/ui/popup.py index 8a50f8f..2146263 100644 --- a/dist/ba_data/python/bastd/ui/popup.py +++ b/dist/ba_data/python/bastd/ui/popup.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Sequence, Callable, Optional, Union + from typing import Any, Sequence, Callable class PopupWindow: @@ -322,7 +322,7 @@ class PopupMenu: self._closing_call = closing_call self.set_choice(self._current_choice) self._on_value_change_call = on_value_change_call - self._window_widget: Optional[ba.Widget] = None + self._window_widget: ba.Widget | None = None # Complain if we outlive our button. ba.uicleanupcheck(self, self._button) @@ -347,7 +347,7 @@ class PopupMenu: """Return the menu's button widget.""" return self._button - def get_window_widget(self) -> Optional[ba.Widget]: + def get_window_widget(self) -> ba.Widget | None: """Return the menu's window widget (or None if nonexistent).""" return self._window_widget @@ -371,7 +371,7 @@ class PopupMenu: def set_choice(self, choice: str) -> None: """Set the selected choice.""" self._current_choice = choice - displayname: Union[str, ba.Lstr] + displayname: str | ba.Lstr if len(self._choices_display) == len(self._choices): displayname = self._choices_display[self._choices.index(choice)] else: diff --git a/dist/ba_data/python/bastd/ui/profile/browser.py b/dist/ba_data/python/bastd/ui/profile/browser.py index e9aef0b..febfa83 100644 --- a/dist/ba_data/python/bastd/ui/profile/browser.py +++ b/dist/ba_data/python/bastd/ui/profile/browser.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class ProfileBrowserWindow(ba.Window): @@ -39,7 +39,7 @@ class ProfileBrowserWindow(ba.Window): ba.app.pause() # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -162,7 +162,7 @@ class ProfileBrowserWindow(ba.Window): border=2, margin=0) v -= 255 - self._profiles: Optional[dict[str, dict[str, Any]]] = None + self._profiles: dict[str, dict[str, Any]] | None = None self._selected_profile = selected_profile self._profile_widgets: list[ba.Widget] = [] self._refresh() @@ -282,7 +282,7 @@ class ProfileBrowserWindow(ba.Window): items = list(self._profiles.items()) items.sort(key=lambda x: asserttype(x[0], str).lower()) index = 0 - account_name: Optional[str] + account_name: str | None if _ba.get_v1_account_state() == 'signed_in': account_name = _ba.get_v1_account_display_string() else: diff --git a/dist/ba_data/python/bastd/ui/profile/edit.py b/dist/ba_data/python/bastd/ui/profile/edit.py index d0df137..64500c2 100644 --- a/dist/ba_data/python/bastd/ui/profile/edit.py +++ b/dist/ba_data/python/bastd/ui/profile/edit.py @@ -11,7 +11,6 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional from bastd.ui.colorpicker import ColorPicker @@ -27,7 +26,7 @@ class EditProfileWindow(ba.Window): self._in_main_menu).get_root_widget()) def __init__(self, - existing_profile: Optional[str], + existing_profile: str | None, in_main_menu: bool, transition: str = 'in_right'): # FIXME: Tidy this up a bit. diff --git a/dist/ba_data/python/bastd/ui/profile/upgrade.py b/dist/ba_data/python/bastd/ui/profile/upgrade.py index 0dc86dd..3142dc9 100644 --- a/dist/ba_data/python/bastd/ui/profile/upgrade.py +++ b/dist/ba_data/python/bastd/ui/profile/upgrade.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any from bastd.ui.profile.edit import EditProfileWindow @@ -30,7 +30,7 @@ class ProfileUpgradeWindow(ba.Window): uiscale = ba.app.ui.uiscale self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.2) - self._upgrade_start_time: Optional[float] = None + self._upgrade_start_time: float | None = None self._name = edit_profile_window.getname() self._edit_profile_window = weakref.ref(edit_profile_window) @@ -106,7 +106,7 @@ class ProfileUpgradeWindow(ba.Window): h_align='center', v_align='center') - self._tickets_text: Optional[ba.Widget] + self._tickets_text: ba.Widget | None if not ba.app.ui.use_toolbars: self._tickets_text = ba.textwidget( parent=self._root_widget, @@ -128,14 +128,14 @@ class ProfileUpgradeWindow(ba.Window): callback=ba.WeakCall(self._profile_check_result)) self._cost = _ba.get_v1_account_misc_read_val('price.global_profile', 500) - self._status: Optional[str] = 'waiting' + self._status: str | None = 'waiting' self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._update() - def _profile_check_result(self, result: Optional[dict[str, Any]]) -> None: + def _profile_check_result(self, result: dict[str, Any] | None) -> None: if result is None: ba.textwidget( edit=self._status_text, diff --git a/dist/ba_data/python/bastd/ui/promocode.py b/dist/ba_data/python/bastd/ui/promocode.py index ce416a5..eb3bf01 100644 --- a/dist/ba_data/python/bastd/ui/promocode.py +++ b/dist/ba_data/python/bastd/ui/promocode.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class PromoCodeWindow(ba.Window): @@ -19,7 +19,7 @@ class PromoCodeWindow(ba.Window): def __init__(self, modal: bool = False, origin_widget: ba.Widget = None): - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/purchase.py b/dist/ba_data/python/bastd/ui/purchase.py index c1a28b1..e5da106 100644 --- a/dist/ba_data/python/bastd/ui/purchase.py +++ b/dist/ba_data/python/bastd/ui/purchase.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class PurchaseWindow(ba.Window): @@ -132,7 +132,7 @@ class PurchaseWindow(ba.Window): if self._items == ['pro']: _ba.purchase('pro') else: - ticket_count: Optional[int] + ticket_count: int | None try: ticket_count = _ba.get_v1_account_ticket_count() except Exception: diff --git a/dist/ba_data/python/bastd/ui/serverdialog.py b/dist/ba_data/python/bastd/ui/serverdialog.py index 1f98ef7..f972d06 100644 --- a/dist/ba_data/python/bastd/ui/serverdialog.py +++ b/dist/ba_data/python/bastd/ui/serverdialog.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class ServerDialogWindow(ba.Window): @@ -47,7 +47,7 @@ class ServerDialogWindow(ba.Window): maxwidth=self._width * 0.85, max_height=(self._height - 110)) show_cancel = data.get('showCancel', True) - self._cancel_button: Optional[ba.Widget] + self._cancel_button: ba.Widget | None if show_cancel: self._cancel_button = ba.buttonwidget( parent=self._root_widget, diff --git a/dist/ba_data/python/bastd/ui/settings/advanced.py b/dist/ba_data/python/bastd/ui/settings/advanced.py index f511a7d..665d86f 100644 --- a/dist/ba_data/python/bastd/ui/settings/advanced.py +++ b/dist/ba_data/python/bastd/ui/settings/advanced.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class AdvancedSettingsWindow(ba.Window): @@ -31,7 +31,7 @@ class AdvancedSettingsWindow(ba.Window): app = ba.app # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -58,9 +58,9 @@ class AdvancedSettingsWindow(ba.Window): stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) self._prev_lang = '' self._prev_lang_list: list[str] = [] - self._complete_langs_list: Optional[list] = None + self._complete_langs_list: list | None = None self._complete_langs_error = False - self._language_popup: Optional[popup_ui.PopupMenu] = None + self._language_popup: popup_ui.PopupMenu | None = None # In vr-mode, the internal keyboard is currently the *only* option, # so no need to show this. @@ -378,7 +378,7 @@ class AdvancedSettingsWindow(ba.Window): scale=1.0, maxwidth=430) - self._disable_gyro_check_box: Optional[ConfigCheckBox] = None + self._disable_gyro_check_box: ConfigCheckBox | None = None if self._show_disable_gyro: v -= 42 self._disable_gyro_check_box = ConfigCheckBox( @@ -391,7 +391,7 @@ class AdvancedSettingsWindow(ba.Window): scale=1.0, maxwidth=430) - self._always_use_internal_keyboard_check_box: Optional[ConfigCheckBox] + self._always_use_internal_keyboard_check_box: ConfigCheckBox | None if self._show_always_use_internal_keyboard: v -= 42 self._always_use_internal_keyboard_check_box = ConfigCheckBox( @@ -470,7 +470,7 @@ class AdvancedSettingsWindow(ba.Window): v -= self._spacing * 0.6 - self._vr_test_button: Optional[ba.Widget] + self._vr_test_button: ba.Widget | None if self._do_vr_test_button: v -= self._extra_button_spacing self._vr_test_button = ba.buttonwidget( @@ -484,7 +484,7 @@ class AdvancedSettingsWindow(ba.Window): else: self._vr_test_button = None - self._net_test_button: Optional[ba.Widget] + self._net_test_button: ba.Widget | None if self._do_net_test_button: v -= self._extra_button_spacing self._net_test_button = ba.buttonwidget( @@ -694,7 +694,7 @@ class AdvancedSettingsWindow(ba.Window): self._save_state() ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL) - def _completed_langs_cb(self, results: Optional[dict[str, Any]]) -> None: + def _completed_langs_cb(self, results: dict[str, Any] | None) -> None: if results is not None and results['langs'] is not None: self._complete_langs_list = results['langs'] self._complete_langs_error = False diff --git a/dist/ba_data/python/bastd/ui/settings/allsettings.py b/dist/ba_data/python/bastd/ui/settings/allsettings.py index 4c994ac..8b18773 100644 --- a/dist/ba_data/python/bastd/ui/settings/allsettings.py +++ b/dist/ba_data/python/bastd/ui/settings/allsettings.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Union + pass class AllSettingsWindow(ba.Window): @@ -28,7 +28,7 @@ class AllSettingsWindow(ba.Window): threading.Thread(target=self._preload_modules).start() ba.set_analytics_screen('Settings Window') - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -98,7 +98,7 @@ class AllSettingsWindow(ba.Window): x_offs5 = x_offs3 def _b_title(x: float, y: float, button: ba.Widget, - text: Union[str, ba.Lstr]) -> None: + text: str | ba.Lstr) -> None: ba.textwidget(parent=self._root_widget, text=text, position=(x + basew * 0.47, y + baseh * 0.22), @@ -265,7 +265,7 @@ class AllSettingsWindow(ba.Window): try: sel_name = ba.app.ui.window_states.get(type(self), {}).get('sel_name') - sel: Optional[ba.Widget] + sel: ba.Widget | None if sel_name == 'Controllers': sel = self._controllers_button elif sel_name == 'Graphics': diff --git a/dist/ba_data/python/bastd/ui/settings/audio.py b/dist/ba_data/python/bastd/ui/settings/audio.py index 0e27e38..9ca2979 100644 --- a/dist/ba_data/python/bastd/ui/settings/audio.py +++ b/dist/ba_data/python/bastd/ui/settings/audio.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class AudioSettingsWindow(ba.Window): @@ -28,7 +28,7 @@ class AudioSettingsWindow(ba.Window): music = ba.app.music # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -122,7 +122,7 @@ class AudioSettingsWindow(ba.Window): v -= 0.5 * spacing - self._vr_head_relative_audio_button: Optional[ba.Widget] + self._vr_head_relative_audio_button: ba.Widget | None if show_vr_head_relative_audio: v -= 40 ba.textwidget(parent=self._root_widget, @@ -165,7 +165,7 @@ class AudioSettingsWindow(ba.Window): else: self._vr_head_relative_audio_button = None - self._soundtrack_button: Optional[ba.Widget] + self._soundtrack_button: ba.Widget | None if show_soundtracks: v -= 1.2 * spacing self._soundtrack_button = ba.buttonwidget( @@ -259,7 +259,7 @@ class AudioSettingsWindow(ba.Window): def _restore_state(self) -> None: try: sel_name = ba.app.ui.window_states.get(type(self)) - sel: Optional[ba.Widget] + sel: ba.Widget | None if sel_name == 'SoundMinus': sel = self._sound_volume_numedit.minusbutton elif sel_name == 'SoundPlus': diff --git a/dist/ba_data/python/bastd/ui/settings/controls.py b/dist/ba_data/python/bastd/ui/settings/controls.py index 3da6c7c..0c17fe8 100644 --- a/dist/ba_data/python/bastd/ui/settings/controls.py +++ b/dist/ba_data/python/bastd/ui/settings/controls.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class ControlsSettingsWindow(ba.Window): @@ -27,7 +27,7 @@ class ControlsSettingsWindow(ba.Window): from bastd.ui import popup as popup_ui self._have_selected_child = False - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None # If they provided an origin-widget, scale up from that. if origin_widget is not None: @@ -132,11 +132,11 @@ class ControlsSettingsWindow(ba.Window): ba.containerwidget(edit=self._root_widget, cancel_button=btn) # We need these vars to exist even if the buttons don't. - self._gamepads_button: Optional[ba.Widget] = None - self._touch_button: Optional[ba.Widget] = None - self._keyboard_button: Optional[ba.Widget] = None - self._keyboard_2_button: Optional[ba.Widget] = None - self._idevices_button: Optional[ba.Widget] = None + self._gamepads_button: ba.Widget | None = None + self._touch_button: ba.Widget | None = None + self._keyboard_button: ba.Widget | None = None + self._keyboard_2_button: ba.Widget | None = None + self._idevices_button: ba.Widget | None = None ba.textwidget(parent=self._root_widget, position=(0, height - 49), diff --git a/dist/ba_data/python/bastd/ui/settings/gamepad.py b/dist/ba_data/python/bastd/ui/settings/gamepad.py index b951138..7f6f184 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepad.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepad.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Union, Callable + from typing import Any, Callable class GamepadSettingsWindow(ba.Window): @@ -134,7 +134,7 @@ class GamepadSettingsWindow(ba.Window): if val != -1: self._settings[skey] = val - back_button: Optional[ba.Widget] + back_button: ba.Widget | None if self._is_secondary: back_button = ba.buttonwidget(parent=self._root_widget, @@ -161,7 +161,7 @@ class GamepadSettingsWindow(ba.Window): ba.containerwidget(edit=self._root_widget, cancel_button=cancel_button) - save_button: Optional[ba.Widget] + save_button: ba.Widget | None if not self._is_secondary: save_button = ba.buttonwidget( parent=self._root_widget, @@ -471,7 +471,7 @@ class GamepadSettingsWindow(ba.Window): transition='in_scale', transition_out='out_scale') - def get_control_value_name(self, control: str) -> Union[str, ba.Lstr]: + def get_control_value_name(self, control: str) -> str | ba.Lstr: """(internal)""" # pylint: disable=too-many-return-statements assert self._settings is not None @@ -796,7 +796,7 @@ class AwaitGamepadInputWindow(ba.Window): size=(width, 25), color=(1, 1, 1, 0.3), text=str(self._counter)) - self._decrement_timer: Optional[ba.Timer] = ba.Timer( + self._decrement_timer: ba.Timer | None = ba.Timer( 1.0, ba.Call(self._decrement), repeat=True, diff --git a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py index 116d0fe..d0eb646 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional, Any + from typing import Any from bastd.ui.settings import gamepad as gpsui @@ -334,7 +334,7 @@ class GamepadAdvancedSettingsWindow(ba.Window): pos: tuple[float, float], name: ba.Lstr, control: str, - message: Optional[ba.Lstr] = None) -> tuple[ba.Widget, ba.Widget]: + message: ba.Lstr | None = None) -> tuple[ba.Widget, ba.Widget]: if message is None: message = ba.Lstr(resource=self._parent_window.get_r() + '.pressAnyButtonText') diff --git a/dist/ba_data/python/bastd/ui/settings/graphics.py b/dist/ba_data/python/bastd/ui/settings/graphics.py index b4e5955..7a0da71 100644 --- a/dist/ba_data/python/bastd/ui/settings/graphics.py +++ b/dist/ba_data/python/bastd/ui/settings/graphics.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional + pass class GraphicsSettingsWindow(ba.Window): @@ -25,7 +25,7 @@ class GraphicsSettingsWindow(ba.Window): from bastd.ui import popup from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit # if they provided an origin-widget, scale up from that - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -103,7 +103,7 @@ class GraphicsSettingsWindow(ba.Window): size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) - self._fullscreen_checkbox: Optional[ba.Widget] + self._fullscreen_checkbox: ba.Widget | None if self._show_fullscreen: v -= fullscreen_spacing_top self._fullscreen_checkbox = ConfigCheckBox( @@ -123,7 +123,7 @@ class GraphicsSettingsWindow(ba.Window): else: self._fullscreen_checkbox = None - self._gamma_controls: Optional[ConfigNumberEdit] + self._gamma_controls: ConfigNumberEdit | None if show_gamma: self._gamma_controls = gmc = ConfigNumberEdit( parent=self._root_widget, diff --git a/dist/ba_data/python/bastd/ui/settings/keyboard.py b/dist/ba_data/python/bastd/ui/settings/keyboard.py index 42c634c..2e93f1f 100644 --- a/dist/ba_data/python/bastd/ui/settings/keyboard.py +++ b/dist/ba_data/python/bastd/ui/settings/keyboard.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class ConfigKeyboardWindow(ba.Window): @@ -279,7 +279,7 @@ class AwaitKeyboardInputWindow(ba.Window): size=(width, 25), color=(1, 1, 1, 0.3), text=str(self._counter)) - self._decrement_timer: Optional[ba.Timer] = ba.Timer( + self._decrement_timer: ba.Timer | None = ba.Timer( 1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL) _ba.capture_keyboard_input(ba.WeakCall(self._button_callback)) diff --git a/dist/ba_data/python/bastd/ui/settings/nettesting.py b/dist/ba_data/python/bastd/ui/settings/nettesting.py index 0cfee59..536e4e0 100644 --- a/dist/ba_data/python/bastd/ui/settings/nettesting.py +++ b/dist/ba_data/python/bastd/ui/settings/nettesting.py @@ -15,7 +15,7 @@ import ba from bastd.ui.settings.testing import TestingWindow if TYPE_CHECKING: - from typing import Callable, Any, Optional + from typing import Callable, Any class NetTestingWindow(ba.Window): @@ -284,7 +284,7 @@ def _test_fetch(baseaddr: str) -> None: raise RuntimeError('Got unexpected response data.') -def _test_nearby_zone_ping(nearest_zone: Optional[tuple[str, float]]) -> None: +def _test_nearby_zone_ping(nearest_zone: tuple[str, float] | None) -> None: """Try to ping nearest v2 zone.""" if nearest_zone is None: raise RuntimeError('No nearest zone.') diff --git a/dist/ba_data/python/bastd/ui/settings/plugins.py b/dist/ba_data/python/bastd/ui/settings/plugins.py index 13706ef..b872368 100644 --- a/dist/ba_data/python/bastd/ui/settings/plugins.py +++ b/dist/ba_data/python/bastd/ui/settings/plugins.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional + pass class PluginSettingsWindow(ba.Window): @@ -22,7 +22,7 @@ class PluginSettingsWindow(ba.Window): app = ba.app # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/settings/testing.py b/dist/ba_data/python/bastd/ui/settings/testing.py index 54e8349..d1f7fb6 100644 --- a/dist/ba_data/python/bastd/ui/settings/testing.py +++ b/dist/ba_data/python/bastd/ui/settings/testing.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class TestingWindow(ba.Window): @@ -21,7 +21,7 @@ class TestingWindow(ba.Window): title: ba.Lstr, entries: list[dict[str, Any]], transition: str = 'in_right', - back_call: Optional[Callable[[], ba.Window]] = None): + back_call: Callable[[], ba.Window] | None = None): uiscale = ba.app.ui.uiscale self._width = 600 self._height = 324 if uiscale is ba.UIScale.SMALL else 400 diff --git a/dist/ba_data/python/bastd/ui/soundtrack/browser.py b/dist/ba_data/python/bastd/ui/soundtrack/browser.py index 5cf74aa..dd98728 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/browser.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/browser.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class SoundtrackBrowserWindow(ba.Window): @@ -24,7 +24,7 @@ class SoundtrackBrowserWindow(ba.Window): # pylint: disable=too-many-statements # If they provided an origin-widget, scale up from that. - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -195,9 +195,9 @@ class SoundtrackBrowserWindow(ba.Window): if ba.app.ui.use_toolbars else self._scrollwidget) self._col = ba.columnwidget(parent=scrollwidget, border=2, margin=0) - self._soundtracks: Optional[dict[str, Any]] = None - self._selected_soundtrack: Optional[str] = None - self._selected_soundtrack_index: Optional[int] = None + self._soundtracks: dict[str, Any] | None = None + self._selected_soundtrack: str | None = None + self._selected_soundtrack_index: int | None = None self._soundtrack_widgets: list[ba.Widget] = [] self._allow_changing_soundtracks = False self._refresh() diff --git a/dist/ba_data/python/bastd/ui/soundtrack/edit.py b/dist/ba_data/python/bastd/ui/soundtrack/edit.py index 7e08f0f..5ab08ca 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/edit.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/edit.py @@ -11,14 +11,14 @@ from typing import TYPE_CHECKING, cast import ba if TYPE_CHECKING: - from typing import Any, Union, Optional + from typing import Any class SoundtrackEditWindow(ba.Window): """Window for editing a soundtrack.""" def __init__(self, - existing_soundtrack: Optional[Union[str, dict[str, Any]]], + existing_soundtrack: str | dict[str, Any] | None, transition: str = 'in_right'): # pylint: disable=too-many-statements appconfig = ba.app.config @@ -68,8 +68,8 @@ class SoundtrackEditWindow(ba.Window): if 'Soundtracks' not in appconfig: appconfig['Soundtracks'] = {} - self._soundtrack_name: Optional[str] - self._existing_soundtrack_name: Optional[str] + self._soundtrack_name: str | None + self._existing_soundtrack_name: str | None if existing_soundtrack is not None: # if they passed just a name, pull info from that soundtrack if isinstance(existing_soundtrack, str): @@ -185,8 +185,8 @@ class SoundtrackEditWindow(ba.Window): # FIXME: We should probably convert this to use translations. type_names_translated = ba.app.lang.get_resource('soundtrackTypeNames') - prev_type_button: Optional[ba.Widget] = None - prev_test_button: Optional[ba.Widget] = None + prev_type_button: ba.Widget | None = None + prev_test_button: ba.Widget | None = None for index, song_type in enumerate(types): row = ba.rowwidget(parent=self._col, @@ -322,11 +322,10 @@ class SoundtrackEditWindow(ba.Window): mode=ba.MusicPlayMode.TEST, testsoundtrack=self._soundtrack) - def _get_entry_button_display_name(self, - entry: Any) -> Union[str, ba.Lstr]: + def _get_entry_button_display_name(self, entry: Any) -> str | ba.Lstr: music = ba.app.music etype = music.get_soundtrack_entry_type(entry) - ename: Union[str, ba.Lstr] + ename: str | ba.Lstr if etype == 'default': ename = ba.Lstr(resource=self._r + '.defaultGameMusicText') elif etype in ('musicFile', 'musicFolder'): @@ -335,7 +334,7 @@ class SoundtrackEditWindow(ba.Window): ename = music.get_soundtrack_entry_name(entry) return ename - def _get_entry_button_display_icon_type(self, entry: Any) -> Optional[str]: + def _get_entry_button_display_icon_type(self, entry: Any) -> str | None: music = ba.app.music etype = music.get_soundtrack_entry_type(entry) if etype == 'musicFile': diff --git a/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py b/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py index 142a6bc..e3bc650 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class SoundtrackEntryTypeSelectWindow(ba.Window): @@ -144,7 +144,7 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): MacMusicAppPlaylistSelectWindow) ba.containerwidget(edit=self._root_widget, transition='out_left') - current_playlist_entry: Optional[str] + current_playlist_entry: str | None if (music.get_soundtrack_entry_type( self._current_entry) == 'iTunesPlaylist'): current_playlist_entry = music.get_soundtrack_entry_name( @@ -181,13 +181,13 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): valid_file_extensions=[], allow_folders=True).get_root_widget()) - def _music_file_selector_cb(self, result: Optional[str]) -> None: + def _music_file_selector_cb(self, result: str | None) -> None: if result is None: self._callback(self._current_entry) else: self._callback({'type': 'musicFile', 'name': result}) - def _music_folder_selector_cb(self, result: Optional[str]) -> None: + def _music_folder_selector_cb(self, result: str | None) -> None: if result is None: self._callback(self._current_entry) else: diff --git a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py index ff50e6b..d5b030a 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py @@ -10,14 +10,14 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Optional, Callable + from typing import Any, Callable class MacMusicAppPlaylistSelectWindow(ba.Window): """Window for selecting an iTunes playlist.""" def __init__(self, callback: Callable[[Any], Any], - existing_playlist: Optional[str], existing_entry: Any): + existing_playlist: str | None, existing_entry: Any): from ba.macmusicapp import MacMusicAppMusicPlayer self._r = 'editSoundtrackWindow' self._callback = callback diff --git a/dist/ba_data/python/bastd/ui/specialoffer.py b/dist/ba_data/python/bastd/ui/specialoffer.py index 0457f4b..82a20d4 100644 --- a/dist/ba_data/python/bastd/ui/specialoffer.py +++ b/dist/ba_data/python/bastd/ui/specialoffer.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any class SpecialOfferWindow(ba.Window): @@ -31,7 +31,7 @@ class SpecialOfferWindow(ba.Window): # If not, abort and go into zombie mode (the user should never see # us that way). - real_price: Optional[str] + real_price: str | None # Misnomer: 'pro' actually means offer 'pro_sale'. if offer['item'] in ['pro', 'pro_fullprice']: @@ -149,7 +149,7 @@ class SpecialOfferWindow(ba.Window): color=(0.3, 1, 0.3)) self._flash_on = False - self._flashing_timer: Optional[ba.Timer] = ba.Timer( + self._flashing_timer: ba.Timer | None = ba.Timer( 0.05, ba.WeakCall(self._flash_cycle), repeat=True, @@ -363,7 +363,7 @@ class SpecialOfferWindow(ba.Window): from ba import SpecialChar if not self._root_widget: return - sval: Union[str, ba.Lstr] + sval: str | ba.Lstr if _ba.get_v1_account_state() == 'signed_in': sval = (ba.charstr(SpecialChar.TICKET) + str(_ba.get_v1_account_ticket_count())) @@ -391,7 +391,7 @@ class SpecialOfferWindow(ba.Window): # With bundle sales, the price is the name of the IAP. _ba.purchase(self._offer['price']) else: - ticket_count: Optional[int] + ticket_count: int | None try: ticket_count = _ba.get_v1_account_ticket_count() except Exception: diff --git a/dist/ba_data/python/bastd/ui/store/browser.py b/dist/ba_data/python/bastd/ui/store/browser.py index 9eb9f57..3966567 100644 --- a/dist/ba_data/python/bastd/ui/store/browser.py +++ b/dist/ba_data/python/bastd/ui/store/browser.py @@ -14,7 +14,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional, Union, Sequence + from typing import Any, Callable, Sequence class StoreBrowserWindow(ba.Window): @@ -45,7 +45,7 @@ class StoreBrowserWindow(ba.Window): ba.set_analytics_screen('Store Window') - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None # If they provided an origin-widget, scale up from that. if origin_widget is not None: @@ -56,8 +56,8 @@ class StoreBrowserWindow(ba.Window): self._transition_out = 'out_right' scale_origin = None - self.button_infos: Optional[dict[str, dict[str, Any]]] = None - self.update_buttons_timer: Optional[ba.Timer] = None + self.button_infos: dict[str, dict[str, Any]] | None = None + self.update_buttons_timer: ba.Timer | None = None self._status_textwidget_update_timer = None self._back_location = back_location @@ -68,12 +68,12 @@ class StoreBrowserWindow(ba.Window): self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 645 if uiscale is ba.UIScale.MEDIUM else 800) - self._current_tab: Optional[StoreBrowserWindow.TabID] = None + self._current_tab: StoreBrowserWindow.TabID | None = None extra_top = 30 if uiscale is ba.UIScale.SMALL else 0 self._request: Any = None self._r = 'store' - self._last_buy_time: Optional[float] = None + self._last_buy_time: float | None = None super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + extra_top), @@ -96,8 +96,8 @@ class StoreBrowserWindow(ba.Window): on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - self._ticket_count_text: Optional[ba.Widget] = None - self._get_tickets_button: Optional[ba.Widget] = None + self._ticket_count_text: ba.Widget | None = None + self._get_tickets_button: ba.Widget | None = None if ba.app.allow_ticket_purchases: self._get_tickets_button = ba.buttonwidget( @@ -265,8 +265,8 @@ class StoreBrowserWindow(ba.Window): self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180 - self._scrollwidget: Optional[ba.Widget] = None - self._status_textwidget: Optional[ba.Widget] = None + self._scrollwidget: ba.Widget | None = None + self._status_textwidget: ba.Widget | None = None self._restore_state() def _update_get_tickets_button_pos(self) -> None: @@ -322,7 +322,7 @@ class StoreBrowserWindow(ba.Window): from ba import SpecialChar if not self._root_widget: return - sval: Union[str, ba.Lstr] + sval: str | ba.Lstr if _ba.get_v1_account_state() == 'signed_in': sval = ba.charstr(SpecialChar.TICKET) + str( _ba.get_v1_account_ticket_count()) @@ -387,7 +387,7 @@ class StoreBrowserWindow(ba.Window): ba.WeakCall(self._on_response, data), timetype=ba.TimeType.REAL) - def _on_response(self, data: Optional[dict[str, Any]]) -> None: + def _on_response(self, data: dict[str, Any] | None) -> None: # FIXME: clean this up. # pylint: disable=protected-access window = self._window() @@ -401,7 +401,7 @@ class StoreBrowserWindow(ba.Window): # Actually start the purchase locally. def _purchase_check_result(self, item: str, is_ticket_purchase: bool, - result: Optional[dict[str, Any]]) -> None: + result: dict[str, Any] | None) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( @@ -564,8 +564,8 @@ class StoreBrowserWindow(ba.Window): purchased = _ba.get_purchased(b_type) sale_opacity = 0.0 - sale_title_text: Union[str, ba.Lstr] = '' - sale_time_text: Union[str, ba.Lstr] = '' + sale_title_text: str | ba.Lstr = '' + sale_time_text: str | ba.Lstr = '' if purchased: title_color = (0.8, 0.7, 0.9, 1.0) @@ -686,7 +686,7 @@ class StoreBrowserWindow(ba.Window): ba.textwidget(edit=b_info['descriptionText'], color=description_color) - def _on_response(self, data: Optional[dict[str, Any]]) -> None: + def _on_response(self, data: dict[str, Any] | None) -> None: # pylint: disable=too-many-statements # clear status text.. @@ -719,7 +719,7 @@ class StoreBrowserWindow(ba.Window): store_data = get_store_layout() self._tab = sdata['tab'] self._sections = copy.deepcopy(store_data[sdata['tab']]) - self._height: Optional[float] = None + self._height: float | None = None uiscale = ba.app.ui.uiscale @@ -856,7 +856,7 @@ class StoreBrowserWindow(ba.Window): maxwidth=700, transition_delay=0.4) - prev_row_buttons: Optional[list] = None + prev_row_buttons: list | None = None this_row_buttons = [] delay = 0.3 @@ -1021,7 +1021,7 @@ class StoreBrowserWindow(ba.Window): def _restore_state(self) -> None: from efro.util import enum_by_value try: - sel: Optional[ba.Widget] + sel: ba.Widget | None sel_name = ba.app.ui.window_states.get(type(self), {}).get('sel_name') assert isinstance(sel_name, (str, type(None))) diff --git a/dist/ba_data/python/bastd/ui/store/button.py b/dist/ba_data/python/bastd/ui/store/button.py index 3492563..c525fec 100644 --- a/dist/ba_data/python/bastd/ui/store/button.py +++ b/dist/ba_data/python/bastd/ui/store/button.py @@ -9,7 +9,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Sequence, Callable, Optional + from typing import Any, Sequence, Callable class StoreButton: @@ -46,8 +46,8 @@ class StoreButton: color=color, button_type=button_type) - self._title_text: Optional[ba.Widget] - self._ticket_text: Optional[ba.Widget] + self._title_text: ba.Widget | None + self._ticket_text: ba.Widget | None if show_tickets: self._title_text = ba.textwidget( diff --git a/dist/ba_data/python/bastd/ui/store/item.py b/dist/ba_data/python/bastd/ui/store/item.py index f7e6198..5a98edd 100644 --- a/dist/ba_data/python/bastd/ui/store/item.py +++ b/dist/ba_data/python/bastd/ui/store/item.py @@ -9,7 +9,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any def instantiate_store_item_display(item_name: str, @@ -39,7 +39,7 @@ def instantiate_store_item_display(item_name: str, item['name'] = title = get_store_item_name_translated(item_name) - btn: Optional[ba.Widget] + btn: ba.Widget | None if button: item['button'] = btn = ba.buttonwidget(parent=parent_widget, position=b_pos, @@ -61,9 +61,9 @@ def instantiate_store_item_display(item_name: str, tint_tex = None tint_color = None tint2_color = None - tex_name: Optional[str] = None - desc: Optional[str] = None - modes: Optional[ba.Lstr] = None + tex_name: str | None = None + desc: str | None = None + modes: ba.Lstr | None = None if item_name.startswith('characters.'): character = ba.app.spaz_appearances[item_info['character']] @@ -272,7 +272,7 @@ def instantiate_store_item_display(item_name: str, # the user knows how much this is worth. total_worth_item = _ba.get_v1_account_misc_read_val('twrths', {}).get(item_name) - total_worth_price: Optional[str] + total_worth_price: str | None if total_worth_item is not None: price = _ba.get_price(total_worth_item) total_worth_price = (get_clean_price(price) diff --git a/dist/ba_data/python/bastd/ui/tabs.py b/dist/ba_data/python/bastd/ui/tabs.py index b3dc38a..99ae0db 100644 --- a/dist/ba_data/python/bastd/ui/tabs.py +++ b/dist/ba_data/python/bastd/ui/tabs.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, TypeVar, Generic import ba if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable @dataclass @@ -71,7 +71,8 @@ class TabRow(Generic[T]): color=(0.52, 0.48, 0.63), textcolor=(0.65, 0.6, 0.7)) # unlit - def _tick_and_call(self, call: Optional[Callable], arg: Any) -> None: + def _tick_and_call(self, call: Callable[[Any], None] | None, + arg: Any) -> None: ba.playsound(ba.getsound('click01')) if call is not None: call(arg) diff --git a/dist/ba_data/python/bastd/ui/tournamententry.py b/dist/ba_data/python/bastd/ui/tournamententry.py index 7419c4c..f521380 100644 --- a/dist/ba_data/python/bastd/ui/tournamententry.py +++ b/dist/ba_data/python/bastd/ui/tournamententry.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Callable, Optional + from typing import Any, Callable class TournamentEntryWindow(popup.PopupWindow): @@ -56,7 +56,7 @@ class TournamentEntryWindow(popup.PopupWindow): self._purchase_name = 'tournament_entry_0' self._purchase_price_name = 'price.tournament_entry_0' - self._purchase_price: Optional[int] = None + self._purchase_price: int | None = None self._on_close_call = on_close_call if scale is None: @@ -153,7 +153,7 @@ class TournamentEntryWindow(popup.PopupWindow): text='', maxwidth=95, color=(0, 0.8, 0)) - self._pay_with_ad_btn: Optional[ba.Widget] + self._pay_with_ad_btn: ba.Widget | None if self._do_ad_btn: btn = self._pay_with_ad_btn = ba.buttonwidget( parent=self.root_widget, @@ -214,8 +214,8 @@ class TournamentEntryWindow(popup.PopupWindow): else: self._pay_with_ad_btn = None - self._get_tickets_button: Optional[ba.Widget] = None - self._ticket_count_text: Optional[ba.Widget] = None + self._get_tickets_button: ba.Widget | None = None + self._ticket_count_text: ba.Widget | None = None if not ba.app.ui.use_toolbars: if ba.app.allow_ticket_purchases: self._get_tickets_button = ba.buttonwidget( @@ -270,7 +270,7 @@ class TournamentEntryWindow(popup.PopupWindow): maxwidth=100, color=(0.7, 0.7, 0.7)) - self._last_query_time: Optional[float] = None + self._last_query_time: float | None = None # If there seems to be a relatively-recent valid cached info for this # tournament, use it. Otherwise we'll kick off a query ourselves. @@ -303,8 +303,8 @@ class TournamentEntryWindow(popup.PopupWindow): self._update() self._restore_state() - def _on_tournament_query_response(self, data: Optional[dict[str, - Any]]) -> None: + def _on_tournament_query_response(self, + data: dict[str, Any] | None) -> None: accounts = ba.app.accounts_v1 self._running_query = False if data is not None: @@ -511,7 +511,7 @@ class TournamentEntryWindow(popup.PopupWindow): return # Deny if we don't have enough tickets. - ticket_count: Optional[int] + ticket_count: int | None try: ticket_count = _ba.get_v1_account_ticket_count() except Exception: diff --git a/dist/ba_data/python/bastd/ui/tournamentscores.py b/dist/ba_data/python/bastd/ui/tournamentscores.py index ec704d9..c3ba873 100644 --- a/dist/ba_data/python/bastd/ui/tournamentscores.py +++ b/dist/ba_data/python/bastd/ui/tournamentscores.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Any, Sequence, Callable, Optional + from typing import Any, Sequence, Callable class TournamentScoresWindow(popup_ui.PopupWindow): @@ -33,7 +33,7 @@ class TournamentScoresWindow(popup_ui.PopupWindow): del tint2_color # unused arg del selected_character # unused arg self._tournament_id = tournament_id - self._subcontainer: Optional[ba.Widget] = None + self._subcontainer: ba.Widget | None = None self._on_close_call = on_close_call uiscale = ba.app.ui.uiscale if scale is None: @@ -107,8 +107,8 @@ class TournamentScoresWindow(popup_ui.PopupWindow): callback=ba.WeakCall( self._on_tournament_query_response)) - def _on_tournament_query_response(self, data: Optional[dict[str, - Any]]) -> None: + def _on_tournament_query_response(self, + data: dict[str, Any] | None) -> None: if data is not None: # this used to be the whole payload data_t: list[dict[str, Any]] = data['t'] diff --git a/dist/ba_data/python/bastd/ui/watch.py b/dist/ba_data/python/bastd/ui/watch.py index c7e871e..fc870c8 100644 --- a/dist/ba_data/python/bastd/ui/watch.py +++ b/dist/ba_data/python/bastd/ui/watch.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any class WatchWindow(ba.Window): @@ -24,13 +24,13 @@ class WatchWindow(ba.Window): TEST_TAB = 'test_tab' def __init__(self, - transition: Optional[str] = 'in_right', + transition: str | None = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.ui.tabs import TabRow ba.set_analytics_screen('Watch Window') - scale_origin: Optional[tuple[float, float]] + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -40,20 +40,20 @@ class WatchWindow(ba.Window): scale_origin = None ba.app.ui.set_main_menu_location('Watch') self._tab_data: dict[str, Any] = {} - self._my_replays_scroll_width: Optional[float] = None - self._my_replays_watch_replay_button: Optional[ba.Widget] = None - self._scrollwidget: Optional[ba.Widget] = None - self._columnwidget: Optional[ba.Widget] = None - self._my_replay_selected: Optional[str] = None - self._my_replays_rename_window: Optional[ba.Widget] = None - self._my_replay_rename_text: Optional[ba.Widget] = None + self._my_replays_scroll_width: float | None = None + self._my_replays_watch_replay_button: ba.Widget | None = None + self._scrollwidget: ba.Widget | None = None + self._columnwidget: ba.Widget | None = None + self._my_replay_selected: str | None = None + self._my_replays_rename_window: ba.Widget | None = None + self._my_replay_rename_text: ba.Widget | None = None self._r = 'watchWindow' uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 670 if uiscale is ba.UIScale.MEDIUM else 800) - self._current_tab: Optional[WatchWindow.TabID] = None + self._current_tab: WatchWindow.TabID | None = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( @@ -137,7 +137,7 @@ class WatchWindow(ba.Window): self._scroll_height + 2 * buffer_v), texture=ba.gettexture('scrollWidget'), model_transparent=ba.getmodel('softEdgeOutside')) - self._tab_container: Optional[ba.Widget] = None + self._tab_container: ba.Widget | None = None self._restore_state() @@ -503,7 +503,7 @@ class WatchWindow(ba.Window): def _restore_state(self) -> None: from efro.util import enum_by_value try: - sel: Optional[ba.Widget] + sel: ba.Widget | None sel_name = ba.app.ui.window_states.get(type(self), {}).get('sel_name') assert isinstance(sel_name, (str, type(None))) diff --git a/dist/ba_data/python/efro/dataclassio/_api.py b/dist/ba_data/python/efro/dataclassio/_api.py index 9c0597b..13eb5f9 100644 --- a/dist/ba_data/python/efro/dataclassio/_api.py +++ b/dist/ba_data/python/efro/dataclassio/_api.py @@ -18,7 +18,7 @@ from efro.dataclassio._inputter import _Inputter from efro.dataclassio._base import Codec if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any T = TypeVar('T') @@ -69,7 +69,7 @@ def dataclass_to_dict(obj: Any, def dataclass_to_json(obj: Any, coerce_to_float: bool = True, pretty: bool = False, - sort_keys: Optional[bool] = None) -> str: + sort_keys: bool | None = None) -> str: """Utility function; return a json string from a dataclass instance. Basically json.dumps(dataclass_to_dict(...)). diff --git a/dist/ba_data/python/efro/dataclassio/_base.py b/dist/ba_data/python/efro/dataclassio/_base.py index 0d0a8a9..ee7834d 100644 --- a/dist/ba_data/python/efro/dataclassio/_base.py +++ b/dist/ba_data/python/efro/dataclassio/_base.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, get_args from typing import _AnnotatedAlias # type: ignore if TYPE_CHECKING: - from typing import Any, Optional, Callable, Union + from typing import Any, Callable # Types which we can pass through as-is. SIMPLE_TYPES = {int, bool, str, float, type(None)} @@ -31,8 +31,7 @@ def _raise_type_error(fieldpath: str, valuetype: type, if len(expected) == 1: expected_str = expected[0].__name__ else: - names = ', '.join(t.__name__ for t in expected) - expected_str = f'Union[{names}]' + expected_str = ' | '.join(t.__name__ for t in expected) raise TypeError(f'Invalid value type for "{fieldpath}";' f' expected "{expected_str}", got' f' "{valuetype.__name__}".') @@ -128,21 +127,21 @@ class IOAttrs: MISSING = _MissingType() - storagename: Optional[str] = None + storagename: str | None = None store_default: bool = True whole_days: bool = False whole_hours: bool = False soft_default: Any = MISSING - soft_default_factory: Union[Callable[[], Any], _MissingType] = MISSING + soft_default_factory: Callable[[], Any] | _MissingType = MISSING def __init__( self, - storagename: Optional[str] = storagename, + storagename: str | None = storagename, store_default: bool = store_default, whole_days: bool = whole_days, whole_hours: bool = whole_hours, soft_default: Any = MISSING, - soft_default_factory: Union[Callable[[], Any], _MissingType] = MISSING, + soft_default_factory: Callable[[], Any] | _MissingType = MISSING, ): # Only store values that differ from class defaults to keep @@ -216,12 +215,12 @@ def _get_origin(anntype: Any) -> Any: return anntype if origin is None else origin -def _parse_annotated(anntype: Any) -> tuple[Any, Optional[IOAttrs]]: +def _parse_annotated(anntype: Any) -> tuple[Any, IOAttrs | None]: """Parse Annotated() constructs, returning annotated type & IOAttrs.""" # If we get an Annotated[foo, bar, eep] we take # foo as the actual type, and we look for IOAttrs instances in # bar/eep to affect our behavior. - ioattrs: Optional[IOAttrs] = None + ioattrs: IOAttrs | None = None if isinstance(anntype, _AnnotatedAlias): annargs = get_args(anntype) for annarg in annargs[1:]: diff --git a/dist/ba_data/python/efro/dataclassio/_inputter.py b/dist/ba_data/python/efro/dataclassio/_inputter.py index 34c1e5f..85f4516 100644 --- a/dist/ba_data/python/efro/dataclassio/_inputter.py +++ b/dist/ba_data/python/efro/dataclassio/_inputter.py @@ -23,7 +23,7 @@ from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, from efro.dataclassio._prep import PrepSession if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any from efro.dataclassio._base import IOAttrs from efro.dataclassio._outputter import _Outputter @@ -44,7 +44,7 @@ class _Inputter(Generic[T]): self._coerce_to_float = coerce_to_float self._allow_unknown_attrs = allow_unknown_attrs self._discard_unknown_attrs = discard_unknown_attrs - self._soft_default_validator: Optional[_Outputter] = None + self._soft_default_validator: _Outputter | None = None if not allow_unknown_attrs and discard_unknown_attrs: raise ValueError('discard_unknown_attrs cannot be True' @@ -63,7 +63,7 @@ class _Inputter(Generic[T]): return out def _value_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: Optional[IOAttrs]) -> Any: + value: Any, ioattrs: IOAttrs | None) -> Any: """Convert an assigned value to what a dataclass field expects.""" # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches @@ -270,7 +270,7 @@ class _Inputter(Generic[T]): fieldpath=fieldpath) def _dict_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: Optional[IOAttrs]) -> Any: + value: Any, ioattrs: IOAttrs | None) -> Any: # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -370,7 +370,7 @@ class _Inputter(Generic[T]): def _sequence_from_input(self, cls: type, fieldpath: str, anntype: Any, value: Any, seqtype: type, - ioattrs: Optional[IOAttrs]) -> Any: + ioattrs: IOAttrs | None) -> Any: # Because we are json-centric, we expect a list for all sequences. if type(value) is not list: @@ -396,7 +396,7 @@ class _Inputter(Generic[T]): for i in value) def _datetime_from_input(self, cls: type, fieldpath: str, value: Any, - ioattrs: Optional[IOAttrs]) -> Any: + ioattrs: IOAttrs | None) -> Any: # For firestore we expect a datetime object. if self._codec is Codec.FIRESTORE: @@ -428,7 +428,7 @@ class _Inputter(Generic[T]): return out def _tuple_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: Optional[IOAttrs]) -> Any: + value: Any, ioattrs: IOAttrs | None) -> Any: out: list = [] diff --git a/dist/ba_data/python/efro/dataclassio/_outputter.py b/dist/ba_data/python/efro/dataclassio/_outputter.py index e268d7d..03965e1 100644 --- a/dist/ba_data/python/efro/dataclassio/_outputter.py +++ b/dist/ba_data/python/efro/dataclassio/_outputter.py @@ -23,7 +23,7 @@ from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, from efro.dataclassio._prep import PrepSession if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any from efro.dataclassio._base import IOAttrs @@ -64,7 +64,7 @@ class _Outputter: recursion_level=0) assert prep is not None fields = dataclasses.fields(obj) - out: Optional[dict[str, Any]] = {} if self._create else None + out: dict[str, Any] | None = {} if self._create else None for field in fields: fieldname = field.name if fieldpath: @@ -118,15 +118,16 @@ class _Outputter: if isinstance(extra_attrs, dict): if not _is_valid_for_codec(extra_attrs, self._codec): raise TypeError( - f'Extra attrs on {fieldpath} contains data type(s)' - f' not supported by json.') + f'Extra attrs on \'{fieldpath}\' contains data type(s)' + f' not supported by \'{self._codec.value}\' codec:' + f' {extra_attrs}.') if self._create: assert out is not None out.update(extra_attrs) return out def _process_value(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: Optional[IOAttrs]) -> Any: + value: Any, ioattrs: IOAttrs | None) -> Any: # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -307,7 +308,7 @@ class _Outputter: return value def _process_dict(self, cls: type, fieldpath: str, anntype: Any, - value: dict, ioattrs: Optional[IOAttrs]) -> Any: + value: dict, ioattrs: IOAttrs | None) -> Any: # pylint: disable=too-many-branches if not isinstance(value, dict): raise TypeError(f'Expected a dict for {fieldpath};' @@ -329,7 +330,7 @@ class _Outputter: # Ok; we've got a definite key type (which we verified as valid # during prep). Make sure all keys match it. - out: Optional[dict] = {} if self._create else None + out: dict | None = {} if self._create else None keyanntype, valanntype = childtypes # str keys we just export directly since that's supported by json. diff --git a/dist/ba_data/python/efro/dataclassio/_prep.py b/dist/ba_data/python/efro/dataclassio/_prep.py index c6fd503..704fb72 100644 --- a/dist/ba_data/python/efro/dataclassio/_prep.py +++ b/dist/ba_data/python/efro/dataclassio/_prep.py @@ -21,7 +21,7 @@ from efro.dataclassio._base import (_parse_annotated, _get_origin, SIMPLE_TYPES) if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any from efro.dataclassio._base import IOAttrs T = TypeVar('T') @@ -115,12 +115,12 @@ class PrepData: class PrepSession: """Context for a prep.""" - def __init__(self, explicit: bool, globalns: Optional[dict] = None): + def __init__(self, explicit: bool, globalns: dict | None = None): self.explicit = explicit self.globalns = globalns def prep_dataclass(self, cls: type, - recursion_level: int) -> Optional[PrepData]: + recursion_level: int) -> PrepData | None: """Run prep on a dataclass if necessary and return its prep data. The only case where this will return None is for recursive types @@ -232,7 +232,7 @@ class PrepSession: return prepdata def prep_type(self, cls: type, attrname: str, anntype: Any, - ioattrs: Optional[IOAttrs], recursion_level: int) -> None: + ioattrs: IOAttrs | None, recursion_level: int) -> None: """Run prep on a dataclass.""" # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches diff --git a/dist/ba_data/python/efro/message/_message.py b/dist/ba_data/python/efro/message/_message.py index 5c4700a..c472d21 100644 --- a/dist/ba_data/python/efro/message/_message.py +++ b/dist/ba_data/python/efro/message/_message.py @@ -42,12 +42,6 @@ class Response: # Some standard response types: -class ErrorType(Enum): - """Type of error that occurred in remote message handling.""" - OTHER = 0 - CLEAN = 1 - - @ioprepped @dataclass class ErrorResponse(Response): @@ -56,6 +50,13 @@ class ErrorResponse(Response): This type is unique in that it is not returned to the user; it instead results in a local exception being raised. """ + + class ErrorType(Enum): + """Type of error that occurred in remote message handling.""" + OTHER = 0 + CLEAN = 1 + LOCAL = 2 + error_message: Annotated[str, IOAttrs('m')] error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.OTHER diff --git a/dist/ba_data/python/efro/message/_module.py b/dist/ba_data/python/efro/message/_module.py index 4e278bb..16c0982 100644 --- a/dist/ba_data/python/efro/message/_module.py +++ b/dist/ba_data/python/efro/message/_module.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING from efro.message._protocol import MessageProtocol if TYPE_CHECKING: - from typing import Optional + pass def create_sender_module( @@ -20,8 +20,8 @@ def create_sender_module( enable_sync_sends: bool, enable_async_sends: bool, private: bool = False, - protocol_module_level_import_code: Optional[str] = None, - build_time_protocol_create_code: Optional[str] = None, + protocol_module_level_import_code: str | None = None, + build_time_protocol_create_code: str | None = None, ) -> str: """Create a Python module defining a MessageSender subclass. @@ -59,8 +59,8 @@ def create_receiver_module( protocol_create_code: str, is_async: bool, private: bool = False, - protocol_module_level_import_code: Optional[str] = None, - build_time_protocol_create_code: Optional[str] = None, + protocol_module_level_import_code: str | None = None, + build_time_protocol_create_code: str | None = None, ) -> str: """"Create a Python module defining a MessageReceiver subclass. diff --git a/dist/ba_data/python/efro/message/_protocol.py b/dist/ba_data/python/efro/message/_protocol.py index 1d35db4..a117102 100644 --- a/dist/ba_data/python/efro/message/_protocol.py +++ b/dist/ba_data/python/efro/message/_protocol.py @@ -15,8 +15,7 @@ from efro.error import CleanError from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict, dataclass_from_dict) from efro.message._message import (Message, Response, ErrorResponse, - EmptyResponse, ErrorType, - UnregisteredMessageIDError) + EmptyResponse, UnregisteredMessageIDError) if TYPE_CHECKING: from typing import Any, Literal @@ -141,11 +140,11 @@ class MessageProtocol: # If anything goes wrong, return a ErrorResponse instead. if isinstance(exc, CleanError) and self.preserve_clean_errors: return ErrorResponse(error_message=str(exc), - error_type=ErrorType.CLEAN) + error_type=ErrorResponse.ErrorType.CLEAN) return ErrorResponse( error_message=(traceback.format_exc() if self.trusted_sender else 'An unknown error has occurred.'), - error_type=ErrorType.OTHER) + error_type=ErrorResponse.ErrorType.OTHER) def _to_dict(self, message: Any, ids_by_type: dict[type, int], opname: str) -> dict: diff --git a/dist/ba_data/python/efro/message/_receiver.py b/dist/ba_data/python/efro/message/_receiver.py index 36cada0..f45b255 100644 --- a/dist/ba_data/python/efro/message/_receiver.py +++ b/dist/ba_data/python/efro/message/_receiver.py @@ -15,7 +15,7 @@ from efro.message._message import (Message, Response, EmptyResponse, ErrorResponse, UnregisteredMessageIDError) if TYPE_CHECKING: - from typing import Any, Callable, Optional, Union + from typing import Any, Callable, Awaitable from efro.message._protocol import MessageProtocol @@ -50,14 +50,19 @@ class MessageReceiver: def __init__(self, protocol: MessageProtocol) -> None: self.protocol = protocol self._handlers: dict[type[Message], Callable] = {} - self._decode_filter_call: Optional[Callable[[Any, dict, Message], - None]] = None - self._encode_filter_call: Optional[Callable[[Any, Response, dict], - None]] = None + self._decode_filter_call: Callable[[Any, dict, Message], + None] | None = None + self._encode_filter_call: Callable[ + [Any, Message | None, Response, dict], None] | None = None + + # TODO: don't currently have async encode equivalent + # or either for sender; can add as needed. + self._decode_filter_async_call: Callable[[Any, dict, Message], + Awaitable[None]] | None = None # noinspection PyProtectedMember def register_handler( - self, call: Callable[[Any, Message], Optional[Response]]) -> None: + self, call: Callable[[Any, Message], Response | None]) -> None: """Register a handler call. The message type handled by the call is determined by its @@ -101,7 +106,7 @@ class MessageReceiver: assert issubclass(msgtype, Message) ret = anns.get('return') - responsetypes: tuple[Union[type[Any], type[None]], ...] + responsetypes: tuple[type[Any] | type[None], ...] # Return types can be a single type or a union of types. if isinstance(ret, (_GenericAlias, types.UnionType)): @@ -152,15 +157,29 @@ class MessageReceiver: """Function decorator for defining a decode filter. Decode filters can be used to extract extra data from incoming - message dicts. + message dicts. This version will work for both handle_raw_message() + and handle_raw_message_async() """ assert self._decode_filter_call is None self._decode_filter_call = call return call + def decode_filter_async_method( + self, call: Callable[[Any, dict, Message], Awaitable[None]] + ) -> Callable[[Any, dict, Message], Awaitable[None]]: + """Function decorator for defining a decode filter. + + Decode filters can be used to extract extra data from incoming + message dicts. Note that this version will only work with + handle_raw_message_async(). + """ + assert self._decode_filter_async_call is None + self._decode_filter_async_call = call + return call + def encode_filter_method( - self, call: Callable[[Any, Response, dict], None] - ) -> Callable[[Any, Response, dict], None]: + self, call: Callable[[Any, Message | None, Response, dict], None] + ) -> Callable[[Any, Message | None, Response, dict], None]: """Function decorator for defining an encode filter. Encode filters can be used to add extra data to the message @@ -183,21 +202,38 @@ class MessageReceiver: else: raise TypeError(msg) - def _decode_incoming_message(self, bound_obj: Any, - msg: str) -> tuple[Message, type[Message]]: + def _decode_incoming_message_base(self, bound_obj: Any, + msg: str) -> tuple[Any, dict, Message]: # Decode the incoming message. msg_dict = self.protocol.decode_dict(msg) msg_decoded = self.protocol.message_from_dict(msg_dict) - msgtype = type(msg_decoded) - assert issubclass(msgtype, Message) + assert isinstance(msg_decoded, Message) if self._decode_filter_call is not None: self._decode_filter_call(bound_obj, msg_dict, msg_decoded) + return bound_obj, msg_dict, msg_decoded - return msg_decoded, msgtype + def _decode_incoming_message(self, bound_obj: Any, msg: str) -> Message: + bound_obj, _msg_dict, msg_decoded = ( + self._decode_incoming_message_base(bound_obj=bound_obj, msg=msg)) - def encode_user_response(self, bound_obj: Any, - response: Optional[Response], - msgtype: type[Message]) -> str: + # If they've set an async filter but are calling sync + # handle_raw_message() its likely a bug. + assert self._decode_filter_async_call is None + + return msg_decoded + + async def _decode_incoming_message_async(self, bound_obj: Any, + msg: str) -> Message: + bound_obj, msg_dict, msg_decoded = (self._decode_incoming_message_base( + bound_obj=bound_obj, msg=msg)) + + if self._decode_filter_async_call is not None: + await self._decode_filter_async_call(bound_obj, msg_dict, + msg_decoded) + return msg_decoded + + def encode_user_response(self, bound_obj: Any, message: Message, + response: Response | None) -> str: """Encode a response provided by the user for sending.""" # A return value of None equals EmptyResponse. @@ -207,18 +243,21 @@ class MessageReceiver: assert isinstance(response, Response) # (user should never explicitly return error-responses) assert not isinstance(response, ErrorResponse) - assert type(response) in msgtype.get_response_types() + assert type(response) in message.get_response_types() response_dict = self.protocol.response_to_dict(response) if self._encode_filter_call is not None: - self._encode_filter_call(bound_obj, response, response_dict) + self._encode_filter_call(bound_obj, message, response, + response_dict) return self.protocol.encode_dict(response_dict) - def encode_error_response(self, bound_obj: Any, exc: Exception) -> str: + def encode_error_response(self, bound_obj: Any, message: Message | None, + exc: Exception) -> str: """Given an error, return a response ready for sending.""" response = self.protocol.error_to_response(exc) response_dict = self.protocol.response_to_dict(response) if self._encode_filter_call is not None: - self._encode_filter_call(bound_obj, response, response_dict) + self._encode_filter_call(bound_obj, message, response, + response_dict) return self.protocol.encode_dict(response_dict) def handle_raw_message(self, @@ -233,21 +272,22 @@ class MessageReceiver: error responses returned to the sender. """ assert not self.is_async, "can't call sync handler on async receiver" + msg_decoded: Message | None = None try: - msg_decoded, msgtype = self._decode_incoming_message( - bound_obj, msg) + msg_decoded = self._decode_incoming_message(bound_obj, msg) + msgtype = type(msg_decoded) handler = self._handlers.get(msgtype) if handler is None: raise RuntimeError(f'Got unhandled message type: {msgtype}.') response = handler(bound_obj, msg_decoded) assert isinstance(response, (Response, type(None))) - return self.encode_user_response(bound_obj, response, msgtype) + return self.encode_user_response(bound_obj, msg_decoded, response) except Exception as exc: if (raise_unregistered and isinstance(exc, UnregisteredMessageIDError)): raise - return self.encode_error_response(bound_obj, exc) + return self.encode_error_response(bound_obj, msg_decoded, exc) async def handle_raw_message_async( self, @@ -259,21 +299,23 @@ class MessageReceiver: The return value is the raw response to the message. """ assert self.is_async, "can't call async handler on sync receiver" + msg_decoded: Message | None = None try: - msg_decoded, msgtype = self._decode_incoming_message( + msg_decoded = await self._decode_incoming_message_async( bound_obj, msg) + msgtype = type(msg_decoded) handler = self._handlers.get(msgtype) if handler is None: raise RuntimeError(f'Got unhandled message type: {msgtype}.') response = await handler(bound_obj, msg_decoded) assert isinstance(response, (Response, type(None))) - return self.encode_user_response(bound_obj, response, msgtype) + return self.encode_user_response(bound_obj, msg_decoded, response) except Exception as exc: if (raise_unregistered and isinstance(exc, UnregisteredMessageIDError)): raise - return self.encode_error_response(bound_obj, exc) + return self.encode_error_response(bound_obj, msg_decoded, exc) class BoundMessageReceiver: @@ -294,5 +336,12 @@ class BoundMessageReceiver: return self._receiver.protocol def encode_error_response(self, exc: Exception) -> str: - """Given an error, return a response ready to send.""" - return self._receiver.encode_error_response(self._obj, exc) + """Given an error, return a response ready to send. + + This should be used for any errors that happen outside of + of standard handle_raw_message calls. Any errors within those + calls should be automatically returned as encoded strings. + """ + # Passing None for Message here; we would only have that available + # for things going wrong in the handler (which this is not for). + return self._receiver.encode_error_response(self._obj, None, exc) diff --git a/dist/ba_data/python/efro/message/_sender.py b/dist/ba_data/python/efro/message/_sender.py index 0a2c015..2446586 100644 --- a/dist/ba_data/python/efro/message/_sender.py +++ b/dist/ba_data/python/efro/message/_sender.py @@ -6,13 +6,14 @@ Supports static typing for message types and possible return types. from __future__ import annotations +import logging from typing import TYPE_CHECKING, TypeVar from efro.error import CleanError, RemoteError -from efro.message._message import (EmptyResponse, ErrorResponse, ErrorType) +from efro.message._message import EmptyResponse, ErrorResponse if TYPE_CHECKING: - from typing import Any, Callable, Optional, Awaitable + from typing import Any, Callable, Awaitable from efro.message._message import Message, Response from efro.message._protocol import MessageProtocol @@ -42,13 +43,13 @@ class MessageSender: def __init__(self, protocol: MessageProtocol) -> None: self.protocol = protocol - self._send_raw_message_call: Optional[Callable[[Any, str], str]] = None - self._send_async_raw_message_call: Optional[Callable[ - [Any, str], Awaitable[str]]] = None - self._encode_filter_call: Optional[Callable[[Any, Message, dict], - None]] = None - self._decode_filter_call: Optional[Callable[[Any, dict, Response], - None]] = None + self._send_raw_message_call: Callable[[Any, str], str] | None = None + self._send_async_raw_message_call: Callable[ + [Any, str], Awaitable[str]] | None = None + self._encode_filter_call: Callable[[Any, Message, dict], + None] | None = None + self._decode_filter_call: Callable[[Any, Message, dict, Response], + None] | None = None def send_method( self, call: Callable[[Any, str], @@ -79,8 +80,8 @@ class MessageSender: return call def decode_filter_method( - self, call: Callable[[Any, dict, Response], None] - ) -> Callable[[Any, dict, Response], None]: + self, call: Callable[[Any, Message, dict, Response], None] + ) -> Callable[[Any, Message, dict, Response], None]: """Function decorator for defining a decode filter. Decode filters can be used to extract extra data from incoming @@ -90,71 +91,137 @@ class MessageSender: self._decode_filter_call = call return call - def send(self, bound_obj: Any, message: Message) -> Optional[Response]: - """Send a message and receive a response. + def send(self, bound_obj: Any, message: Message) -> Response | None: + """Send a message synchronously.""" + return self.send_split_part_2( + message=message, + raw_response=self.send_split_part_1( + bound_obj=bound_obj, + message=message, + ), + ) - Will encode the message for transport and call dispatch_raw_message() + async def send_async(self, bound_obj: Any, + message: Message) -> Response | None: + """Send a message asynchronously.""" + return self.send_split_part_2( + message=message, + raw_response=await self.send_split_part_1_async( + bound_obj=bound_obj, + message=message, + ), + ) + + def send_split_part_1(self, bound_obj: Any, message: Message) -> Response: + """Send a message synchronously. + + Generally you can just call send(); these split versions are + for when message sending and response handling need to happen + in different contexts/threads. """ if self._send_raw_message_call is None: raise RuntimeError('send() is unimplemented for this type.') - msg_encoded = self.encode_message(bound_obj, message) - + msg_encoded = self._encode_message(bound_obj, message) response_encoded = self._send_raw_message_call(bound_obj, msg_encoded) + return self._decode_raw_response(bound_obj, message, response_encoded) - response = self.decode_response(bound_obj, response_encoded) + async def send_split_part_1_async(self, bound_obj: Any, + message: Message) -> Response: + """Send a message asynchronously. + + Generally you can just call send(); these split versions are + for when message sending and response handling need to happen + in different contexts/threads. + """ + + if self._send_async_raw_message_call is None: + raise RuntimeError('send_async() is unimplemented for this type.') + + msg_encoded = self._encode_message(bound_obj, message) + response_encoded = await self._send_async_raw_message_call( + bound_obj, msg_encoded) + + return self._decode_raw_response(bound_obj, message, response_encoded) + + def send_split_part_2(self, message: Message, + raw_response: Response) -> Response | None: + """Complete message sending (both sync and async). + + Generally you can just call send(); these split versions are + for when message sending and response handling need to happen + in different contexts/threads. + """ + response = self._unpack_raw_response(raw_response) assert (response is None or type(response) in type(message).get_response_types()) return response - def encode_message(self, bound_obj: Any, message: Message) -> str: + def _encode_message(self, bound_obj: Any, message: Message) -> str: """Encode a message for sending.""" msg_dict = self.protocol.message_to_dict(message) if self._encode_filter_call is not None: self._encode_filter_call(bound_obj, message, msg_dict) return self.protocol.encode_dict(msg_dict) - def decode_response(self, bound_obj: Any, - response_encoded: str) -> Optional[Response]: - """Decode, filter, and possibly act on raw response data.""" - response_dict = self.protocol.decode_dict(response_encoded) - response = self.protocol.response_from_dict(response_dict) - if self._decode_filter_call is not None: - self._decode_filter_call(bound_obj, response_dict, response) + def _decode_raw_response(self, bound_obj: Any, message: Message, + response_encoded: str) -> Response: + """Create a Response from returned data. - # Special case: if we get EmptyResponse, we simply return None. - if isinstance(response, EmptyResponse): + These Responses may encapsulate things like remote errors and + should not be handed directly to users. _unpack_raw_response() + should be used to translate to special values like None or raise + Exceptions. This function itself should never raise Exceptions. + """ + try: + response_dict = self.protocol.decode_dict(response_encoded) + response = self.protocol.response_from_dict(response_dict) + if self._decode_filter_call is not None: + self._decode_filter_call(bound_obj, message, response_dict, + response) + except Exception: + # If we got to this point, we successfully communicated + # with the other end so errors represent protocol mismatches + # or other invalid data. For now let's just log it but perhaps + # we'd want to somehow embed it in the ErrorResponse to be raised + # directly to the user later. + logging.exception('Error decoding raw response') + response = ErrorResponse( + error_message= + 'Error decoding raw response; see log for details.', + error_type=ErrorResponse.ErrorType.LOCAL) + return response + + def _unpack_raw_response(self, raw_response: Response) -> Response | None: + """Given a raw Response, unpacks to special values or Exceptions. + + The result of this call is what should be passed to users. + For complex messaging situations such as response callbacks + operating across different threads, this last stage should be + run such that any raised Exception is active when the callback + fires; not on the thread where the message was sent. + """ + # EmptyResponse translates to None + if isinstance(raw_response, EmptyResponse): return None - # Special case: a remote error occurred. Raise a local Exception - # instead of returning the message. - if isinstance(response, ErrorResponse): - if (self.protocol.preserve_clean_errors - and response.error_type is ErrorType.CLEAN): - raise CleanError(response.error_message) - raise RemoteError(response.error_message) + # Some error occurred. Raise a local Exception for it. + if isinstance(raw_response, ErrorResponse): - return response + # If something went wrong on our end of the connection, + # don't say it was a remote error. + if raw_response.error_type is ErrorResponse.ErrorType.LOCAL: + raise RuntimeError(raw_response.error_message) - async def send_async(self, bound_obj: Any, - message: Message) -> Optional[Response]: - """Send a message asynchronously using asyncio. + # If they want to support clean errors, do those. + if (self.protocol.preserve_clean_errors and + raw_response.error_type is ErrorResponse.ErrorType.CLEAN): + raise CleanError(raw_response.error_message) - The message will be encoded for transport and passed to - dispatch_raw_message_async. - """ - if self._send_async_raw_message_call is None: - raise RuntimeError('send_async() is unimplemented for this type.') + # In all other cases, just say something went wrong 'out there'. + raise RemoteError(raw_response.error_message) - msg_encoded = self.encode_message(bound_obj, message) - - response_encoded = await self._send_async_raw_message_call( - bound_obj, msg_encoded) - - response = self.decode_response(bound_obj, response_encoded) - assert (response is None - or type(response) in type(message).get_response_types()) - return response + return raw_response class BoundMessageSender: @@ -171,20 +238,34 @@ class BoundMessageSender: """Protocol associated with this sender.""" return self._sender.protocol - def send_untyped(self, message: Message) -> Optional[Response]: + def send_untyped(self, message: Message) -> Response | None: """Send a message synchronously. Whenever possible, use the send() call provided by generated subclasses instead of this; it will provide better type safety. """ assert self._obj is not None - return self._sender.send(self._obj, message) + return self._sender.send(bound_obj=self._obj, message=message) - async def send_async_untyped(self, message: Message) -> Optional[Response]: + async def send_async_untyped(self, message: Message) -> Response | None: """Send a message asynchronously. Whenever possible, use the send_async() call provided by generated subclasses instead of this; it will provide better type safety. """ assert self._obj is not None - return await self._sender.send_async(self._obj, message) + return await self._sender.send_async(bound_obj=self._obj, + message=message) + + async def send_split_part_1_async_untyped(self, + message: Message) -> Response: + """Split send (part 1 of 2).""" + assert self._obj is not None + return await self._sender.send_split_part_1_async(bound_obj=self._obj, + message=message) + + def send_split_part_2_untyped(self, message: Message, + raw_response: Response) -> Response | None: + """Split send (part 2 of 2).""" + return self._sender.send_split_part_2(message=message, + raw_response=raw_response) diff --git a/dist/ba_data/python/efro/rpc.py b/dist/ba_data/python/efro/rpc.py index 3484eff..6afc310 100644 --- a/dist/ba_data/python/efro/rpc.py +++ b/dist/ba_data/python/efro/rpc.py @@ -19,7 +19,7 @@ from efro.dataclassio import (dataclass_to_json, dataclass_from_json, ioprepped, IOAttrs) if TYPE_CHECKING: - from typing import Literal, Awaitable, Callable, Optional + from typing import Literal, Awaitable, Callable # Terminology: # Packet: A chunk of data consisting of a type and some type-dependent @@ -33,6 +33,8 @@ class _PacketType(Enum): KEEPALIVE = 1 MESSAGE = 2 RESPONSE = 3 + MESSAGE_BIG = 4 + RESPONSE_BIG = 5 _BYTE_ORDER: Literal['big'] = 'big' @@ -49,14 +51,20 @@ class _PeerInfo: keepalive_interval: Annotated[float, IOAttrs('k')] -OUR_PROTOCOL = 1 +# Note: we are expected to be forward and backward compatible; we can +# increment protocol freely and expect everyone else to still talk to us. +# Likewise we should retain logic to communicate with older protocols. +# Protocol history: +# 1 - initial release +# 2 - gained big (32-bit len val) package/response packets +OUR_PROTOCOL = 2 class _InFlightMessage: """Represents a message that is out on the wire.""" def __init__(self) -> None: - self._response: Optional[bytes] = None + self._response: bytes | None = None self._got_response = asyncio.Event() self.wait_task = asyncio.create_task(self._wait()) @@ -126,7 +134,7 @@ class RPCEndpoint: self._out_packets: list[bytes] = [] self._have_out_packets = asyncio.Event() self._run_called = False - self._peer_info: Optional[_PeerInfo] = None + self._peer_info: _PeerInfo | None = None self._keepalive_interval = keepalive_interval self._keepalive_timeout = keepalive_timeout @@ -135,7 +143,7 @@ class RPCEndpoint: self._tasks: list[weakref.ref[asyncio.Task]] = [] # When we last got a keepalive or equivalent (time.monotonic value) - self._last_keepalive_receive_time: Optional[float] = None + self._last_keepalive_receive_time: float | None = None # (Start near the end to make sure our looping logic is sound). self._next_message_id = 65530 @@ -193,7 +201,7 @@ class RPCEndpoint: async def send_message(self, message: bytes, - timeout: Optional[float] = None) -> bytes: + timeout: float | None = None) -> bytes: """Send a message to the peer and return a response. If timeout is not provided, the default will be used. @@ -201,21 +209,38 @@ class RPCEndpoint: for any reason. """ self._check_env() - if len(message) > 65535: - raise RuntimeError('Message cannot be larger than 65535 bytes') if self._closing: raise CommunicationError('Endpoint is closed') - # Go with 16 bit looping value for message_id. + # We need to know their protocol, so if we haven't gotten a handshake + # from them yet, just wait. + while self._peer_info is None: + await asyncio.sleep(0.01) + assert self._peer_info is not None + + if self._peer_info.protocol == 1: + if len(message) > 65535: + raise RuntimeError('Message cannot be larger than 65535 bytes') + + # message_id is a 16 bit looping value. message_id = self._next_message_id self._next_message_id = (self._next_message_id + 1) % 65536 - # Payload consists of type (1b), message_id (2b), len (2b), and data. - self._enqueue_outgoing_packet( - _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(message).to_bytes(2, _BYTE_ORDER) + message) + if len(message) > 65535: + # Payload consists of type (1b), message_id (2b), + # len (4b), and data. + self._enqueue_outgoing_packet( + _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(4, _BYTE_ORDER) + message) + else: + # Payload consists of type (1b), message_id (2b), + # len (2b), and data. + self._enqueue_outgoing_packet( + _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(2, _BYTE_ORDER) + message) # Make an entry so we know this message is out there. assert message_id not in self._in_flight_messages @@ -381,17 +406,27 @@ class RPCEndpoint: self._last_keepalive_receive_time = time.monotonic() elif mtype is _PacketType.MESSAGE: - await self._handle_message_packet() + await self._handle_message_packet(big=False) + + elif mtype is _PacketType.MESSAGE_BIG: + await self._handle_message_packet(big=True) elif mtype is _PacketType.RESPONSE: - await self._handle_response_packet() + await self._handle_response_packet(big=False) + + elif mtype is _PacketType.RESPONSE_BIG: + await self._handle_response_packet(big=True) else: assert_never(mtype) - async def _handle_message_packet(self) -> None: + async def _handle_message_packet(self, big: bool) -> None: + assert self._peer_info is not None msgid = await self._read_int_16() - msglen = await self._read_int_16() + if big: + msglen = await self._read_int_32() + else: + msglen = await self._read_int_16() msg = await self._reader.readexactly(msglen) if self._debug_print_io: self._debug_print_call(f'{self._label}: received message {msgid}' @@ -408,9 +443,14 @@ class RPCEndpoint: self._debug_print_call( f'{self._label}: done handling message at {self._tm()}.') - async def _handle_response_packet(self) -> None: + async def _handle_response_packet(self, big: bool) -> None: + assert self._peer_info is not None msgid = await self._read_int_16() - rsplen = await self._read_int_16() + # Protocol 2 gained 32 bit data lengths. + if big: + rsplen = await self._read_int_32() + else: + rsplen = await self._read_int_16() if self._debug_print_io: self._debug_print_call(f'{self._label}: received response {msgid}' f' of size {rsplen} at {self._tm()}.') @@ -520,12 +560,25 @@ class RPCEndpoint: logging.exception('Error handling message') return + assert self._peer_info is not None + + if self._peer_info.protocol == 1: + if len(response) > 65535: + raise RuntimeError( + 'Response cannot be larger than 65535 bytes') + # Now send back our response. # Payload consists of type (1b), msgid (2b), len (2b), and data. - self._enqueue_outgoing_packet( - _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(response).to_bytes(2, _BYTE_ORDER) + response) + if len(response) > 65535: + self._enqueue_outgoing_packet( + _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(4, _BYTE_ORDER) + response) + else: + self._enqueue_outgoing_packet( + _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(2, _BYTE_ORDER) + response) async def _read_int_8(self) -> int: return int.from_bytes(await self._reader.readexactly(1), _BYTE_ORDER)