baclassic package¶
- class baclassic.Achievement(name: str, icon: TextureHandle, icon_color: tuple[float, float, float], level_name: str, *, award: int, hard_mode_only: bool = False)[source]¶
Bases:
objectRepresents attributes and state for an individual achievement.
- announce_completion(sound: bool = True) None[source]¶
Kick off an announcement for this achievement’s completion.
- create_display(x: float, y: float, delay: float, *, outdelay: float | None = None, color: Sequence[float] | None = None, style: str = 'post_game') list[Actor][source]¶
Create a display for the Achievement.
Shows the Achievement icon, name, and description.
- property description_complete: Lstr¶
Get a babase.Lstr for the Achievement’s description when complete.
- property description_complete_langstr: LangStr¶
The brief description for this achievement once earned.
This is the
LangStrflavor ofdescription_complete; seedisplay_name_langstr.
- property description_full_complete: Lstr¶
Get a babase.Lstr for the Achievement’s full desc. when completed.
- property description_full_complete_langstr: LangStr¶
The full description for this achievement once earned.
This is the
LangStrflavor ofdescription_full_complete; seedisplay_name_langstr.
- property description_full_langstr: LangStr¶
The full description for this achievement.
This is the
LangStrflavor ofdescription_full; seedisplay_name_langstr.
- property description_langstr: LangStr¶
The brief description for this achievement.
This is the
LangStrflavor ofdescription; seedisplay_name_langstr.
- property display_name_langstr: LangStr¶
The localized name for this achievement.
This is the
LangStrflavor ofdisplay_name. It exists only for the transition; once api 9 support ends,display_namereturns this and this property goes away with the removal of api 10.
- get_icon_color(complete: bool) Sequence[float][source]¶
Return the color tint for this Achievement’s icon.
- get_icon_texture(complete: bool) Texture[source]¶
Return the icon texture to display for this achievement
- get_icon_ui_texture(complete: bool) bauiv1.Texture[source]¶
Return the icon texture to display for this achievement
- class baclassic.AchievementSubsystem[source]¶
Bases:
objectSubsystem for achievement handling.
Access the single shared instance of this class at ‘ba.app.ach’.
- achievements_for_coop_level(level_name: str) list[Achievement][source]¶
Given a level name, return achievements available for it.
- award_local_achievement(achname: str) None[source]¶
For non-game-based achievements such as controller-connection.
- display_achievement_banner(achname: str) None[source]¶
Display a completion banner for an achievement.
(internal)
Used for server-driven achievements.
- get_achievement(name: str) Achievement[source]¶
Return an Achievement by name.
- baclassic.CHEST_APPEARANCE_DISPLAY_INFOS: dict[ClassicChestAppearance, ChestAppearanceDisplayInfo] = {ClassicChestAppearance.L2: ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(0.8, 1.0, 0.93), tint=(0.65, 1.0, 0.8), tint2=(0.65, 1.0, 0.8), tint3=(1.0, 1.0, 1.0)), ClassicChestAppearance.L3: ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(0.75, 0.9, 1.3), tint=(0.7, 1, 1.9), tint2=(0.7, 1, 1.9), tint3=(1.0, 1.0, 1.0)), ClassicChestAppearance.L4: ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(0.7, 1.0, 1.4), tint=(1.4, 1.6, 2.0), tint2=(1.4, 1.6, 2.0), tint3=(1.0, 1.0, 1.0)), ClassicChestAppearance.L5: ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(0.75, 0.5, 2.4), tint=(1.0, 0.8, 0.0), tint2=(1.0, 0.8, 0.0), tint3=(1.0, 1.0, 1.0)), ClassicChestAppearance.L6: ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(1.1, 0.8, 0.0), tint=(2, 2, 2), tint2=(2, 2, 2), tint3=(1.0, 1.0, 1.0))}¶
Components for the classic BombSquad experience.
This package/feature-set contains functionality related to the classic BombSquad experience. Note that much legacy BombSquad code is still a bit tangled and thus this feature-set is largely inseperable from
bascenev1andbauiv1. Future feature-sets will be designed in a more modular way.
- baclassic.CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT: baclassic._chest.ChestAppearanceDisplayInfo = ChestAppearanceDisplayInfo(texclosed=TextureHandle(_apvernum=807, _name='textures/chest_icon'), texclosedtint=TextureHandle(_apvernum=807, _name='textures/chest_icon_tint'), texopen=TextureHandle(_apvernum=807, _name='textures/chest_open_icon'), texopentint=TextureHandle(_apvernum=807, _name='textures/chest_open_icon_tint'), color=(1, 1, 1), tint=(1, 1, 1), tint2=(1, 1, 1), tint3=(1.0, 1.0, 1.0))¶
Fallback
ChestAppearanceDisplayInfoused when a chest’s declared appearance has no entry inCHEST_APPEARANCE_DISPLAY_INFOS.
- class baclassic.ChestAppearanceDisplayInfo(texclosed: bascenev1.TextureHandle, texclosedtint: bascenev1.TextureHandle, texopen: bascenev1.TextureHandle, texopentint: bascenev1.TextureHandle, color: tuple[float, float, float], tint: tuple[float, float, float], tint2: tuple[float, float, float], tint3: tuple[float, float, float] = (1.0, 1.0, 1.0))[source]¶
Bases:
objectInfo about how to locally display chest appearances.
- texclosed: bascenev1.TextureHandle¶
- texclosedtint: bascenev1.TextureHandle¶
- texopen: bascenev1.TextureHandle¶
- texopentint: bascenev1.TextureHandle¶
- class baclassic.ClassicAppMode[source]¶
Bases:
AppModeAppMode for the classic BombSquad experience.
- classmethod can_handle_intent(intent: AppIntent) bool[source]¶
Override this to define indent handling for an app-mode.
- get_dev_console_ui_debug_draw_toggles() list[DevConsoleToggleDef][source]¶
Define debug-drawing toggles to show up in the UI dev console.
These join the tab’s own in its ‘Debug Drawing’ row (in the console’s expanded view). For ui that the tab itself can’t know about, such as a ui package’s layout guides.
- get_dev_console_ui_tab_buttons() list[DevConsoleButtonDef][source]¶
Define buttons to show up in the UI dev console.
This can be useful for exposing UI code examples or debugging functionality.
- new_app_mode_config() AppModeConfig[source]¶
Create a fresh config describing how this mode should run.
Called by the framework each time this mode is about to become the active one. Override to return your mode’s own
AppModeConfigsubclass, built with the mode’s defaults; the framework then offers it to plugins and passes the final result toon_activate().A fresh instance is built per activation, so nothing can persist between modes (or activations) by construction.
- on_activate(config: AppModeConfig) None[source]¶
Called when the mode is becoming the active one for the app.
configis the object created bynew_app_mode_config()for this activation, possibly amended by plugins. Modes that define their own config type should begin withassert isinstance(config, TheirConfig)to recover the concrete type, then set themselves (and any app-subsystems they use) up from it.
- on_app_active_changed() None[source]¶
Called when the app’s active state changes while in this app-mode.
This corresponds to the app’s
activeattr. App-active state becomes false when the app is hidden, minimized, backgrounded, etc. The app-mode may want to take action such as pausing a running game or saving state when this occurs.On platforms such as mobile where apps get suspended and later silently terminated by the OS, this is likely to be the last reliable place to save state/etc.
To best cover both mobile and desktop style platforms, actions such as saving state should generally happen in response to both
on_deactivate()andon_app_active_changed()(when active is False).
- on_control_permission_request(request: ControlPermissionRequest, on_result: Callable[[ControlPermission], None]) None[source]¶
Ask the user whether something may control the app.
‘Control’ is deliberately broad: running commands, reading this app’s log, and whatever that grows into. Answer via
on_result, which may be called later (from a dialog) or immediately (from policy).The default answers
CANNOT_ASK, which is the honest answer for a mode with no way to prompt – the request is then held and re-put once a mode that can ask activates. An app-mode with a UI should override this; one running without a user present (a dedicated server) should answerALLOW, since nobody is there to ask and the operator already owns the account.
- on_deactivate() None[source]¶
Called when the mode stops being the active one for the app.
On platforms where the app is explicitly exited (such as desktop PC) this will also be called at app shutdown.
To best cover both mobile and desktop style platforms, actions such as saving state should generally happen in response to both
on_deactivate()andon_app_active_changed()(when active is False).
- on_engine_did_reset() None[source]¶
Called just after classic resets the engine.
This happens at various times such as session switches.
- class baclassic.ClassicAppModeConfig[source]¶
Bases:
AppModeConfigDescribes how a
ClassicAppModeshould run.Built fresh (with classic’s defaults) for each activation via
ClassicAppMode.new_app_mode_config(); amend attrs here – from a mode subclass’s override of that method or a plugin’son_app_mode_config()– to alter how the mode sets itself up.- base_assets¶
The classic-flavored art baked into base’s own draw paths (debris, smoke, VR hands, reflections; see
babase.BaseAssetSet). Starts at classic’s real art; reassign any slot to restyle.
- scene_assets¶
The art the scene_v1 node layer draws itself with (see
bascenev1.SceneV1AssetSet). Every slot starts at scene_v1’s own art; reassign any to restyle.
- ui_assets¶
The art the ui layer will be drawn with (see
bauiv1.UIAssetSet). Starts out as classic’s standard look; reassign any slot to restyle.
- class baclassic.ClassicAppSubsystem[source]¶
Bases:
AppSubsystemSubsystem for classic bombsquad functionality in the app.
The single shared instance of this app can be accessed at babase.app.classic. Note that it is possible for babase.app.classic to be None if the classic package is not present, and futureproof code should handle that case gracefully.
- class MusicPlayMode(*values)¶
Bases:
EnumInfluences behavior when playing music.
- REGULAR = 'regular'¶
- TEST = 'test'¶
- class V2AuthData(account_id: str, account_tag: str, player_profiles: dict, classic_purchases: list[str] | None, cloud_characters: list[str] | None, account_name_depiction: str | None, expire_time: float)[source]¶
Bases:
objectAuthenticated data we store for accepted clients.
- account_name_depiction: str | None¶
The account’s name depiction json as the master server composed it, or
Nonewhen it didn’t send one.
- classic_purchases: list[str] | None¶
List of classic-inventory purchase legacy-ids owned by the connecting account, or
Nonewhen the master server didn’t provide one (unknown — fall back to the legacy hacky character-list path).
- cloud_characters: list[str] | None¶
The account’s cloud profiles, each as one cloud-composed character json (name + icon + spaz), or
Nonewhen the master server didn’t provide them (older master or unknown) — in which case the lobby falls back to the legacyplayer_profileswith the legacy-profile warning.
- class V2AuthRequest(account_id: str, account_tag: str, account_create_time: datetime.datetime, account_total_active_days: int, app_instance_signature: str, address_signature: str, device_signature: str, classic_purchases: list[str] | None = None)[source]¶
Bases:
objectWhat is passed in to V2 auth handler.
- account_create_time: datetime.datetime¶
When the connecting account was created.
- address_signature: str¶
An abstract value generated from the connecting client’s ip address. Can be used to identify repeat connection attempts/etc.
- app_instance_signature: str¶
An abstract value generated from the connecting client’s app-instance-id. Can be used to identify repeat connection attempts/etc. Note that this value changes each time the client re-launches the app.
- classic_purchases: list[str] | None = None¶
List of classic-inventory purchase legacy-ids owned by the connecting account (e.g.
'characters.kronk').Nonewhen the master server isn’t able to determine this — for example on older master-server versions or when the account has no classic-inventory record. Custom handlers should treatNoneas “unknown” and not as “owns nothing”.
- class V2AuthResponse(allow: bool, error_message: str | None = None)[source]¶
Bases:
objectWhat a V2 auth handler returns.
- account_name_depiction¶
The signed-in account’s name as the cloud composes it, a
bacommon.depiction.NameDepictionas json fed live by the cloud (empty when signed out or not known yet). Pass it as an image or button widget’sdepictionto show the name.
- add_coop_practice_level(level: Level) None[source]¶
Adds an individual level to the ‘practice’ section in Co-op.
- property campaigns: dict[str, Campaign]¶
Co-op campaigns by name.
The built-in campaigns get built on first access rather than at launch, since building them imports every co-op game module. The main menu pre-imports those modules in the background, so first access is normally cheap.
- character_preview¶
Shows characters at the master server’s request (someone trying one they are building; see
_characterpreview).
- cloud_profiles¶
The primary account’s cloud profiles (synced + cached).
- create_in_game_tournament_prize_image(entry: dict[str, Any], index: int, position: tuple[float, float]) None[source]¶
Given a tournament entry, return strings for its prize levels.
- async default_v2_auth_handler(request: V2AuthRequest) V2AuthResponse[source]¶
Default auth handler function. Just allows everyone.
- get_input_device_config(inputdevice: InputDevice, default: bool) tuple[dict, str][source]¶
Given an input device, return its config dict in the app config.
The dict will be created if it does not exist.
- get_input_device_map_hash(inputdevice: InputDevice) str[source]¶
Given an input device, return hash based on its raw input values.
- get_input_device_mapped_value(device: InputDevice, name: str, default: bool = False) Any[source]¶
Return a mapped value for an input device.
This checks the user config and falls back to default values where available.
- get_player_profile_colors(profilename: str | None, profiles: dict[str, dict[str, Any]] | None = None) tuple[tuple[float, float, float], tuple[float, float, float]][source]¶
Given a profile, return colors for them.
- get_player_profile_icon(profilename: str) str[source]¶
Given a profile name, returns an icon string for it.
(non-account profiles only)
- get_tournament_prize_strings(entry: dict[str, Any], include_tickets: bool) list[str][source]¶
Given a tournament entry, return strings for its prize levels.
- getmaps(playtype: str) list[str][source]¶
Return a list of bascenev1.Map types supporting a playtype str.
Maps supporting a given playtype must provide a particular set of features and lend themselves to a certain style of play.
Play Types:
- ‘melee’
General fighting map. Has one or more ‘spawn’ locations.
- ‘team_flag’
For games such as Capture The Flag where each team spawns by a flag. Has two or more ‘spawn’ locations, each with a corresponding ‘flag’ location (based on index).
- ‘single_flag’
For games such as King of the Hill or Keep Away where multiple teams are fighting over a single flag. Has two or more ‘spawn’ locations and 1 ‘flag_default’ location.
- ‘conquest’
For games such as Conquest where flags are spread throughout the map - has 2+ ‘flag’ locations, 2+ ‘spawn_by_flag’ locations.
- ‘king_of_the_hill’ - has 2+ ‘spawn’ locations,
1+ ‘flag_default’ locations, and 1+ ‘powerup_spawn’ locations
- ‘hockey’
For hockey games. Has two ‘goal’ locations, corresponding ‘spawn’ locations, and one ‘flag_default’ location (for where puck spawns)
- ‘football’
For football games. Has two ‘goal’ locations, corresponding ‘spawn’ locations, and one ‘flag_default’ location (for where flag/ball/etc. spawns)
- ‘race’
For racing games where players much touch each region in order. Has two or more ‘race_point’ locations.
- property host_ui: HostUISubsystem¶
The buttons, pages and party notices a host gives its clients.
(And shows itself.) Created on first use: most runs never host anyone or join a host that uses it, and its code is costly to load at launch. It gets loaded in the background as soon as it looks needed (a client connecting to us, us connecting to a host, or the main menu warming up), so first use is normally cheap.
Bring up main menu ui.
- launch_coop_game(game: str, force: bool = False, args: dict | None = None) bool[source]¶
High level way to launch a local co-op session.
- on_app_loading() None[source]¶
Called when the app reaches the
LOADINGstate.Note that subsystems created after the app switches to the loading state will not receive this callback. Subsystems created by plugins are an example of this.
- on_app_shutdown() None[source]¶
Called when the app enters the
SHUTTING_DOWNstate.
- pause() None[source]¶
Pause the game due to a user request or menu popping up.
If there’s a foreground host-activity that says it’s pausable, tell it to pause. Note: we now no longer pause if there are connected clients.
- profile_browser_window(transition: str = 'in_right', origin_widget: Widget | None = None) None[source]¶
Pop up a browser window from within a game.
- required_purchases_for_game(game: str) list[str][source]¶
Return which purchase (if any) is required for a game.
- resume() None[source]¶
Resume the game due to a user request or menu closing.
If there’s a foreground host-activity that’s currently paused, tell it to resume.
Attempt to cleanly get back to the main menu.
- run_stress_test(*, playlist_type: str = 'Random', playlist_name: str = '__default__', player_count: int = 8, round_duration: int = 30, attract_mode: bool = False, randomize: bool = True, churn: bool = True) None[source]¶
Run a stress test.
With
randomizeFalse the playlist plays in listed order; withchurnFalse all fake players join at once and stay (both for repeatable performance A/B measurements).
- set_tournament_prize_image(entry: dict[str, Any], index: int, image: Widget) None[source]¶
Given a tournament entry, return strings for its prize levels.
- property subplatform: str¶
String for subplatform.
Can be empty. For the ‘android’ platform, subplatform may be ‘google’, ‘amazon’, etc.
- v2_auth_handler: Callable[[ClassicAppSubsystem.V2AuthRequest], Awaitable[ClassicAppSubsystem.V2AuthResponse]]¶
V2 authentication handler.
To customize who is allowed in your server, assign your own custom handler function to this attribute. This will be called for all connecting clients before they are allowed in the game. Note that protocol must be set to 36 or newer and authenticate_clients must be enabled. For servers you can set those values in the server config.
- class baclassic.ClientEffectContext(targets: EffectTargets | None = None)[source]¶
Bases:
objectWhat client-effects are running alongside, if anything.
Effects that need a context (animating things, say) do nothing without the relevant piece.
- targets: EffectTargets | None = None¶
Targets for
bacommon.clienteffect.KeyframeAnimation.
- class baclassic.EffectTarget(*args, **kwargs)[source]¶
Bases:
ProtocolSomething client-effects can animate.
Implemented by whatever UI system the effects run alongside (a doc-ui window’s widgets, say); effects know nothing about it beyond this.
- class baclassic.EffectTargets(*args, **kwargs)[source]¶
Bases:
ProtocolA set of animatable targets client-effects can look up by id.
- get_target(target_id: str) EffectTarget | None[source]¶
Return the target for an id, if there is a live one.
- class baclassic.HostUIButton(label: str | babase.LangStr | LangStrSpec, browse: bacommon.docui.v2.Browse, placement: Literal['in_game_menu', 'party_menu'] = 'in_game_menu')[source]¶
Bases:
objectA button a host puts on its clients’ (and its own) ui.
Pressing it opens a page in a doc-ui window, as
browsesays, which the host’sHostUIHandlerserves. Make one with a route’sbrowse(), as for a page’s own links:HostUIButton( label='Rules', browse=Rules().browse( layout=bacommon.docui.v2.WindowLayout.WIDE, background_color=(0.3, 0.2, 0.4), ), )
- browse: bacommon.docui.v2.Browse¶
How the button opens its page, as made by a route’s
browse(). It carries the route’s request, any state to send with it, and the window’s look (layout, scroll edge style, frame colors). Itssets,add_to_historyanddefault_sounddon’t apply to menu buttons. Clients before build 23075 use only its request, state and layout.
- label: str | babase.LangStr | LangStrSpec¶
The label. Plain text is shown as-is; a language-string must come from one of the host’s own asset-packages (clients refuse anything else).
- placement: Literal['in_game_menu', 'party_menu'] = 'in_game_menu'¶
Where the button goes.
- class baclassic.HostUIClientPage[source]¶
Bases:
objectThe page for one party member: a connected client, or the host.
Shows their account, connection, and players (each linking to the host’s player page; see
baclassic.HostUIPlayerPage.get_browse()), plus a way to kick them. Party windows (build 23075 and up) open this when a member’s name is pressed, in the party list or in chat; seebaclassic.HostUISubsystem.set_client_page().Customize it as for
baclassic.HostUIStatsPage: subclass this, override the methods holding the decisions you want to change, and assign an instance toapp.classic.host_ui.client_page(or copy this module as a starting point; see its notes). Pages are built for each viewer (viewerbeing their client id, -1 for the host).- build(route: DocUIRoute, client_id: int, viewer: int) bacommon.docui.v2.Response[source]¶
Build the page on
client_idforviewer.routeis the one being served (it is what the page refreshes itself with). Gathers everything through the methods above and lays it out. Override those to change what the page shows; for a different layout, copy this module (see its notes).
- get_account_action(client_id: int, account_id: str) bacommon.docui.v2.Action | None[source]¶
What pressing the member’s verified account does.
By default it opens the account’s page from the master server (see
bacommon.docui.routes.classichost.ShowAccount; clients before its build do nothing). None shows the account without a press.
- get_browse(client_id: int) bacommon.docui.v2.Browse[source]¶
How party windows open a member’s page (window look included).
- get_kick_route(client_id: int, viewer: int) DocUIRoute | None[source]¶
The page the kick button opens (None: the button is disabled).
By default the host’s kick page for this viewer (see
baclassic.HostUISubsystem.set_kick_page()).
- get_kick_text(client_id: int, viewer: int) str | LangStr[source]¶
The kick button’s label.
No name: the page it opens (a confirmation or vote) names everyone the kick would remove.
Why the kick button is disabled, noted under it (None: no note).
By default the host’s reason (see
baclassic.HostUISubsystem.get_kick_unavailable_text()). Only shown whileget_kick_route()returns None.
- get_players(client_id: int) list[SessionPlayer][source]¶
The member’s players to list.
By default all of them (see
baclassic.HostUISubsystem.get_client_players()).
- get_refresh_interval() float | None[source]¶
How often (seconds) the page refreshes while open (None: never).
- class baclassic.HostUIHandler[source]¶
Bases:
objectServes the doc-ui pages a host’s buttons open.
Adding your own pages takes three pieces: routes naming the pages, a handler serving them, and buttons opening them. A complete plugin-sized example:
from dataclasses import dataclass from efro.dataclassio import ioprepped from bacommon.docui.routes import DocUIRoute, family_members import bacommon.docui.v2 as dui2 from bacommon.langstr import LangStrSpecValue import babase import baclassic # 1. Routes: one family class, one dataclass per page (fields # are the page's args). class MyRoute(DocUIRoute): @classmethod def get_route_types(cls) -> tuple[type[DocUIRoute], ...]: return family_members(AnyMyRoute) @ioprepped @dataclass class Rules(MyRoute, path='/myplugin/rules'): pass AnyMyRoute = Rules # 2. A handler serving them (everything else goes to super()). class MyHandler(baclassic.HostUIHandler): def fulfill_request(self, request, client_id): try: route = MyRoute.from_request(request) except Exception: return super().fulfill_request(request, client_id) assert isinstance(route, Rules) lit = LangStrSpecValue.literal return dui2.Response( page=dui2.Page( title=lit('Rules'), rows=[ dui2.ButtonRow( title=lit('Be nice.'), buttons=[ dui2.Button( label=lit('Ok'), action=dui2.Local(close_window=True), ) ], ) ], ) ) # 3. Plug in: the handler, plus a button opening the page # (alongside the default ones). host_ui = babase.app.classic.host_ui host_ui.handler = MyHandler() host_ui.set_buttons( host_ui.get_buttons() + [baclassic.HostUIButton(label='Rules', browse=Rules().browse())] )
To change the default stats page rather than add one, see
baclassic.HostUIStatsPage. If something goes wrong building a page (an exception, or returning something that isn’t a response), the error is logged here and the client gets its standard error page.Keeping pages cheap.
fulfill_request()runs in the logic thread, the one the game itself runs in, so time spent there is time the game stands still. A page built from a handful of values costs next to nothing, and that’s the common case. A page that grows (big tables, sorting or filtering many records, file or network access) should do the heavy part elsewhere:Make
fulfill_request()async.Gather what you need from the game first, on the logic thread (game objects such as players and activities may only be touched there), into plain values.
Build the page from those values in a background thread with
await self.run_in_bg(build_page, values). That function must not touch game objects or ui.
Handlers that spend more than about 10 ms on the logic thread get a warning saying so. (Turning the page into wire data and compressing it already happens in the background.)
- fulfill_request(request: bacommon.docui.v2.Request, client_id: int) bacommon.docui.v2.Response | Awaitable[bacommon.docui.v2.Response][source]¶
Return the page for a request from a client.
client_idis -1 for the host’s own requests. Runs in the logic thread, so game state can be read freely; return an awaitable (or make thisasync) to answer asynchronously. Remember that requests come from clients and are untrusted input.
- async run_in_bg(call: Callable[[], T]) T[source]¶
Run a call in a background thread; return its result.
For the heavy parts of building a page (see the class docs).
callmust not touch game objects or ui; hand it plain values gathered beforehand (functools.partialor a lambda does that nicely). Time spent here doesn’t count toward the slow-handler warning.
- class baclassic.HostUIKickRequest(client_id: int, viewer: int, player_id: int | None = None)[source]¶
Bases:
objectSomeone asking to kick a party member.
What a kick page getter is called with (see
baclassic.HostUISubsystem.set_kick_page()).
- class baclassic.HostUIKickVote(target_client_id: int, target_name: str, starter_client_id: int, end_time: float, votes: dict[int, bool]=<factory>, target_others: list[str] = <factory>)[source]¶
Bases:
objectA kick vote in progress.
- target_others: list[str]¶
Other players on the target’s device (who go with them), as of the vote starting; see
HostUIKickVotes.get_target_others().
- class baclassic.HostUIKickVotes[source]¶
Bases:
objectThe standard kick vote, run by classic hosts.
Lives at
app.classic.host_ui.kick_votes. Clients pick someone in their party window and choose to kick them, which opens this vote’s page. There they can start a vote; everyone else (who can see host pages: build 23067 and up) gets a ‘Kick X?’ notice with the party icon and ‘…’ button flashing, and votes on the page the ‘…’ menu leads to. Admins (seehost_ui.admins) and the host kick without a vote and can’t be kicked.Customizing. The methods come in sections, in this order:
The rules – every number and decision (vote length, minimum voters, votes required, cooldowns, ban time, who’s an admin, who votes, why a vote can’t start).
What people see – every bit of text; return your own (plain strings are fine) to reword anything.
Running votes – the machinery that applies the rules.
The page – the vote’s doc-ui page.
Subclass this, override just the methods you want to change, and assign an instance to
host_ui.kick_votes; for example, for longer votes:class MyKickVotes(baclassic.HostUIKickVotes): def get_vote_duration(self) -> float: return 60.0 babase.app.classic.host_ui.kick_votes = MyKickVotes()
Where kicking leads is decided by one public hook,
baclassic.HostUISubsystem.set_kick_page(), whose default is this class’sget_kick_page()(nothing else treats this vote specially). To replace kick votes with something else entirely, point that hook at a page of your own, copying or callingget_kick_page()for any of its behavior you want to keep.The vote’s notice uses the single party notice clients have; the most recently posted notice wins, so a notice posted during a vote replaces the vote’s (and the vote’s ending replaces whatever is showing then).
Votes starting and ending, and kicks, are logged (
ba.net, at info level); a kick that fails is a warning.- build_page(client_id: int, target_client_id: int | None, player_id: int | None = None) Response[source]¶
The vote’s page, for one client.
target_client_idis who they chose to kick, if they came here to kick someone (andplayer_idthe player they chose them by, if any). Admins (and the host) get asked to confirm a kick instead. Refreshes itself every second. Its text comes from the methods above; override those to change it. For a different layout, copy this module (see its notes).
- cast_vote(client_id: int, yes: bool) None[source]¶
Record a vote (ignored if there’s no vote or they voted).
- end_vote(passed: bool, quiet: bool = False) None[source]¶
End the vote in progress, kicking its target if it passed.
quietends it without telling anyone how it went (the target left, say).
- get_also_kicks_text(others: list[str], named_player: bool) str | LangStr[source]¶
The page’s line on who else a kick removes.
otherscome fromget_target_others();named_playeris whether the target is named by one of their players (soothersare the rest) or by their client (soothersare all of them).
- get_buttons(client_id: int) list[HostUIButton][source]¶
Buttons a client gets from us (on top of the host’s own).
While a vote runs, a button to its page in everyone’s party menu.
- get_failed_vote_delay(starter: bool) float[source]¶
Seconds before someone can start a vote after one fails.
starteris whether they started the failed one.
- get_host_ui() HostUISubsystem[source]¶
The host-ui subsystem we run on (
app.classic.host_ui).
- get_kick_page(request: HostUIKickRequest) DocUIRoute | None[source]¶
Where a kick request goes; the default kick page getter.
baclassic.HostUISubsystem.set_kick_page()uses this unless set otherwise, and it uses only public api, so it doubles as an example of a getter: the vote page (get_kick_route()) for admins and the host (who confirm a kick there instead of starting a vote) and, while voting is on, for everyone else. None (no kick option) for kicking admins or the host, or for non-admins while voting is off. (Kicking the host, yourself, or someone not here is ruled out before this is asked.)
- get_kick_route(client_id: int, player_id: int | None = None) DocUIRoute[source]¶
The page clients get when they choose to kick someone.
player_idis the player they picked them by, if any (seeget_target_name()).
Why
get_kick_page()offers no kick, if it doesn’t.For showing beside a disabled kick button. None if it does offer one.
- get_kicked_text(name: str) str | LangStr[source]¶
The screen message everyone gets when someone is kicked.
- get_minimum_clients() int[source]¶
Fewest voting clients (not counting the host) for a vote.
A host that is also playing doesn’t count, so require one more then (two of three clients in a 2-on-2 shouldn’t be able to kick the third).
- get_question_text(name: str) str | LangStr[source]¶
‘Kick X?’: the voters’ notice, and the page’s question.
- get_refusal(starter_client_id: int, target_client_id: int) KickVoteRefusal | None[source]¶
Why a client can’t start a vote to kick another (None: they can).
Admins (and the host) skip this; they kick outright.
- get_refusal_text(refusal: KickVoteRefusal, starter_client_id: int) str | LangStr[source]¶
Why a vote can’t start, as told to whoever tried.
- get_start_wait(client_id: int) float[source]¶
Seconds until a client may start a vote (0 if they may now).
- get_target_name(client_id: int, player_id: int | None = None) str | None[source]¶
What to call whoever a kick would remove (None if not here).
Kicks remove a whole client (every player on that device), but people pick who to kick by player or by party member. So: the player picked (
player_id), else the client’s one player, else the client’s name as the party list shows it.
- get_target_others(client_id: int, player_id: int | None = None) list[str][source]¶
Names of players a kick would also remove.
That is, those on the client besides the one
get_target_name()names (all of them when it names the client).
- get_time_left_text(ends: datetime) str | LangStr[source]¶
The countdown under the vote’s buttons.
endsis when the vote runs out; clients count down to it live. (Passing adatetimeto a string’s duration param does that; seebacommon.langstr.LangStrSpecTimeTarget.)
- get_voters() list[int][source]¶
Who votes: clients that can see host pages, plus a playing host.
(Clients too old for host pages can’t take part at all.)
- get_votes_needed_text(count: int) str | LangStr[source]¶
The tally: how many more yes votes are needed.
- get_votes_required(client_count: int) int[source]¶
Yes votes needed to kick, given the voting clients present.
client_countdoesn’t include the host; a host that is also playing counts as one more participant (and votes too).
- kick(client_id: int, name: str | None = None) None[source]¶
Kick a client (banning them for
get_ban_time()).
- set_enabled(enabled: bool) None[source]¶
Turn kick voting on or off.
Admins (and the host) can still kick while it’s off (see
get_kick_page()).
- start_vote(starter_client_id: int, target_client_id: int, player_id: int | None = None) KickVoteRefusal | None[source]¶
Start a vote (or, for admins, just kick).
player_idis the player the target was picked by, if any (seeget_target_name()). Returns why it couldn’t be started, if it couldn’t.
- update() None[source]¶
Check the vote in progress: end it if it passed or failed.
Runs whenever a vote is cast, and every second.
- vote: HostUIKickVote | None¶
The vote in progress, if any.
- class baclassic.HostUIPlayerPage[source]¶
Bases:
objectThe page for one player in the game.
Shows who they’re playing as, how they’re doing this game, and the party member they play from (linking to the host’s member page; see
baclassic.HostUISubsystem.get_client_page()), plus a way to kick them. The stats page’s rows and the member page’s player buttons lead here (seeget_browse()).Customize it as for
baclassic.HostUIStatsPage: subclass this, override the methods holding the decisions you want to change, and assign an instance toapp.classic.host_ui.player_page(or copy this module as a starting point; see its notes). Pages are built for each viewer (viewerbeing their client id, -1 for the host).- build(route: DocUIRoute, player_id: int, viewer: int) bacommon.docui.v2.Response[source]¶
Build the page on
player_idforviewer.routeis the one being served (it is what the page refreshes itself with). Gathers everything through the methods above and lays it out. Override those to change what the page shows; for a different layout, copy this module (see its notes).
- get_browse(player_id: int) bacommon.docui.v2.Browse | None[source]¶
How links to a player open their page (window look included).
Every link to a player comes through here: the stats page’s rows and the party member page’s player buttons. Override it to send them to a page of your own (served by your
baclassic.HostUISubsystem.handler), or return None for no player links.
- get_kick_route(player: bascenev1.SessionPlayer, viewer: int) DocUIRoute | None[source]¶
The page the kick button opens (None: the button is disabled).
Kicks remove the player’s whole party member (every player on their device; the kick page names the others). By default the host’s kick page for this viewer and player (see
baclassic.HostUISubsystem.set_kick_page()).
- get_kick_text(player: SessionPlayer, viewer: int) str | LangStr[source]¶
The kick button’s label.
No name: the page it opens (a confirmation or vote) names everyone the kick would remove.
Why the kick button is disabled, noted under it (None: no note).
By default the host’s reason (see
baclassic.HostUISubsystem.get_kick_unavailable_text()). Only shown whileget_kick_route()returns None.
- get_player(player_id: int) SessionPlayer | None[source]¶
The player with a session player id (None if not here).
- class baclassic.HostUIStatsColumn(title: str | babase.LangStr, width: float, value: Callable[[bascenev1.SessionPlayer, HostUIStatsContext], str | babase.LangStr | bacommon.depiction.Depiction | None], hide_when_empty: bool = False)[source]¶
Bases:
objectOne column of values in the stats table, right of the player.
- hide_when_empty: bool = False¶
Leave the column out when nobody has a value for it (rather than showing a column of dashes).
- title: str | babase.LangStr¶
The column’s title. A language-string must come from one of the host’s own asset-packages (clients refuse anything else).
- value: Callable[[bascenev1.SessionPlayer, HostUIStatsContext], str | babase.LangStr | bacommon.depiction.Depiction | None]¶
A player’s value – text, a language-string (each client shows it in its own language), a depiction, or None for none (drawn as a dash). Called on the logic thread for every player each time the page is built, so keep it cheap.
- class baclassic.HostUIStatsContext(account_ids: dict[int, str], records: dict[int, PlayerRecord])[source]¶
Bases:
objectWhat stats column values may need beyond the player itself.
Gathered once per page, so columns can look things up cheaply.
- account_ids: dict[int, str]¶
Verified account ids by client id (-1 being the host’s own), for those who have one; see
get_client_account_id().
- record(player: SessionPlayer) PlayerRecord | None[source]¶
A player’s stats record, if they have one.
Records’
accum*values are the current game’s (despite the name; they reset as each game starts) and are what score screens show; the plain ones run for the whole series.
- records: dict[int, PlayerRecord]¶
The session’s stats records (what between-round score screens show), by their players’ ids; see
record().
- class baclassic.HostUIStatsPage[source]¶
Bases:
objectThe stats page classic hosts serve: a table of the party.
One row per player who has joined (icon and name, then a value per column), in scoreboard order during games. Serve your own variation by subclassing this, overriding
get_columns()(or anything else), and assigning an instance toapp.classic.host_ui.stats_page; for example, to add a column:class MyStatsPage(baclassic.HostUIStatsPage): def get_columns(self) -> list[baclassic.HostUIStatsColumn]: return super().get_columns() + [ baclassic.HostUIStatsColumn( 'Streak', 70.0, self.streak_value ) ] def streak_value(self, player, context): rec = context.record(player) return None if rec is None else str(rec.streak) babase.app.classic.host_ui.stats_page = MyStatsPage()
Subclassing keeps everything you don’t override up to date as the engine updates. For a page that differs more (a different layout, say), copy this module as a starting point instead; see the module’s notes (
baclassic._hostuistats) for what a copy needs. A table under a route of your own works either way: returnself.build(route)from yourbaclassic.HostUIHandler. Pages are built on the logic thread; seebaclassic.HostUIHandlerfor keeping big ones cheap.- account_column() HostUIStatsColumn[source]¶
The default Account column (hidden when nobody has one).
- account_value(player: bascenev1.SessionPlayer, context: HostUIStatsContext) bacommon.depiction.Depiction | None[source]¶
A player’s verified account, as its name depiction.
Known for the host’s own account and for clients that joined with v2 auth; so with client auth off, usually None.
- build(route: DocUIRoute) bacommon.docui.v2.Response[source]¶
Build the page;
routeis the one being served.(It is what the page refreshes itself with.) Gathers players and values through the methods above and lays out the table (see the module docs for making a version of your own). Each row opens its player’s page (see
baclassic.HostUIPlayerPage.get_browse()).
- deaths_column() HostUIStatsColumn[source]¶
The default Deaths column.
- deaths_value(player: SessionPlayer, context: HostUIStatsContext) str | None[source]¶
A player’s deaths this game.
- get_columns() list[HostUIStatsColumn][source]¶
The table’s columns, left to right (after the player’s).
- get_players(session: Session) list[SessionPlayer][source]¶
The players to list (before sorting).
By default everyone who has joined; players still choosing a profile in the lobby have no name or look yet.
- get_refresh_interval() float | None[source]¶
How often (seconds) the page refreshes while open (None: never).
- kills_column() HostUIStatsColumn[source]¶
The default Kills column.
- kills_value(player: SessionPlayer, context: HostUIStatsContext) str | None[source]¶
A player’s kills this game.
- ping_column() HostUIStatsColumn[source]¶
The default Ping column.
- ping_value(player: SessionPlayer, context: HostUIStatsContext) str | None[source]¶
A player’s connection’s ping in ms (as the host measures it).
- score_column() HostUIStatsColumn[source]¶
The default Score column.
- score_value(player: SessionPlayer, context: HostUIStatsContext) str | None[source]¶
A player’s score this game (as score screens show it).
- sort_players(players: list[SessionPlayer], context: HostUIStatsContext, session: Session) list[SessionPlayer][source]¶
Players in the order to list them.
By default scoreboard order during a game: grouped by team in team games, then by score (highest first), ties going to more kills and then fewer deaths, players without stats yet last. Outside a game (nobody has stats), join order.
- class baclassic.HostUISubsystem[source]¶
Bases:
objectHost-defined ui, from both ends.
Access the single shared instance of this class via
app.classic.host_ui.As a host: set the buttons everyone gets with
set_buttons()(or one client’s withset_client_buttons()), post notices withpost_notice(), flash things withflash_party_icon()andflash_party_menu(), and serve pages by assigninghandler. Clients are kept up to date automatically, joiners included.As a client, this holds what our host has declared to us.
- admins: set[str]¶
V2 account ids (a-XXX) of this host’s admins. Admins can’t be kicked by vote, and kick without one.
- clear_notice(*, clients: Sequence[int] | None = None) None[source]¶
Take down the notice (for some clients, or everyone).
- client_has_host_ui(client_id: int) bool[source]¶
Whether a client can see host buttons, notices, and pages.
(Clients predating them can’t.) True for the host (-1) while it has ui.
- client_page: HostUIClientPage¶
The default party member page (assign a subclass to customize it).
- flash_party_icon(duration: float, *, clients: Sequence[int] | None = None) None[source]¶
Flash the party icon for a while (0 to stop).
Flash the party window’s ‘…’ button for a while (0 to stop).
- get_account_name_depiction(account_id: str) bacommon.depiction.Depiction | None[source]¶
An account’s name as it draws in game, if we know it.
Known for our own account, and for clients that joined with v2 auth (so it is verified by the master server); see
get_game_roster()for each client’s account id. None otherwise.
- get_buttons() list[HostUIButton][source]¶
The buttons every client gets (unless overridden per client).
- get_client_account_id(client_id: int) str | None[source]¶
A client’s verified v2 account id (a-XXX), if it has one.
Client id -1 is the host (our own signed-in account).
- get_client_name(client_id: int) str | None[source]¶
A client’s name as the party list shows it (None if not here).
Repeated names are numbered (“iPhone 2”); see
bascenev1.get_party_member_names().
- get_client_page(client_id: int) Browse | None[source]¶
How to open a party member’s page, as
set_client_page()says.None while no member page is offered. For linking to one from a page of your own.
- get_client_players(client_id: int) list[SessionPlayer][source]¶
A client’s players in the game (-1 for the host’s own).
In join order; not including players still choosing a profile in the lobby.
- get_kick_page(request: HostUIKickRequest) DocUIRoute | None[source]¶
Where a kick request goes, as set by
set_kick_page().None if the viewer isn’t offered kicking that client.
Why
get_kick_page()offers no kick, if it can say.For showing beside a disabled kick button. None if a kick is offered, or if a custom kick page getter (see
set_kick_page()) declined, since only it knows why.
- handler: HostUIHandler¶
Serves the pages host buttons open.
- kick_votes: HostUIKickVotes¶
The standard kick vote (assign a subclass to customize it).
- player_page: HostUIPlayerPage¶
The default player page (assign a subclass to customize it).
- post_notice(text: str | babase.LangStr | LangStrSpec, duration: float, *, clients: Sequence[int] | None = None) None[source]¶
Show a notice below the party icon for a while.
Replaces any notice showing (the most recently posted notice wins).
clientslimits it to some clients (-1 is the host); None means everyone. A language-string must come from one of the host’s own asset-packages.
- refresh_clients() None[source]¶
Send everyone their current state again.
For when something the state is built from changed without going through this class (such as the buttons
baclassic.HostUIKickVotes.get_buttons()adds).
- set_buttons(buttons: Sequence[HostUIButton]) None[source]¶
Set the buttons every client gets.
Clients with their own (see
set_client_buttons()) keep theirs.
- set_client_buttons(client_id: int, buttons: Sequence[HostUIButton] | None) None[source]¶
Give one client its own set of buttons (None to undo).
Use -1 for the host’s own ui. Forgotten when the client leaves.
- set_client_page(get_browse: Callable[[int], Browse] | None) None[source]¶
Set what opens when clients pick a party member.
get_browseis called with a client id (-1 for the host) and returns how to open the page for that member (which yourhandlerserves): a route’sbrowse(), which may also set the window’s look. Party windows (build 23075 and up) open it when a member’s name is pressed, in the party list or in chat. Pages linking to a member (the player page’s Connection button) use it too (seeget_client_page()). By defaultclient_page’s (baclassic.HostUIClientPage.get_browse(), of whateverclient_pageis at the time). None offers no page; party lists then fall back to their older kick option (seeset_kick_page()).
- set_kick_page(get_route: Callable[[HostUIKickRequest], DocUIRoute | None] | None) None[source]¶
Set where clients go when they choose to kick someone.
The one place kicking is decided.
get_routeis called with abaclassic.HostUIKickRequest(who would be kicked, who is asking, and the player they picked, if any) and returns the page for it (which yourhandlerserves), or None to offer that viewer no kick option there. Nobody is offered kicking the host, themselves, or someone not here; you’re only asked about the rest. None offers kicking to nobody.By default the standard kick vote’s (
baclassic.HostUIKickVotes.get_kick_page(), of whateverkick_votesis at the time), which uses only public api and so works as an example to copy or wrap.Clients from build 23075 on get to it from party member and player pages (see
set_client_page()); older ones from their party list, which asks without a player.
- stats_page: HostUIStatsPage¶
The default stats page (assign a subclass to customize it).
- class baclassic.KickVoteRefusal(*values)[source]¶
Bases:
EnumWhy a kick vote can’t be started.
- ADMIN = 'admin'¶
The host and admins can’t be kicked.
- COOLDOWN = 'cooldown'¶
The starter has to wait a while (they just joined, or a vote just failed).
- DISABLED = 'disabled'¶
Kick voting is turned off.
- IN_PROGRESS = 'in_progress'¶
A vote is already running.
- NOT_ENOUGH_PLAYERS = 'not_enough_players'¶
Too few voters for a vote to mean anything.
- NOT_PRESENT = 'not_present'¶
The player isn’t here (anymore).
- SELF = 'self'¶
Nobody can kick themselves.
- class baclassic.MusicPlayer[source]¶
Bases:
objectWrangles soundtrack music playback.
Music can be played either through the game itself or via a platform-specific external player.
- on_select_entry(callback: Callable[[Any], None], current_entry: Any, selection_target_name: str) bauiv1.MainWindow[source]¶
Present a GUI to select an entry.
The callback should be called with a valid entry or None to signify that the default soundtrack should be used..
- select_entry(callback: Callable[[Any], None], current_entry: Any, selection_target_name: str) bauiv1.MainWindow[source]¶
Summons a UI to select a new soundtrack entry.
- baclassic.host_ui_player_icon(player: SessionPlayer, center: tuple[float, float], size: float) list[Decoration][source]¶
A player’s icon, as page decorations centered at
center.Drawn as it is in game (their team’s color in team games): their icon depiction if they have one; else their old-style icon if it can be shown; else a faint box with a ‘?’ holding the spot (bots, say).
- baclassic.host_ui_player_name(player: SessionPlayer) Depiction[source]¶
A player’s name for a page, as a depiction.
Shown as in game: shortened and numbered as
getname()is (the depiction by itsmax_charsandnumber), since that’s what everyone sees and what tells repeated names apart, and in their color. Players with no name depiction of their own (bots, say) get a plain one of their name. Put it on a page with abacommon.docui.v2.Depictiondecoration.
- baclassic.host_ui_text(text: str | LangStr | LangStrSpec) LangStrSpec[source]¶
Text for a host-ui page, as doc-ui wants it.
Plain strings show as-is; a language-string (
babase.LangStr) shows in each client’s own language, and must come from one of the host’s own asset-packages (clients refuse anything else). A spec passes through.
- baclassic.reload_hooks() None[source]¶
Reload functions and other objects held by the native layer. Call this if you replace things in a hooks module to get the native layer to see your changes.
Submodules¶
baclassic.macmusicapp module¶
Music playback functionality using the Mac Music (formerly iTunes) app.
- class baclassic.macmusicapp.MacMusicAppMusicPlayer[source]¶
Bases:
MusicPlayerA music-player that utilizes the macOS Music.app for playback.
Allows selecting playlists as entries.
- get_playlists(callback: Callable) None[source]¶
Asynchronously fetch the list of available iTunes playlists.
- on_select_entry(callback: Callable[[Any], None], current_entry: Any, selection_target_name: str) bauiv1.MainWindow[source]¶
Present a GUI to select an entry.
The callback should be called with a valid entry or None to signify that the default soundtrack should be used..
baclassic.osmusic module¶
Music playback using OS functionality exposed through the C++ layer.
- class baclassic.osmusic.OSMusicPlayer[source]¶
Bases:
MusicPlayerMusic player that talks to internal C++ layer for functionality.
(internal)
- classmethod get_valid_music_file_extensions() list[str][source]¶
Return file extensions for types playable on this device.
- on_select_entry(callback: Callable[[Any], None], current_entry: Any, selection_target_name: str) bauiv1.MainWindow[source]¶
Present a GUI to select an entry.
The callback should be called with a valid entry or None to signify that the default soundtrack should be used..