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._nodeactor import NodeActor
from ba._app import App
from ba._cloud import CloudSubsystem
from ba._coopgame import CoopGameActivity
from ba._coopsession import CoopSession
from ba._dependency import (Dependency, DependencyComponent, DependencySet,
@ -89,7 +90,7 @@ __all__ = [
'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported',
'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget',
'containerwidget', 'Context', 'ContextCall', 'ContextError',
'CoopGameActivity', 'CoopSession', 'Data', 'DeathType',
'CloudSubsystem', 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType',
'DelegateNotFoundError', 'Dependency', 'DependencyComponent',
'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage',
'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam',

View file

@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
import _ba
if TYPE_CHECKING:
from typing import Any, Optional
from typing import Any
class AccountV1Subsystem:
@ -23,12 +23,12 @@ class AccountV1Subsystem:
"""
def __init__(self) -> None:
self.account_tournament_list: Optional[tuple[int, list[str]]] = None
self.account_tournament_list: tuple[int, list[str]] | None = None
# FIXME: should abstract/structure these.
self.tournament_info: dict = {}
self.league_rank_cache: dict = {}
self.last_post_purchase_message_time: Optional[float] = None
self.last_post_purchase_message_time: float | None = None
# If we try to run promo-codes due to launch-args/etc we might
# not be signed in yet; go ahead and queue them up in that case.
@ -73,7 +73,7 @@ class AccountV1Subsystem:
return self.league_rank_cache.get('info', None)
def get_league_rank_points(self,
data: Optional[dict[str, Any]],
data: dict[str, Any] | None,
subset: str = None) -> int:
"""(internal)"""
if data is None:

View file

@ -6,8 +6,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
if TYPE_CHECKING:
from typing import Optional
pass
class AccountV2Subsystem:
@ -18,10 +20,20 @@ class AccountV2Subsystem:
Access the single shared instance of this class at 'ba.app.accounts_v2'.
"""
def __init__(self) -> None:
# Whether or not everything related to an initial login
# (or lack thereof) has completed. This includes things like
# workspace syncing. Completion of this is what flips the app
# into 'running' state.
self._initial_login_completed = False
self._kicked_off_workspace_load = False
def on_app_launch(self) -> None:
"""Should be called at standard on_app_launch time."""
def set_primary_credentials(self, credentials: Optional[str]) -> None:
def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account."""
raise RuntimeError('This should be overridden.')
@ -35,17 +47,74 @@ class AccountV2Subsystem:
raise RuntimeError('This should be overridden.')
@property
def primary(self) -> Optional[AccountV2Handle]:
def primary(self) -> AccountV2Handle | None:
"""The primary account for the app, or None if not logged in."""
return None
def get_primary(self) -> Optional[AccountV2Handle]:
def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass."""
return None
def on_primary_account_changed(self,
account: AccountV2Handle | None) -> None:
"""Callback run after the primary account changes.
Will be called with None on log-outs or when new credentials
are set but have not yet been verified.
"""
# Currently don't do anything special on sign-outs.
if account is None:
return
# If this new account has a workspace, update it and ask to be
# informed when that process completes.
if account.workspaceid is not None:
assert account.workspacename is not None
if (not self._initial_login_completed
and not self._kicked_off_workspace_load):
self._kicked_off_workspace_load = True
_ba.app.workspaces.set_active_workspace(
workspaceid=account.workspaceid,
workspacename=account.workspacename,
on_completed=self._on_set_active_workspace_completed)
else:
# Don't activate workspaces if we've already told the game
# that initial-log-in is done or if we've already kicked
# off a workspace load.
_ba.screenmessage(
f'\'{account.workspacename}\''
f' will be activated at next app launch.',
color=(1, 1, 0))
_ba.playsound(_ba.getsound('error'))
return
# Ok; no workspace to worry about; carry on.
if not self._initial_login_completed:
self._initial_login_completed = True
_ba.app.on_initial_login_completed()
def on_no_initial_primary_account(self) -> None:
"""Callback run if the app has no primary account after launch.
Either this callback or on_primary_account_changed will be called
within a few seconds of app launch; the app can move forward
with the startup sequence at that point.
"""
if not self._initial_login_completed:
self._initial_login_completed = True
_ba.app.on_initial_login_completed()
def _on_set_active_workspace_completed(self) -> None:
if not self._initial_login_completed:
self._initial_login_completed = True
_ba.app.on_initial_login_completed()
class AccountV2Handle:
"""Handle for interacting with a v2 account."""
def __init__(self) -> None:
self.tag = '?'
self.workspacename: str | None = None
self.workspaceid: str | None = None

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ from ba._messages import DieMessage, DeathType, OutOfBoundsMessage, UNHANDLED
from ba._error import print_exception, ActivityNotFoundError
if TYPE_CHECKING:
from typing import Any, Optional, Literal
from typing import Any, Literal
import ba
TA = TypeVar('TA', bound='Actor')
@ -187,10 +187,10 @@ class Actor:
...
@overload
def getactivity(self, doraise: Literal[False]) -> Optional[ba.Activity]:
def getactivity(self, doraise: Literal[False]) -> ba.Activity | None:
...
def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]:
def getactivity(self, doraise: bool = True) -> ba.Activity | None:
"""Return the ba.Activity this Actor is associated with.
If the Activity no longer exists, raises a ba.ActivityNotFoundError

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ from ba._generated.enums import TimeType
if TYPE_CHECKING:
from types import FrameType
from typing import Any, Optional
from typing import Any
from efro.call import Call as Call # 'as Call' so we re-export.
@ -37,15 +37,15 @@ ExistableType = TypeVar('ExistableType', bound=Existable)
T = TypeVar('T')
def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]:
def existing(obj: ExistableType | None) -> ExistableType | None:
"""Convert invalid references to None for any ba.Existable object.
Category: **Gameplay Functions**
To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead
objects (Optional[FooType]) into functions expecting only live ones
That way the type checker can properly flag attempts to pass possibly-dead
objects (FooType | None) into functions expecting only live ones
(FooType), etc. This call can be used on any 'existable' object
(one with an exists() method) and will convert it to a None value
if it does not exist.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,18 +3,34 @@
"""Functionality related to the bacloud tool."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional
from efro.dataclassio import ioprepped
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated
from efro.dataclassio import ioprepped, IOAttrs
if TYPE_CHECKING:
pass
# Version is sent to the master-server with all commands. Can be incremented
# if we need to change behavior server-side to go along with client changes.
BACLOUD_VERSION = 6
@ioprepped
@dataclass
class Response:
class RequestData:
"""Request sent to bacloud server."""
command: Annotated[str, IOAttrs('c')]
token: Annotated[str | None, IOAttrs('t')]
payload: Annotated[dict, IOAttrs('p')]
tzoffset: Annotated[float, IOAttrs('z')]
isatty: Annotated[bool, IOAttrs('y')]
@ioprepped
@dataclass
class ResponseData:
# noinspection PyUnresolvedReferences
"""Response sent from the bacloud server to the client.
@ -35,10 +51,10 @@ class Response:
uploads_inline: If present, a list of pathnames that should be base64
gzipped and uploaded to an 'uploads_inline' dict in end_command args.
This should be limited to relatively small files.
deletes: If present, file paths that should be deleted on the client.
downloads_inline: If present, pathnames mapped to base64 gzipped data to
be written to the client. This should only be used for relatively
small files as they are all included inline as part of the response.
deletes: If present, file paths that should be deleted on the client.
dir_prune_empty: If present, all empty dirs under this one should be
removed.
open_url: If present, url to display to the user.
@ -52,20 +68,29 @@ class Response:
end_command: If present, this command is run with these args at the end
of response processing.
"""
message: Optional[str] = None
message_end: str = '\n'
error: Optional[str] = None
delay_seconds: float = 0.0
login: Optional[str] = None
logout: bool = False
dir_manifest: Optional[str] = None
uploads: Optional[tuple[list[str], str, dict]] = None
uploads_inline: Optional[list[str]] = None
downloads_inline: Optional[dict[str, str]] = None
deletes: Optional[list[str]] = None
dir_prune_empty: Optional[str] = None
open_url: Optional[str] = None
input_prompt: Optional[tuple[str, bool]] = None
end_message: Optional[str] = None
end_message_end: str = '\n'
end_command: Optional[tuple[str, dict]] = None
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
dir_manifest: Annotated[str | None,
IOAttrs('man', store_default=False)] = None
uploads: Annotated[tuple[list[str], str, dict] | None,
IOAttrs('u', store_default=False)] = None
uploads_inline: Annotated[list[str] | None,
IOAttrs('uinl', store_default=False)] = None
deletes: Annotated[list[str] | None,
IOAttrs('dlt', store_default=False)] = None
downloads_inline: Annotated[dict[str, str] | None,
IOAttrs('dinl', store_default=False)] = None
dir_prune_empty: Annotated[str | None,
IOAttrs('dpe', store_default=False)] = None
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
input_prompt: Annotated[tuple[str, bool] | None,
IOAttrs('inp', store_default=False)] = None
end_message: Annotated[str | None,
IOAttrs('em', store_default=False)] = None
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
end_command: Annotated[tuple[str, dict] | None,
IOAttrs('ec', store_default=False)] = None

View file

@ -3,12 +3,13 @@
"""Functionality related to cloud functionality."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated, Optional
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated
from enum import Enum
from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs
from bacommon.transfer import DirectoryManifest
if TYPE_CHECKING:
pass
@ -65,7 +66,7 @@ class LoginProxyStateQueryResponse(Response):
state: Annotated[State, IOAttrs('s')]
# On success, these will be filled out.
credentials: Annotated[Optional[str], IOAttrs('tk')]
credentials: Annotated[str | None, IOAttrs('tk')]
@ioprepped
@ -77,27 +78,57 @@ class LoginProxyCompleteMessage(Message):
@ioprepped
@dataclass
class AccountSessionReleaseMessage(Message):
"""We're done using this particular session."""
token: Annotated[str, IOAttrs('tk')]
@ioprepped
@dataclass
class CredentialsCheckMessage(Message):
"""Are our current credentials valid?"""
class TestMessage(Message):
"""Can I get some of that workspace action?"""
testfoo: Annotated[int, IOAttrs('f')]
@classmethod
def get_response_types(cls) -> list[type[Response]]:
return [CredentialsCheckResponse]
return [TestResponse]
@ioprepped
@dataclass
class CredentialsCheckResponse(Response):
"""Info returned when checking credentials."""
class TestResponse(Response):
"""Here's that workspace you asked for, boss."""
verified: Annotated[bool, IOAttrs('v')]
testfoo: Annotated[int, IOAttrs('f')]
# Current account tag (good time to check if it has changed).
tag: Annotated[str, IOAttrs('t')]
@ioprepped
@dataclass
class WorkspaceFetchState:
"""Common state data for a workspace fetch."""
manifest: Annotated[DirectoryManifest, IOAttrs('m')]
iteration: Annotated[int, IOAttrs('i')] = 0
total_deletes: Annotated[int, IOAttrs('tdels')] = 0
total_downloads: Annotated[int, IOAttrs('tdlds')] = 0
total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None
@ioprepped
@dataclass
class WorkspaceFetchMessage(Message):
"""Can I get some of that workspace action?"""
workspaceid: Annotated[str, IOAttrs('w')]
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
@classmethod
def get_response_types(cls) -> list[type[Response]]:
return [WorkspaceFetchResponse]
@ioprepped
@dataclass
class WorkspaceFetchResponse(Response):
"""Here's that workspace you asked for, boss."""
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
deletes: Annotated[list[str],
IOAttrs('dlt', store_default=False)] = field(
default_factory=list)
downloads_inline: Annotated[dict[str, bytes],
IOAttrs('dinl', store_default=False)] = field(
default_factory=dict)
done: Annotated[bool, IOAttrs('d')] = False

View file

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

View file

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

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."""
# ba_meta require api 6
# ba_meta require api 7

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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