sync changes with master

This commit is contained in:
Ayush Saini 2022-06-30 00:31:52 +05:30
parent 03034e4aa0
commit de0199ad50
178 changed files with 2191 additions and 1481 deletions

View file

@ -24,6 +24,7 @@ from ba._actor import Actor
from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation
from ba._nodeactor import NodeActor from ba._nodeactor import NodeActor
from ba._app import App from ba._app import App
from ba._cloud import CloudSubsystem
from ba._coopgame import CoopGameActivity from ba._coopgame import CoopGameActivity
from ba._coopsession import CoopSession from ba._coopsession import CoopSession
from ba._dependency import (Dependency, DependencyComponent, DependencySet, from ba._dependency import (Dependency, DependencyComponent, DependencySet,
@ -89,7 +90,7 @@ __all__ = [
'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported', 'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported',
'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget', 'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget',
'containerwidget', 'Context', 'ContextCall', 'ContextError', 'containerwidget', 'Context', 'ContextCall', 'ContextError',
'CoopGameActivity', 'CoopSession', 'Data', 'DeathType', 'CloudSubsystem', 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType',
'DelegateNotFoundError', 'Dependency', 'DependencyComponent', 'DelegateNotFoundError', 'Dependency', 'DependencyComponent',
'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage', 'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage',
'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam', 'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam',

View file

@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class AccountV1Subsystem: class AccountV1Subsystem:
@ -23,12 +23,12 @@ class AccountV1Subsystem:
""" """
def __init__(self) -> None: 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. # FIXME: should abstract/structure these.
self.tournament_info: dict = {} self.tournament_info: dict = {}
self.league_rank_cache: 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 # 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. # 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) return self.league_rank_cache.get('info', None)
def get_league_rank_points(self, def get_league_rank_points(self,
data: Optional[dict[str, Any]], data: dict[str, Any] | None,
subset: str = None) -> int: subset: str = None) -> int:
"""(internal)""" """(internal)"""
if data is None: if data is None:

View file

@ -6,8 +6,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
class AccountV2Subsystem: class AccountV2Subsystem:
@ -18,10 +20,20 @@ class AccountV2Subsystem:
Access the single shared instance of this class at 'ba.app.accounts_v2'. 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: def on_app_launch(self) -> None:
"""Should be called at standard on_app_launch time.""" """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.""" """Set credentials for the primary app account."""
raise RuntimeError('This should be overridden.') raise RuntimeError('This should be overridden.')
@ -35,17 +47,74 @@ class AccountV2Subsystem:
raise RuntimeError('This should be overridden.') raise RuntimeError('This should be overridden.')
@property @property
def primary(self) -> Optional[AccountV2Handle]: def primary(self) -> AccountV2Handle | None:
"""The primary account for the app, or None if not logged in.""" """The primary account for the app, or None if not logged in."""
return None return None
def get_primary(self) -> Optional[AccountV2Handle]: def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass.""" """Internal - should be overridden by subclass."""
return None 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: class AccountV2Handle:
"""Handle for interacting with a v2 account.""" """Handle for interacting with a v2 account."""
def __init__(self) -> None: def __init__(self) -> None:
self.tag = '?' self.tag = '?'
self.workspacename: str | None = None
self.workspaceid: str | None = None

View file

@ -9,7 +9,7 @@ import _ba
from ba._error import print_exception from ba._error import print_exception
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Union, Optional from typing import Any, Sequence
import ba import ba
# This could use some cleanup. # This could use some cleanup.
@ -73,7 +73,7 @@ class AchievementSubsystem:
def __init__(self) -> None: def __init__(self) -> None:
self.achievements: list[Achievement] = [] self.achievements: list[Achievement] = []
self.achievements_to_display: (list[tuple[ba.Achievement, bool]]) = [] 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.last_achievement_display_time: float = 0.0
self.achievement_completion_banner_slots: set[int] = set() self.achievement_completion_banner_slots: set[int] = set()
self._init_achievements() self._init_achievements()
@ -450,7 +450,7 @@ class Achievement:
self._icon_name = icon_name self._icon_name = icon_name
self._icon_color: Sequence[float] = list(icon_color) + [1] self._icon_color: Sequence[float] = list(icon_color) + [1]
self._level_name = level_name self._level_name = level_name
self._completion_banner_slot: Optional[int] = None self._completion_banner_slot: int | None = None
self._award = award self._award = award
self._hard_mode_only = hard_mode_only self._hard_mode_only = hard_mode_only
@ -534,7 +534,7 @@ class Achievement:
def display_name(self) -> ba.Lstr: def display_name(self) -> ba.Lstr:
"""Return a ba.Lstr for this Achievement's name.""" """Return a ba.Lstr for this Achievement's name."""
from ba._language import Lstr from ba._language import Lstr
name: Union[ba.Lstr, str] name: ba.Lstr | str
try: try:
if self._level_name != '': if self._level_name != '':
from ba._campaign import getcampaign from ba._campaign import getcampaign

View file

@ -16,7 +16,7 @@ from ba._general import Call, verify_object_death
from ba._messages import UNHANDLED from ba._messages import UNHANDLED
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any from typing import Any
import ba import ba
# pylint: disable=invalid-name # pylint: disable=invalid-name
@ -139,7 +139,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
assert isinstance(settings, dict) assert isinstance(settings, dict)
assert _ba.getactivity() is self 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; # Player/Team types should have been specified as type args;
# grab those. # grab those.
@ -148,7 +148,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._setup_player_and_team_types() self._setup_player_and_team_types()
# FIXME: Relocate or remove the need for this stuff. # 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()) self._session = weakref.ref(_ba.getsession())
@ -163,7 +163,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._has_transitioned_in = False self._has_transitioned_in = False
self._has_begun = False self._has_begun = False
self._has_ended = 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._expired = False
self._delay_delete_players: list[PlayerType] = [] self._delay_delete_players: list[PlayerType] = []
self._delay_delete_teams: list[TeamType] = [] self._delay_delete_teams: list[TeamType] = []
@ -177,14 +177,14 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._actor_refs: list[ba.Actor] = [] self._actor_refs: list[ba.Actor] = []
self._actor_weak_refs: list[weakref.ref[ba.Actor]] = [] self._actor_weak_refs: list[weakref.ref[ba.Actor]] = []
self._last_prune_dead_actors_time = _ba.time() 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.teams = []
self.players = [] self.players = []
self.lobby = None self.lobby = None
self._stats: Optional[ba.Stats] = None self._stats: ba.Stats | None = None
self._customdata: Optional[dict] = {} self._customdata: dict | None = {}
def __del__(self) -> 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 whether ba.Activity.on_transition_out() has been called."""
return self._transitioning_out 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. """Called by Session to kick off transition-in.
(internal) (internal)

View file

@ -14,7 +14,6 @@ from ba._player import EmptyPlayer # pylint: disable=W0611
from ba._team import EmptyTeam # pylint: disable=W0611 from ba._team import EmptyTeam # pylint: disable=W0611
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional
import ba import ba
from ba._lobby import JoinInfo from ba._lobby import JoinInfo
@ -65,9 +64,9 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]):
# In vr mode we don't want stuff moving around. # In vr mode we don't want stuff moving around.
self.use_fixed_vr_overlay = True self.use_fixed_vr_overlay = True
self._background: Optional[ba.Actor] = None self._background: ba.Actor | None = None
self._tips_text: Optional[ba.Actor] = None self._tips_text: ba.Actor | None = None
self._join_info: Optional[JoinInfo] = None self._join_info: JoinInfo | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -99,7 +98,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._background: Optional[ba.Actor] = None self._background: ba.Actor | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -127,20 +126,20 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
inherits_vr_camera_offset = True inherits_vr_camera_offset = True
use_fixed_vr_overlay = True use_fixed_vr_overlay = True
default_music: Optional[MusicType] = MusicType.SCORES default_music: MusicType | None = MusicType.SCORES
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._birth_time = _ba.time() self._birth_time = _ba.time()
self._min_view_time = 5.0 self._min_view_time = 5.0
self._allow_server_transition = False self._allow_server_transition = False
self._background: Optional[ba.Actor] = None self._background: ba.Actor | None = None
self._tips_text: Optional[ba.Actor] = None self._tips_text: ba.Actor | None = None
self._kicked_off_server_shutdown = False self._kicked_off_server_shutdown = False
self._kicked_off_server_restart = False self._kicked_off_server_restart = False
self._default_show_tips = True self._default_show_tips = True
self._custom_continue_message: Optional[ba.Lstr] = None self._custom_continue_message: ba.Lstr | None = None
self._server_transitioning: Optional[bool] = None self._server_transitioning: bool | None = None
def on_player_join(self, player: EmptyPlayer) -> None: def on_player_join(self, player: EmptyPlayer) -> None:
from ba._general import WeakCall from ba._general import WeakCall

View file

@ -12,7 +12,7 @@ from ba._messages import DieMessage, DeathType, OutOfBoundsMessage, UNHANDLED
from ba._error import print_exception, ActivityNotFoundError from ba._error import print_exception, ActivityNotFoundError
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Literal from typing import Any, Literal
import ba import ba
TA = TypeVar('TA', bound='Actor') TA = TypeVar('TA', bound='Actor')
@ -187,10 +187,10 @@ class Actor:
... ...
@overload @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. """Return the ba.Activity this Actor is associated with.
If the Activity no longer exists, raises a ba.ActivityNotFoundError If the Activity no longer exists, raises a ba.ActivityNotFoundError

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Callable, Any from typing import Callable, Any
class AdsSubsystem: class AdsSubsystem:
@ -23,11 +23,11 @@ class AdsSubsystem:
def __init__(self) -> None: def __init__(self) -> None:
self.last_ad_network = 'unknown' self.last_ad_network = 'unknown'
self.last_ad_network_set_time = time.time() 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.last_ad_purpose = 'invalid'
self.attempted_first_ad = False self.attempted_first_ad = False
self.last_in_game_ad_remove_message_show_time: Optional[float] = None self.last_in_game_ad_remove_message_show_time: float | None = None
self.last_ad_completion_time: Optional[float] = None self.last_ad_completion_time: float | None = None
self.last_ad_was_short = False self.last_ad_was_short = False
def do_remove_in_game_ads_message(self) -> None: def do_remove_in_game_ads_message(self) -> None:
@ -89,7 +89,7 @@ class AdsSubsystem:
show = False # Never show ads during tournaments. show = False # Never show ads during tournaments.
if show: if show:
interval: Optional[float] interval: float | None
launch_count = app.config.get('launchCount', 0) launch_count = app.config.get('launchCount', 0)
# If we're seeing short ads we may want to space them differently. # If we're seeing short ads we may want to space them differently.

View file

@ -19,13 +19,14 @@ from ba._accountv1 import AccountV1Subsystem
from ba._meta import MetadataSubsystem from ba._meta import MetadataSubsystem
from ba._ads import AdsSubsystem from ba._ads import AdsSubsystem
from ba._net import NetworkSubsystem from ba._net import NetworkSubsystem
from ba._workspace import WorkspaceSubsystem
if TYPE_CHECKING: if TYPE_CHECKING:
import asyncio import asyncio
from typing import Optional, Any, Callable from typing import Any, Callable
import ba import ba
from ba.cloud import CloudSubsystem from ba._cloud import CloudSubsystem
from bastd.actor import spazappearance from bastd.actor import spazappearance
from ba._accountv2 import AccountV2Subsystem from ba._accountv2 import AccountV2Subsystem
@ -49,10 +50,21 @@ class App:
class State(Enum): class State(Enum):
"""High level state the app can be in.""" """High level state the app can be in."""
# Python-level systems being inited but should not interact.
LAUNCHING = 0 LAUNCHING = 0
RUNNING = 1
PAUSED = 2 # Initial account logins, workspace & asset downloads, etc.
SHUTTING_DOWN = 3 LOADING = 1
# Normal running state.
RUNNING = 2
# App is backgrounded or otherwise suspended.
PAUSED = 3
# App is shutting down.
SHUTTING_DOWN = 4
@property @property
def aioloop(self) -> asyncio.AbstractEventLoop: def aioloop(self) -> asyncio.AbstractEventLoop:
@ -208,7 +220,9 @@ class App:
self.state = self.State.LAUNCHING 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 self._app_paused = False
# Config. # Config.
@ -219,7 +233,7 @@ class App:
# refreshed/etc. # refreshed/etc.
self.fg_state = 0 self.fg_state = 0
self._aioloop: Optional[asyncio.AbstractEventLoop] = None self._aioloop: asyncio.AbstractEventLoop | None = None
self._env = _ba.env() self._env = _ba.env()
self.protocol_version: int = self._env['protocol_version'] self.protocol_version: int = self._env['protocol_version']
@ -243,12 +257,12 @@ class App:
# Misc. # Misc.
self.tips: list[str] = [] 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.did_weak_call_warning = False
self.log_have_new = False self.log_have_new = False
self.log_upload_timer_started = 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 self.printed_live_object_warning = False
# We include this extra hash with shared input-mapping names so # 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 # systems. For instance, different android devices may give different
# key values for the same controller type so we keep their mappings # key values for the same controller type so we keep their mappings
# distinct. # distinct.
self.input_map_hash: Optional[str] = None self.input_map_hash: str | None = None
# Co-op Campaigns. # Co-op Campaigns.
self.campaigns: dict[str, ba.Campaign] = {} self.campaigns: dict[str, ba.Campaign] = {}
# Server Mode. # Server Mode.
self.server: Optional[ba.ServerController] = None self.server: ba.ServerController | None = None
self.meta = MetadataSubsystem() self.meta = MetadataSubsystem()
self.accounts_v1 = AccountV1Subsystem() self.accounts_v1 = AccountV1Subsystem()
@ -273,15 +287,16 @@ class App:
self.ui = UISubsystem() self.ui = UISubsystem()
self.ads = AdsSubsystem() self.ads = AdsSubsystem()
self.net = NetworkSubsystem() self.net = NetworkSubsystem()
self.workspaces = WorkspaceSubsystem()
# Lobby. # Lobby.
self.lobby_random_profile_index: int = 1 self.lobby_random_profile_index: int = 1
self.lobby_random_char_index_offset = random.randrange(1000) 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. # Main Menu.
self.main_menu_did_initial_transition = False 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. # Spaz.
self.spaz_appearances: dict[str, spazappearance.Appearance] = {} self.spaz_appearances: dict[str, spazappearance.Appearance] = {}
@ -300,19 +315,19 @@ class App:
self.did_menu_intro = False # FIXME: Move to mainmenu class. 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_window_refresh_check_count = 0 # FIXME: Mv to mainmenu.
self.main_menu_resume_callbacks: list = [] # Can probably go away. 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.ping_thread_count = 0
self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any. self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any.
self.store_layout: Optional[dict[str, list[dict[str, Any]]]] = None self.store_layout: dict[str, list[dict[str, Any]]] | None = None
self.store_items: Optional[dict[str, dict]] = None self.store_items: dict[str, dict] | None = None
self.pro_sale_start_time: Optional[int] = None self.pro_sale_start_time: int | None = None
self.pro_sale_start_val: Optional[int] = None self.pro_sale_start_val: int | None = None
self.delegate: Optional[ba.AppDelegate] = None self.delegate: ba.AppDelegate | None = None
self._asyncio_timer: Optional[ba.Timer] = None self._asyncio_timer: ba.Timer | None = None
def on_app_launch(self) -> None: def on_app_launch(self) -> None:
"""Runs after the app finishes bootstrapping. """Runs after the app finishes low level bootstrapping.
(internal)""" (internal)"""
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -326,8 +341,7 @@ class App:
from bastd import maps as stdmaps from bastd import maps as stdmaps
from bastd.actor import spazappearance from bastd.actor import spazappearance
from ba._generated.enums import TimeType from ba._generated.enums import TimeType
import custom_hooks
custom_hooks.on_app_launch()
self._aioloop = _asyncio.setup_asyncio() self._aioloop = _asyncio.setup_asyncio()
@ -400,19 +414,25 @@ class App:
if not self.headless_mode: if not self.headless_mode:
_ba.timer(3.0, check_special_offer, timetype=TimeType.REAL) _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)
self.meta.on_app_launch()
self.accounts_v2.on_app_launch() self.accounts_v2.on_app_launch()
self.accounts_v1.on_app_launch() self.accounts_v1.on_app_launch()
self.plugins.on_app_launch()
# See note below in on_app_pause. # See note below in on_app_pause.
if self.state != self.State.LAUNCHING: if self.state != self.State.LAUNCHING:
logging.error('on_app_launch found state %s; expected LAUNCHING.', logging.error('on_app_launch found state %s; expected LAUNCHING.',
self.state) self.state)
self._app_launched = True self._launch_completed = True
self._update_state() 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 # from ba._dependency import test_depset
# test_depset() # test_depset()
if bool(False): if bool(False):
@ -422,8 +442,13 @@ class App:
if self._app_paused: if self._app_paused:
self.state = self.State.PAUSED self.state = self.State.PAUSED
else: else:
if self._app_launched: if self._initial_login_completed:
self.state = self.State.RUNNING 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: else:
self.state = self.State.LAUNCHING 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 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. 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 if (activity is not None and activity.allow_pausing
and not _ba.have_connected_clients()): and not _ba.have_connected_clients()):
from ba._language import Lstr from ba._language import Lstr
@ -525,7 +550,7 @@ class App:
# If we're in a host-session, tell them to end. # If we're in a host-session, tell them to end.
# This lets them tear themselves down gracefully. # 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: if host_session is not None:
# Kick off a little transaction so we'll hopefully have all the # 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.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
_ba.playsound(_ba.getsound('error')) _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: def _test_https(self) -> None:
"""Testing https support. """Testing https support.

View file

@ -6,7 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Callable from typing import Callable
import ba import ba
@ -18,8 +18,8 @@ class AppDelegate:
def create_default_game_settings_ui( def create_default_game_settings_ui(
self, gameclass: type[ba.GameActivity], self, gameclass: type[ba.GameActivity],
sessiontype: type[ba.Session], settings: Optional[dict], sessiontype: type[ba.Session], settings: dict | None,
completion_call: Callable[[Optional[dict]], None]) -> None: completion_call: Callable[[dict | None], None]) -> None:
"""Launch a UI to configure the given game config. """Launch a UI to configure the given game config.
It should manipulate the contents of config and call completion_call It should manipulate the contents of config and call completion_call

View file

@ -13,12 +13,11 @@ from typing import TYPE_CHECKING
import asyncio import asyncio
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional
import ba import ba
# Our timer and event loop for the ballistica game thread. # Our timer and event loop for the ballistica game thread.
_asyncio_timer: Optional[ba.Timer] = None _asyncio_timer: ba.Timer | None = None
_asyncio_event_loop: Optional[asyncio.AbstractEventLoop] = None _asyncio_event_loop: asyncio.AbstractEventLoop | None = None
def setup_asyncio() -> asyncio.AbstractEventLoop: def setup_asyncio() -> asyncio.AbstractEventLoop:

93
dist/ba_data/python/ba/_cloud.py vendored Normal file
View file

@ -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.')

View file

@ -10,7 +10,7 @@ from ba._gameactivity import GameActivity
from ba._general import WeakCall from ba._general import WeakCall
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional from typing import Any, Sequence
from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.playerspaz import PlayerSpaz
import ba import ba
@ -40,8 +40,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
# Cache these for efficiency. # Cache these for efficiency.
self._achievements_awarded: set[str] = set() self._achievements_awarded: set[str] = set()
self._life_warning_beep: Optional[ba.Actor] = None self._life_warning_beep: ba.Actor | None = None
self._life_warning_beep_timer: Optional[ba.Timer] = None self._life_warning_beep_timer: ba.Timer | None = None
self._warn_beeps_sound = _ba.getsound('warnBeeps') self._warn_beeps_sound = _ba.getsound('warnBeeps')
def on_begin(self) -> None: def on_begin(self) -> None:

View file

@ -9,7 +9,7 @@ import _ba
from ba._session import Session from ba._session import Session
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Callable, Sequence from typing import Any, Callable, Sequence
import ba import ba
TEAM_COLORS = [(0.2, 0.4, 1.6)] 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 # Note: even though these are instance vars, we annotate them at the
# class level so that docs generation can access their types. # 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: def __init__(self) -> None:
"""Instantiate a co-op mode session.""" """Instantiate a co-op mode session."""
@ -69,21 +72,21 @@ class CoopSession(Session):
max_players=max_players) max_players=max_players)
# Tournament-ID if we correspond to a co-op tournament (otherwise None) # 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')) app.coop_session_args.get('tournament_id'))
self.campaign = getcampaign(app.coop_session_args['campaign']) self.campaign = getcampaign(app.coop_session_args['campaign'])
self.campaign_level_name: str = app.coop_session_args['level'] self.campaign_level_name: str = app.coop_session_args['level']
self._ran_tutorial_activity = False 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]] = [] self._custom_menu_ui: list[dict[str, Any]] = []
# Start our joining screen. # Start our joining screen.
self.setactivity(_ba.newactivity(CoopJoinActivity)) self.setactivity(_ba.newactivity(CoopJoinActivity))
self._next_game_instance: Optional[ba.GameActivity] = None self._next_game_instance: ba.GameActivity | None = None
self._next_game_level_name: Optional[str] = None self._next_game_level_name: str | None = None
self._update_on_deck_game_instances() self._update_on_deck_game_instances()
def get_current_game_instance(self) -> ba.GameActivity: def get_current_game_instance(self) -> ba.GameActivity:
@ -126,7 +129,8 @@ class CoopSession(Session):
levels = self.campaign.levels levels = self.campaign.levels
level = self.campaign.getlevel(self.campaign_level_name) level = self.campaign.getlevel(self.campaign_level_name)
nextlevel: Optional[ba.Level]
nextlevel: ba.Level | None
# if level.index < len(levels) - 1: # if level.index < len(levels) - 1:
# nextlevel = levels[level.index + 1] # nextlevel = levels[level.index + 1]
# else: # else:

View file

@ -10,7 +10,7 @@ from typing import (Generic, TypeVar, TYPE_CHECKING)
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any from typing import Any
import ba import ba
T = TypeVar('T', bound='DependencyComponent') T = TypeVar('T', bound='DependencyComponent')
@ -39,7 +39,7 @@ class Dependency(Generic[T]):
""" """
self.cls: type[T] = cls self.cls: type[T] = cls
self.config = config self.config = config
self._hash: Optional[int] = None self._hash: int | None = None
def get_hash(self) -> int: def get_hash(self) -> int:
"""Return the dependency's hash, calculating it if necessary.""" """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 # Arbitrary data for use by dependencies in the resolved set
# (the static instance for static-deps, etc). # (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). # Weakref to the depset that includes us (to avoid ref loop).
self.depset = weakref.ref(depset) self.depset = weakref.ref(depset)

View file

@ -19,7 +19,7 @@ from ba._player import PlayerInfo
from ba import _map from ba import _map
if TYPE_CHECKING: 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.playerspaz import PlayerSpaz
from bastd.actor.bomb import TNTSpawner from bastd.actor.bomb import TNTSpawner
import ba import ba
@ -38,19 +38,19 @@ class GameActivity(Activity[PlayerType, TeamType]):
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
# Tips to be presented to the user at the start of the game. # 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. # 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. # 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. # 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. # Default getscoreconfig() will return this if not None.
scoreconfig: Optional[ba.ScoreConfig] = None scoreconfig: ba.ScoreConfig | None = None
# Override some defaults. # Override some defaults.
allow_pausing = True 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() # If not None, the music type that should play in on_transition_in()
# (unless overridden by the map). # (unless overridden by the map).
default_music: Optional[ba.MusicType] = None default_music: ba.MusicType | None = None
@classmethod @classmethod
def create_settings_ui( def create_settings_ui(
cls, cls,
sessiontype: type[ba.Session], sessiontype: type[ba.Session],
settings: Optional[dict], settings: dict | None,
completion_call: Callable[[Optional[dict]], None], completion_call: Callable[[dict | None], None],
) -> None: ) -> None:
"""Launch an in-game UI to configure settings for a game type. """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' return cls.name if cls.name is not None else 'Untitled Game'
@classmethod @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. """Return a descriptive name for this game/settings combo.
Subclasses should override getname(); not this. 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 # Holds some flattened info about the player set at the point
# when on_begin() is called. # 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. # Go ahead and get our map loading.
self._map_type = _map.get_map_class(self._calc_map_name(settings)) self._map_type = _map.get_map_class(self._calc_map_name(settings))
self._spawn_sound = _ba.getsound('spawn') self._spawn_sound = _ba.getsound('spawn')
self._map_type.preload() self._map_type.preload()
self._map: Optional[ba.Map] = None self._map: ba.Map | None = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: ba.Timer | None = None
self._tnt_spawners: Optional[dict[int, TNTSpawner]] = None self._tnt_spawners: dict[int, TNTSpawner] | None = None
self._tnt_drop_timer: Optional[ba.Timer] = None self._tnt_drop_timer: ba.Timer | None = None
self._game_scoreboard_name_text: Optional[ba.Actor] = None self._game_scoreboard_name_text: ba.Actor | None = None
self._game_scoreboard_description_text: Optional[ba.Actor] = None self._game_scoreboard_description_text: ba.Actor | None = None
self._standard_time_limit_time: Optional[int] = None self._standard_time_limit_time: int | None = None
self._standard_time_limit_timer: Optional[ba.Timer] = None self._standard_time_limit_timer: ba.Timer | None = None
self._standard_time_limit_text: Optional[ba.NodeActor] = None self._standard_time_limit_text: ba.NodeActor | None = None
self._standard_time_limit_text_input: Optional[ba.NodeActor] = None self._standard_time_limit_text_input: ba.NodeActor | None = None
self._tournament_time_limit: Optional[int] = None self._tournament_time_limit: int | None = None
self._tournament_time_limit_timer: Optional[ba.Timer] = None self._tournament_time_limit_timer: ba.Timer | None = None
self._tournament_time_limit_title_text: Optional[ba.NodeActor] = None self._tournament_time_limit_title_text: ba.NodeActor | None = None
self._tournament_time_limit_text: Optional[ba.NodeActor] = None self._tournament_time_limit_text: ba.NodeActor | None = None
self._tournament_time_limit_text_input: Optional[ba.NodeActor] = None self._tournament_time_limit_text_input: ba.NodeActor | None = None
self._zoom_message_times: dict[int, float] = {} self._zoom_message_times: dict[int, float] = {}
self._is_waiting_for_continue = False self._is_waiting_for_continue = False
@ -280,7 +280,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
print_error('error getting campaign level name') print_error('error getting campaign level name')
return self.get_instance_display_string() 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. """Return a description for this game instance, in English.
This is shown in the center of the screen below the game name at the 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)) 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. """Return a short description for this game instance in English.
This description is used above the game scoreboard in the 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), callback=WeakCall(self._on_tournament_query_response),
) )
def _on_tournament_query_response(self, data: Optional[dict[str, def _on_tournament_query_response(self,
Any]]) -> None: data: dict[str, Any] | None) -> None:
if data is not None: if data is not None:
data_t = data['t'] # This used to be the whole payload. 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 = self.tips.pop(random.randrange(len(self.tips)))
tip_title = Lstr(value='${A}:', tip_title = Lstr(value='${A}:',
subs=[('${A}', Lstr(resource='tipText'))]) subs=[('${A}', Lstr(resource='tipText'))])
icon: Optional[ba.Texture] = None icon: ba.Texture | None = None
sound: Optional[ba.Sound] = None sound: ba.Sound | None = None
if isinstance(tip, GameTip): if isinstance(tip, GameTip):
icon = tip.icon icon = tip.icon
sound = tip.sound sound = tip.sound
@ -781,7 +781,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
def respawn_player(self, def respawn_player(self,
player: PlayerType, player: PlayerType,
respawn_time: Optional[float] = None) -> None: respawn_time: float | None = None) -> None:
""" """
Given a ba.Player, sets up a standard respawn timer, Given a ba.Player, sets up a standard respawn timer,
along with the standard counter display, etc. along with the standard counter display, etc.
@ -853,9 +853,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
color = player.color color = player.color
highlight = player.highlight highlight = player.highlight
playerspaztype = getattr(player, 'playerspaztype', PlayerSpaz)
if not issubclass(playerspaztype, PlayerSpaz):
playerspaztype = PlayerSpaz
light_color = _math.normalized_color(color) light_color = _math.normalized_color(color)
display_color = _ba.safecolor(color, target_intensity=0.75) display_color = _ba.safecolor(color, target_intensity=0.75)
spaz = PlayerSpaz(color=color, spaz = playerspaztype(color=color,
highlight=highlight, highlight=highlight,
character=player.character, character=player.character,
player=player) player=player)

View file

@ -12,14 +12,14 @@ from efro.util import asserttype
from ba._team import Team, SessionTeam from ba._team import Team, SessionTeam
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Optional from typing import Sequence
import ba import ba
@dataclass @dataclass
class WinnerGroup: class WinnerGroup:
"""Entry for a winning team or teams calculated by game-results.""" """Entry for a winning team or teams calculated by game-results."""
score: Optional[int] score: int | None
teams: Sequence[ba.SessionTeam] teams: Sequence[ba.SessionTeam]
@ -36,13 +36,13 @@ class GameResults:
def __init__(self) -> None: def __init__(self) -> None:
self._game_set = False self._game_set = False
self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam], self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam],
Optional[int]]] = {} int | None]] = {}
self._sessionteams: Optional[list[weakref.ref[ba.SessionTeam]]] = None self._sessionteams: list[weakref.ref[ba.SessionTeam]] | None = None
self._playerinfos: Optional[list[ba.PlayerInfo]] = None self._playerinfos: list[ba.PlayerInfo] | None = None
self._lower_is_better: Optional[bool] = None self._lower_is_better: bool | None = None
self._score_label: Optional[str] = None self._score_label: str | None = None
self._none_is_winner: Optional[bool] = None self._none_is_winner: bool | None = None
self._scoretype: Optional[ba.ScoreType] = None self._scoretype: ba.ScoreType | None = None
def set_game(self, game: ba.GameActivity) -> None: def set_game(self, game: ba.GameActivity) -> None:
"""Set the game instance these results are applying to.""" """Set the game instance these results are applying to."""
@ -59,7 +59,7 @@ class GameResults:
self._none_is_winner = scoreconfig.none_is_winner self._none_is_winner = scoreconfig.none_is_winner
self._scoretype = scoreconfig.scoretype 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. """Set the score for a given team.
This can be a number or None. This can be a number or None.
@ -69,8 +69,7 @@ class GameResults:
sessionteam = team.sessionteam sessionteam = team.sessionteam
self._scores[sessionteam.id] = (weakref.ref(sessionteam), score) self._scores[sessionteam.id] = (weakref.ref(sessionteam), score)
def get_sessionteam_score(self, def get_sessionteam_score(self, sessionteam: ba.SessionTeam) -> int | None:
sessionteam: ba.SessionTeam) -> Optional[int]:
"""Return the score for a given ba.SessionTeam.""" """Return the score for a given ba.SessionTeam."""
assert isinstance(sessionteam, SessionTeam) assert isinstance(sessionteam, SessionTeam)
for score in list(self._scores.values()): for score in list(self._scores.values()):
@ -157,7 +156,7 @@ class GameResults:
return self._lower_is_better return self._lower_is_better
@property @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.""" """The winning ba.SessionTeam if there is exactly one, or else None."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get winners until game is set.") raise RuntimeError("Can't get winners until game is set.")
@ -184,7 +183,7 @@ class GameResults:
team = score[0]() team = score[0]()
assert team is not None assert team is not None
sval.append(team) sval.append(team)
results: list[tuple[Optional[int], results: list[tuple[int | None,
list[ba.SessionTeam]]] = list(winners.items()) list[ba.SessionTeam]]] = list(winners.items())
results.sort(reverse=not self._lower_is_better, results.sort(reverse=not self._lower_is_better,
key=lambda x: asserttype(x[0], int)) key=lambda x: asserttype(x[0], int))
@ -199,7 +198,7 @@ class GameResults:
# Add the Nones to the list (either as winners or losers # Add the Nones to the list (either as winners or losers
# depending on the rules). # depending on the rules).
if none_sessionteams: if none_sessionteams:
nones: list[tuple[Optional[int], list[ba.SessionTeam]]] = [ nones: list[tuple[int | None, list[ba.SessionTeam]]] = [
(None, none_sessionteams) (None, none_sessionteams)
] ]
if self._none_is_winner: if self._none_is_winner:

View file

@ -12,7 +12,7 @@ from ba._generated.enums import TimeType, TimeFormat, SpecialChar, UIScale
from ba._error import ActivityNotFoundError from ba._error import ActivityNotFoundError
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Optional from typing import Sequence
import ba import ba
TROPHY_CHARS = { TROPHY_CHARS = {
@ -32,8 +32,8 @@ class GameTip:
Category: **Gameplay Classes** Category: **Gameplay Classes**
""" """
text: str text: str
icon: Optional[ba.Texture] = None icon: ba.Texture | None = None
sound: Optional[ba.Sound] = None sound: ba.Sound | None = None
def get_trophy_string(trophy_id: str) -> str: def get_trophy_string(trophy_id: str) -> str:

View file

@ -17,7 +17,7 @@ from ba._generated.enums import TimeType
if TYPE_CHECKING: if TYPE_CHECKING:
from types import FrameType 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. 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') 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. """Convert invalid references to None for any ba.Existable object.
Category: **Gameplay Functions** Category: **Gameplay Functions**
To best support type checking, it is important that invalid references To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None. not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead That way the type checker can properly flag attempts to pass possibly-dead
objects (Optional[FooType]) into functions expecting only live ones objects (FooType | None) into functions expecting only live ones
(FooType), etc. This call can be used on any 'existable' object (FooType), etc. This call can be used on any 'existable' object
(one with an exists() method) and will convert it to a None value (one with an exists() method) and will convert it to a None value
if it does not exist. if it does not exist.

View file

@ -18,7 +18,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Optional, Any from typing import Sequence, Any
import ba import ba
@ -325,7 +325,8 @@ def party_invite_revoke(invite_id: str) -> None:
transition='out_right') transition='out_right')
import custom_hooks as chooks 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. """Intercept/filter chat messages.
Called for all chat messages while hosting. Called for all chat messages while hosting.

View file

@ -11,7 +11,7 @@ import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
import ba import ba
from typing import Any, Optional, Union, Sequence from typing import Any, Sequence
class LanguageSubsystem: class LanguageSubsystem:
@ -23,8 +23,8 @@ class LanguageSubsystem:
""" """
def __init__(self) -> None: def __init__(self) -> None:
self.language_target: Optional[AttrDict] = None self.language_target: AttrDict | None = None
self.language_merged: Optional[AttrDict] = None self.language_merged: AttrDict | None = None
self.default_language = self._get_default_language() self.default_language = self._get_default_language()
def _can_display_language(self, language: str) -> bool: def _can_display_language(self, language: str) -> bool:
@ -139,7 +139,7 @@ class LanguageSubsystem:
if self._can_display_language(name)) if self._can_display_language(name))
def setlanguage(self, def setlanguage(self,
language: Optional[str], language: str | None,
print_change: bool = True, print_change: bool = True,
store_to_config: bool = True) -> None: store_to_config: bool = True) -> None:
"""Set the active language used for the game. """Set the active language used for the game.
@ -408,7 +408,7 @@ class Lstr:
resource: str, resource: str,
fallback_resource: str = '', fallback_resource: str = '',
fallback_value: 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.""" """Create an Lstr from a string resource."""
# noinspection PyShadowingNames,PyDefaultArgument # noinspection PyShadowingNames,PyDefaultArgument
@ -416,7 +416,7 @@ class Lstr:
def __init__(self, def __init__(self,
*, *,
translate: tuple[str, str], 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.""" """Create an Lstr by translating a string in a category."""
# noinspection PyDefaultArgument # noinspection PyDefaultArgument
@ -424,7 +424,7 @@ class Lstr:
def __init__(self, def __init__(self,
*, *,
value: str, 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.""" """Create an Lstr from a raw string value."""
# pylint: enable=redefined-outer-name, dangerous-default-value # pylint: enable=redefined-outer-name, dangerous-default-value

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
import ba import ba
@ -31,9 +31,9 @@ class Level:
self._settings = settings self._settings = settings
self._preview_texture_name = preview_texture_name self._preview_texture_name = preview_texture_name
self._displayname = displayname self._displayname = displayname
self._campaign: Optional[weakref.ref[ba.Campaign]] = None self._campaign: weakref.ref[ba.Campaign] | None = None
self._index: Optional[int] = None self._index: int | None = None
self._score_version_string: Optional[str] = None self._score_version_string: str | None = None
def __repr__(self) -> str: def __repr__(self) -> str:
cls = type(self) cls = type(self)
@ -78,7 +78,7 @@ class Level:
return self._gametype return self._gametype
@property @property
def campaign(self) -> Optional[ba.Campaign]: def campaign(self) -> ba.Campaign | None:
"""The ba.Campaign this Level is associated with, or None.""" """The ba.Campaign this Level is associated with, or None."""
return None if self._campaign is None else self._campaign() return None if self._campaign is None else self._campaign()

View file

@ -16,7 +16,7 @@ from ba._generated.enums import SpecialChar, InputType
from ba._profile import get_player_profile_colors from ba._profile import get_player_profile_colors
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any, Sequence, Union from typing import Any, Sequence
import ba import ba
MAX_QUICK_CHANGE_COUNT = 30 MAX_QUICK_CHANGE_COUNT = 30
@ -32,12 +32,10 @@ class JoinInfo:
from ba._nodeactor import NodeActor from ba._nodeactor import NodeActor
from ba._general import WeakCall from ba._general import WeakCall
self._state = 0 self._state = 0
self._press_to_punch: Union[str, self._press_to_punch: str | ba.Lstr = ('C' if _ba.app.iircade_mode else
ba.Lstr] = ('C' if _ba.app.iircade_mode _ba.charstr(
else _ba.charstr(
SpecialChar.LEFT_BUTTON)) SpecialChar.LEFT_BUTTON))
self._press_to_bomb: Union[str, self._press_to_bomb: str | ba.Lstr = ('B' if _ba.app.iircade_mode else
ba.Lstr] = ('B' if _ba.app.iircade_mode else
_ba.charstr( _ba.charstr(
SpecialChar.RIGHT_BUTTON)) SpecialChar.RIGHT_BUTTON))
self._joinmsg = Lstr(resource='pressAnyButtonToJoinText') self._joinmsg = Lstr(resource='pressAnyButtonToJoinText')
@ -150,12 +148,12 @@ class Chooser:
self._sessionplayer = sessionplayer self._sessionplayer = sessionplayer
self._inited = False self._inited = False
self._dead = False self._dead = False
self._text_node: Optional[ba.Node] = None self._text_node: ba.Node | None = None
self._profilename = '' self._profilename = ''
self._profilenames: list[str] = [] self._profilenames: list[str] = []
self._ready: bool = False self._ready: bool = False
self._character_names: list[str] = [] 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]] = {} self._profiles: dict[str, dict[str, Any]] = {}
app = _ba.app app = _ba.app
@ -318,7 +316,7 @@ class Chooser:
raise NotFoundError('Lobby does not exist.') raise NotFoundError('Lobby does not exist.')
return lobby 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 this chooser's lobby if it still exists; otherwise None."""
return self._lobby() return self._lobby()

View file

@ -11,7 +11,7 @@ from ba import _math
from ba._actor import Actor from ba._actor import Actor
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Sequence, Any from typing import Sequence, Any
import ba import ba
@ -161,7 +161,7 @@ class Map(Actor):
return [] return []
@classmethod @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 the name of the preview texture for this map."""
return None return None
@ -179,7 +179,7 @@ class Map(Actor):
return cls.name return cls.name
@classmethod @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. """Return a music-type string that should be played on this map.
If None is returned, default music will be used. If None is returned, default music will be used.
@ -187,13 +187,13 @@ class Map(Actor):
return None return None
def __init__(self, def __init__(self,
vr_overlay_offset: Optional[Sequence[float]] = None) -> None: vr_overlay_offset: Sequence[float] | None = None) -> None:
"""Instantiate a map.""" """Instantiate a map."""
super().__init__() super().__init__()
# This is expected to always be a ba.Node object (whether valid or not) # This is expected to always be a ba.Node object (whether valid or not)
# should be set to something meaningful by child classes. # 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 # Make our class' preload-data available to us
# (and instruct the user if we weren't preloaded properly). # (and instruct the user if we weren't preloaded properly).
@ -209,8 +209,9 @@ class Map(Actor):
# Set various globals. # Set various globals.
gnode = _ba.getactivity().globalsnode gnode = _ba.getactivity().globalsnode
import ba import ba
from features import text_on_map import custom_hooks
text_on_map.textonmap() custom_hooks.on_map_init()
# Set area-of-interest bounds. # Set area-of-interest bounds.
aoi_bounds = self.get_def_bound_box('area_of_interest_bounds') aoi_bounds = self.get_def_bound_box('area_of_interest_bounds')
@ -298,7 +299,7 @@ class Map(Actor):
def get_def_bound_box( def get_def_bound_box(
self, name: str self, name: str
) -> Optional[tuple[float, float, float, float, float, float]]: ) -> tuple[float, float, float, float, float, float] | None:
"""Return a 6 member bounds tuple or None if it is not defined.""" """Return a 6 member bounds tuple or None if it is not defined."""
try: try:
box = self.defs.boxes[name] box = self.defs.boxes[name]
@ -308,7 +309,7 @@ class Map(Actor):
except Exception: except Exception:
return None 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.""" """Return a single defined point or a default value in its absence."""
val = self.defs.points.get(name) val = self.defs.points.get(name)
return (None if val is None else return (None if val is None else

View file

@ -11,7 +11,7 @@ from enum import Enum
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Optional, Any from typing import Sequence, Any
import ba import ba
@ -84,7 +84,7 @@ class PlayerDiedMessage:
"""The particular type of death.""" """The particular type of death."""
def __init__(self, player: ba.Player, was_killed: bool, 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.""" """Instantiate a message with the given values."""
# Invalid refs should never be passed as args. # Invalid refs should never be passed as args.
@ -98,7 +98,7 @@ class PlayerDiedMessage:
self.how = how self.how = how
def getkillerplayer(self, def getkillerplayer(self,
playertype: type[PlayerType]) -> Optional[PlayerType]: playertype: type[PlayerType]) -> PlayerType | None:
"""Return the ba.Player responsible for the killing, if any. """Return the ba.Player responsible for the killing, if any.
Pass the Player type being used by the current game. Pass the Player type being used by the current game.
@ -267,8 +267,8 @@ class HitMessage:
self.force_direction = (force_direction self.force_direction = (force_direction
if force_direction is not None else velocity) if force_direction is not None else velocity)
def get_source_player( def get_source_player(self,
self, playertype: type[PlayerType]) -> Optional[PlayerType]: playertype: type[PlayerType]) -> PlayerType | None:
"""Return the source-player if one exists and is the provided type.""" """Return the source-player if one exists and is the provided type."""
player: Any = self._source_player player: Any = self._source_player

View file

@ -6,22 +6,23 @@ from __future__ import annotations
import os import os
import time import time
import pathlib
import threading import threading
from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dataclasses import dataclass, field from dataclasses import dataclass, field
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Union, Optional
import ba import ba
# The meta api version of this build of the game. # The meta api version of this build of the game.
# Only packages and modules requiring this exact api version # Only packages and modules requiring this exact api version
# will be considered when scanning directories. # will be considered when scanning directories.
# See: https://ballistica.net/wiki/Meta-Tags # 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 @dataclass
@ -43,10 +44,11 @@ class MetadataSubsystem:
""" """
def __init__(self) -> None: 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: def on_app_running(self) -> None:
"""Should be called when the app is done bootstrapping.""" """Should be called when the app enters the running state."""
# Start scanning for things exposed via ba_meta. # Start scanning for things exposed via ba_meta.
self.start_scan() self.start_scan()
@ -58,7 +60,8 @@ class MetadataSubsystem:
app = _ba.app app = _ba.app
if self.metascan is not None: if self.metascan is not None:
print('WARNING: meta scan run more than once.') 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 = ScanThread(pythondirs)
thread.start() thread.start()
@ -99,16 +102,10 @@ class MetadataSubsystem:
class_path=class_path, class_path=class_path,
available=True)) available=True))
if class_path not in plugstates: if class_path not in plugstates:
if _ba.app.headless_mode: # Go ahead and enable new plugins by default, but we'll
# If we running in headless mode, enable plugin by default # inform the user that they need to restart to pick them up.
# to allow server admins to get their modified build # they can also disable them in settings so they never load.
# working 'out-of-the-box', without manually updating the
# config.
plugstates[class_path] = {'enabled': True} 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}
config_changed = True config_changed = True
found_new = True found_new = True
@ -223,18 +220,17 @@ class DirectoryScan:
""" """
# Skip non-existent paths completely. # 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() self.results = ScanResults()
def _get_path_module_entries( def _get_path_module_entries(self, path: Path, subpath: str | Path,
self, path: pathlib.Path, subpath: Union[str, pathlib.Path], modules: list[tuple[Path, Path]]) -> None:
modules: list[tuple[pathlib.Path, pathlib.Path]]) -> None:
"""Scan provided path and add module entries to provided list.""" """Scan provided path and add module entries to provided list."""
try: try:
# Special case: let's save some time and skip the whole 'ba' # Special case: let's save some time and skip the whole 'ba'
# package since we know it doesn't contain any meta tags. # package since we know it doesn't contain any meta tags.
fullpath = pathlib.Path(path, subpath) fullpath = Path(path, subpath)
entries = [(path, pathlib.Path(subpath, name)) entries = [(path, Path(subpath, name))
for name in os.listdir(fullpath) if name != 'ba'] for name in os.listdir(fullpath) if name != 'ba']
except PermissionError: except PermissionError:
# Expected sometimes. # Expected sometimes.
@ -248,13 +244,13 @@ class DirectoryScan:
for entry in entries: for entry in entries:
if entry[1].name.endswith('.py'): if entry[1].name.endswith('.py'):
modules.append(entry) modules.append(entry)
elif (pathlib.Path(entry[0], entry[1]).is_dir() and pathlib.Path( elif (Path(entry[0], entry[1]).is_dir()
entry[0], entry[1], '__init__.py').is_file()): and Path(entry[0], entry[1], '__init__.py').is_file()):
modules.append(entry) modules.append(entry)
def scan(self) -> None: def scan(self) -> None:
"""Scan provided paths.""" """Scan provided paths."""
modules: list[tuple[pathlib.Path, pathlib.Path]] = [] modules: list[tuple[Path, Path]] = []
for path in self.paths: for path in self.paths:
self._get_path_module_entries(path, '', modules) self._get_path_module_entries(path, '', modules)
for moduledir, subpath in modules: for moduledir, subpath in modules:
@ -269,14 +265,13 @@ class DirectoryScan:
self.results.games.sort() self.results.games.sort()
self.results.plugins.sort() self.results.plugins.sort()
def scan_module(self, moduledir: pathlib.Path, def scan_module(self, moduledir: Path, subpath: Path) -> None:
subpath: pathlib.Path) -> None:
"""Scan an individual module and add the findings to results.""" """Scan an individual module and add the findings to results."""
if subpath.name.endswith('.py'): if subpath.name.endswith('.py'):
fpath = pathlib.Path(moduledir, subpath) fpath = Path(moduledir, subpath)
ispackage = False ispackage = False
else: else:
fpath = pathlib.Path(moduledir, subpath, '__init__.py') fpath = Path(moduledir, subpath, '__init__.py')
ispackage = True ispackage = True
with fpath.open(encoding='utf-8') as infile: with fpath.open(encoding='utf-8') as infile:
flines = infile.readlines() flines = infile.readlines()
@ -293,7 +288,7 @@ class DirectoryScan:
# If we find a module requiring a different api version, warn # If we find a module requiring a different api version, warn
# and ignore. # 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 += ( self.results.warnings += (
f'Warning: {subpath} requires api {required_api} but' f'Warning: {subpath} requires api {required_api} but'
f' we are running {CURRENT_API_VERSION}; ignoring module.\n') 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 its a package, recurse into its subpackages.
if ispackage: if ispackage:
try: try:
submodules: list[tuple[pathlib.Path, pathlib.Path]] = [] submodules: list[tuple[Path, Path]] = []
self._get_path_module_entries(moduledir, subpath, submodules) self._get_path_module_entries(moduledir, subpath, submodules)
for submodule in submodules: for submodule in submodules:
if submodule[1].name != '__init__.py': if submodule[1].name != '__init__.py':
@ -315,8 +310,7 @@ class DirectoryScan:
self.results.warnings += ( self.results.warnings += (
f"Error scanning '{subpath}': {traceback.format_exc()}\n") f"Error scanning '{subpath}': {traceback.format_exc()}\n")
def _process_module_meta_tags(self, subpath: pathlib.Path, def _process_module_meta_tags(self, subpath: Path, flines: list[str],
flines: list[str],
meta_lines: dict[int, list[str]]) -> None: meta_lines: dict[int, list[str]]) -> None:
"""Pull data from a module based on its ba_meta tags.""" """Pull data from a module based on its ba_meta tags."""
for lindex, mline in meta_lines.items(): for lindex, mline in meta_lines.items():
@ -360,8 +354,8 @@ class DirectoryScan:
': unrecognized export type "' + exporttype + ': unrecognized export type "' + exporttype +
'" on line ' + str(lindex + 1) + '.\n') '" on line ' + str(lindex + 1) + '.\n')
def _get_export_class_name(self, subpath: pathlib.Path, lines: list[str], def _get_export_class_name(self, subpath: Path, lines: list[str],
lindex: int) -> Optional[str]: lindex: int) -> str | None:
"""Given line num of an export tag, returns its operand class name.""" """Given line num of an export tag, returns its operand class name."""
lindexorig = lindex lindexorig = lindex
classname = None classname = None
@ -386,9 +380,12 @@ class DirectoryScan:
str(lindexorig + 1) + '.\n') str(lindexorig + 1) + '.\n')
return classname return classname
def get_api_requirement(self, subpath: pathlib.Path, def get_api_requirement(
self,
subpath: Path,
meta_lines: dict[int, list[str]], meta_lines: dict[int, list[str]],
toplevel: bool) -> Optional[int]: toplevel: bool,
) -> int | None:
"""Return an API requirement integer or None if none present. """Return an API requirement integer or None if none present.
Malformed api requirement strings will be logged as warnings. Malformed api requirement strings will be logged as warnings.

View file

@ -12,7 +12,7 @@ from ba._session import Session
from ba._error import NotFoundError, print_error from ba._error import NotFoundError, print_error
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any, Sequence from typing import Any, Sequence
import ba import ba
DEFAULT_TEAM_COLORS = ((0.1, 0.25, 1.0), (1.0, 0.25, 0.2)) 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) show_tutorial = cfg.get('Show Tutorial', True)
self._tutorial_activity_instance: Optional[ba.Activity] self._tutorial_activity_instance: ba.Activity | None
if show_tutorial: if show_tutorial:
from bastd.tutorial import TutorialActivity from bastd.tutorial import TutorialActivity
@ -105,7 +105,7 @@ class MultiTeamSession(Session):
shuffle=self._playlist_randomize) shuffle=self._playlist_randomize)
# Get a game on deck ready to go. # 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_spec: dict[str, Any] = self._playlist.pull_next()
self._next_game: type[ba.GameActivity] = ( self._next_game: type[ba.GameActivity] = (
self._next_game_spec['resolved_type']) self._next_game_spec['resolved_type'])
@ -278,7 +278,7 @@ class ShuffleList:
self.source_list = items self.source_list = items
self.shuffle = shuffle self.shuffle = shuffle
self.shuffle_list: list[dict[str, Any]] = [] 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]: def pull_next(self) -> dict[str, Any]:
"""Pull and return the next item on the shuffle-list.""" """Pull and return the next item on the shuffle-list."""

View file

@ -11,7 +11,7 @@ from enum import Enum
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Any, Optional, Union from typing import Callable, Any
import ba import ba
@ -127,11 +127,11 @@ class MusicSubsystem:
def __init__(self) -> None: def __init__(self) -> None:
# pylint: disable=cyclic-import # 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_mode: MusicPlayMode = MusicPlayMode.REGULAR
self._music_player: Optional[MusicPlayer] = None self._music_player: MusicPlayer | None = None
self._music_player_type: Optional[type[MusicPlayer]] = None self._music_player_type: type[MusicPlayer] | None = None
self.music_types: dict[MusicPlayMode, Optional[MusicType]] = { self.music_types: dict[MusicPlayMode, MusicType | None] = {
MusicPlayMode.REGULAR: None, MusicPlayMode.REGULAR: None,
MusicPlayMode.TEST: None MusicPlayMode.TEST: None
} }
@ -270,7 +270,7 @@ class MusicSubsystem:
self.do_play_music(None) self.do_play_music(None)
def do_play_music(self, def do_play_music(self,
musictype: Union[MusicType, str, None], musictype: MusicType | str | None,
continuous: bool = False, continuous: bool = False,
mode: MusicPlayMode = MusicPlayMode.REGULAR, mode: MusicPlayMode = MusicPlayMode.REGULAR,
testsoundtrack: dict[str, Any] = None) -> None: testsoundtrack: dict[str, Any] = None) -> None:
@ -352,7 +352,7 @@ class MusicSubsystem:
# Do the thing. # Do the thing.
self.get_music_player().play(entry) 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. # Stop any existing music-player playback.
if self._music_player is not None: if self._music_player is not None:
@ -393,7 +393,7 @@ class MusicPlayer:
def __init__(self) -> None: def __init__(self) -> None:
self._have_set_initial_volume = False self._have_set_initial_volume = False
self._entry_to_play: Optional[Any] = None self._entry_to_play: Any = None
self._volume = 1.0 self._volume = 1.0
self._actually_playing = False self._actually_playing = False
@ -470,8 +470,7 @@ class MusicPlayer:
self._actually_playing = False self._actually_playing = False
def setmusic(musictype: Optional[ba.MusicType], def setmusic(musictype: ba.MusicType | None, continuous: bool = False) -> None:
continuous: bool = False) -> None:
"""Set the app to play (or stop playing) a certain type of music. """Set the app to play (or stop playing) a certain type of music.
category: **Gameplay Functions** category: **Gameplay Functions**

View file

@ -12,9 +12,9 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Union, Callable, Optional from typing import Any, Callable
import socket 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. # Timeout for standard functions talking to the master-server/etc.
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60 DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
@ -72,8 +72,8 @@ class MasterServerCallThread(threading.Thread):
"""Thread to communicate with the master-server.""" """Thread to communicate with the master-server."""
def __init__(self, request: str, request_type: str, def __init__(self, request: str, request_type: str,
data: Optional[dict[str, Any]], data: dict[str, Any] | None,
callback: Optional[MasterServerCallback], callback: MasterServerCallback | None,
response_type: MasterServerResponseType): response_type: MasterServerResponseType):
super().__init__() super().__init__()
self._request = request self._request = request
@ -82,7 +82,7 @@ class MasterServerCallThread(threading.Thread):
raise TypeError(f'Invalid response type: {response_type}') raise TypeError(f'Invalid response type: {response_type}')
self._response_type = response_type self._response_type = response_type
self._data = {} if data is None else copy.deepcopy(data) 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') self._context = _ba.Context('current')
# Save and restore the context we were created from. # Save and restore the context we were created from.
@ -90,7 +90,7 @@ class MasterServerCallThread(threading.Thread):
self._activity = weakref.ref( self._activity = weakref.ref(
activity) if activity is not None else None 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 # If we were created in an activity context and that activity has
# since died, do nothing. # since died, do nothing.
# FIXME: Should we just be using a ContextCall instead of doing # FIXME: Should we just be using a ContextCall instead of doing
@ -182,7 +182,7 @@ class MasterServerCallThread(threading.Thread):
def master_server_get( def master_server_get(
request: str, request: str,
data: dict[str, Any], data: dict[str, Any],
callback: Optional[MasterServerCallback] = None, callback: MasterServerCallback | None = None,
response_type: MasterServerResponseType = MasterServerResponseType.JSON response_type: MasterServerResponseType = MasterServerResponseType.JSON
) -> None: ) -> None:
"""Make a call to the master server via a http GET.""" """Make a call to the master server via a http GET."""
@ -193,7 +193,7 @@ def master_server_get(
def master_server_post( def master_server_post(
request: str, request: str,
data: dict[str, Any], data: dict[str, Any],
callback: Optional[MasterServerCallback] = None, callback: MasterServerCallback | None = None,
response_type: MasterServerResponseType = MasterServerResponseType.JSON response_type: MasterServerResponseType = MasterServerResponseType.JSON
) -> None: ) -> None:
"""Make a call to the master server via a http POST.""" """Make a call to the master server via a http POST."""

View file

@ -13,7 +13,7 @@ from ba._error import (SessionPlayerNotFoundError, print_exception,
from ba._messages import DeathType, DieMessage from ba._messages import DeathType, DieMessage
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Sequence, Any, Union, Callable from typing import Sequence, Any, Callable
import ba import ba
# pylint: disable=invalid-name # pylint: disable=invalid-name
@ -39,7 +39,7 @@ class StandLocation:
Category: Gameplay Classes Category: Gameplay Classes
""" """
position: ba.Vec3 position: ba.Vec3
angle: Optional[float] = None angle: float | None = None
class Player(Generic[TeamType]): class Player(Generic[TeamType]):
@ -56,7 +56,7 @@ class Player(Generic[TeamType]):
# their type annotations are introspectable (for docs generation). # their type annotations are introspectable (for docs generation).
character: str character: str
actor: Optional[ba.Actor] actor: ba.Actor | None
"""The ba.Actor associated with the player.""" """The ba.Actor associated with the player."""
color: Sequence[float] color: Sequence[float]
@ -64,7 +64,7 @@ class Player(Generic[TeamType]):
_team: TeamType _team: TeamType
_sessionplayer: ba.SessionPlayer _sessionplayer: ba.SessionPlayer
_nodeactor: Optional[ba.NodeActor] _nodeactor: ba.NodeActor | None
_expired: bool _expired: bool
_postinited: bool _postinited: bool
_customdata: dict _customdata: dict
@ -94,7 +94,7 @@ class Player(Generic[TeamType]):
self.actor = None self.actor = None
self.character = '' self.character = ''
self._nodeactor: Optional[ba.NodeActor] = None self._nodeactor: ba.NodeActor | None = None
self._sessionplayer = sessionplayer self._sessionplayer = sessionplayer
self.character = sessionplayer.character self.character = sessionplayer.character
self.color = sessionplayer.color self.color = sessionplayer.color
@ -249,8 +249,7 @@ class Player(Generic[TeamType]):
assert not self._expired assert not self._expired
return self._sessionplayer.get_icon() 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: call: Callable) -> None:
""" """
Set the python callable to be run for one or more types of input. 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. # for the optional variety, but that currently seems to not be working.
# See: https://github.com/python/mypy/issues/8800 # See: https://github.com/python/mypy/issues/8800
def playercast_o(totype: type[PlayerType], 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. """A variant of ba.playercast() for use with optional ba.Player values.
Category: Gameplay Functions Category: Gameplay Functions

View file

@ -25,16 +25,16 @@ class PluginSubsystem:
self.potential_plugins: list[ba.PotentialPlugin] = [] self.potential_plugins: list[ba.PotentialPlugin] = []
self.active_plugins: dict[str, ba.Plugin] = {} self.active_plugins: dict[str, ba.Plugin] = {}
def on_app_launch(self) -> None: def on_app_running(self) -> None:
"""Should be called at app launch time.""" """Should be called when the app reaches the running state."""
# Load up our plugins and go ahead and call their on_app_launch calls. # Load up our plugins and go ahead and call their on_app_running calls.
self.load_plugins() self.load_plugins()
for plugin in self.active_plugins.values(): for plugin in self.active_plugins.values():
try: try:
plugin.on_app_launch() plugin.on_app_running()
except Exception: except Exception:
from ba import _error 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: def on_app_pause(self) -> None:
"""Called when the app goes to a suspended state.""" """Called when the app goes to a suspended state."""
@ -80,16 +80,23 @@ class PluginSubsystem:
try: try:
cls = getclass(plugkey, Plugin) cls = getclass(plugkey, Plugin)
except Exception as exc: except Exception as exc:
_ba.log(f"Error loading plugin class '{plugkey}': {exc}", _ba.playsound(_ba.getsound('error'))
to_server=False) # TODO: Lstr.
errstr = f"Error loading plugin class '{plugkey}': {exc}"
_ba.screenmessage(errstr, color=(1, 0, 0))
_ba.log(errstr, to_server=False)
continue continue
try: try:
plugin = cls() plugin = cls()
assert plugkey not in self.active_plugins assert plugkey not in self.active_plugins
self.active_plugins[plugkey] = plugin self.active_plugins[plugkey] = plugin
except Exception: except Exception as exc:
from ba import _error 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 @dataclass
@ -119,8 +126,8 @@ class Plugin:
app is running in order to modify its behavior in some way. app is running in order to modify its behavior in some way.
""" """
def on_app_launch(self) -> None: def on_app_running(self) -> None:
"""Called when the app is being launched.""" """Called when the app reaches the running state."""
def on_app_pause(self) -> None: def on_app_pause(self) -> None:
"""Called after pausing game activity.""" """Called after pausing game activity."""

View file

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
from dataclasses import dataclass from dataclasses import dataclass
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Optional from typing import Sequence
import ba import ba
@ -25,7 +25,7 @@ class PowerupMessage:
"""The type of powerup to be granted (a string). """The type of powerup to be granted (a string).
See ba.Powerup.poweruptype for available type values.""" 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. """The node the powerup game from, or None otherwise.
If a powerup is accepted, a ba.PowerupAcceptMessage should be sent If a powerup is accepted, a ba.PowerupAcceptMessage should be sent
back to the sourcenode to inform it of the fact. This will generally back to the sourcenode to inform it of the fact. This will generally

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
# NOTE: player color options are enforced server-side for non-pro accounts # NOTE: player color options are enforced server-side for non-pro accounts
# so don't change these or they won't stick... # 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( def get_player_profile_colors(
profilename: Optional[str], profilename: str | None,
profiles: dict[str, dict[str, Any]] = None profiles: dict[str, dict[str, Any]] = None
) -> tuple[tuple[float, float, float], tuple[float, float, float]]: ) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
"""Given a profile, return colors for them.""" """Given a profile, return colors for them."""

View file

@ -19,7 +19,7 @@ from ba._dualteamsession import DualTeamSession
from ba._coopsession import CoopSession from ba._coopsession import CoopSession
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any from typing import Any
import ba import ba
from bacommon.servermanager import ServerConfig from bacommon.servermanager import ServerConfig
@ -85,10 +85,10 @@ class ServerController:
self._config = config self._config = config
self._playlist_name = '__default__' self._playlist_name = '__default__'
self._ran_access_check = False 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._next_stuck_login_warn_time = time.time() + 10.0
self._first_run = True self._first_run = True
self._shutdown_reason: Optional[ShutdownReason] = None self._shutdown_reason: ShutdownReason | None = None
self._executing_shutdown = False self._executing_shutdown = False
# Make note if they want us to import a playlist; # Make note if they want us to import a playlist;
@ -129,7 +129,7 @@ class ServerController:
out += f'\n{clientid:<{col1}} {name:<{col2}} {players}' out += f'\n{clientid:<{col1}} {name:<{col2}} {players}'
print(out) 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. """Kick the provided client id.
ban_time is provided in seconds. ban_time is provided in seconds.
@ -198,7 +198,7 @@ class ServerController:
callback=self._access_check_response, 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 import os
if data is None: if data is None:
print('error on UDP port access check (internet down?)') print('error on UDP port access check (internet down?)')
@ -267,7 +267,7 @@ class ServerController:
def _on_playlist_fetch_response( def _on_playlist_fetch_response(
self, self,
result: Optional[dict[str, Any]], result: dict[str, Any] | None,
) -> None: ) -> None:
if result is None: if result is None:
print('Error fetching playlist; aborting.') print('Error fetching playlist; aborting.')

View file

@ -12,7 +12,7 @@ from ba._language import Lstr
from ba._player import Player from ba._player import Player
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Any, Optional from typing import Sequence, Any
import ba import ba
@ -134,7 +134,7 @@ class Session:
self._sessiondata = _ba.register_session(self) self._sessiondata = _ba.register_session(self)
# Should remove this if possible. # Should remove this if possible.
self.tournament_id: Optional[str] = None self.tournament_id: str | None = None
self.sessionteams = [] self.sessionteams = []
self.sessionplayers = [] self.sessionplayers = []
@ -144,16 +144,16 @@ class Session:
self.customdata = {} self.customdata = {}
self._in_set_activity = False self._in_set_activity = False
self._next_team_id = 0 self._next_team_id = 0
self._activity_retained: Optional[ba.Activity] = None self._activity_retained: ba.Activity | None = None
self._launch_end_session_activity_time: Optional[float] = None self._launch_end_session_activity_time: float | None = None
self._activity_end_timer: Optional[ba.Timer] = None self._activity_end_timer: ba.Timer | None = None
self._activity_weak = empty_weakref(Activity) 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._wants_to_end = False
self._ending = False self._ending = False
self._activity_should_end_immediately = False self._activity_should_end_immediately = False
self._activity_should_end_immediately_results: ( self._activity_should_end_immediately_results: (ba.GameResults
Optional[ba.GameResults]) = None | None) = None
self._activity_should_end_immediately_delay = 0.0 self._activity_should_end_immediately_delay = 0.0
# Create static teams if we're using them. # Create static teams if we're using them.
@ -285,7 +285,7 @@ class Session:
self.sessionplayers.remove(sessionplayer) self.sessionplayers.remove(sessionplayer)
def _remove_player_team(self, sessionteam: ba.SessionTeam, 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.""" """Remove the player-specific team in non-teams mode."""
# They should have been the only one on their team. # They should have been the only one on their team.
@ -481,7 +481,7 @@ class Session:
timetype=TimeType.REAL) timetype=TimeType.REAL)
self._in_set_activity = False 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 the current foreground activity for this session."""
return self._activity_weak() return self._activity_weak()

View file

@ -14,7 +14,7 @@ from ba._error import (print_exception, print_error, SessionTeamNotFoundError,
if TYPE_CHECKING: if TYPE_CHECKING:
import ba import ba
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
@dataclass @dataclass
@ -49,12 +49,12 @@ class PlayerRecord:
self.accum_kill_count = 0 self.accum_kill_count = 0
self.killed_count = 0 self.killed_count = 0
self.accum_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._multi_kill_count = 0
self._stats = weakref.ref(stats) self._stats = weakref.ref(stats)
self._last_sessionplayer: Optional[ba.SessionPlayer] = None self._last_sessionplayer: ba.SessionPlayer | None = None
self._sessionplayer: Optional[ba.SessionPlayer] = None self._sessionplayer: ba.SessionPlayer | None = None
self._sessionteam: Optional[weakref.ref[ba.SessionTeam]] = None self._sessionteam: weakref.ref[ba.SessionTeam] | None = None
self.streak = 0 self.streak = 0
self.associate_with_sessionplayer(sessionplayer) self.associate_with_sessionplayer(sessionplayer)
@ -96,7 +96,7 @@ class PlayerRecord:
"""Cancel any multi-kill timer for this player entry.""" """Cancel any multi-kill timer for this player entry."""
self._multi_kill_timer = None 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. """Return the ba.Activity this instance is currently associated with.
Returns None if the activity no longer exists.""" Returns None if the activity no longer exists."""
@ -178,12 +178,12 @@ class PlayerRecord:
def _apply(name2: Lstr, score2: int, showpoints2: bool, def _apply(name2: Lstr, score2: int, showpoints2: bool,
color2: tuple[float, float, float, float], scale2: float, color2: tuple[float, float, float, float], scale2: float,
sound2: Optional[ba.Sound]) -> None: sound2: ba.Sound | None) -> None:
from bastd.actor.popuptext import PopupText from bastd.actor.popuptext import PopupText
# Only award this if they're still alive and we can get # Only award this if they're still alive and we can get
# a current position for them. # a current position for them.
our_pos: Optional[ba.Vec3] = None our_pos: ba.Vec3 | None = None
if self._sessionplayer: if self._sessionplayer:
if self._sessionplayer.activityplayer is not None: if self._sessionplayer.activityplayer is not None:
try: try:
@ -233,14 +233,14 @@ class Stats:
""" """
def __init__(self) -> None: 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._player_records: dict[str, PlayerRecord] = {}
self.orchestrahitsound1: Optional[ba.Sound] = None self.orchestrahitsound1: ba.Sound | None = None
self.orchestrahitsound2: Optional[ba.Sound] = None self.orchestrahitsound2: ba.Sound | None = None
self.orchestrahitsound3: Optional[ba.Sound] = None self.orchestrahitsound3: ba.Sound | None = None
self.orchestrahitsound4: Optional[ba.Sound] = 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.""" """Set the current activity for this instance."""
self._activity = None if activity is None else weakref.ref(activity) self._activity = None if activity is None else weakref.ref(activity)
@ -253,7 +253,7 @@ class Stats:
with _ba.Context(activity): with _ba.Context(activity):
self._load_activity_media() self._load_activity_media()
def getactivity(self) -> Optional[ba.Activity]: def getactivity(self) -> ba.Activity | None:
"""Get the activity associated with this instance. """Get the activity associated with this instance.
May return None. May return None.
@ -319,7 +319,7 @@ class Stats:
victim_player: ba.Player = None, victim_player: ba.Player = None,
scale: float = 1.0, scale: float = 1.0,
color: Sequence[float] = None, color: Sequence[float] = None,
title: Union[str, ba.Lstr] = None, title: str | ba.Lstr | None = None,
screenmessage: bool = True, screenmessage: bool = True,
display: bool = True, display: bool = True,
importance: int = 1, importance: int = 1,

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any from typing import Any
import ba import ba
@ -434,7 +434,7 @@ def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int,
return count return count
def get_available_sale_time(tab: str) -> Optional[int]: def get_available_sale_time(tab: str) -> int | None:
"""(internal)""" """(internal)"""
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
@ -443,7 +443,7 @@ def get_available_sale_time(tab: str) -> Optional[int]:
import datetime import datetime
from ba._generated.enums import TimeType, TimeFormat from ba._generated.enums import TimeType, TimeFormat
app = _ba.app app = _ba.app
sale_times: list[Optional[int]] = [] sale_times: list[int | None] = []
# Calc time for our pro sale (old special case). # Calc time for our pro sale (old special case).
if tab == 'extras': if tab == 'extras':
@ -475,7 +475,7 @@ def get_available_sale_time(tab: str) -> Optional[int]:
return None return None
assert app.pro_sale_start_val is not None assert app.pro_sale_start_val is not None
val: Optional[int] = max( val: int | None = max(
0, app.pro_sale_start_val - 0, app.pro_sale_start_val -
(_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) - (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) -
app.pro_sale_start_time)) app.pro_sale_start_time))

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, TypeVar, Generic
from ba._error import print_exception from ba._error import print_exception
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Sequence, Union, Optional from typing import Sequence
import ba import ba
@ -26,7 +26,7 @@ class SessionTeam:
# Annotate our attr types at the class level so they're introspectable. # 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.""" """The team's name."""
color: tuple[float, ...] # FIXME: can't we make this fixed len? color: tuple[float, ...] # FIXME: can't we make this fixed len?
@ -46,7 +46,7 @@ class SessionTeam:
def __init__(self, def __init__(self,
team_id: int = 0, team_id: int = 0,
name: Union[ba.Lstr, str] = '', name: ba.Lstr | str = '',
color: Sequence[float] = (1.0, 1.0, 1.0)): color: Sequence[float] = (1.0, 1.0, 1.0)):
"""Instantiate a ba.SessionTeam. """Instantiate a ba.SessionTeam.
@ -59,7 +59,7 @@ class SessionTeam:
self.color = tuple(color) self.color = tuple(color)
self.players = [] self.players = []
self.customdata = {} self.customdata = {}
self.activityteam: Optional[Team] = None self.activityteam: Team | None = None
def leave(self) -> None: def leave(self) -> None:
"""(internal)""" """(internal)"""
@ -84,7 +84,7 @@ class Team(Generic[PlayerType]):
# that types are introspectable (these are still instance attrs). # that types are introspectable (these are still instance attrs).
players: list[PlayerType] players: list[PlayerType]
id: int id: int
name: Union[ba.Lstr, str] name: ba.Lstr | str
color: tuple[float, ...] # FIXME: can't we make this fixed length? color: tuple[float, ...] # FIXME: can't we make this fixed length?
_sessionteam: weakref.ref[SessionTeam] _sessionteam: weakref.ref[SessionTeam]
_expired: bool _expired: bool
@ -120,7 +120,7 @@ class Team(Generic[PlayerType]):
self._expired = False self._expired = False
self._postinited = True 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: color: tuple[float, ...]) -> None:
"""Manually init a team for uses such as bots.""" """Manually init a team for uses such as bots."""
self.id = team_id self.id = team_id

View file

@ -10,7 +10,7 @@ import _ba
from ba._generated.enums import UIScale from ba._generated.enums import UIScale
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any, Callable from typing import Any, Callable
from ba.ui import UICleanupCheck from ba.ui import UICleanupCheck
import ba import ba
@ -26,10 +26,10 @@ class UISubsystem:
def __init__(self) -> None: def __init__(self) -> None:
env = _ba.env() 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_window: ba.Widget | None = None
self._main_menu_location: Optional[str] = None self._main_menu_location: str | None = None
self._uiscale: ba.UIScale self._uiscale: ba.UIScale
@ -44,13 +44,12 @@ class UISubsystem:
raise RuntimeError(f'Invalid UIScale value: {interfacetype}') raise RuntimeError(f'Invalid UIScale value: {interfacetype}')
self.window_states: dict[type, Any] = {} # FIXME: Kill this. 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.have_party_queue_window = False
self.quit_window: Any = None self.quit_window: Any = None
self.dismiss_wii_remotes_window_call: (Optional[Callable[[], self.dismiss_wii_remotes_window_call: (Callable[[], Any] | None) = None
Any]]) = None
self.cleanupchecks: list[UICleanupCheck] = [] 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.use_toolbars = env.get('toolbar_test', True)
self.party_window: Any = None # FIXME: Don't use Any. self.party_window: Any = None # FIXME: Don't use Any.
self.title_color = (0.72, 0.7, 0.75) 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.""" """Set the location represented by the current main menu window."""
self._main_menu_location = location 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 the current named main menu location, if any."""
return self._main_menu_location return self._main_menu_location

196
dist/ba_data/python/ba/_workspace.py vendored Normal file
View file

@ -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)

View file

@ -10,7 +10,7 @@ import _ba
from ba._music import MusicPlayer from ba._music import MusicPlayer
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Callable, Any from typing import Callable, Any
class MacMusicAppMusicPlayer(MusicPlayer): class MacMusicAppMusicPlayer(MusicPlayer):
@ -62,8 +62,8 @@ class _MacMusicAppThread(threading.Thread):
self._commands_available = threading.Event() self._commands_available = threading.Event()
self._commands: list[list] = [] self._commands: list[list] = []
self._volume = 1.0 self._volume = 1.0
self._current_playlist: Optional[str] = None self._current_playlist: str | None = None
self._orig_volume: Optional[int] = None self._orig_volume: int | None = None
def run(self) -> None: def run(self) -> None:
"""Run the Music.app thread.""" """Run the Music.app thread."""
@ -136,7 +136,7 @@ class _MacMusicAppThread(threading.Thread):
if old_volume == 0.0: if old_volume == 0.0:
self._play_current_playlist() self._play_current_playlist()
def play_playlist(self, musictype: Optional[str]) -> None: def play_playlist(self, musictype: str | None) -> None:
"""Play the given playlist.""" """Play the given playlist."""
self._commands.append(['PLAY', musictype]) self._commands.append(['PLAY', musictype])
self._commands_available.set() self._commands_available.set()
@ -170,7 +170,7 @@ class _MacMusicAppThread(threading.Thread):
playlists = [] playlists = []
_ba.pushcall(Call(target, playlists), from_other_thread=True) _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 target is None:
if self._current_playlist is not None and self._volume > 0: if self._current_playlist is not None and self._volume > 0:
try: try:

View file

@ -9,7 +9,7 @@ import os
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Sequence from typing import Sequence
def get_human_readable_user_scripts_path() -> str: def get_human_readable_user_scripts_path() -> str:
@ -19,7 +19,7 @@ def get_human_readable_user_scripts_path() -> str:
""" """
from ba import _language from ba import _language
app = _ba.app app = _ba.app
path: Optional[str] = app.python_directory_user path: str | None = app.python_directory_user
if path is None: if path is None:
return '<Not Available>' return '<Not Available>'
@ -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 # only visible to the user's processes and thus not really useful printed
# in its entirety; lets print it as <External Storage>/myfilepath. # in its entirety; lets print it as <External Storage>/myfilepath.
if app.platform == 'android': if app.platform == 'android':
ext_storage_path: Optional[str] = ( ext_storage_path: str | None = (
_ba.android_get_external_storage_path()) _ba.android_get_external_storage_path())
if (ext_storage_path is not None if (ext_storage_path is not None
and app.python_directory_user.startswith(ext_storage_path)): and app.python_directory_user.startswith(ext_storage_path)):
@ -70,7 +70,7 @@ def show_user_scripts() -> None:
# they can see it. # they can see it.
if app.platform == 'android': if app.platform == 'android':
try: 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): if usd is not None and os.path.isdir(usd):
file_name = usd + '/about_this_folder.txt' file_name = usd + '/about_this_folder.txt'
with open(file_name, 'w', encoding='utf-8') as outfile: with open(file_name, 'w', encoding='utf-8') as outfile:

View file

@ -12,7 +12,7 @@ import _ba
from ba._music import MusicPlayer from ba._music import MusicPlayer
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Any, Union, Optional from typing import Callable, Any
class OSMusicPlayer(MusicPlayer): class OSMusicPlayer(MusicPlayer):
@ -60,8 +60,8 @@ class OSMusicPlayer(MusicPlayer):
self._on_play_folder_cb).start() self._on_play_folder_cb).start()
def _on_play_folder_cb(self, def _on_play_folder_cb(self,
result: Union[str, list[str]], result: str | list[str],
error: Optional[str] = None) -> None: error: str | None = None) -> None:
from ba import _language from ba import _language
if error is not None: if error is not None:
rstr = (_language.Lstr( rstr = (_language.Lstr(
@ -95,8 +95,7 @@ class OSMusicPlayer(MusicPlayer):
class _PickFolderSongThread(threading.Thread): class _PickFolderSongThread(threading.Thread):
def __init__(self, path: str, valid_extensions: list[str], def __init__(self, path: str, valid_extensions: list[str],
callback: Callable[[Union[str, list[str]], Optional[str]], callback: Callable[[str | list[str], str | None], None]):
None]):
super().__init__() super().__init__()
self._valid_extensions = valid_extensions self._valid_extensions = valid_extensions
self._callback = callback self._callback = callback

View file

@ -14,7 +14,7 @@ from ba._generated.enums import TimeType
from ba._general import print_active_refs from ba._general import print_active_refs
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any from typing import Any
import ba import ba
@ -46,7 +46,7 @@ class UICleanupCheck:
"""Holds info about a uicleanupcheck target.""" """Holds info about a uicleanupcheck target."""
obj: weakref.ref obj: weakref.ref
widget: ba.Widget widget: ba.Widget
widget_death_time: Optional[float] widget_death_time: float | None
class UILocation: class UILocation:
@ -76,7 +76,7 @@ class UILocationWindow(UILocation):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._root_widget: Optional[ba.Widget] = None self._root_widget: ba.Widget | None = None
def get_root_widget(self) -> ba.Widget: def get_root_widget(self) -> ba.Widget:
"""Return the root widget for this window.""" """Return the root widget for this window."""
@ -91,7 +91,7 @@ class UIEntry:
self._name = name self._name = name
self._state = None self._state = None
self._args = None self._args = None
self._instance: Optional[UILocation] = None self._instance: UILocation | None = None
self._controller = weakref.ref(controller) self._controller = weakref.ref(controller)
def create(self) -> None: def create(self) -> None:
@ -129,7 +129,7 @@ class UIController:
self._main_stack_menu: list[UIEntry] = [] self._main_stack_menu: list[UIEntry] = []
# This points at either the game or menu stack. # 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 # There's only one of these since we don't need to preserve its state
# between sessions. # between sessions.

View file

@ -5,7 +5,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Optional, Annotated from typing import TYPE_CHECKING, Annotated
from enum import Enum from enum import Enum
from efro.dataclassio import ioprepped, IOAttrs from efro.dataclassio import ioprepped, IOAttrs
@ -57,4 +57,4 @@ class AssetPackageBuildState:
# Build error string. If this is present, it should be presented # Build error string. If this is present, it should be presented
# to the user and they should required to explicitly restart the build # to the user and they should required to explicitly restart the build
# in some way if desired. # in some way if desired.
error: Annotated[Optional[str], IOAttrs('e')] = None error: Annotated[str | None, IOAttrs('e')] = None

View file

@ -3,18 +3,34 @@
"""Functionality related to the bacloud tool.""" """Functionality related to the bacloud tool."""
from __future__ import annotations 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: if TYPE_CHECKING:
pass 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 @ioprepped
@dataclass @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 # noinspection PyUnresolvedReferences
"""Response sent from the bacloud server to the client. """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 uploads_inline: If present, a list of pathnames that should be base64
gzipped and uploaded to an 'uploads_inline' dict in end_command args. gzipped and uploaded to an 'uploads_inline' dict in end_command args.
This should be limited to relatively small files. 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 downloads_inline: If present, pathnames mapped to base64 gzipped data to
be written to the client. This should only be used for relatively 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. 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 dir_prune_empty: If present, all empty dirs under this one should be
removed. removed.
open_url: If present, url to display to the user. 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 end_command: If present, this command is run with these args at the end
of response processing. of response processing.
""" """
message: Optional[str] = None message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
message_end: str = '\n' message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
error: Optional[str] = None error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
delay_seconds: float = 0.0 delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
login: Optional[str] = None login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
logout: bool = False logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
dir_manifest: Optional[str] = None dir_manifest: Annotated[str | None,
uploads: Optional[tuple[list[str], str, dict]] = None IOAttrs('man', store_default=False)] = None
uploads_inline: Optional[list[str]] = None uploads: Annotated[tuple[list[str], str, dict] | None,
downloads_inline: Optional[dict[str, str]] = None IOAttrs('u', store_default=False)] = None
deletes: Optional[list[str]] = None uploads_inline: Annotated[list[str] | None,
dir_prune_empty: Optional[str] = None IOAttrs('uinl', store_default=False)] = None
open_url: Optional[str] = None deletes: Annotated[list[str] | None,
input_prompt: Optional[tuple[str, bool]] = None IOAttrs('dlt', store_default=False)] = None
end_message: Optional[str] = None downloads_inline: Annotated[dict[str, str] | None,
end_message_end: str = '\n' IOAttrs('dinl', store_default=False)] = None
end_command: Optional[tuple[str, dict]] = 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

View file

@ -3,12 +3,13 @@
"""Functionality related to cloud functionality.""" """Functionality related to cloud functionality."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated, Optional from typing import TYPE_CHECKING, Annotated
from enum import Enum from enum import Enum
from efro.message import Message, Response from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs from efro.dataclassio import ioprepped, IOAttrs
from bacommon.transfer import DirectoryManifest
if TYPE_CHECKING: if TYPE_CHECKING:
pass pass
@ -65,7 +66,7 @@ class LoginProxyStateQueryResponse(Response):
state: Annotated[State, IOAttrs('s')] state: Annotated[State, IOAttrs('s')]
# On success, these will be filled out. # On success, these will be filled out.
credentials: Annotated[Optional[str], IOAttrs('tk')] credentials: Annotated[str | None, IOAttrs('tk')]
@ioprepped @ioprepped
@ -77,27 +78,57 @@ class LoginProxyCompleteMessage(Message):
@ioprepped @ioprepped
@dataclass @dataclass
class AccountSessionReleaseMessage(Message): class TestMessage(Message):
"""We're done using this particular session.""" """Can I get some of that workspace action?"""
token: Annotated[str, IOAttrs('tk')] testfoo: Annotated[int, IOAttrs('f')]
@ioprepped
@dataclass
class CredentialsCheckMessage(Message):
"""Are our current credentials valid?"""
@classmethod @classmethod
def get_response_types(cls) -> list[type[Response]]: def get_response_types(cls) -> list[type[Response]]:
return [CredentialsCheckResponse] return [TestResponse]
@ioprepped @ioprepped
@dataclass @dataclass
class CredentialsCheckResponse(Response): class TestResponse(Response):
"""Info returned when checking credentials.""" """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

View file

@ -4,7 +4,7 @@
from __future__ import annotations 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 dataclasses import dataclass, field
from efro.dataclassio import ioprepped, IOAttrs from efro.dataclassio import ioprepped, IOAttrs
@ -28,7 +28,7 @@ class ServerNodeQueryResponse:
"""A response to a query about server-nodes.""" """A response to a query about server-nodes."""
# If present, something went wrong, and this describes it. # 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. # The set of servernodes.
servers: Annotated[list[ServerNodeEntry], servers: Annotated[list[ServerNodeEntry],
@ -40,11 +40,11 @@ class ServerNodeQueryResponse:
@dataclass @dataclass
class PrivateHostingState: class PrivateHostingState:
"""Combined state of whether we're hosting, whether we can, etc.""" """Combined state of whether we're hosting, whether we can, etc."""
unavailable_error: Optional[str] = None unavailable_error: str | None = None
party_code: Optional[str] = None party_code: str | None = None
tickets_to_host_now: int = 0 tickets_to_host_now: int = 0
minutes_until_free_host: Optional[float] = None minutes_until_free_host: float | None = None
free_host_minutes_remaining: Optional[float] = None free_host_minutes_remaining: float | None = None
@ioprepped @ioprepped
@ -55,10 +55,10 @@ class PrivateHostingConfig:
playlist_name: str = 'Unknown' playlist_name: str = 'Unknown'
randomize: bool = False randomize: bool = False
tutorial: bool = False tutorial: bool = False
custom_team_names: Optional[tuple[str, str]] = None custom_team_names: tuple[str, str] | None = None
custom_team_colors: Optional[tuple[tuple[float, float, float], custom_team_colors: tuple[tuple[float, float, float],
tuple[float, float, float]]] = None tuple[float, float, float]] | None = None
playlist: Optional[list[dict[str, Any]]] = None playlist: list[dict[str, Any]] | None = None
exit_minutes: float = 120.0 exit_minutes: float = 120.0
exit_minutes_unclean: float = 180.0 exit_minutes_unclean: float = 180.0
exit_minutes_idle: float = 10.0 exit_minutes_idle: float = 10.0
@ -68,7 +68,7 @@ class PrivateHostingConfig:
@dataclass @dataclass
class PrivatePartyConnectResult: class PrivatePartyConnectResult:
"""Info about a server we get back when connecting.""" """Info about a server we get back when connecting."""
error: Optional[str] = None error: str | None = None
addr: Optional[str] = None addr: str | None = None
port: Optional[int] = None port: int | None = None
password: Optional[str] = None password: str | None = None

View file

@ -5,7 +5,7 @@ from __future__ import annotations
from enum import Enum from enum import Enum
from dataclasses import field, dataclass from dataclasses import field, dataclass
from typing import TYPE_CHECKING, Optional, Any from typing import TYPE_CHECKING, Any
from efro.dataclassio import ioprepped from efro.dataclassio import ioprepped
@ -60,11 +60,11 @@ class ServerConfig:
# playlist editor in the regular version of the game. # playlist editor in the regular version of the game.
# This will give you a numeric code you can enter here to host that # This will give you a numeric code you can enter here to host that
# playlist. # playlist.
playlist_code: Optional[int] = None playlist_code: int | None = None
# Alternately, you can embed playlist data here instead of using codes. # 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. # 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. # Whether to shuffle the playlist or play its games in designated order.
playlist_shuffle: bool = True playlist_shuffle: bool = True
@ -105,7 +105,7 @@ class ServerConfig:
# currently-signed-in account's id. To fetch info about an account, # currently-signed-in account's id. To fetch info about an account,
# your back-end server can use the following url: # your back-end server can use the following url:
# https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE # 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 # 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 # 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 # default) will then spin up a fresh subprocess. This mechanism can be
# useful to clear out any memory leaks or other accumulated bad state # useful to clear out any memory leaks or other accumulated bad state
# in the server subprocess. # 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 # 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. # 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 # The server manager will then spin up a fresh server subprocess if
# auto-restart is enabled (the default). # 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 # If present, the server subprocess will shut down immediately if this
# amount of time passes with no activity from any players. The server # 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 # manager will then spin up a fresh server subprocess if auto-restart is
# enabled (the default). # 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? # Should the tutorial be shown at the beginning of games?
show_tutorial: bool = False show_tutorial: bool = False
# Team names (teams mode only). # 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 (teams mode only).
team_colors: Optional[tuple[tuple[float, float, float], team_colors: tuple[tuple[float, float, float], tuple[float, float,
tuple[float, float, float]]] = None float]] | None = None
# (internal) stress-testing mode. # (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 # NOTE: as much as possible, communication from the server-manager to the
@ -171,15 +171,15 @@ class ShutdownCommand(ServerCommand):
class ChatMessageCommand(ServerCommand): class ChatMessageCommand(ServerCommand):
"""Chat message from the server.""" """Chat message from the server."""
message: str message: str
clients: Optional[list[int]] clients: list[int] | None
@dataclass @dataclass
class ScreenMessageCommand(ServerCommand): class ScreenMessageCommand(ServerCommand):
"""Screen-message from the server.""" """Screen-message from the server."""
message: str message: str
color: Optional[tuple[float, float, float]] color: tuple[float, float, float] | None
clients: Optional[list[int]] clients: list[int] | None
@dataclass @dataclass
@ -191,4 +191,4 @@ class ClientListCommand(ServerCommand):
class KickCommand(ServerCommand): class KickCommand(ServerCommand):
"""Kick a client.""" """Kick a client."""
client_id: int client_id: int
ban_time: Optional[int] ban_time: int | None

View file

@ -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

View file

@ -2,4 +2,4 @@
# #
"""Ballistica standard library: games, UI, etc.""" """Ballistica standard library: games, UI, etc."""
# ba_meta require api 6 # ba_meta require api 7

View file

@ -11,7 +11,7 @@ import ba
from ba.internal import JoinActivity from ba.internal import JoinActivity
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class CoopJoinActivity(JoinActivity): class CoopJoinActivity(JoinActivity):
@ -54,7 +54,7 @@ class CoopJoinActivity(JoinActivity):
ControlsGuide(delay=1.0).autoretain() ControlsGuide(delay=1.0).autoretain()
def _on_got_scores_to_beat(self, 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-locals
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from efro.util import asserttype from efro.util import asserttype
@ -87,7 +87,7 @@ class CoopJoinActivity(JoinActivity):
delay_inc = 0.1 delay_inc = 0.1
def _add_t( def _add_t(
text: Union[str, ba.Lstr], text: str | ba.Lstr,
h_offs: float = 0.0, h_offs: float = 0.0,
scale: float = 1.0, scale: float = 1.0,
color: Sequence[float] = (1.0, 1.0, 1.0, 0.46) color: Sequence[float] = (1.0, 1.0, 1.0, 0.46)

View file

@ -14,7 +14,7 @@ from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText from bastd.actor.zoomtext import ZoomText
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Any, Sequence from typing import Any, Sequence
from bastd.ui.store.button import StoreButton from bastd.ui.store.button import StoreButton
from bastd.ui.league.rankbutton import LeagueRankButton 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 if _ba.get_v1_account_state() == 'signed_in' else
None) None)
self._game_service_icon_color: Optional[Sequence[float]] self._game_service_icon_color: Sequence[float] | None
self._game_service_achievements_texture: Optional[ba.Texture] self._game_service_achievements_texture: ba.Texture | None
self._game_service_leaderboards_texture: Optional[ba.Texture] self._game_service_leaderboards_texture: ba.Texture | None
with ba.Context('ui'): with ba.Context('ui'):
if self._account_type == 'Game Center': if self._account_type == 'Game Center':
@ -89,53 +89,53 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._cashregistersound = ba.getsound('cashRegister') self._cashregistersound = ba.getsound('cashRegister')
self._gun_cocking_sound = ba.getsound('gunCocking') self._gun_cocking_sound = ba.getsound('gunCocking')
self._dingsound = ba.getsound('ding') self._dingsound = ba.getsound('ding')
self._score_link: Optional[str] = None self._score_link: str | None = None
self._root_ui: Optional[ba.Widget] = None self._root_ui: ba.Widget | None = None
self._background: Optional[ba.Actor] = None self._background: ba.Actor | None = None
self._old_best_rank = 0.0 self._old_best_rank = 0.0
self._game_name_str: Optional[str] = None self._game_name_str: str | None = None
self._game_config_str: Optional[str] = None self._game_config_str: str | None = None
# Ui bits. # Ui bits.
self._corner_button_offs: Optional[tuple[float, float]] = None self._corner_button_offs: tuple[float, float] | None = None
self._league_rank_button: Optional[LeagueRankButton] = None self._league_rank_button: LeagueRankButton | None = None
self._store_button_instance: Optional[StoreButton] = None self._store_button_instance: StoreButton | None = None
self._restart_button: Optional[ba.Widget] = None self._restart_button: ba.Widget | None = None
self._update_corner_button_positions_timer: Optional[ba.Timer] = None self._update_corner_button_positions_timer: ba.Timer | None = None
self._next_level_error: Optional[ba.Actor] = None self._next_level_error: ba.Actor | None = None
# Score/gameplay bits. # Score/gameplay bits.
self._was_complete: Optional[bool] = None self._was_complete: bool | None = None
self._is_complete: Optional[bool] = None self._is_complete: bool | None = None
self._newly_complete: Optional[bool] = None self._newly_complete: bool | None = None
self._is_more_levels: Optional[bool] = None self._is_more_levels: bool | None = None
self._next_level_name: Optional[str] = None self._next_level_name: str | None = None
self._show_friend_scores: Optional[bool] = None self._show_friend_scores: bool | None = None
self._show_info: Optional[dict[str, Any]] = None self._show_info: dict[str, Any] | None = None
self._name_str: Optional[str] = None self._name_str: str | None = None
self._friends_loading_status: Optional[ba.Actor] = None self._friends_loading_status: ba.Actor | None = None
self._score_loading_status: Optional[ba.Actor] = None self._score_loading_status: ba.Actor | None = None
self._tournament_time_remaining: Optional[float] = None self._tournament_time_remaining: float | None = None
self._tournament_time_remaining_text: Optional[Text] = None self._tournament_time_remaining_text: Text | None = None
self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None self._tournament_time_remaining_text_timer: ba.Timer | None = None
# Stuff for activity skip by pressing button # Stuff for activity skip by pressing button
self._birth_time = ba.time() self._birth_time = ba.time()
self._min_view_time = 5.0 self._min_view_time = 5.0
self._allow_server_transition = False self._allow_server_transition = False
self._server_transitioning: Optional[bool] = None self._server_transitioning: bool | None = None
self._playerinfos: list[ba.PlayerInfo] = settings['playerinfos'] self._playerinfos: list[ba.PlayerInfo] = settings['playerinfos']
assert isinstance(self._playerinfos, list) assert isinstance(self._playerinfos, list)
assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos) 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))) 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))) 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 self._score_order: str
if 'score_order' in settings: if 'score_order' in settings:
@ -410,7 +410,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
texture=self._replay_icon_texture, texture=self._replay_icon_texture,
opacity=0.8) 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 # Our 'next' button is disabled if we haven't unlocked the next
# level yet and invisible if there is none. # 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', []) str(len(self._playerinfos)) + ' Player', [])
if self._score is not None: if self._score is not None:
our_score: Optional[list] = [ our_score: list | None = [
self._score, { self._score, {
'players': [{ 'players': [{
'name': p.name, 'name': p.name,
@ -931,7 +931,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
'loop': False 'loop': False
})).autoretain() })).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 # FIXME: tidy this up
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@ -1046,7 +1046,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
transition=Text.Transition.IN_RIGHT, transition=Text.Transition.IN_RIGHT,
transition_delay=tdelay2).autoretain() 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 # FIXME: tidy this up
# pylint: disable=too-many-locals # pylint: disable=too-many-locals

View file

@ -10,7 +10,7 @@ import ba
from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
@ -236,7 +236,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
transtime2: ts_h_offs + (xval - slide_amt) * scale 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: value: Any) -> None:
if node: if node:
setattr(node, attr, value) setattr(node, attr, value)
@ -259,7 +259,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
self._score_display_sound_small)) self._score_display_sound_small))
v_offs -= spacing 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: keys: dict[float, float]) -> None:
"""Run an animation on a node if the node still exists.""" """Run an animation on a node if the node still exists."""
if node: if node:

View file

@ -11,7 +11,7 @@ from ba.internal import JoinActivity
from bastd.actor.text import Text from bastd.actor.text import Text
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
class MultiTeamJoinActivity(JoinActivity): class MultiTeamJoinActivity(JoinActivity):
@ -19,7 +19,7 @@ class MultiTeamJoinActivity(JoinActivity):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._next_up_text: Optional[Text] = None self._next_up_text: Text | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
from bastd.actor.controlsguide import ControlsGuide from bastd.actor.controlsguide import ControlsGuide

View file

@ -11,7 +11,7 @@ from bastd.actor.text import Text
from bastd.actor.image import Image from bastd.actor.image import Image
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Union pass
class MultiTeamScoreScreenActivity(ScoreScreenActivity): class MultiTeamScoreScreenActivity(ScoreScreenActivity):
@ -52,7 +52,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
def show_player_scores(self, def show_player_scores(self,
delay: float = 2.5, delay: float = 2.5,
results: Optional[ba.GameResults] = None, results: ba.GameResults | None = None,
scale: float = 1.0, scale: float = 1.0,
x_offset: float = 0.0, x_offset: float = 0.0,
y_offset: float = 0.0) -> None: y_offset: float = 0.0) -> None:
@ -67,7 +67,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
is_free_for_all = isinstance(self.session, ba.FreeForAllSession) 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: if is_free_for_all and results is not None:
assert isinstance(results, ba.GameResults) assert isinstance(results, ba.GameResults)
assert p_rec.team.activityteam is not None assert p_rec.team.activityteam is not None
@ -75,7 +75,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
return val return val
return p_rec.accumscore 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: if is_free_for_all and results is not None:
assert isinstance(results, ba.GameResults) assert isinstance(results, ba.GameResults)
assert p_rec.team.activityteam is not None assert p_rec.team.activityteam is not None
@ -96,7 +96,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
def _get_player_score_set_entry( def _get_player_score_set_entry(
player: ba.SessionPlayer) -> Optional[ba.PlayerRecord]: player: ba.SessionPlayer) -> ba.PlayerRecord | None:
for p_rec in valid_players: for p_rec in valid_players:
if p_rec[1].player is player: if p_rec[1].player is player:
return p_rec[1] return p_rec[1]
@ -129,7 +129,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
text: ba.Lstr, text: ba.Lstr,
h_align: Text.HAlign = Text.HAlign.RIGHT, h_align: Text.HAlign = Text.HAlign.RIGHT,
extrascale: float = 1.0, extrascale: float = 1.0,
maxwidth: Optional[float] = 120.0) -> None: maxwidth: float | None = 120.0) -> None:
Text(text, Text(text,
color=(0.5, 0.5, 0.6, 0.5), color=(0.5, 0.5, 0.6, 0.5),
position=(ts_h_offs + xoffs * scale, position=(ts_h_offs + xoffs * scale,
@ -169,7 +169,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
topkillcount = max(topkillcount, prec.accum_kill_count) topkillcount = max(topkillcount, prec.accum_kill_count)
topkilledcount = min(topkilledcount, prec.accum_killed_count) topkilledcount = min(topkilledcount, prec.accum_killed_count)
def _scoretxt(text: Union[str, ba.Lstr], def _scoretxt(text: str | ba.Lstr,
x_offs: float, x_offs: float,
highlight: bool, highlight: bool,
delay2: float, delay2: float,

View file

@ -10,7 +10,7 @@ import ba
from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
@ -146,8 +146,8 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
else: else:
v_extra = 0 v_extra = 0
mvp: Optional[ba.PlayerRecord] = None mvp: ba.PlayerRecord | None = None
mvp_name: Optional[str] = None mvp_name: str | None = None
# Show game MVP. # Show game MVP.
if not self._is_ffa: if not self._is_ffa:

View file

@ -14,7 +14,7 @@ import ba
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Callable from typing import Any, Sequence, Callable
# pylint: disable=invalid-name # pylint: disable=invalid-name
PlayerType = TypeVar('PlayerType', bound='ba.Player') PlayerType = TypeVar('PlayerType', bound='ba.Player')
@ -678,7 +678,7 @@ class Bomb(ba.Actor):
self._exploded = False self._exploded = False
self.scale = bomb_scale self.scale = bomb_scale
self.texture_sequence: Optional[ba.Node] = None self.texture_sequence: ba.Node | None = None
if self.bomb_type == 'sticky': if self.bomb_type == 'sticky':
self._last_sticky_sound_time = 0.0 self._last_sticky_sound_time = 0.0
@ -846,8 +846,8 @@ class Bomb(ba.Actor):
0.26: self.scale 0.26: self.scale
}) })
def get_source_player( def get_source_player(self,
self, playertype: type[PlayerType]) -> Optional[PlayerType]: playertype: type[PlayerType]) -> PlayerType | None:
"""Return the source-player if one exists and is the provided type.""" """Return the source-player if one exists and is the provided type."""
player: Any = self._source_player player: Any = self._source_player
return (player if isinstance(player, playertype) and player.exists() 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): def __init__(self, position: Sequence[float], respawn_time: float = 20.0):
"""Instantiate with given position and respawn_time (in seconds).""" """Instantiate with given position and respawn_time (in seconds)."""
self._position = position 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._respawn_time = random.uniform(0.8, 1.2) * respawn_time
self._wait_time = 0.0 self._wait_time = 0.0
self._update() self._update()

View file

@ -10,7 +10,7 @@ import _ba
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class ControlsGuide(ba.Actor): class ControlsGuide(ba.Actor):
@ -52,13 +52,13 @@ class ControlsGuide(ba.Actor):
self._lifespan = lifespan self._lifespan = lifespan
self._dead = False self._dead = False
self._bright = bright self._bright = bright
self._cancel_timer: Optional[ba.Timer] = None self._cancel_timer: ba.Timer | None = None
self._fade_in_timer: Optional[ba.Timer] = None self._fade_in_timer: ba.Timer | None = None
self._update_timer: Optional[ba.Timer] = None self._update_timer: ba.Timer | None = None
self._title_text: Optional[ba.Node] self._title_text: ba.Node | None
clr: Sequence[float] clr: Sequence[float]
extra_pos_1: Optional[tuple[float, float]] extra_pos_1: tuple[float, float] | None
extra_pos_2: Optional[tuple[float, float]] extra_pos_2: tuple[float, float] | None
if ba.app.iircade_mode: if ba.app.iircade_mode:
xtweak = 0.2 xtweak = 0.2
ytweak = 0.2 ytweak = 0.2
@ -238,7 +238,7 @@ class ControlsGuide(ba.Actor):
}) })
if extra_pos_1 is not None: 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', 'image',
attrs={ attrs={
'texture': ba.gettexture('nub'), 'texture': ba.gettexture('nub'),
@ -252,7 +252,7 @@ class ControlsGuide(ba.Actor):
else: else:
self._extra_image_1 = None self._extra_image_1 = None
if extra_pos_2 is not 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', 'image',
attrs={ attrs={
'texture': ba.gettexture('nub'), 'texture': ba.gettexture('nub'),
@ -317,8 +317,9 @@ class ControlsGuide(ba.Actor):
# an input device that is *not* the touchscreen. # an input device that is *not* the touchscreen.
# (otherwise it is confusing to see the touchscreen buttons right # (otherwise it is confusing to see the touchscreen buttons right
# next to our display buttons) # next to our display buttons)
touchscreen: Optional[ba.InputDevice] = _ba.getinputdevice( touchscreen: ba.InputDevice | None = _ba.getinputdevice('TouchScreen',
'TouchScreen', '#1', doraise=False) '#1',
doraise=False)
if touchscreen is not None: if touchscreen is not None:
# We look at the session's players; not the activity's. # We look at the session's players; not the activity's.
@ -477,7 +478,7 @@ class ControlsGuide(ba.Actor):
pickup_button_names.clear() pickup_button_names.clear()
self._run_text.text = run_text 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: if only_remote and self._lifespan is None:
w_text = ba.Lstr(resource='fireTVRemoteWarningText', w_text = ba.Lstr(resource='fireTVRemoteWarningText',
subs=[('${REMOTE_APP_NAME}', subs=[('${REMOTE_APP_NAME}',

View file

@ -11,7 +11,7 @@ import ba
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional from typing import Any, Sequence
class FlagFactory: class FlagFactory:
@ -185,7 +185,7 @@ class Flag(ba.Actor):
super().__init__() super().__init__()
self._initial_position: Optional[Sequence[float]] = None self._initial_position: Sequence[float] | None = None
self._has_moved = False self._has_moved = False
shared = SharedObjects.get() shared = SharedObjects.get()
factory = FlagFactory.get() factory = FlagFactory.get()
@ -214,7 +214,7 @@ class Flag(ba.Actor):
if dropped_timeout is not None: if dropped_timeout is not None:
dropped_timeout = int(dropped_timeout) dropped_timeout = int(dropped_timeout)
self._dropped_timeout = dropped_timeout self._dropped_timeout = dropped_timeout
self._counter: Optional[ba.Node] self._counter: ba.Node | None
if self._dropped_timeout is not None: if self._dropped_timeout is not None:
self._count = self._dropped_timeout self._count = self._dropped_timeout
self._tick_timer = ba.Timer(1.0, self._tick_timer = ba.Timer(1.0,
@ -234,8 +234,8 @@ class Flag(ba.Actor):
self._counter = None self._counter = None
self._held_count = 0 self._held_count = 0
self._score_text: Optional[ba.Node] = None self._score_text: ba.Node | None = None
self._score_text_hide_timer: Optional[ba.Timer] = None self._score_text_hide_timer: ba.Timer | None = None
def _tick(self) -> None: def _tick(self) -> None:
if self.node: if self.node:

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Union, Optional from typing import Any, Sequence
class Image(ba.Actor): class Image(ba.Actor):
@ -33,9 +33,9 @@ class Image(ba.Actor):
BOTTOM_CENTER = 'bottomCenter' BOTTOM_CENTER = 'bottomCenter'
def __init__(self, def __init__(self,
texture: Union[ba.Texture, dict[str, Any]], texture: ba.Texture | dict[str, Any],
position: tuple[float, float] = (0, 0), position: tuple[float, float] = (0, 0),
transition: Optional[Transition] = None, transition: Transition | None = None,
transition_delay: float = 0.0, transition_delay: float = 0.0,
attach: Attach = Attach.CENTER, attach: Attach = Attach.CENTER,
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), 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. # If they provided a dict as texture, assume its an icon.
# otherwise its just a texture value itself. # otherwise its just a texture value itself.
mask_texture: Optional[ba.Texture] mask_texture: ba.Texture | None
if isinstance(texture, dict): if isinstance(texture, dict):
tint_color = texture['tint_color'] tint_color = texture['tint_color']
tint2_color = texture['tint2_color'] tint2_color = texture['tint2_color']

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Callable, Optional from typing import Any, Callable
class OnScreenCountdown(ba.Actor): class OnScreenCountdown(ba.Actor):
@ -57,7 +57,7 @@ class OnScreenCountdown(ba.Actor):
2: ba.getsound('announceTwo'), 2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne') 1: ba.getsound('announceOne')
} }
self._timer: Optional[ba.Timer] = None self._timer: ba.Timer | None = None
def start(self) -> None: def start(self) -> None:
"""Start the timer.""" """Start the timer."""

View file

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, overload
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Union, Any, Literal from typing import Any, Literal
class OnScreenTimer(ba.Actor): class OnScreenTimer(ba.Actor):
@ -21,7 +21,7 @@ class OnScreenTimer(ba.Actor):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._starttime_ms: Optional[int] = None self._starttime_ms: int | None = None
self.node = ba.newnode('text', self.node = ba.newnode('text',
attrs={ attrs={
'v_attach': 'top', 'v_attach': 'top',
@ -55,7 +55,7 @@ class OnScreenTimer(ba.Actor):
return self._starttime_ms is not None return self._starttime_ms is not None
def stop(self, def stop(self,
endtime: Union[int, float] = None, endtime: int | float | None = None,
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None: timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None:
"""End the timer. """End the timer.
@ -97,8 +97,7 @@ class OnScreenTimer(ba.Actor):
def getstarttime( def getstarttime(
self, self,
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> int | float:
) -> Union[int, float]:
"""Return the sim-time when start() was called. """Return the sim-time when start() was called.
Time will be returned in seconds if timeformat is SECONDS or Time will be returned in seconds if timeformat is SECONDS or

View file

@ -10,7 +10,7 @@ import ba
from bastd.actor.spaz import Spaz from bastd.actor.spaz import Spaz
from spazmod import modifyspaz from spazmod import modifyspaz
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Literal from typing import Any, Sequence, Literal
# pylint: disable=invalid-name # pylint: disable=invalid-name
PlayerType = TypeVar('PlayerType', bound=ba.Player) PlayerType = TypeVar('PlayerType', bound=ba.Player)
@ -64,14 +64,13 @@ class PlayerSpaz(Spaz):
source_player=player, source_player=player,
start_invincible=True, start_invincible=True,
powerups_expire=powerups_expire) 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_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.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._player = player
self._drive_player_position() self._drive_player_position()
import custom_hooks import custom_hooks
custom_hooks.playerspaz_init(self, self.node, self._player) custom_hooks.playerspaz_init(self, self.node, self._player)
@ -80,7 +79,7 @@ class PlayerSpaz(Spaz):
@overload @overload
def getplayer(self, def getplayer(self,
playertype: type[PlayerType], playertype: type[PlayerType],
doraise: Literal[False] = False) -> Optional[PlayerType]: doraise: Literal[False] = False) -> PlayerType | None:
... ...
@overload @overload
@ -90,7 +89,7 @@ class PlayerSpaz(Spaz):
def getplayer(self, def getplayer(self,
playertype: type[PlayerType], playertype: type[PlayerType],
doraise: bool = False) -> Optional[PlayerType]: doraise: bool = False) -> PlayerType | None:
"""Get the ba.Player associated with this Spaz. """Get the ba.Player associated with this Spaz.
By default this will return None if the Player no longer exists. By default this will return None if the Player no longer exists.

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Union, Sequence from typing import Any, Sequence
class PopupText(ba.Actor): class PopupText(ba.Actor):
@ -20,7 +20,7 @@ class PopupText(ba.Actor):
""" """
def __init__(self, def __init__(self,
text: Union[str, ba.Lstr], text: str | ba.Lstr,
position: Sequence[float] = (0.0, 0.0, 0.0), position: Sequence[float] = (0.0, 0.0, 0.0),
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
random_offset: float = 0.5, random_offset: float = 0.5,

View file

@ -11,7 +11,7 @@ import ba
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence from typing import Any, Sequence
DEFAULT_POWERUP_INTERVAL = 8.0 DEFAULT_POWERUP_INTERVAL = 8.0
@ -88,7 +88,7 @@ class PowerupBoxFactory:
""" """
from ba.internal import get_default_powerup_distribution from ba.internal import get_default_powerup_distribution
shared = SharedObjects.get() shared = SharedObjects.get()
self._lastpoweruptype: Optional[str] = None self._lastpoweruptype: str | None = None
self.model = ba.getmodel('powerup') self.model = ba.getmodel('powerup')
self.model_simple = ba.getmodel('powerupSimple') self.model_simple = ba.getmodel('powerupSimple')
self.tex_bomb = ba.gettexture('powerupBomb') self.tex_bomb = ba.gettexture('powerupBomb')

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
class RespawnIcon: class RespawnIcon:
@ -49,7 +49,7 @@ class RespawnIcon:
texture = icon['texture'] texture = icon['texture']
h_offs = -10 h_offs = -10
ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs) 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', ba.newnode('image',
attrs={ attrs={
'texture': texture, 'texture': texture,
@ -68,7 +68,7 @@ class RespawnIcon:
ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7}) 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) 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', ba.newnode('text',
attrs={ attrs={
'v_attach': 'top', 'v_attach': 'top',
@ -88,7 +88,7 @@ class RespawnIcon:
ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) 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) 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', ba.newnode('text',
attrs={ attrs={
'position': tpos, 'position': tpos,
@ -107,7 +107,7 @@ class RespawnIcon:
self._respawn_time = ba.time() + respawn_time self._respawn_time = ba.time() + respawn_time
self._update() self._update()
self._timer: Optional[ba.Timer] = ba.Timer(1.0, self._timer: ba.Timer | None = ba.Timer(1.0,
ba.WeakCall(self._update), ba.WeakCall(self._update),
repeat=True) repeat=True)

View file

@ -10,13 +10,13 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class _Entry: class _Entry:
def __init__(self, scoreboard: Scoreboard, team: ba.Team, do_cover: bool, 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 # pylint: disable=too-many-statements
self._scoreboard = weakref.ref(scoreboard) self._scoreboard = weakref.ref(scoreboard)
self._do_cover = do_cover self._do_cover = do_cover
@ -29,11 +29,11 @@ class _Entry:
self._bar_tex = self._backing_tex = ba.gettexture('bar') self._bar_tex = self._backing_tex = ba.gettexture('bar')
self._cover_tex = ba.gettexture('uiAtlas') self._cover_tex = ba.gettexture('uiAtlas')
self._model = ba.getmodel('meterTransparent') self._model = ba.getmodel('meterTransparent')
self._pos: Optional[Sequence[float]] = None self._pos: Sequence[float] | None = None
self._flash_timer: Optional[ba.Timer] = None self._flash_timer: ba.Timer | None = None
self._flash_counter: Optional[int] = None self._flash_counter: int | None = None
self._flash_colors: Optional[bool] = None self._flash_colors: bool | None = None
self._score: Optional[float] = None self._score: float | None = None
safe_team_color = ba.safecolor(team.color, target_intensity=1.0) safe_team_color = ba.safecolor(team.color, target_intensity=1.0)
@ -126,7 +126,7 @@ class _Entry:
clr = safe_team_color clr = safe_team_color
team_name_label: Union[str, ba.Lstr] team_name_label: str | ba.Lstr
if label is not None: if label is not None:
team_name_label = label team_name_label = label
else: else:
@ -207,7 +207,7 @@ class _Entry:
def _set_flash_colors(self, flash: bool) -> None: def _set_flash_colors(self, flash: bool) -> None:
self._flash_colors = flash self._flash_colors = flash
def _safesetcolor(node: Optional[ba.Node], val: Any) -> None: def _safesetcolor(node: ba.Node | None, val: Any) -> None:
if node: if node:
node.color = val node.color = val

View file

@ -15,7 +15,7 @@ from bastd.actor.spazfactory import SpazFactory
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union, Callable from typing import Any, Sequence, Callable
POWERUP_WEAR_OFF_TIME = 20000 POWERUP_WEAR_OFF_TIME = 20000
BASE_PUNCH_COOLDOWN = 400 BASE_PUNCH_COOLDOWN = 400
@ -57,7 +57,7 @@ class Spaz(ba.Actor):
"""The 'spaz' ba.Node.""" """The 'spaz' ba.Node."""
points_mult = 1 points_mult = 1
curse_time: Optional[float] = 5.0 curse_time: float | None = 5.0
default_bomb_count = 1 default_bomb_count = 1
default_bomb_type = 'normal' default_bomb_type = 'normal'
default_boxing_gloves = False default_boxing_gloves = False
@ -102,7 +102,7 @@ class Spaz(ba.Actor):
self._hockey = False self._hockey = False
self._punched_nodes: set[ba.Node] = set() self._punched_nodes: set[ba.Node] = set()
self._cursed = False self._cursed = False
self._connected_to_player: Optional[ba.Player] = None self._connected_to_player: ba.Player | None = None
materials = [ materials = [
factory.spaz_material, shared.object_material, factory.spaz_material, shared.object_material,
shared.player_material shared.player_material
@ -155,11 +155,11 @@ class Spaz(ba.Actor):
'invincible': start_invincible, 'invincible': start_invincible,
'source_player': source_player 'source_player': source_player
}) })
self.shield: Optional[ba.Node] = None self.shield: ba.Node | None = None
if start_invincible: if start_invincible:
def _safesetattr(node: Optional[ba.Node], attr: str, def _safesetattr(node: ba.Node | None, attr: str,
val: Any) -> None: val: Any) -> None:
if node: if node:
setattr(node, attr, val) setattr(node, attr, val)
@ -168,15 +168,15 @@ class Spaz(ba.Actor):
False)) False))
self.hitpoints = 1000 self.hitpoints = 1000
self.hitpoints_max = 1000 self.hitpoints_max = 1000
self.shield_hitpoints: Optional[int] = None self.shield_hitpoints: int | None = None
self.shield_hitpoints_max = 650 self.shield_hitpoints_max = 650
self.shield_decay_rate = 0 self.shield_decay_rate = 0
self.shield_decay_timer: Optional[ba.Timer] = None self.shield_decay_timer: ba.Timer | None = None
self._boxing_gloves_wear_off_timer: Optional[ba.Timer] = None self._boxing_gloves_wear_off_timer: ba.Timer | None = None
self._boxing_gloves_wear_off_flash_timer: Optional[ba.Timer] = None self._boxing_gloves_wear_off_flash_timer: ba.Timer | None = None
self._bomb_wear_off_timer: Optional[ba.Timer] = None self._bomb_wear_off_timer: ba.Timer | None = None
self._bomb_wear_off_flash_timer: Optional[ba.Timer] = None self._bomb_wear_off_flash_timer: ba.Timer | None = None
self._multi_bomb_wear_off_timer: Optional[ba.Timer] = None self._multi_bomb_wear_off_timer: ba.Timer | None = None
self.bomb_count = self.default_bomb_count self.bomb_count = self.default_bomb_count
self._max_bomb_count = self.default_bomb_count self._max_bomb_count = self.default_bomb_count
self.bomb_type_default = self.default_bomb_type self.bomb_type_default = self.default_bomb_type
@ -205,7 +205,7 @@ class Spaz(ba.Actor):
self._turbo_filter_counts: dict[str, int] = {} self._turbo_filter_counts: dict[str, int] = {}
self.frozen = False self.frozen = False
self.shattered = False self.shattered = False
self._last_hit_time: Optional[int] = None self._last_hit_time: int | None = None
self._num_times_hit = 0 self._num_times_hit = 0
self._bomb_held = False self._bomb_held = False
if self.default_shields: if self.default_shields:
@ -213,13 +213,13 @@ class Spaz(ba.Actor):
self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor],
Any]] = [] Any]] = []
self._score_text: Optional[ba.Node] = None self._score_text: ba.Node | None = None
self._score_text_hide_timer: Optional[ba.Timer] = None self._score_text_hide_timer: ba.Timer | None = None
self._last_stand_pos: Optional[Sequence[float]] = None self._last_stand_pos: Sequence[float] | None = None
# Deprecated stuff.. should make these into lists. # Deprecated stuff.. should make these into lists.
self.punch_callback: Optional[Callable[[Spaz], Any]] = None self.punch_callback: Callable[[Spaz], Any] | None = None
self.pick_up_powerup_callback: Optional[Callable[[Spaz], Any]] = None self.pick_up_powerup_callback: Callable[[Spaz], Any] | None = None
def exists(self) -> bool: def exists(self) -> bool:
return bool(self.node) return bool(self.node)
@ -297,7 +297,7 @@ class Spaz(ba.Actor):
self._turbo_filter_counts = {source: 1} self._turbo_filter_counts = {source: 1}
def set_score_text(self, def set_score_text(self,
text: Union[str, ba.Lstr], text: str | ba.Lstr,
color: Sequence[float] = (1.0, 1.0, 0.4), color: Sequence[float] = (1.0, 1.0, 0.4),
flash: bool = False) -> None: flash: bool = False) -> None:
""" """
@ -1208,7 +1208,7 @@ class Spaz(ba.Actor):
return super().handlemessage(msg) return super().handlemessage(msg)
return None 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 Tell the spaz to drop one of his bombs, and returns
the resulting bomb object. the resulting bomb object.

View file

@ -9,7 +9,7 @@ import _ba
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
def get_appearances(include_locked: bool = False) -> list[str]: def get_appearances(include_locked: bool = False) -> list[str]:
@ -111,8 +111,8 @@ class Appearance:
self.pickup_sounds: list[str] = [] self.pickup_sounds: list[str] = []
self.fall_sounds: list[str] = [] self.fall_sounds: list[str] = []
self.style = 'spaz' self.style = 'spaz'
self.default_color: Optional[tuple[float, float, float]] = None self.default_color: tuple[float, float, float] | None = None
self.default_highlight: Optional[tuple[float, float, float]] = None self.default_highlight: tuple[float, float, float] | None = None
def register_appearances() -> None: def register_appearances() -> None:

View file

@ -13,7 +13,7 @@ import ba
from bastd.actor.spaz import Spaz from bastd.actor.spaz import Spaz
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Callable from typing import Any, Sequence, Callable
from bastd.actor.flag import Flag from bastd.actor.flag import Flag
LITE_BOT_COLOR = (1.2, 0.9, 0.2) LITE_BOT_COLOR = (1.2, 0.9, 0.2)
@ -51,13 +51,13 @@ class SpazBotDiedMessage:
spazbot: SpazBot spazbot: SpazBot
"""The SpazBot that was killed.""" """The SpazBot that was killed."""
killerplayer: Optional[ba.Player] killerplayer: ba.Player | None
"""The ba.Player that killed it (or None).""" """The ba.Player that killed it (or None)."""
how: ba.DeathType how: ba.DeathType
"""The particular type of death.""" """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): how: ba.DeathType):
"""Instantiate with given values.""" """Instantiate with given values."""
self.spazbot = spazbot 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 # 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 # which takes one arg (the bot) and returns False if the bot's normal
# update should be run and True if not. # 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 activity = self.activity
assert isinstance(activity, ba.GameActivity) assert isinstance(activity, ba.GameActivity)
self._map = weakref.ref(activity.map) 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_time = 0.0
self.last_attacked_type: Optional[tuple[str, str]] = None self.last_attacked_type: tuple[str, str] | None = None
self.target_point_default: Optional[ba.Vec3] = None self.target_point_default: ba.Vec3 | None = None
self.held_count = 0 self.held_count = 0
self.last_player_held_by: Optional[ba.Player] = None self.last_player_held_by: ba.Player | None = None
self.target_flag: Optional[Flag] = None self.target_flag: Flag | None = None
self._charge_speed = 0.5 * (self.charge_speed_min + self._charge_speed = 0.5 * (self.charge_speed_min +
self.charge_speed_max) self.charge_speed_max)
self._lead_amount = 0.5 self._lead_amount = 0.5
@ -135,9 +135,9 @@ class SpazBot(Spaz):
self._running = False self._running = False
self._last_jump_time = 0.0 self._last_jump_time = 0.0
self._throw_release_time: Optional[float] = None self._throw_release_time: float | None = None
self._have_dropped_throw_bomb: Optional[bool] = None self._have_dropped_throw_bomb: bool | None = None
self._player_pts: Optional[list[tuple[ba.Vec3, ba.Vec3]]] = None self._player_pts: list[tuple[ba.Vec3, ba.Vec3]] | None = None
# These cooldowns didn't exist when these bots were calibrated, # These cooldowns didn't exist when these bots were calibrated,
# so take them out of the equation. # so take them out of the equation.
@ -156,17 +156,16 @@ class SpazBot(Spaz):
assert mval is not None assert mval is not None
return mval return mval
def _get_target_player_pt( def _get_target_player_pt(self) -> tuple[ba.Vec3 | None, ba.Vec3 | None]:
self) -> tuple[Optional[ba.Vec3], Optional[ba.Vec3]]:
"""Returns the position and velocity of our target. """Returns the position and velocity of our target.
Both values will be None in the case of no target. Both values will be None in the case of no target.
""" """
assert self.node assert self.node
botpt = ba.Vec3(self.node.position) botpt = ba.Vec3(self.node.position)
closest_dist: Optional[float] = None closest_dist: float | None = None
closest_vel: Optional[ba.Vec3] = None closest_vel: ba.Vec3 | None = None
closest: Optional[ba.Vec3] = None closest: ba.Vec3 | None = None
assert self._player_pts is not None assert self._player_pts is not None
for plpt, plvel in self._player_pts: for plpt, plvel in self._player_pts:
dist = (plpt - botpt).length() dist = (plpt - botpt).length()
@ -206,8 +205,8 @@ class SpazBot(Spaz):
our_pos = ba.Vec3(pos[0], 0, pos[2]) our_pos = ba.Vec3(pos[0], 0, pos[2])
can_attack = True can_attack = True
target_pt_raw: Optional[ba.Vec3] target_pt_raw: ba.Vec3 | None
target_vel: Optional[ba.Vec3] target_vel: ba.Vec3 | None
# If we're a flag-bearer, we're pretty simple-minded - just walk # If we're a flag-bearer, we're pretty simple-minded - just walk
# towards the flag and try to pick it up. # towards the flag and try to pick it up.
@ -517,7 +516,7 @@ class SpazBot(Spaz):
# Report normal deaths for scoring purposes. # Report normal deaths for scoring purposes.
if not self._dead and not msg.immediate: 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 # If this guy was being held at the time of death, the
# holder is the killer. # holder is the killer.
@ -883,7 +882,7 @@ class SpazBotSet:
] ]
self._spawn_sound = ba.getsound('spawn') self._spawn_sound = ba.getsound('spawn')
self._spawning_count = 0 self._spawning_count = 0
self._bot_update_timer: Optional[ba.Timer] = None self._bot_update_timer: ba.Timer | None = None
self.start_moving() self.start_moving()
def __del__(self) -> None: def __del__(self) -> None:
@ -904,7 +903,7 @@ class SpazBotSet:
self._spawning_count += 1 self._spawning_count += 1
def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], 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() spaz = bot_type()
ba.playsound(self._spawn_sound, position=pos) ba.playsound(self._spawn_sound, position=pos)
assert spaz.node assert spaz.node

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Union, Sequence, Optional from typing import Any, Sequence
class Text(ba.Actor): class Text(ba.Actor):
@ -49,12 +49,12 @@ class Text(ba.Actor):
TOP = 'top' TOP = 'top'
def __init__(self, def __init__(self,
text: Union[str, ba.Lstr], text: str | ba.Lstr,
position: tuple[float, float] = (0.0, 0.0), position: tuple[float, float] = (0.0, 0.0),
h_align: HAlign = HAlign.LEFT, h_align: HAlign = HAlign.LEFT,
v_align: VAlign = VAlign.NONE, v_align: VAlign = VAlign.NONE,
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
transition: Optional[Transition] = None, transition: Transition | None = None,
transition_delay: float = 0.0, transition_delay: float = 0.0,
flash: bool = False, flash: bool = False,
v_attach: VAttach = VAttach.CENTER, v_attach: VAttach = VAttach.CENTER,

View file

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Union, Sequence from typing import Any, Sequence
class ZoomText(ba.Actor): class ZoomText(ba.Actor):
@ -22,7 +22,7 @@ class ZoomText(ba.Actor):
""" """
def __init__(self, def __init__(self,
text: Union[str, ba.Lstr], text: str | ba.Lstr,
position: tuple[float, float] = (0.0, 0.0), position: tuple[float, float] = (0.0, 0.0),
shiftposition: tuple[float, float] = None, shiftposition: tuple[float, float] = None,
shiftdelay: float = None, shiftdelay: float = None,

View file

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Callable, Optional from typing import Any, Callable
class AppDelegate(ba.AppDelegate): class AppDelegate(ba.AppDelegate):
@ -16,8 +16,8 @@ class AppDelegate(ba.AppDelegate):
def create_default_game_settings_ui( def create_default_game_settings_ui(
self, gameclass: type[ba.GameActivity], self, gameclass: type[ba.GameActivity],
sessiontype: type[ba.Session], settings: Optional[dict], sessiontype: type[ba.Session], settings: dict | None,
completion_call: Callable[[Optional[dict]], Any]) -> None: completion_call: Callable[[dict | None], Any]) -> None:
"""(internal)""" """(internal)"""
# Replace the main window once we come up successfully. # Replace the main window once we come up successfully.

View file

@ -2,7 +2,7 @@
# #
"""Defines assault minigame.""" """Defines assault minigame."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -17,7 +17,7 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Union from typing import Any, Sequence
class Player(ba.Player['Team']): 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 self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.FORWARD_MARCH) 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: if self._score_to_win == 1:
return 'Touch the enemy flag.' return 'Touch the enemy flag.'
return 'Touch the enemy flag ${ARG1} times.', self._score_to_win 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: if self._score_to_win == 1:
return 'touch 1 flag' return 'touch 1 flag'
return 'touch ${ARG1} flags', self._score_to_win return 'touch ${ARG1} flags', self._score_to_win

View file

@ -2,7 +2,7 @@
# #
"""Defines a capture-the-flag game.""" """Defines a capture-the-flag game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -16,7 +16,7 @@ from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage,
FlagDroppedMessage, FlagDiedMessage) FlagDroppedMessage, FlagDiedMessage)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Union, Optional from typing import Any, Sequence
class CTFFlag(Flag): class CTFFlag(Flag):
@ -39,9 +39,9 @@ class CTFFlag(Flag):
'h_align': 'center' 'h_align': 'center'
}) })
self.reset_return_times() self.reset_return_times()
self.last_player_to_hold: Optional[Player] = None self.last_player_to_hold: Player | None = None
self.time_out_respawn_time: Optional[int] = None self.time_out_respawn_time: int | None = None
self.touch_return_time: Optional[float] = None self.touch_return_time: float | None = None
def reset_return_times(self) -> None: def reset_return_times(self) -> None:
"""Clear flag related times in the activity.""" """Clear flag related times in the activity."""
@ -78,11 +78,11 @@ class Team(ba.Team[Player]):
self.score = 0 self.score = 0
self.flag_return_touches = 0 self.flag_return_touches = 0
self.home_flag_at_base = True 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.enemy_flag_at_base = False
self.flag: Optional[CTFFlag] = None self.flag: CTFFlag | None = None
self.last_flag_leave_time: Optional[float] = None self.last_flag_leave_time: float | None = None
self.touch_return_timer_ticking: Optional[ba.NodeActor] = None self.touch_return_timer_ticking: ba.NodeActor | None = None
# ba_meta export game # 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 self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.FLAG_CATCHER) 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: if self._score_to_win == 1:
return 'Steal the enemy flag.' return 'Steal the enemy flag.'
return 'Steal the enemy flag ${ARG1} times.', self._score_to_win 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: if self._score_to_win == 1:
return 'return 1 flag' return 'return 1 flag'
return 'return ${ARG1} flags', self._score_to_win 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 We keep track of when each player is touching their own flag so we
can award points when returned. can award points when returned.
""" """
player: Optional[Player] player: Player | None
try: try:
spaz = ba.getcollision().sourcenode.getdelegate(PlayerSpaz, True) spaz = ba.getcollision().sourcenode.getdelegate(PlayerSpaz, True)
except ba.NotFoundError: except ba.NotFoundError:

View file

@ -2,7 +2,7 @@
# #
"""Provides the chosen-one mini-game.""" """Provides the chosen-one mini-game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -16,14 +16,14 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.chosen_light: Optional[ba.NodeActor] = None self.chosen_light: ba.NodeActor | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
@ -87,7 +87,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._chosen_one_player: Optional[Player] = None self._chosen_one_player: Player | None = None
self._swipsound = ba.getsound('swip') self._swipsound = ba.getsound('swip')
self._countdownsounds: dict[int, ba.Sound] = { self._countdownsounds: dict[int, ba.Sound] = {
10: ba.getsound('announceTen'), 10: ba.getsound('announceTen'),
@ -101,10 +101,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
2: ba.getsound('announceTwo'), 2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne') 1: ba.getsound('announceOne')
} }
self._flag_spawn_pos: Optional[Sequence[float]] = None self._flag_spawn_pos: Sequence[float] | None = None
self._reset_region_material: Optional[ba.Material] = None self._reset_region_material: ba.Material | None = None
self._flag: Optional[Flag] = None self._flag: Flag | None = None
self._reset_region: Optional[ba.Node] = None self._reset_region: ba.Node | None = None
self._epic_mode = bool(settings['Epic Mode']) self._epic_mode = bool(settings['Epic Mode'])
self._chosen_one_time = int(settings['Chosen One Time']) self._chosen_one_time = int(settings['Chosen One Time'])
self._time_limit = float(settings['Time Limit']) self._time_limit = float(settings['Time Limit'])
@ -116,7 +116,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
self.default_music = (ba.MusicType.EPIC self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.CHOSEN_ONE) 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.' return 'There can be only one.'
def create_team(self, sessionteam: ba.SessionTeam) -> Team: def create_team(self, sessionteam: ba.SessionTeam) -> Team:
@ -165,7 +165,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
'materials': [mat] '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. # Should never return invalid references; return None in that case.
if self._chosen_one_player: if self._chosen_one_player:
return 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._chosen_one_time - team.time_remaining)
self.end(results=results, announce_delay=0) 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() existing = self._get_chosen_one_player()
if existing: if existing:
existing.chosen_light = None existing.chosen_light = None

View file

@ -2,7 +2,7 @@
# #
"""Provides the Conquest game.""" """Provides the Conquest game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -17,7 +17,7 @@ from bastd.actor.playerspaz import PlayerSpaz
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
from bastd.actor.respawnicon import RespawnIcon from bastd.actor.respawnicon import RespawnIcon
@ -26,11 +26,11 @@ class ConquestFlag(Flag):
def __init__(self, *args: Any, **keywds: Any): def __init__(self, *args: Any, **keywds: Any):
super().__init__(*args, **keywds) super().__init__(*args, **keywds)
self._team: Optional[Team] = None self._team: Team | None = None
self.light: Optional[ba.Node] = None self.light: ba.Node | None = None
@property @property
def team(self) -> Optional[Team]: def team(self) -> Team | None:
"""The team that owns this flag.""" """The team that owns this flag."""
return self._team return self._team
@ -46,21 +46,21 @@ class Player(ba.Player['Team']):
# FIXME: We shouldn't be using customdata here # FIXME: We shouldn't be using customdata here
# (but need to update respawn funcs accordingly first). # (but need to update respawn funcs accordingly first).
@property @property
def respawn_timer(self) -> Optional[ba.Timer]: def respawn_timer(self) -> ba.Timer | None:
"""Type safe access to standard respawn timer.""" """Type safe access to standard respawn timer."""
return self.customdata.get('respawn_timer', None) return self.customdata.get('respawn_timer', None)
@respawn_timer.setter @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 self.customdata['respawn_timer'] = value
@property @property
def respawn_icon(self) -> Optional[RespawnIcon]: def respawn_icon(self) -> RespawnIcon | None:
"""Type safe access to standard respawn icon.""" """Type safe access to standard respawn icon."""
return self.customdata.get('respawn_icon', None) return self.customdata.get('respawn_icon', None)
@respawn_icon.setter @respawn_icon.setter
def respawn_icon(self, value: Optional[RespawnIcon]) -> None: def respawn_icon(self, value: RespawnIcon | None) -> None:
self.customdata['respawn_icon'] = value self.customdata['respawn_icon'] = value
@ -136,10 +136,10 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
('call', 'at_connect', self._handle_flag_player_collide), ('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) 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) return 'secure all ${ARG1} flags', len(self.map.flag_points)
def on_team_join(self, team: Team) -> None: def on_team_join(self, team: Team) -> None:

View file

@ -2,7 +2,7 @@
# #
"""DeathMatch game and support classes.""" """DeathMatch game and support classes."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -14,7 +14,7 @@ from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Union, Sequence, Optional from typing import Any, Sequence
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
@ -97,7 +97,7 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None self._score_to_win: int | None = None
self._dingsound = ba.getsound('dingSmall') self._dingsound = ba.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode']) self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int( 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 self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.TO_THE_DEATH) 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 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 return 'kill ${ARG1} enemies', self._score_to_win
def on_team_join(self, team: Team) -> None: def on_team_join(self, team: Team) -> None:

View file

@ -2,7 +2,7 @@
# #
"""Provides an easter egg hunt game.""" """Provides an easter egg hunt game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -20,15 +20,15 @@ from bastd.actor.respawnicon import RespawnIcon
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None self.respawn_timer: ba.Timer | None = None
self.respawn_icon: Optional[RespawnIcon] = None self.respawn_icon: RespawnIcon | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
@ -76,9 +76,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
conditions=('they_have_material', shared.player_material), conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect', self._on_egg_player_collide), )) actions=(('call', 'at_connect', self._on_egg_player_collide), ))
self._eggs: list[Egg] = [] self._eggs: list[Egg] = []
self._update_timer: Optional[ba.Timer] = None self._update_timer: ba.Timer | None = None
self._countdown: Optional[OnScreenCountdown] = None self._countdown: OnScreenCountdown | None = None
self._bots: Optional[SpazBotSet] = None self._bots: SpazBotSet | None = None
# Base class overrides # Base class overrides
self.default_music = ba.MusicType.FORWARD_MARCH self.default_music = ba.MusicType.FORWARD_MARCH

View file

@ -2,7 +2,7 @@
# #
"""Elimination mini-game.""" """Elimination mini-game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -14,7 +14,7 @@ from bastd.actor.spazfactory import SpazFactory
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union from typing import Any, Sequence
class Icon(ba.Actor): class Icon(ba.Actor):
@ -162,7 +162,7 @@ class Team(ba.Team[Player]):
"""Our team type for this game.""" """Our team type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.survival_seconds: Optional[int] = None self.survival_seconds: int | None = None
self.spawn_order: list[Player] = [] self.spawn_order: list[Player] = []
@ -234,9 +234,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None self._start_time: float | None = None
self._vs_text: Optional[ba.Actor] = None self._vs_text: ba.Actor | None = None
self._round_end_timer: Optional[ba.Timer] = None self._round_end_timer: ba.Timer | None = None
self._epic_mode = bool(settings['Epic Mode']) self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Lives Per Player']) self._lives_per_player = int(settings['Lives Per Player'])
self._time_limit = float(settings['Time Limit']) self._time_limit = float(settings['Time Limit'])
@ -249,11 +249,11 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
self.default_music = (ba.MusicType.EPIC self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL) 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( return 'Last team standing wins.' if isinstance(
self.session, ba.DualTeamSession) else 'Last one standing wins.' 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( return 'last team standing wins' if isinstance(
self.session, ba.DualTeamSession) else 'last one standing wins' self.session, ba.DualTeamSession) else 'last one standing wins'
@ -401,7 +401,7 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
icon.update_for_lives() icon.update_for_lives()
xval += x_offs 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. del player # Unused.
# In solo-mode, if there's an existing live player on the map, spawn at # In solo-mode, if there's an existing live player on the map, spawn at

View file

@ -2,7 +2,7 @@
# #
"""Implements football games (both co-op and teams varieties).""" """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) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -26,7 +26,7 @@ from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage,
StickyBot, ExplodeyBot) StickyBot, ExplodeyBot)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union from typing import Any, Sequence
from bastd.actor.spaz import Spaz from bastd.actor.spaz import Spaz
from bastd.actor.spazbot import SpazBot from bastd.actor.spazbot import SpazBot
@ -39,9 +39,9 @@ class FootballFlag(Flag):
dropped_timeout=20, dropped_timeout=20,
color=(1.0, 1.0, 0.3)) color=(1.0, 1.0, 0.3))
assert self.node 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.node.is_area_of_interest = True
self.respawn_timer: Optional[ba.Timer] = None self.respawn_timer: ba.Timer | None = None
self.scored = False self.scored = False
self.held_count = 0 self.held_count = 0
self.light = ba.newnode('light', self.light = ba.newnode('light',
@ -59,8 +59,8 @@ class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None self.respawn_timer: ba.Timer | None = None
self.respawn_icon: Optional[RespawnIcon] = None self.respawn_icon: RespawnIcon | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
@ -120,7 +120,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
def __init__(self, settings: dict): def __init__(self, settings: dict):
super().__init__(settings) super().__init__(settings)
self._scoreboard: Optional[Scoreboard] = Scoreboard() self._scoreboard: Scoreboard | None = Scoreboard()
# Load some media we need. # Load some media we need.
self._cheer_sound = ba.getsound('cheer') self._cheer_sound = ba.getsound('cheer')
@ -136,15 +136,15 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
('modify_part_collision', 'physical', False), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score), ('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._score_regions: list[ba.NodeActor] = []
self._flag: Optional[FootballFlag] = None self._flag: FootballFlag | None = None
self._flag_respawn_timer: Optional[ba.Timer] = None self._flag_respawn_timer: ba.Timer | None = None
self._flag_respawn_light: Optional[ba.NodeActor] = None self._flag_respawn_light: ba.NodeActor | None = None
self._score_to_win = int(settings['Score to Win']) self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit']) 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 touchdowns = self._score_to_win / 7
# NOTE: if use just 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 ${ARG1} touchdowns.', touchdowns
return 'Score a touchdown.' 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 = self._score_to_win / 7
touchdowns = math.ceil(touchdowns) touchdowns = math.ceil(touchdowns)
if touchdowns > 1: if touchdowns > 1:
@ -336,14 +336,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
def get_score_type(self) -> str: def get_score_type(self) -> str:
return 'time' return 'time'
def get_instance_description(self) -> Union[str, Sequence]: def get_instance_description(self) -> str | Sequence:
touchdowns = self._score_to_win / 7 touchdowns = self._score_to_win / 7
touchdowns = math.ceil(touchdowns) touchdowns = math.ceil(touchdowns)
if touchdowns > 1: if touchdowns > 1:
return 'Score ${ARG1} touchdowns.', touchdowns return 'Score ${ARG1} touchdowns.', touchdowns
return 'Score a touchdown.' 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 = self._score_to_win / 7
touchdowns = math.ceil(touchdowns) touchdowns = math.ceil(touchdowns)
if touchdowns > 1: if touchdowns > 1:
@ -375,27 +375,27 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._powerup_spread = (10, 5.5) self._powerup_spread = (10, 5.5)
self._player_has_dropped_bomb = False self._player_has_dropped_bomb = False
self._player_has_punched = False self._player_has_punched = False
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Scoreboard | None = None
self._flag_spawn_pos: Optional[Sequence[float]] = None self._flag_spawn_pos: Sequence[float] | None = None
self._score_regions: list[ba.NodeActor] = [] self._score_regions: list[ba.NodeActor] = []
self._exclude_powerups: list[str] = [] self._exclude_powerups: list[str] = []
self._have_tnt = False self._have_tnt = False
self._bot_types_initial: Optional[list[type[SpazBot]]] = None self._bot_types_initial: list[type[SpazBot]] | None = None
self._bot_types_7: Optional[list[type[SpazBot]]] = None self._bot_types_7: list[type[SpazBot]] | None = None
self._bot_types_14: Optional[list[type[SpazBot]]] = None self._bot_types_14: list[type[SpazBot]] | None = None
self._bot_team: Optional[Team] = None self._bot_team: Team | None = None
self._starttime_ms: Optional[int] = None self._starttime_ms: int | None = None
self._time_text: Optional[ba.NodeActor] = None self._time_text: ba.NodeActor | None = None
self._time_text_input: Optional[ba.NodeActor] = None self._time_text_input: ba.NodeActor | None = None
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: TNTSpawner | None = None
self._bots = SpazBotSet() self._bots = SpazBotSet()
self._bot_spawn_timer: Optional[ba.Timer] = None self._bot_spawn_timer: ba.Timer | None = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: ba.Timer | None = None
self._scoring_team: Optional[Team] = None self._scoring_team: Team | None = None
self._final_time_ms: Optional[int] = None self._final_time_ms: int | None = None
self._time_text_timer: Optional[ba.Timer] = None self._time_text_timer: ba.Timer | None = None
self._flag_respawn_light: Optional[ba.Actor] = None self._flag_respawn_light: ba.Actor | None = None
self._flag: Optional[FootballFlag] = None self._flag: FootballFlag | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
@ -581,7 +581,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
return return
flagpos = ba.Vec3(self._flag.node.position) 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. closest_dist = 0.0 # Always gets assigned first time through.
for bot in bots: for bot in bots:
# If a bot is picked up, he should forget about the flag. # If a bot is picked up, he should forget about the flag.

View file

@ -2,7 +2,7 @@
# #
"""Hockey game and support classes.""" """Hockey game and support classes."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -16,7 +16,7 @@ from bastd.actor.powerupbox import PowerupBoxFactory
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union from typing import Any, Sequence
class PuckDiedMessage: class PuckDiedMessage:
@ -198,18 +198,18 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
actions=(('modify_part_collision', 'collide', actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False), True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score))) ('call', 'at_connect', self._handle_score)))
self._puck_spawn_pos: Optional[Sequence[float]] = None self._puck_spawn_pos: Sequence[float] | None = None
self._score_regions: Optional[list[ba.NodeActor]] = None self._score_regions: list[ba.NodeActor] | None = None
self._puck: Optional[Puck] = None self._puck: Puck | None = None
self._score_to_win = int(settings['Score to Win']) self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit']) 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: if self._score_to_win == 1:
return 'Score a goal.' return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win 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: if self._score_to_win == 1:
return 'score a goal' return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win return 'score ${ARG1} goals', self._score_to_win

View file

@ -2,7 +2,7 @@
# #
"""Defines a keep-away game type.""" """Defines a keep-away game type."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -17,7 +17,7 @@ from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage,
FlagPickedUpMessage) FlagPickedUpMessage)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class FlagState(Enum): class FlagState(Enum):
@ -106,20 +106,20 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
2: ba.getsound('announceTwo'), 2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne') 1: ba.getsound('announceOne')
} }
self._flag_spawn_pos: Optional[Sequence[float]] = None self._flag_spawn_pos: Sequence[float] | None = None
self._update_timer: Optional[ba.Timer] = None self._update_timer: ba.Timer | None = None
self._holding_players: list[Player] = [] self._holding_players: list[Player] = []
self._flag_state: Optional[FlagState] = None self._flag_state: FlagState | None = None
self._flag_light: Optional[ba.Node] = None self._flag_light: ba.Node | None = None
self._scoring_team: Optional[Team] = None self._scoring_team: Team | None = None
self._flag: Optional[Flag] = None self._flag: Flag | None = None
self._hold_time = int(settings['Hold Time']) self._hold_time = int(settings['Hold Time'])
self._time_limit = float(settings['Time Limit']) 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 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 return 'carry the flag for ${ARG1} seconds', self._hold_time
def create_team(self, sessionteam: ba.SessionTeam) -> Team: def create_team(self, sessionteam: ba.SessionTeam) -> Team:

View file

@ -2,7 +2,7 @@
# #
"""Defines the King of the Hill game.""" """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) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -18,7 +18,7 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Union from typing import Any, Sequence
class FlagState(Enum): class FlagState(Enum):
@ -108,11 +108,11 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
2: ba.getsound('announceTwo'), 2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne') 1: ba.getsound('announceOne')
} }
self._flag_pos: Optional[Sequence[float]] = None self._flag_pos: Sequence[float] | None = None
self._flag_state: Optional[FlagState] = None self._flag_state: FlagState | None = None
self._flag: Optional[Flag] = None self._flag: Flag | None = None
self._flag_light: Optional[ba.Node] = None self._flag_light: ba.Node | None = None
self._scoring_team: Optional[weakref.ref[Team]] = None self._scoring_team: weakref.ref[Team] | None = None
self._hold_time = int(settings['Hold Time']) self._hold_time = int(settings['Hold Time'])
self._time_limit = float(settings['Time Limit']) self._time_limit = float(settings['Time Limit'])
self._flag_region_material = ba.Material() self._flag_region_material = ba.Material()
@ -130,10 +130,10 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
# Base class overrides. # Base class overrides.
self.default_music = ba.MusicType.SCARY 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 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 return 'secure the flag for ${ARG1} seconds', self._hold_time
def create_team(self, sessionteam: ba.SessionTeam) -> Team: 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: def _handle_player_flag_region_collide(self, colliding: bool) -> None:
try: try:
spaz = ba.getcollision().sourcenode.getdelegate(PlayerSpaz, True) spaz = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
except ba.NotFoundError: except ba.NotFoundError:
return return

View file

@ -2,7 +2,7 @@
# #
"""Defines a bomb-dodging mini-game.""" """Defines a bomb-dodging mini-game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -15,7 +15,7 @@ from bastd.actor.bomb import Bomb
from bastd.actor.onscreentimer import OnScreenTimer from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional from typing import Any, Sequence
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
@ -23,7 +23,7 @@ class Player(ba.Player['Team']):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.death_time: Optional[float] = None self.death_time: float | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
@ -64,9 +64,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
super().__init__(settings) super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False) 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._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None self._timer: OnScreenTimer | None = None
# Some base class overrides: # Some base class overrides:
self.default_music = (ba.MusicType.EPIC 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 # Drop them somewhere within our bounds with velocity pointing
# toward the opposite side. # toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11, 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) 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)) ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1 delay += 0.1
self._set_meteor_timer() self._set_meteor_timer()

View file

@ -2,7 +2,7 @@
# #
"""Provides Ninja Fight mini-game.""" """Provides Ninja Fight mini-game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -15,7 +15,7 @@ from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage
from bastd.actor.onscreentimer import OnScreenTimer from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
@ -58,7 +58,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
super().__init__(settings) super().__init__(settings)
self._winsound = ba.getsound('score') self._winsound = ba.getsound('score')
self._won = False self._won = False
self._timer: Optional[OnScreenTimer] = None self._timer: OnScreenTimer | None = None
self._bots = SpazBotSet() self._bots = SpazBotSet()
self._preset = str(settings['preset']) self._preset = str(settings['preset'])

View file

@ -28,22 +28,22 @@ from bastd.actor.spazbot import (
TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded) TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Union, Sequence from typing import Any, Sequence
from bastd.actor.spazbot import SpazBot from bastd.actor.spazbot import SpazBot
@dataclass @dataclass
class Wave: class Wave:
"""A wave of enemies.""" """A wave of enemies."""
entries: list[Union[Spawn, Spacing, Delay, None]] entries: list[Spawn | Spacing | Delay | None]
base_angle: float = 0.0 base_angle: float = 0.0
@dataclass @dataclass
class Spawn: class Spawn:
"""A bot spawn event in a wave.""" """A bot spawn event in a wave."""
bottype: Union[type[SpazBot], str] bottype: type[SpazBot] | str
point: Optional[Point] = None point: Point | None = None
spacing: float = 5.0 spacing: float = 5.0
@ -123,7 +123,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
name = 'Onslaught' name = 'Onslaught'
description = 'Defeat all enemies.' description = 'Defeat all enemies.'
tips: list[Union[str, ba.GameTip]] = [ tips: list[str | ba.GameTip] = [
'Hold any button to run.' 'Hold any button to run.'
' (Trigger buttons work well if you have them)', ' (Trigger buttons work well if you have them)',
'Try tricking enemies into killing eachother or running off cliffs.', '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) self._powerup_spread = (4.6, 2.7)
else: else:
raise Exception('Unsupported map: ' + str(settings['map'])) raise Exception('Unsupported map: ' + str(settings['map']))
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Scoreboard | None = None
self._game_over = False self._game_over = False
self._wavenum = 0 self._wavenum = 0
self._can_end_wave = True self._can_end_wave = True
self._score = 0 self._score = 0
self._time_bonus = 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._dingsound = ba.getsound('dingSmall')
self._dingsoundhigh = ba.getsound('dingSmallHigh') self._dingsoundhigh = ba.getsound('dingSmallHigh')
self._have_tnt = False self._have_tnt = False
self._excluded_powerups: Optional[list[str]] = None self._excluded_powerups: list[str] | None = None
self._waves: list[Wave] = [] self._waves: list[Wave] = []
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: TNTSpawner | None = None
self._bots: Optional[SpazBotSet] = None self._bots: SpazBotSet | None = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: ba.Timer | None = None
self._time_bonus_timer: Optional[ba.Timer] = None self._time_bonus_timer: ba.Timer | None = None
self._time_bonus_text: Optional[ba.NodeActor] = None self._time_bonus_text: ba.NodeActor | None = None
self._flawless_bonus: Optional[int] = None self._flawless_bonus: int | None = None
self._wave_text: Optional[ba.NodeActor] = None self._wave_text: ba.NodeActor | None = None
self._wave_update_timer: Optional[ba.Timer] = None self._wave_update_timer: ba.Timer | None = None
self._throw_off_kills = 0 self._throw_off_kills = 0
self._land_mine_kills = 0 self._land_mine_kills = 0
self._tnt_kills = 0 self._tnt_kills = 0
@ -729,7 +729,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
"""End the game with the specified outcome.""" """End the game with the specified outcome."""
if outcome == 'defeat': if outcome == 'defeat':
self.fade_to_red() self.fade_to_red()
score: Optional[int] score: int | None
if self._wavenum >= 2: if self._wavenum >= 2:
score = self._score score = self._score
fail_message = None 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): if not any(player.is_alive() for player in self.teams[0].players):
self._spawn_info_text.node.text = '' self._spawn_info_text.node.text = ''
else: else:
text: Union[str, ba.Lstr] = '' text: str | ba.Lstr = ''
for player in self.players: for player in self.players:
if (not player.is_alive() if (not player.is_alive()
and (self._preset and (self._preset
@ -1070,8 +1070,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _add_entries_for_distribution_group( def _add_entries_for_distribution_group(
self, group: list[tuple[int, int]], self, group: list[tuple[int, int]],
bot_levels: list[list[type[SpazBot]]], bot_levels: list[list[type[SpazBot]]],
all_entries: list[Union[Spawn, Spacing, Delay, None]]) -> None: all_entries: list[Spawn | Spacing | Delay | None]) -> None:
entries: list[Union[Spawn, Spacing, Delay, None]] = [] entries: list[Spawn | Spacing | Delay | None] = []
for entry in group: for entry in group:
bot_level = bot_levels[entry[0] - 1] bot_level = bot_levels[entry[0] - 1]
bot_type = bot_level[random.randrange(len(bot_level))] 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, distribution = self._get_distribution(target_points, min_dudes,
max_dudes, group_count, max_dudes, group_count,
max_level) max_level)
all_entries: list[Union[Spawn, Spacing, Delay, None]] = [] all_entries: list[Spawn | Spacing | Delay | None] = []
for group in distribution: for group in distribution:
self._add_entries_for_distribution_group(group, bot_levels, self._add_entries_for_distribution_group(group, bot_levels,
all_entries) all_entries)
@ -1206,7 +1206,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
pts, importance = msg.spazbot.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:
self._handle_kill_achievements(msg) self._handle_kill_achievements(msg)
target: Optional[Sequence[float]] target: Sequence[float] | None
if msg.spazbot.node: if msg.spazbot.node:
target = msg.spazbot.node.position target = msg.spazbot.node.position
else: else:

View file

@ -2,7 +2,7 @@
# #
"""Defines Race mini-game.""" """Defines Race mini-game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -18,7 +18,7 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects from bastd.gameutils import SharedObjects
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union from typing import Any, Sequence
from bastd.actor.onscreentimer import OnScreenTimer from bastd.actor.onscreentimer import OnScreenTimer
@ -26,7 +26,7 @@ if TYPE_CHECKING:
class RaceMine: class RaceMine:
"""Holds info about a mine on the track.""" """Holds info about a mine on the track."""
point: Sequence[float] point: Sequence[float]
mine: Optional[Bomb] mine: Bomb | None
class RaceRegion(ba.Actor): class RaceRegion(ba.Actor):
@ -53,19 +53,19 @@ class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.distance_txt: Optional[ba.Node] = None self.distance_txt: ba.Node | None = None
self.last_region = 0 self.last_region = 0
self.lap = 0 self.lap = 0
self.distance = 0.0 self.distance = 0.0
self.finished = False self.finished = False
self.rank: Optional[int] = None self.rank: int | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
"""Our team type for this game.""" """Our team type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.time: Optional[float] = None self.time: float | None = None
self.lap = 0 self.lap = 0
self.finished = False self.finished = False
@ -141,22 +141,22 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._score_sound = ba.getsound('score') self._score_sound = ba.getsound('score')
self._swipsound = ba.getsound('swip') self._swipsound = ba.getsound('swip')
self._last_team_time: Optional[float] = None self._last_team_time: float | None = None
self._front_race_region: Optional[int] = None self._front_race_region: int | None = None
self._nub_tex = ba.gettexture('nub') self._nub_tex = ba.gettexture('nub')
self._beep_1_sound = ba.getsound('raceBeep1') self._beep_1_sound = ba.getsound('raceBeep1')
self._beep_2_sound = ba.getsound('raceBeep2') 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._regions: list[RaceRegion] = []
self._team_finish_pts: Optional[int] = None self._team_finish_pts: int | None = None
self._time_text: Optional[ba.Actor] = None self._time_text: ba.Actor | None = None
self._timer: Optional[OnScreenTimer] = None self._timer: OnScreenTimer | None = None
self._race_mines: Optional[list[RaceMine]] = None self._race_mines: list[RaceMine] | None = None
self._race_mine_timer: Optional[ba.Timer] = None self._race_mine_timer: ba.Timer | None = None
self._scoreboard_timer: Optional[ba.Timer] = None self._scoreboard_timer: ba.Timer | None = None
self._player_order_update_timer: Optional[ba.Timer] = None self._player_order_update_timer: ba.Timer | None = None
self._start_lights: Optional[list[ba.Node]] = None self._start_lights: list[ba.Node] | None = None
self._bomb_spawn_timer: Optional[ba.Timer] = None self._bomb_spawn_timer: ba.Timer | None = None
self._laps = int(settings['Laps']) self._laps = int(settings['Laps'])
self._entire_team_must_finish = bool( self._entire_team_must_finish = bool(
settings.get('Entire Team Must Finish', False)) settings.get('Entire Team Must Finish', False))
@ -170,7 +170,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self.default_music = (ba.MusicType.EPIC_RACE self.default_music = (ba.MusicType.EPIC_RACE
if self._epic_mode else ba.MusicType.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) if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish): and self._entire_team_must_finish):
t_str = ' Your entire team has to 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 ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str 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: if self._laps > 1:
return 'run ${ARG1} laps', self._laps return 'run ${ARG1} laps', self._laps
return 'run 1 lap' return 'run 1 lap'
@ -522,7 +522,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Calc all player distances. # Calc all player distances.
for player in self.players: for player in self.players:
pos: Optional[ba.Vec3] pos: ba.Vec3 | None
try: try:
pos = player.position pos = player.position
except ba.NotFoundError: except ba.NotFoundError:

View file

@ -26,7 +26,7 @@ from bastd.actor.spazbot import (
BomberBotPro, BrawlerBotPro) BomberBotPro, BrawlerBotPro)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union from typing import Any, Sequence
class Preset(Enum): class Preset(Enum):
@ -54,7 +54,7 @@ class Spawn:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
type: type[SpazBot] type: type[SpazBot]
path: int = 0 path: int = 0
point: Optional[Point] = None point: Point | None = None
@dataclass @dataclass
@ -66,15 +66,15 @@ class Spacing:
@dataclass @dataclass
class Wave: class Wave:
"""Defines a wave of enemies.""" """Defines a wave of enemies."""
entries: list[Union[Spawn, Spacing, None]] entries: list[Spawn | Spacing | None]
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None: def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None self.respawn_timer: ba.Timer | None = None
self.respawn_icon: Optional[RespawnIcon] = None self.respawn_icon: RespawnIcon | None = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
@ -144,31 +144,31 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._last_wave_end_time = ba.time() self._last_wave_end_time = ba.time()
self._player_has_picked_up_powerup = False self._player_has_picked_up_powerup = False
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Scoreboard | None = None
self._game_over = False self._game_over = False
self._wavenum = 0 self._wavenum = 0
self._can_end_wave = True self._can_end_wave = True
self._score = 0 self._score = 0
self._time_bonus = 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._dingsound = ba.getsound('dingSmall')
self._dingsoundhigh = ba.getsound('dingSmallHigh') self._dingsoundhigh = ba.getsound('dingSmallHigh')
self._exclude_powerups: Optional[list[str]] = None self._exclude_powerups: list[str] | None = None
self._have_tnt: Optional[bool] = None self._have_tnt: bool | None = None
self._waves: Optional[list[Wave]] = None self._waves: list[Wave] | None = None
self._bots = SpazBotSet() self._bots = SpazBotSet()
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: TNTSpawner | None = None
self._lives_bg: Optional[ba.NodeActor] = None self._lives_bg: ba.NodeActor | None = None
self._start_lives = 10 self._start_lives = 10
self._lives = self._start_lives self._lives = self._start_lives
self._lives_text: Optional[ba.NodeActor] = None self._lives_text: ba.NodeActor | None = None
self._flawless = True self._flawless = True
self._time_bonus_timer: Optional[ba.Timer] = None self._time_bonus_timer: ba.Timer | None = None
self._time_bonus_text: Optional[ba.NodeActor] = None self._time_bonus_text: ba.NodeActor | None = None
self._time_bonus_mult: Optional[float] = None self._time_bonus_mult: float | None = None
self._wave_text: Optional[ba.NodeActor] = None self._wave_text: ba.NodeActor | None = None
self._flawless_bonus: Optional[int] = None self._flawless_bonus: int | None = None
self._wave_update_timer: Optional[ba.Timer] = None self._wave_update_timer: ba.Timer | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
@ -556,7 +556,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
else: else:
delay = 0 delay = 0
score: Optional[int] score: int | None
if self._wavenum >= 2: if self._wavenum >= 2:
score = self._score score = self._score
fail_message = None fail_message = None
@ -723,13 +723,13 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
t_sec = 0.0 t_sec = 0.0
base_delay = 0.5 base_delay = 0.5
delay = 0.0 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}: if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
level = self._wavenum level = self._wavenum
target_points = (level + 1) * 8.0 target_points = (level + 1) * 8.0
group_count = random.randint(1, 3) group_count = random.randint(1, 3)
entries: list[Union[Spawn, Spacing, None]] = [] entries: list[Spawn | Spacing | None] = []
spaz_types: list[tuple[type[SpazBot], float]] = [] spaz_types: list[tuple[type[SpazBot], float]] = []
if level < 6: if level < 6:
spaz_types += [(BomberBot, 5.0)] spaz_types += [(BomberBot, 5.0)]
@ -1121,7 +1121,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
return None return None
pts, importance = msg.spazbot.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:
target: Optional[Sequence[float]] target: Sequence[float] | None
try: try:
assert msg.spazbot is not None assert msg.spazbot is not None
assert msg.spazbot.node assert msg.spazbot.node

View file

@ -2,7 +2,7 @@
# #
"""Implements Target Practice game.""" """Implements Target Practice game."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations
@ -17,7 +17,7 @@ from bastd.actor.bomb import Bomb
from bastd.actor.popuptext import PopupText from bastd.actor.popuptext import PopupText
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence from typing import Any, Sequence
from bastd.actor.bomb import Blast from bastd.actor.bomb import Blast
@ -62,8 +62,8 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
super().__init__(settings) super().__init__(settings)
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._targets: list[Target] = [] self._targets: list[Target] = []
self._update_timer: Optional[ba.Timer] = None self._update_timer: ba.Timer | None = None
self._countdown: Optional[OnScreenCountdown] = None self._countdown: OnScreenCountdown | None = None
self._target_count = int(settings['Target Count']) self._target_count = int(settings['Target Count'])
self._enable_impact_bombs = bool(settings['Enable Impact Bombs']) self._enable_impact_bombs = bool(settings['Enable Impact Bombs'])
self._enable_triple_bombs = bool(settings['Enable Triple Bombs']) self._enable_triple_bombs = bool(settings['Enable Triple Bombs'])

View file

@ -21,7 +21,7 @@ from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot,
ChargerBot, StickyBot, ExplodeyBot) ChargerBot, StickyBot, ExplodeyBot)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Sequence from typing import Any, Sequence
from bastd.actor.spazbot import SpazBot from bastd.actor.spazbot import SpazBot
@ -71,14 +71,14 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._powerup_spread = (7, 2) self._powerup_spread = (7, 2)
self._preset = str(settings.get('preset', 'default')) self._preset = str(settings.get('preset', 'default'))
self._excludepowerups: list[str] = [] self._excludepowerups: list[str] = []
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Scoreboard | None = None
self._score = 0 self._score = 0
self._bots = SpazBotSet() self._bots = SpazBotSet()
self._dingsound = ba.getsound('dingSmall') self._dingsound = ba.getsound('dingSmall')
self._dingsoundhigh = ba.getsound('dingSmallHigh') self._dingsoundhigh = ba.getsound('dingSmallHigh')
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: TNTSpawner | None = None
self._bot_update_interval: Optional[float] = None self._bot_update_interval: float | None = None
self._bot_update_timer: Optional[ba.Timer] = None self._bot_update_timer: ba.Timer | None = None
self._powerup_drop_timer = None self._powerup_drop_timer = None
# For each bot type: [spawnrate, increase, d_increase] # 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. # Now go back through and see where this value falls.
total = 0 total = 0
bottype: Optional[type[SpazBot]] = None bottype: type[SpazBot] | None = None
for spawntype, spawninfo in self._bot_spawn_types.items(): for spawntype, spawninfo in self._bot_spawn_types.items():
total += spawninfo.spawnrate total += spawninfo.spawnrate
if randval <= total: if randval <= total:
@ -262,7 +262,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
pts, importance = msg.spazbot.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
target: Optional[Sequence[float]] target: Sequence[float] | None
if msg.killerplayer: if msg.killerplayer:
assert msg.spazbot.node assert msg.spazbot.node
target = msg.spazbot.node.position target = msg.spazbot.node.position

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional pass
class SharedObjects: class SharedObjects:
@ -29,14 +29,14 @@ class SharedObjects:
if self._STORENAME in activity.customdata: if self._STORENAME in activity.customdata:
raise RuntimeError('Use SharedObjects.get() to fetch the' raise RuntimeError('Use SharedObjects.get() to fetch the'
' shared instance for this activity.') ' shared instance for this activity.')
self._object_material: Optional[ba.Material] = None self._object_material: ba.Material | None = None
self._player_material: Optional[ba.Material] = None self._player_material: ba.Material | None = None
self._pickup_material: Optional[ba.Material] = None self._pickup_material: ba.Material | None = None
self._footing_material: Optional[ba.Material] = None self._footing_material: ba.Material | None = None
self._attack_material: Optional[ba.Material] = None self._attack_material: ba.Material | None = None
self._death_material: Optional[ba.Material] = None self._death_material: ba.Material | None = None
self._region_material: Optional[ba.Material] = None self._region_material: ba.Material | None = None
self._railing_material: Optional[ba.Material] = None self._railing_material: ba.Material | None = None
@classmethod @classmethod
def get(cls) -> SharedObjects: def get(cls) -> SharedObjects:

View file

@ -2,7 +2,7 @@
# #
"""Defines a default keyboards.""" """Defines a default keyboards."""
# ba_meta require api 6 # ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system) # (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations from __future__ import annotations

View file

@ -13,7 +13,7 @@ import ba
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
# FIXME: Clean this up if I ever revisit it. # FIXME: Clean this up if I ever revisit it.
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
@ -32,8 +32,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
random.seed(123) random.seed(123)
self._logo_node: Optional[ba.Node] = None self._logo_node: ba.Node | None = None
self._custom_logo_tex_name: Optional[str] = None self._custom_logo_tex_name: str | None = None
self._word_actors: list[ba.Actor] = [] self._word_actors: list[ba.Actor] = []
app = ba.app app = ba.app
@ -246,7 +246,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
self._ts = 0.86 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_timer = ba.Timer(1.0, self._update, repeat=True)
self._update() self._update()
@ -263,12 +263,12 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
self._valid = True self._valid = True
self._message_duration = 10.0 self._message_duration = 10.0
self._message_spacing = 2.0 self._message_spacing = 2.0
self._text: Optional[ba.NodeActor] = None self._text: ba.NodeActor | None = None
self._activity = weakref.ref(activity) self._activity = weakref.ref(activity)
# If we're signed in, fetch news immediately. # If we're signed in, fetch news immediately.
# Otherwise wait until we are signed in. # 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) 1.0, ba.WeakCall(self._try_fetching_news), repeat=True)
self._try_fetching_news() 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 # (unless we're in VR mode in which case its best to
# leave things still). # leave things still).
if not ba.app.vr_mode: if not ba.app.vr_mode:
cmb: Optional[ba.Node] cmb: ba.Node | None
cmb2: Optional[ba.Node] cmb2: ba.Node | None
if not shadow: if not shadow:
cmb = ba.newnode('combine', cmb = ba.newnode('combine',
owner=word_obj.node, owner=word_obj.node,
@ -756,7 +756,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
delay + 0.2: scale 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): if _ba.get_v1_account_misc_read_val('easter', False):
return 'logoEaster' return 'logoEaster'
return None return None

View file

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
def _get_map_data(name: str) -> dict[str, 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. """A map completely defined by asset data.
""" """
_data: Optional[dict[str, Any]] = None _data: dict[str, Any] | None = None
@classmethod @classmethod
def _getdata(cls) -> dict[str, Any]: def _getdata(cls) -> dict[str, Any]:

View file

@ -23,10 +23,10 @@ import ba
from bastd.actor import spaz as basespaz from bastd.actor import spaz as basespaz
if TYPE_CHECKING: 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: if node:
setattr(node, attr, value) setattr(node, attr, value)
@ -46,9 +46,9 @@ class ButtonPress:
def run(self, a: TutorialActivity) -> None: def run(self, a: TutorialActivity) -> None:
s = a.current_spaz s = a.current_spaz
assert s is not None assert s is not None
img: Optional[ba.Node] img: ba.Node | None
release_call: Optional[Callable] release_call: Callable[[], None] | None
color: Optional[Sequence[float]] color: Sequence[float] | None
if self._button == 'punch': if self._button == 'punch':
call = s.on_punch_press call = s.on_punch_press
release_call = s.on_punch_release release_call = s.on_punch_release
@ -124,9 +124,9 @@ class ButtonRelease:
def run(self, a: TutorialActivity) -> None: def run(self, a: TutorialActivity) -> None:
s = a.current_spaz s = a.current_spaz
assert s is not None assert s is not None
call: Optional[Callable] call: Callable[[], None] | None
img: Optional[ba.Node] img: ba.Node | None
color: Optional[Sequence[float]] color: Sequence[float] | None
if self._button == 'punch': if self._button == 'punch':
call = s.on_punch_release call = s.on_punch_release
img = a.punch_image img = a.punch_image
@ -183,9 +183,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
if settings is None: if settings is None:
settings = {} settings = {}
super().__init__(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._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.cycle_times: list[int] = []
self.allow_pausing = True self.allow_pausing = True
self.allow_kick_idle_players = False self.allow_kick_idle_players = False
@ -200,31 +200,31 @@ class TutorialActivity(ba.Activity[Player, Team]):
self._have_skipped = False self._have_skipped = False
self.stick_image_position_x = self.stick_image_position_y = 0.0 self.stick_image_position_x = self.stick_image_position_y = 0.0
self.spawn_sound = ba.getsound('spawn') self.spawn_sound = ba.getsound('spawn')
self.map: Optional[ba.Map] = None self.map: ba.Map | None = None
self.text: Optional[ba.Node] = None self.text: ba.Node | None = None
self._skip_text: Optional[ba.Node] = None self._skip_text: ba.Node | None = None
self._skip_count_text: Optional[ba.Node] = None self._skip_count_text: ba.Node | None = None
self._scale: Optional[float] = None self._scale: float | None = None
self._stick_base_position: tuple[float, float] = (0.0, 0.0) self._stick_base_position: tuple[float, float] = (0.0, 0.0)
self._stick_nub_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_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._stick_nub_image_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0)
self._time: int = -1 self._time: int = -1
self.punch_image_color = (1.0, 1.0, 1.0) self.punch_image_color = (1.0, 1.0, 1.0)
self.punch_image: Optional[ba.Node] = None self.punch_image: ba.Node | None = None
self.bomb_image: Optional[ba.Node] = None self.bomb_image: ba.Node | None = None
self.jump_image: Optional[ba.Node] = None self.jump_image: ba.Node | None = None
self.pickup_image: Optional[ba.Node] = None self.pickup_image: ba.Node | None = None
self._stick_base_image: Optional[ba.Node] = None self._stick_base_image: ba.Node | None = None
self._stick_nub_image: Optional[ba.Node] = None self._stick_nub_image: ba.Node | None = None
self.bomb_image_color = (1.0, 1.0, 1.0) self.bomb_image_color = (1.0, 1.0, 1.0)
self.pickup_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.control_ui_nodes: list[ba.Node] = []
self.spazzes: dict[int, basespaz.Spaz] = {} self.spazzes: dict[int, basespaz.Spaz] = {}
self.jump_image_color = (1.0, 1.0, 1.0) self.jump_image_color = (1.0, 1.0, 1.0)
self._entries: list[Any] = [] self._entries: list[Any] = []
self._read_entries_timer: Optional[ba.Timer] = None self._read_entries_timer: ba.Timer | None = None
self._entry_timer: Optional[ba.Timer] = None self._entry_timer: ba.Timer | None = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
@ -493,7 +493,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
color: Sequence[float] = (1.0, 1.0, 1.0), color: Sequence[float] = (1.0, 1.0, 1.0),
make_current: bool = False, make_current: bool = False,
relative_to: int = None, relative_to: int = None,
name: Union[str, ba.Lstr] = '', name: str | ba.Lstr = '',
flash: bool = True, flash: bool = True,
angle: float = 0.0): angle: float = 0.0):
self._num = num self._num = num
@ -752,7 +752,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
class Text: class Text:
def __init__(self, text: Union[str, ba.Lstr]): def __init__(self, text: str | ba.Lstr):
self.text = text self.text = text
def run(self, a: TutorialActivity) -> None: def run(self, a: TutorialActivity) -> None:

View file

@ -12,14 +12,14 @@ import _ba
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class AccountLinkWindow(ba.Window): class AccountLinkWindow(ba.Window):
"""Window for linking accounts.""" """Window for linking accounts."""
def __init__(self, origin_widget: ba.Widget = None): 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: if origin_widget is not None:
self._transition_out = 'out_scale' self._transition_out = 'out_scale'
scale_origin = origin_widget.get_screen_space_center() scale_origin = origin_widget.get_screen_space_center()

View file

@ -12,7 +12,7 @@ import _ba
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Union pass
class AccountSettingsWindow(ba.Window): class AccountSettingsWindow(ba.Window):
@ -25,15 +25,15 @@ class AccountSettingsWindow(ba.Window):
close_once_signed_in: bool = False): close_once_signed_in: bool = False):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
self._sign_in_game_circle_button: Optional[ba.Widget] = None self._sign_in_game_circle_button: ba.Widget | None = None
self._sign_in_v2_button: Optional[ba.Widget] = None self._sign_in_v2_button: ba.Widget | None = None
self._sign_in_device_button: Optional[ba.Widget] = None self._sign_in_device_button: ba.Widget | None = None
self._close_once_signed_in = close_once_signed_in self._close_once_signed_in = close_once_signed_in
ba.set_analytics_screen('Account Window') ba.set_analytics_screen('Account Window')
# If they provided an origin-widget, scale up from that. # 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: if origin_widget is not None:
self._transition_out = 'out_scale' self._transition_out = 'out_scale'
scale_origin = origin_widget.get_screen_space_center() scale_origin = origin_widget.get_screen_space_center()
@ -56,7 +56,7 @@ class AccountSettingsWindow(ba.Window):
repeat=True) repeat=True)
# Currently we can only reset achievements on game-center. # Currently we can only reset achievements on game-center.
account_type: Optional[str] account_type: str | None
if self._signed_in: if self._signed_in:
account_type = _ba.get_v1_account_type() account_type = _ba.get_v1_account_type()
else: else:
@ -145,7 +145,7 @@ class AccountSettingsWindow(ba.Window):
claims_left_right=True, claims_left_right=True,
claims_tab=True, claims_tab=True,
selection_loops_to_parent=True) selection_loops_to_parent=True)
self._subcontainer: Optional[ba.Widget] = None self._subcontainer: ba.Widget | None = None
self._refresh() self._refresh()
self._restore_state() self._restore_state()
@ -344,7 +344,7 @@ class AccountSettingsWindow(ba.Window):
v_align='center') v_align='center')
v -= local_signed_in_as_space * 0.4 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: if show_signed_in_as:
v -= signed_in_as_space * 0.2 v -= signed_in_as_space * 0.2
txt = ba.Lstr( txt = ba.Lstr(
@ -383,7 +383,7 @@ class AccountSettingsWindow(ba.Window):
if show_sign_in_benefits: if show_sign_in_benefits:
v -= sign_in_benefits_space v -= sign_in_benefits_space
app = ba.app app = ba.app
extra: Optional[Union[str, ba.Lstr]] extra: str | ba.Lstr | None
if (app.platform in ['mac', 'ios'] if (app.platform in ['mac', 'ios']
and app.subplatform == 'appstore'): and app.subplatform == 'appstore'):
extra = ba.Lstr( extra = ba.Lstr(
@ -616,7 +616,7 @@ class AccountSettingsWindow(ba.Window):
else: else:
self.game_service_button = None self.game_service_button = None
self._achievements_text: Optional[ba.Widget] self._achievements_text: ba.Widget | None
if show_achievements_text: if show_achievements_text:
v -= achievements_text_space * 0.5 v -= achievements_text_space * 0.5
self._achievements_text = ba.textwidget( self._achievements_text = ba.textwidget(
@ -632,7 +632,7 @@ class AccountSettingsWindow(ba.Window):
else: else:
self._achievements_text = None self._achievements_text = None
self._achievements_button: Optional[ba.Widget] self._achievements_button: ba.Widget | None
if show_achievements_button: if show_achievements_button:
button_width = 300 button_width = 300
v -= achievements_button_space * 0.85 v -= achievements_button_space * 0.85
@ -661,7 +661,7 @@ class AccountSettingsWindow(ba.Window):
if show_achievements_text or show_achievements_button: if show_achievements_text or show_achievements_button:
self._refresh_achievements() self._refresh_achievements()
self._leaderboards_button: Optional[ba.Widget] self._leaderboards_button: ba.Widget | None
if show_leaderboards_button: if show_leaderboards_button:
button_width = 300 button_width = 300
v -= leaderboards_button_space * 0.85 v -= leaderboards_button_space * 0.85
@ -686,7 +686,7 @@ class AccountSettingsWindow(ba.Window):
else: else:
self._leaderboards_button = None self._leaderboards_button = None
self._campaign_progress_text: Optional[ba.Widget] self._campaign_progress_text: ba.Widget | None
if show_campaign_progress: if show_campaign_progress:
v -= campaign_progress_space * 0.5 v -= campaign_progress_space * 0.5
self._campaign_progress_text = ba.textwidget( self._campaign_progress_text = ba.textwidget(
@ -703,7 +703,7 @@ class AccountSettingsWindow(ba.Window):
else: else:
self._campaign_progress_text = None self._campaign_progress_text = None
self._tickets_text: Optional[ba.Widget] self._tickets_text: ba.Widget | None
if show_tickets: if show_tickets:
v -= tickets_space * 0.5 v -= tickets_space * 0.5
self._tickets_text = ba.textwidget(parent=self._subcontainer, self._tickets_text = ba.textwidget(parent=self._subcontainer,
@ -753,7 +753,7 @@ class AccountSettingsWindow(ba.Window):
right_widget=_ba.get_special_widget('party_button')) right_widget=_ba.get_special_widget('party_button'))
ba.widget(edit=btn, left_widget=bbtn) 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: if show_linked_accounts_text:
v -= linked_accounts_text_space * 0.8 v -= linked_accounts_text_space * 0.8
self._linked_accounts_text = ba.textwidget( self._linked_accounts_text = ba.textwidget(
@ -808,7 +808,7 @@ class AccountSettingsWindow(ba.Window):
right_widget=_ba.get_special_widget('party_button')) right_widget=_ba.get_special_widget('party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 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: if show_unlink_accounts_button:
v -= unlink_accounts_button_space v -= unlink_accounts_button_space
self._unlink_accounts_button = btn = ba.buttonwidget( self._unlink_accounts_button = btn = ba.buttonwidget(
@ -957,7 +957,7 @@ class AccountSettingsWindow(ba.Window):
from ba.internal import getcampaign from ba.internal import getcampaign
if self._campaign_progress_text is None: if self._campaign_progress_text is None:
return return
p_str: Union[str, ba.Lstr] p_str: str | ba.Lstr
try: try:
campaign = getcampaign('Default') campaign = getcampaign('Default')
levels = campaign.levels levels = campaign.levels

View file

@ -11,14 +11,14 @@ import _ba
import ba import ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional from typing import Any
class AccountUnlinkWindow(ba.Window): class AccountUnlinkWindow(ba.Window):
"""A window to kick off account unlinks.""" """A window to kick off account unlinks."""
def __init__(self, origin_widget: ba.Widget = None): 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: if origin_widget is not None:
self._transition_out = 'out_scale' self._transition_out = 'out_scale'
scale_origin = origin_widget.get_screen_space_center() scale_origin = origin_widget.get_screen_space_center()

Some files were not shown because too many files have changed in this diff Show more