updating core files 1.7.10

This commit is contained in:
Ayush Saini 2022-10-01 14:51:35 +05:30
parent daff57f1e9
commit b110f8a12a
163 changed files with 4836 additions and 1960 deletions

241
dist/ba_data/python/_bainternal.py vendored Normal file
View file

@ -0,0 +1,241 @@
# Released under the MIT License. See LICENSE for details.
#
"""A dummy stub module for the real _bainternal.
The real _bainternal is a compiled extension module and only available
in the live engine. This dummy-module allows Pylint/Mypy/etc. to
function reasonably well outside of that environment.
Make sure this file is never included in dirs seen by the engine!
In the future perhaps this can be a stub (.pyi) file, but we will need
to make sure that it works with all our tools (mypy, pylint, pycharm).
NOTE: This file was autogenerated by batools.dummymodule; do not edit by hand.
"""
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
# pylint: disable=unnecessary-pass
# pylint: disable=use-dict-literal
# pylint: disable=use-list-literal
# pylint: disable=unused-argument
# pylint: disable=missing-docstring
# pylint: disable=too-many-locals
# pylint: disable=redefined-builtin
# pylint: disable=too-many-lines
# pylint: disable=redefined-outer-name
# pylint: disable=invalid-name
# pylint: disable=no-value-for-parameter
from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from typing import Any, Callable
_T = TypeVar('_T')
def _uninferrable() -> Any:
"""Get an "Any" in mypy and "uninferrable" in Pylint."""
# pylint: disable=undefined-variable
return _not_a_real_variable # type: ignore
def add_transaction(transaction: dict,
callback: Callable | None = None) -> None:
"""(internal)"""
return None
def game_service_has_leaderboard(game: str, config: str) -> bool:
"""(internal)
Given a game and config string, returns whether there is a leaderboard
for it on the game service.
"""
return bool()
def get_master_server_address(source: int = -1, version: int = 1) -> str:
"""(internal)
Return the address of the master server.
"""
return str()
def get_news_show() -> str:
"""(internal)"""
return str()
def get_price(item: str) -> str | None:
"""(internal)"""
return ''
def get_public_login_id() -> str | None:
"""(internal)"""
return ''
def get_purchased(item: str) -> bool:
"""(internal)"""
return bool()
def get_purchases_state() -> int:
"""(internal)"""
return int()
def get_v1_account_display_string(full: bool = True) -> str:
"""(internal)"""
return str()
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
"""(internal)"""
return _uninferrable()
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
"""(internal)"""
return _uninferrable()
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
"""(internal)"""
return _uninferrable()
def get_v1_account_name() -> str:
"""(internal)"""
return str()
def get_v1_account_state() -> str:
"""(internal)"""
return str()
def get_v1_account_state_num() -> int:
"""(internal)"""
return int()
def get_v1_account_ticket_count() -> int:
"""(internal)
Returns the number of tickets for the current account.
"""
return int()
def get_v1_account_type() -> str:
"""(internal)"""
return str()
def get_v2_fleet() -> str:
"""(internal)"""
return str()
def have_outstanding_transactions() -> bool:
"""(internal)"""
return bool()
def in_game_purchase(item: str, price: int) -> None:
"""(internal)"""
return None
def is_blessed() -> bool:
"""(internal)"""
return bool()
def mark_config_dirty() -> None:
"""(internal)
Category: General Utility Functions
"""
return None
def power_ranking_query(callback: Callable, season: Any = None) -> None:
"""(internal)"""
return None
def purchase(item: str) -> None:
"""(internal)"""
return None
def report_achievement(achievement: str, pass_to_account: bool = True) -> None:
"""(internal)"""
return None
def reset_achievements() -> None:
"""(internal)"""
return None
def restore_purchases() -> None:
"""(internal)"""
return None
def run_transactions() -> None:
"""(internal)"""
return None
def sign_in_v1(account_type: str) -> None:
"""(internal)
Category: General Utility Functions
"""
return None
def sign_out_v1(v2_embedded: bool = False) -> None:
"""(internal)
Category: General Utility Functions
"""
return None
def submit_score(game: str,
config: str,
name: Any,
score: int | None,
callback: Callable,
friend_callback: Callable | None,
order: str = 'increasing',
tournament_id: str | None = None,
score_type: str = 'points',
campaign: str | None = None,
level: str | None = None) -> None:
"""(internal)
Submit a score to the server; callback will be called with the results.
As a courtesy, please don't send fake scores to the server. I'd prefer
to devote my time to improving the game instead of trying to make the
score server more mischief-proof.
"""
return None
def tournament_query(callback: Callable[[dict | None], None],
args: dict) -> None:
"""(internal)"""
return None

View file

@ -13,11 +13,11 @@ from _ba import (
Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget,
camerashake, checkboxwidget, columnwidget, containerwidget, do_once,
emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession,
getsound, gettexture, hscrollwidget, imagewidget, log, newactivity,
newnode, playsound, printnodes, printobjects, pushcall, quit, rowwidget,
safecolor, screenmessage, scrollwidget, set_analytics_screen, charstr,
textwidget, time, timer, open_url, widget, clipboard_is_supported,
clipboard_has_text, clipboard_get_text, clipboard_set_text, getdata)
getsound, gettexture, hscrollwidget, imagewidget, newactivity, newnode,
playsound, printnodes, printobjects, pushcall, quit, rowwidget, safecolor,
screenmessage, scrollwidget, set_analytics_screen, charstr, textwidget,
time, timer, open_url, widget, clipboard_is_supported, clipboard_has_text,
clipboard_get_text, clipboard_set_text, getdata, in_logic_thread)
from ba._activity import Activity
from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem
from ba._actor import Actor
@ -99,10 +99,10 @@ __all__ = [
'GameTip', 'garbage_collect', 'getactivity', 'getclass', 'getcollidemodel',
'getcollision', 'getdata', 'getmaps', 'getmodel', 'getnodes', 'getsession',
'getsound', 'gettexture', 'HitMessage', 'hscrollwidget', 'imagewidget',
'ImpactDamageMessage', 'InputDevice', 'InputDeviceNotFoundError',
'InputType', 'IntChoiceSetting', 'IntSetting',
'ImpactDamageMessage', 'in_logic_thread', 'InputDevice',
'InputDeviceNotFoundError', 'InputType', 'IntChoiceSetting', 'IntSetting',
'is_browser_likely_available', 'is_point_in_box', 'Keyboard',
'LanguageSubsystem', 'Level', 'Lobby', 'log', 'Lstr', 'Map', 'Material',
'LanguageSubsystem', 'Level', 'Lobby', 'Lstr', 'Map', 'Material',
'MetadataSubsystem', 'Model', 'MultiTeamSession', 'MusicPlayer',
'MusicPlayMode', 'MusicSubsystem', 'MusicType', 'newactivity', 'newnode',
'Node', 'NodeActor', 'NodeNotFoundError', 'normalized_color',

View file

@ -9,6 +9,7 @@ import time
from typing import TYPE_CHECKING
import _ba
from ba import _internal
if TYPE_CHECKING:
from typing import Any
@ -41,7 +42,7 @@ class AccountV1Subsystem:
def do_auto_sign_in() -> None:
if _ba.app.headless_mode or _ba.app.config.get(
'Auto Account State') == 'Local':
_ba.sign_in_v1('Local')
_internal.sign_in_v1('Local')
_ba.pushcall(do_auto_sign_in)
@ -108,8 +109,8 @@ class AccountV1Subsystem:
if data['p']:
pro_mult = 1.0 + float(
_ba.get_v1_account_misc_read_val('proPowerRankingBoost',
0.0)) * 0.01
_internal.get_v1_account_misc_read_val('proPowerRankingBoost',
0.0)) * 0.01
else:
pro_mult = 1.0
@ -135,12 +136,13 @@ class AccountV1Subsystem:
"""(internal)"""
# pylint: disable=cyclic-import
from ba import _store
if _ba.get_v1_account_state() != 'signed_in':
if _internal.get_v1_account_state() != 'signed_in':
return []
icons = []
store_items = _store.get_store_items()
for item_name, item in list(store_items.items()):
if item_name.startswith('icons.') and _ba.get_purchased(item_name):
if item_name.startswith('icons.') and _internal.get_purchased(
item_name):
icons.append(item['icon'])
return icons
@ -152,12 +154,13 @@ class AccountV1Subsystem:
(internal)
"""
# This only applies when we're signed in.
if _ba.get_v1_account_state() != 'signed_in':
if _internal.get_v1_account_state() != 'signed_in':
return
# If the short version of our account name currently cant be
# displayed by the game, cancel.
if not _ba.have_chars(_ba.get_v1_account_display_string(full=False)):
if not _ba.have_chars(
_internal.get_v1_account_display_string(full=False)):
return
config = _ba.app.config
@ -165,7 +168,7 @@ class AccountV1Subsystem:
or '__account__' not in config['Player Profiles']):
# Create a spaz with a nice default purply color.
_ba.add_transaction({
_internal.add_transaction({
'type': 'ADD_PLAYER_PROFILE',
'name': '__account__',
'profile': {
@ -174,7 +177,7 @@ class AccountV1Subsystem:
'highlight': [0.5, 0.25, 1.0]
}
})
_ba.run_transactions()
_internal.run_transactions()
def have_pro(self) -> bool:
"""Return whether pro is currently unlocked."""
@ -182,9 +185,9 @@ class AccountV1Subsystem:
# Check our tickets-based pro upgrade and our two real-IAP based
# upgrades. Also always unlock this stuff in ballistica-core builds.
return bool(
_ba.get_purchased('upgrades.pro')
or _ba.get_purchased('static.pro')
or _ba.get_purchased('static.pro_sale')
_internal.get_purchased('upgrades.pro')
or _internal.get_purchased('static.pro')
or _internal.get_purchased('static.pro_sale')
or 'ballistica' + 'core' == _ba.appname())
def have_pro_options(self) -> bool:
@ -199,7 +202,8 @@ class AccountV1Subsystem:
# or also if we've been grandfathered in or are using ballistica-core
# builds.
return self.have_pro() or bool(
_ba.get_v1_account_misc_read_val_2('proOptionsUnlocked', False)
_internal.get_v1_account_misc_read_val_2('proOptionsUnlocked',
False)
or _ba.app.config.get('lc14292', 0) > 1)
def show_post_purchase_message(self) -> None:
@ -221,17 +225,17 @@ class AccountV1Subsystem:
from ba._language import Lstr
# Run any pending promo codes we had queued up while not signed in.
if _ba.get_v1_account_state(
if _internal.get_v1_account_state(
) == 'signed_in' and self.pending_promo_codes:
for code in self.pending_promo_codes:
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
color=(0, 1, 0))
_ba.add_transaction({
_internal.add_transaction({
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
'code': code
})
_ba.run_transactions()
_internal.run_transactions()
self.pending_promo_codes = []
def add_pending_promo_code(self, code: str) -> None:
@ -242,7 +246,7 @@ class AccountV1Subsystem:
# If we're not signed in, queue up the code to run the next time we
# are and issue a warning if we haven't signed in within the next
# few seconds.
if _ba.get_v1_account_state() != 'signed_in':
if _internal.get_v1_account_state() != 'signed_in':
def check_pending_codes() -> None:
"""(internal)"""
@ -259,9 +263,9 @@ class AccountV1Subsystem:
return
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
color=(0, 1, 0))
_ba.add_transaction({
_internal.add_transaction({
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
'code': code
})
_ba.run_transactions()
_internal.run_transactions()

View file

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
from ba import _internal
from ba._error import print_exception
if TYPE_CHECKING:
@ -317,10 +318,13 @@ class AchievementSubsystem:
if not ach.complete:
# Report new achievements to the game-service.
_ba.report_achievement(achname)
_internal.report_achievement(achname)
# And to our account.
_ba.add_transaction({'type': 'ACHIEVEMENT', 'name': achname})
_internal.add_transaction({
'type': 'ACHIEVEMENT',
'name': achname
})
# Now attempt to show a banner.
self.display_achievement_banner(achname)
@ -409,7 +413,7 @@ def _get_ach_mult(include_pro_bonus: bool = False) -> int:
(just for display; changing this here won't affect actual rewards)
"""
val: int = _ba.get_v1_account_misc_read_val('achAwardMult', 5)
val: int = _internal.get_v1_account_misc_read_val('achAwardMult', 5)
assert isinstance(val, int)
if include_pro_bonus and _ba.app.accounts_v1.have_pro():
val *= 2
@ -496,7 +500,7 @@ class Achievement:
# signed in, lets not show them (otherwise we tend to get
# confusing 'controller connected' achievements popping up while
# waiting to log in which can be confusing).
if _ba.get_v1_account_state() != 'signed_in':
if _internal.get_v1_account_state() != 'signed_in':
return
# If we're being freshly complete, display/report it and whatnot.
@ -592,8 +596,8 @@ class Achievement:
def get_award_ticket_value(self, include_pro_bonus: bool = False) -> int:
"""Get the ticket award value for this achievement."""
val: int = (_ba.get_v1_account_misc_read_val('achAward.' + self._name,
self._award) *
val: int = (_internal.get_v1_account_misc_read_val(
'achAward.' + self._name, self._award) *
_get_ach_mult(include_pro_bonus))
assert isinstance(val, int)
return val
@ -601,7 +605,7 @@ class Achievement:
@property
def power_ranking_value(self) -> int:
"""Get the power-ranking award value for this achievement."""
val: int = _ba.get_v1_account_misc_read_val(
val: int = _internal.get_v1_account_misc_read_val(
'achLeaguePoints.' + self._name, self._award)
assert isinstance(val, int)
return val

View file

@ -15,7 +15,7 @@ if TYPE_CHECKING:
from typing import Any, Literal
import ba
TA = TypeVar('TA', bound='Actor')
ActorT = TypeVar('ActorT', bound='Actor')
class Actor:
@ -95,7 +95,7 @@ class Actor:
return UNHANDLED
def autoretain(self: TA) -> TA:
def autoretain(self: ActorT) -> ActorT:
"""Keep this Actor alive without needing to hold a reference to it.
This keeps the ba.Actor in existence by storing a reference to it

View file

@ -7,6 +7,7 @@ import time
from typing import TYPE_CHECKING
import _ba
from ba import _internal
if TYPE_CHECKING:
from typing import Callable, Any
@ -94,15 +95,15 @@ class AdsSubsystem:
launch_count = app.config.get('launchCount', 0)
# If we're seeing short ads we may want to space them differently.
interval_mult = (_ba.get_v1_account_misc_read_val(
interval_mult = (_internal.get_v1_account_misc_read_val(
'ads.shortIntervalMult', 1.0)
if self.last_ad_was_short else 1.0)
if self.ad_amt is None:
if launch_count <= 1:
self.ad_amt = _ba.get_v1_account_misc_read_val(
self.ad_amt = _internal.get_v1_account_misc_read_val(
'ads.startVal1', 0.99)
else:
self.ad_amt = _ba.get_v1_account_misc_read_val(
self.ad_amt = _internal.get_v1_account_misc_read_val(
'ads.startVal2', 1.0)
interval = None
else:
@ -111,15 +112,17 @@ class AdsSubsystem:
# (we reach our threshold faster the longer we've been
# playing).
base = 'ads' if _ba.has_video_ads() else 'ads2'
min_lc = _ba.get_v1_account_misc_read_val(base + '.minLC', 0.0)
max_lc = _ba.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
min_lc_scale = (_ba.get_v1_account_misc_read_val(
min_lc = _internal.get_v1_account_misc_read_val(
base + '.minLC', 0.0)
max_lc = _internal.get_v1_account_misc_read_val(
base + '.maxLC', 5.0)
min_lc_scale = (_internal.get_v1_account_misc_read_val(
base + '.minLCScale', 0.25))
max_lc_scale = (_ba.get_v1_account_misc_read_val(
max_lc_scale = (_internal.get_v1_account_misc_read_val(
base + '.maxLCScale', 0.34))
min_lc_interval = (_ba.get_v1_account_misc_read_val(
min_lc_interval = (_internal.get_v1_account_misc_read_val(
base + '.minLCInterval', 360))
max_lc_interval = (_ba.get_v1_account_misc_read_val(
max_lc_interval = (_internal.get_v1_account_misc_read_val(
base + '.maxLCInterval', 300))
if launch_count < min_lc:
lc_amt = 0.0

View file

@ -20,11 +20,13 @@ from ba._meta import MetadataSubsystem
from ba._ads import AdsSubsystem
from ba._net import NetworkSubsystem
from ba._workspace import WorkspaceSubsystem
from ba import _internal
if TYPE_CHECKING:
import asyncio
from typing import Any, Callable
import efro.log
import ba
from ba._cloud import CloudSubsystem
from bastd.actor import spazappearance
@ -48,6 +50,7 @@ class App:
# Implementations for these will be filled in by internal libs.
accounts_v2: AccountV2Subsystem
cloud: CloudSubsystem
log_handler: efro.log.LogHandler
class State(Enum):
"""High level state the app can be in."""
@ -91,6 +94,12 @@ class App:
assert isinstance(self._env['build_number'], int)
return self._env['build_number']
@property
def device_name(self) -> str:
"""Name of the device running the game."""
assert isinstance(self._env['device_name'], str)
return self._env['device_name']
@property
def config_file_path(self) -> str:
"""Where the game's config file is stored on disk."""
@ -223,6 +232,7 @@ class App:
self._launch_completed = False
self._initial_login_completed = False
self._meta_scan_completed = False
self._called_on_app_running = False
self._app_paused = False
@ -344,6 +354,7 @@ class App:
from bastd.actor import spazappearance
from ba._generated.enums import TimeType
assert _ba.in_logic_thread()
self._aioloop = _asyncio.setup_asyncio()
@ -370,12 +381,12 @@ class App:
# Non-test, non-debug builds should generally be blessed; warn if not.
# (so I don't accidentally release a build that can't play tourneys)
if (not self.debug_build and not self.test_build
and not _ba.is_blessed()):
and not _internal.is_blessed()):
_ba.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))
# If there's a leftover log file, attempt to upload it to the
# master-server and/or get rid of it.
_apputils.handle_leftover_log_file()
_apputils.handle_leftover_v1_cloud_log_file()
# Only do this stuff if our config file is healthy so we don't
# overwrite a broken one or whatnot and wipe out data.
@ -408,7 +419,8 @@ class App:
def check_special_offer() -> None:
from bastd.ui.specialoffer import show_offer
config = self.config
if ('pendingSpecialOffer' in config and _ba.get_public_login_id()
if ('pendingSpecialOffer' in config
and _internal.get_public_login_id()
== config['pendingSpecialOffer']['a']):
self.special_offer = config['pendingSpecialOffer']['o']
show_offer()
@ -416,6 +428,9 @@ class App:
if not self.headless_mode:
_ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)
# Get meta-system scanning built-in stuff in the bg.
self.meta.start_scan(scan_complete_cb=self.on_meta_scan_complete)
self.accounts_v2.on_app_launch()
self.accounts_v1.on_app_launch()
@ -430,17 +445,27 @@ class App:
def on_app_running(self) -> None:
"""Called when initially entering the running state."""
self.meta.on_app_running()
self.plugins.on_app_running()
# from ba._dependency import test_depset
# test_depset()
def on_meta_scan_complete(self) -> None:
"""Called by meta-scan when it is done doing its thing."""
assert _ba.in_logic_thread()
self.plugins.on_meta_scan_complete()
assert not self._meta_scan_completed
self._meta_scan_completed = True
self._update_state()
def _update_state(self) -> None:
assert _ba.in_logic_thread()
if self._app_paused:
self.state = self.State.PAUSED
else:
if self._initial_login_completed:
if self._initial_login_completed and self._meta_scan_completed:
self.state = self.State.RUNNING
if not self._called_on_app_running:
self._called_on_app_running = True
@ -562,11 +587,11 @@ class App:
# Kick off a little transaction so we'll hopefully have all the
# latest account state when we get back to the menu.
_ba.add_transaction({
_internal.add_transaction({
'type': 'END_SESSION',
'sType': str(type(host_session))
})
_ba.run_transactions()
_internal.run_transactions()
host_session.end()
@ -651,5 +676,9 @@ class App:
This should also run after a short amount of time if no login
has occurred.
"""
# Tell meta it can start scanning extra stuff that just showed up
# (account workspaces).
self.meta.start_extra_scan()
self._initial_login_completed = True
self._update_state()

View file

@ -128,12 +128,6 @@ def read_config() -> tuple[AppConfig, bool]:
shutil.copyfile(config_file_path, config_file_path + '.broken')
except Exception as exc2:
print('EXC copying broken config:', exc2)
try:
_ba.log('broken config contents:\n' +
config_contents.replace('\000', '<NULL_BYTE>'),
to_stdout=False)
except Exception as exc2:
print('EXC logging broken config contents:', exc2)
config = AppConfig()
# Now attempt to read one of our 'prev' backup copies.
@ -159,8 +153,9 @@ def commit_app_config(force: bool = False) -> None:
(internal)
"""
from ba._internal import mark_config_dirty
if not _ba.app.config_file_healthy and not force:
print('Current config file is broken; '
'skipping write to avoid losing settings.')
return
_ba.mark_config_dirty()
mark_config_dirty()

View file

@ -50,7 +50,7 @@ def should_submit_debug_info() -> bool:
return _ba.app.config.get('Submit Debug Info', True)
def handle_log() -> None:
def handle_v1_cloud_log() -> None:
"""Called on debug log prints.
When this happens, we can upload our log to the server
@ -58,6 +58,7 @@ def handle_log() -> None:
"""
from ba._net import master_server_post
from ba._generated.enums import TimeType
from ba._internal import get_news_show
app = _ba.app
app.log_have_new = True
if not app.log_upload_timer_started:
@ -73,7 +74,7 @@ def handle_log() -> None:
activityname = 'unavailable'
info = {
'log': _ba.getlog(),
'log': _ba.get_v1_cloud_log(),
'version': app.version,
'build': app.build_number,
'userAgentString': app.user_agent_string,
@ -82,8 +83,8 @@ def handle_log() -> None:
'fatal': 0,
'userRanCommands': _ba.has_user_run_commands(),
'time': _ba.time(TimeType.REAL),
'userModded': _ba.has_user_mods(),
'newsShow': _ba.get_news_show(),
'userModded': _ba.workspaces_in_use(),
'newsShow': get_news_show(),
}
def response(data: Any) -> None:
@ -107,7 +108,7 @@ def handle_log() -> None:
def _reset() -> None:
app.log_upload_timer_started = False
if app.log_have_new:
handle_log()
handle_v1_cloud_log()
if not _ba.is_log_full():
with _ba.Context('ui'):
@ -117,14 +118,15 @@ def handle_log() -> None:
suppress_format_warning=True)
def handle_leftover_log_file() -> None:
"""Handle an un-uploaded log from a previous run."""
def handle_leftover_v1_cloud_log_file() -> None:
"""Handle an un-uploaded v1-cloud-log from a previous run."""
try:
import json
from ba._net import master_server_post
if os.path.exists(_ba.get_log_file_path()):
with open(_ba.get_log_file_path(), encoding='utf-8') as infile:
if os.path.exists(_ba.get_v1_cloud_log_file_path()):
with open(_ba.get_v1_cloud_log_file_path(),
encoding='utf-8') as infile:
info = json.loads(infile.read())
infile.close()
do_send = should_submit_debug_info()
@ -135,7 +137,7 @@ def handle_leftover_log_file() -> None:
# lets kill it.
if data is not None:
try:
os.remove(_ba.get_log_file_path())
os.remove(_ba.get_v1_cloud_log_file_path())
except FileNotFoundError:
# Saw this in the wild. The file just existed
# a moment ago but I suppose something could have
@ -145,7 +147,7 @@ def handle_leftover_log_file() -> None:
master_server_post('bsLog', info, response)
else:
# If they don't want logs uploaded just kill it.
os.remove(_ba.get_log_file_path())
os.remove(_ba.get_v1_cloud_log_file_path())
except Exception:
from ba import _error
_error.print_exception('Error handling leftover log file.')

View file

@ -18,7 +18,7 @@ import os
if TYPE_CHECKING:
import ba
# Our timer and event loop for the ballistica game thread.
# Our timer and event loop for the ballistica logic thread.
_asyncio_timer: ba.Timer | None = None
_asyncio_event_loop: asyncio.AbstractEventLoop | None = None
@ -33,7 +33,7 @@ def setup_asyncio() -> asyncio.AbstractEventLoop:
import ba
from ba._generated.enums import TimeType
assert _ba.in_game_thread()
assert _ba.in_logic_thread()
# Create our event-loop. We don't expect there to be one
# running on this thread before we do.

175
dist/ba_data/python/ba/_bootstrap.py vendored Normal file
View file

@ -0,0 +1,175 @@
# Released under the MIT License. See LICENSE for details.
#
"""Bootstrapping."""
from __future__ import annotations
import os
import sys
from typing import TYPE_CHECKING
from efro.log import setup_logging, LogLevel
import _ba
if TYPE_CHECKING:
from typing import Any
from efro.log import LogEntry
_g_did_bootstrap = False # pylint: disable=invalid-name
def bootstrap() -> None:
"""Run bootstrapping logic.
This is the very first ballistica code that runs (aside from imports).
It sets up low level environment bits and creates the app instance.
"""
global _g_did_bootstrap # pylint: disable=global-statement, invalid-name
if _g_did_bootstrap:
raise RuntimeError('Bootstrap has already been called.')
_g_did_bootstrap = True
# The first thing we do is set up our logging system and feed
# Python's stdout/stderr into it. Then we can at least debug problems
# on systems where native stdout/stderr is not easily accessible
# such as Android.
log_handler = setup_logging(log_path=None,
level=LogLevel.DEBUG,
suppress_non_root_debug=True,
log_stdout_stderr=True,
cache_size_limit=1024 * 1024)
log_handler.add_callback(_on_log)
env = _ba.env()
# Give a soft warning if we're being used with a different binary
# version than we expect.
expected_build = 20882
running_build: int = env['build_number']
if running_build != expected_build:
print(
f'WARNING: These script files are meant to be used with'
f' Ballistica build {expected_build}.\n'
f' You are running build {running_build}.'
f' This might cause the app to error or misbehave.',
file=sys.stderr)
# In bootstrap_monolithic.py we told Python not to handle SIGINT itself
# (because that must be done in the main thread). Now we finish the
# job by adding our own handler to replace it.
# Note: I've found we need to set up our C signal handling AFTER
# we've told Python to disable its own; otherwise (on Mac at least) it
# wipes out our existing C handler.
_ba.setup_sigint()
# Sanity check: we should always be run in UTF-8 mode.
if sys.flags.utf8_mode != 1:
print(
'ERROR: Python\'s UTF-8 mode is not set.'
' This will likely result in errors.',
file=sys.stderr)
debug_build = env['debug_build']
# We expect dev_mode on in debug builds and off otherwise.
if debug_build != sys.flags.dev_mode:
print(
f'WARNING: Mismatch in debug_build {debug_build}'
f' and sys.flags.dev_mode {sys.flags.dev_mode}',
file=sys.stderr)
# In embedded situations (when we're providing our own Python) let's
# also provide our own root certs so ssl works. We can consider overriding
# this in particular embedded cases if we can verify that system certs
# are working.
# (We also allow forcing this via an env var if the user desires)
if (_ba.contains_python_dist()
or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1'):
import certifi
# Let both OpenSSL and requests (if present) know to use this.
os.environ['SSL_CERT_FILE'] = os.environ['REQUESTS_CA_BUNDLE'] = (
certifi.where())
# On Windows I'm seeing the following error creating asyncio loops in
# background threads with the default proactor setup:
# ValueError: set_wakeup_fd only works in main thread of the main
# interpreter
# So let's explicitly request selector loops.
# Interestingly this error only started showing up once I moved
# Python init to the main thread; previously the various asyncio
# bg thread loops were working fine (maybe something caused them
# to default to selector in that case?..
if sys.platform == 'win32':
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# pylint: disable=c-extension-no-member
if not TYPE_CHECKING:
import __main__
# Clear out the standard quit/exit messages since they don't
# work for us.
del __main__.__builtins__.quit
del __main__.__builtins__.exit
# Also replace standard interactive help with our simplified
# one which is more friendly to cloud/in-game console situations.
__main__.__builtins__.help = _CustomHelper()
# Now spin up our App instance and store it on both _ba and ba.
from ba._app import App
import ba
_ba.app = ba.app = App()
_ba.app.log_handler = log_handler
class _CustomHelper:
"""Replacement 'help' that behaves better for our setup."""
def __repr__(self) -> str:
return 'Type help(object) for help about object.'
def __call__(self, *args: Any, **kwds: Any) -> Any:
# We get an ugly error importing pydoc on our embedded
# platforms due to _sysconfigdata_xxx.py not being present
# (but then things mostly work). Let's get the ugly error out
# of the way explicitly.
import sysconfig
try:
# This errors once but seems to run cleanly after, so let's
# get the error out of the way.
sysconfig.get_path('stdlib')
except ModuleNotFoundError:
pass
import pydoc
# Disable pager and interactive help since neither works well
# with our funky multi-threaded setup or in-game/cloud consoles.
# Let's just do simple text dumps.
pydoc.pager = pydoc.plainpager
if not args and not kwds:
print('Interactive help is not available in this environment.\n'
'Type help(object) for help about object.')
return None
return pydoc.help(*args, **kwds)
def _on_log(entry: LogEntry) -> None:
# Just forward this along to the engine to display in the in-game console,
# in the Android log, etc.
_ba.display_log(
name=entry.name,
level=entry.level.name,
message=entry.message,
)
# We also want to feed some logs to the old V1-cloud-log system.
# Let's go with anything warning or higher as well as the stdout/stderr
# log messages that ba.app.log_handler creates for us.
if entry.level.value >= LogLevel.WARNING.value or entry.name in ('stdout',
'stderr'):
_ba.v1_cloud_log(entry.message)

View file

@ -99,3 +99,46 @@ class CloudSubsystem:
Must be called from a background thread.
"""
raise RuntimeError('Cloud functionality is not available.')
def cloud_console_exec(code: str) -> None:
"""Called by the cloud console to run code in the logic thread."""
import sys
import logging
import __main__
from ba._generated.enums import TimeType
try:
# First try it as eval.
try:
evalcode = compile(code, '<console>', 'eval')
except SyntaxError:
evalcode = None
except Exception:
# hmm; when we can't compile it as eval will we always get
# syntax error?
logging.exception(
'unexpected error compiling code for cloud-console eval.')
evalcode = None
if evalcode is not None:
# pylint: disable=eval-used
value = eval(evalcode, vars(__main__), vars(__main__))
# For eval-able statements, print the resulting value if
# it is not None (just like standard Python interpreter).
if value is not None:
print(repr(value), file=sys.stderr)
# Fall back to exec if we couldn't compile it as eval.
else:
execcode = compile(code, '<console>', 'exec')
# pylint: disable=exec-used
exec(execcode, vars(__main__), vars(__main__))
except Exception:
import traceback
apptime = _ba.time(TimeType.REAL)
print(f'Exec error at time {apptime:.2f}.', file=sys.stderr)
traceback.print_exc()
# This helps the logging system ship stderr back to the
# cloud promptly.
sys.stderr.flush()

View file

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar
import _ba
from ba import _internal
from ba._gameactivity import GameActivity
from ba._general import WeakCall
@ -54,19 +55,6 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
# Preload achievement images in case we get some.
_ba.timer(2.0, WeakCall(self._preload_achievements))
# Let's ask the server for a 'time-to-beat' value.
levelname = self._get_coop_level_name()
campaign = self.session.campaign
assert campaign is not None
config_str = (str(len(self.players)) + 'p' + campaign.getlevel(
self.settings_raw['name']).get_score_version_string().replace(
' ', '_'))
_ba.get_scores_to_beat(levelname, config_str,
WeakCall(self._on_got_scores_to_beat))
def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None:
pass
def _show_standard_scores_to_beat_ui(self,
scores: list[dict[str, Any]]) -> None:
from efro.util import asserttype
@ -217,10 +205,10 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
self._achievements_awarded.add(achievement_name)
# Report new achievements to the game-service.
_ba.report_achievement(achievement_name)
_internal.report_achievement(achievement_name)
# ...and to our account.
_ba.add_transaction({
_internal.add_transaction({
'type': 'ACHIEVEMENT',
'name': achievement_name
})

View file

@ -125,6 +125,11 @@ class WidgetNotFoundError(NotFoundError):
"""
# TODO: Should integrate some sort of context printing into our
# log handling so we can just use logging.exception() and kill these
# two functions.
def print_exception(*args: Any, **keywds: Any) -> None:
"""Print info about an exception along with pertinent context state.

View file

@ -9,6 +9,7 @@ import random
from typing import TYPE_CHECKING, TypeVar
import _ba
from ba import _internal
from ba._activity import Activity
from ba._score import ScoreConfig
from ba._language import Lstr
@ -17,6 +18,7 @@ from ba._error import NotFoundError, print_error, print_exception
from ba._general import Call, WeakCall
from ba._player import PlayerInfo
from ba import _map
from ba import _store
if TYPE_CHECKING:
from typing import Any, Callable, Sequence
@ -239,11 +241,11 @@ class GameActivity(Activity[PlayerType, TeamType]):
self._zoom_message_times: dict[int, float] = {}
self._is_waiting_for_continue = False
self._continue_cost = _ba.get_v1_account_misc_read_val(
self._continue_cost = _internal.get_v1_account_misc_read_val(
'continueStartCost', 25)
self._continue_cost_mult = _ba.get_v1_account_misc_read_val(
self._continue_cost_mult = _internal.get_v1_account_misc_read_val(
'continuesMult', 2)
self._continue_cost_offset = _ba.get_v1_account_misc_read_val(
self._continue_cost_offset = _internal.get_v1_account_misc_read_val(
'continuesOffset', 0)
@property
@ -363,11 +365,11 @@ class GameActivity(Activity[PlayerType, TeamType]):
if do_continue:
_ba.playsound(_ba.getsound('shieldUp'))
_ba.playsound(_ba.getsound('cashRegister'))
_ba.add_transaction({
_internal.add_transaction({
'type': 'CONTINUE',
'cost': self._continue_cost
})
_ba.run_transactions()
_internal.run_transactions()
self._continue_cost = (
self._continue_cost * self._continue_cost_mult +
self._continue_cost_offset)
@ -390,7 +392,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
from ba._generated.enums import TimeType
try:
if _ba.get_v1_account_misc_read_val('enableContinues', False):
if _internal.get_v1_account_misc_read_val('enableContinues',
False):
session = self.session
# We only support continuing in non-tournament games.
@ -453,7 +456,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
# time is left.
tournament_id = self.session.tournament_id
if tournament_id is not None:
_ba.tournament_query(
_internal.tournament_query(
args={
'tournamentIDs': [tournament_id],
'source': 'in-game time remaining query'
@ -1159,7 +1162,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
else:
# If settings doesn't specify a map, pick a random one from the
# list of supported ones.
unowned_maps = _map.get_unowned_maps()
unowned_maps = _store.get_unowned_maps()
valid_maps: list[str] = [
m for m in self.get_supported_maps(type(self.session))
if m not in unowned_maps

View file

@ -31,13 +31,11 @@ class Existable(Protocol):
"""Whether this object exists."""
# pylint: disable=invalid-name
ExistableType = TypeVar('ExistableType', bound=Existable)
# pylint: enable=invalid-name
ExistableT = TypeVar('ExistableT', bound=Existable)
T = TypeVar('T')
def existing(obj: ExistableType | None) -> ExistableType | None:
def existing(obj: ExistableT | None) -> ExistableT | None:
"""Convert invalid references to None for any ba.Existable object.
Category: **Gameplay Functions**
@ -251,6 +249,10 @@ class _Call:
if TYPE_CHECKING:
# Some interaction between our ballistica pylint plugin
# and this code is crashing starting on pylint 2.15.0.
# This seems to fix things for now.
# pylint: disable=all
WeakCall = Call
Call = Call
else:

View file

@ -16,6 +16,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
from ba import _internal
if TYPE_CHECKING:
from typing import Sequence, Any
@ -24,10 +25,10 @@ if TYPE_CHECKING:
def finish_bootstrapping() -> None:
"""Do final bootstrapping related bits."""
assert _ba.in_game_thread()
assert _ba.in_logic_thread()
# Kick off our asyncio event handling, allowing us to use coroutines
# in our game thread alongside our internal event handling.
# in our logic thread alongside our internal event handling.
# setup_asyncio()
# Ok, bootstrapping is done; time to get the show started.
@ -189,8 +190,8 @@ def unavailable_message() -> None:
def submit_analytics_counts(sval: str) -> None:
_ba.add_transaction({'type': 'ANALYTICS_COUNTS', 'values': sval})
_ba.run_transactions()
_internal.add_transaction({'type': 'ANALYTICS_COUNTS', 'values': sval})
_internal.run_transactions()
def set_last_ad_network(sval: str) -> None:

View file

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
from ba._internal import get_v1_account_display_string
if TYPE_CHECKING:
from typing import Any
@ -639,5 +640,5 @@ def get_last_player_name_from_input_device(device: ba.InputDevice) -> str:
if profilename == '_random':
profilename = device.get_default_player_name()
if profilename == '__account__':
profilename = _ba.get_v1_account_display_string()
profilename = get_v1_account_display_string()
return profilename

367
dist/ba_data/python/ba/_internal.py vendored Normal file
View file

@ -0,0 +1,367 @@
# Released under the MIT License. See LICENSE for details.
#
"""A soft wrapper around _bainternal.
This allows code to use _bainternal functionality and get warnings
or fallbacks in some cases instead of hard errors. Code that absolutely
relies on the presence of _bainternal can just use that module directly.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
try:
# noinspection PyUnresolvedReferences
import _bainternal
HAVE_INTERNAL = True
except ImportError:
HAVE_INTERNAL = False
if TYPE_CHECKING:
from typing import Callable, Any
# Code that will function without _bainternal but which should be updated
# to account for its absence should call this to draw attention to itself.
def _no_bainternal_warning() -> None:
import logging
logging.warning('INTERNAL CALL RUN WITHOUT INTERNAL PRESENT.')
# Code that won't work without _bainternal should raise these errors.
def _no_bainternal_error() -> RuntimeError:
raise RuntimeError('_bainternal is not present')
def get_v2_fleet() -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v2_fleet()
raise _no_bainternal_error()
def get_master_server_address(source: int = -1, version: int = 1) -> str:
"""(internal)
Return the address of the master server.
"""
if HAVE_INTERNAL:
return _bainternal.get_master_server_address(source=source,
version=version)
raise _no_bainternal_error()
def is_blessed() -> bool:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.is_blessed()
# Harmless to always just say no here.
return False
def get_news_show() -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_news_show()
raise _no_bainternal_error()
def game_service_has_leaderboard(game: str, config: str) -> bool:
"""(internal)
Given a game and config string, returns whether there is a leaderboard
for it on the game service.
"""
if HAVE_INTERNAL:
return _bainternal.game_service_has_leaderboard(game=game,
config=config)
# Harmless to always just say no here.
return False
def report_achievement(achievement: str, pass_to_account: bool = True) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.report_achievement(achievement=achievement,
pass_to_account=pass_to_account)
return
# Need to see if this actually still works as expected.. warning for now.
_no_bainternal_warning()
# noinspection PyUnresolvedReferences
def submit_score(game: str,
config: str,
name: Any,
score: int | None,
callback: Callable,
friend_callback: Callable | None,
order: str = 'increasing',
tournament_id: str | None = None,
score_type: str = 'points',
campaign: str | None = None,
level: str | None = None) -> None:
"""(internal)
Submit a score to the server; callback will be called with the results.
As a courtesy, please don't send fake scores to the server. I'd prefer
to devote my time to improving the game instead of trying to make the
score server more mischief-proof.
"""
if HAVE_INTERNAL:
_bainternal.submit_score(game=game,
config=config,
name=name,
score=score,
callback=callback,
friend_callback=friend_callback,
order=order,
tournament_id=tournament_id,
score_type=score_type,
campaign=campaign,
level=level)
return
# This technically breaks since callback will never be called/etc.
raise _no_bainternal_error()
def tournament_query(callback: Callable[[dict | None], None],
args: dict) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.tournament_query(callback=callback, args=args)
return
# This technically breaks since callback will never be called/etc.
raise _no_bainternal_error()
def power_ranking_query(callback: Callable, season: Any = None) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.power_ranking_query(callback=callback, season=season)
return
# This technically breaks since callback will never be called/etc.
raise _no_bainternal_error()
def restore_purchases() -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.restore_purchases()
return
# This shouldn't break anything but should try to avoid calling it.
_no_bainternal_warning()
def purchase(item: str) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.purchase(item)
return
# This won't break messily but won't function as intended.
_no_bainternal_warning()
def get_purchases_state() -> int:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_purchases_state()
# This won't function correctly without internal.
raise _no_bainternal_error()
def get_purchased(item: str) -> bool:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_purchased(item)
# Without internal we can just assume no purchases.
return False
def get_price(item: str) -> str | None:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_price(item)
# Without internal we can just assume no prices.
return None
def in_game_purchase(item: str, price: int) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.in_game_purchase(item=item, price=price)
return
# Without internal this doesn't function as expected.
raise _no_bainternal_error()
# noinspection PyUnresolvedReferences
def add_transaction(transaction: dict,
callback: Callable | None = None) -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.add_transaction(transaction=transaction, callback=callback)
return
# This won't function correctly without internal (callback never called).
raise _no_bainternal_error()
def reset_achievements() -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.reset_achievements()
return
# Technically doesnt break but won't do anything.
_no_bainternal_warning()
def get_public_login_id() -> str | None:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_public_login_id()
# Harmless to return nothing in this case.
return None
def have_outstanding_transactions() -> bool:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.have_outstanding_transactions()
# Harmless to return False here.
return False
def run_transactions() -> None:
"""(internal)"""
if HAVE_INTERNAL:
_bainternal.run_transactions()
# Harmless no-op in this case.
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_misc_read_val(
name=name, default_value=default_value)
raise _no_bainternal_error()
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_misc_read_val_2(
name=name, default_value=default_value)
raise _no_bainternal_error()
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_misc_val(name=name,
default_value=default_value)
raise _no_bainternal_error()
def get_v1_account_ticket_count() -> int:
"""(internal)
Returns the number of tickets for the current account.
"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_ticket_count()
return 0
def get_v1_account_state_num() -> int:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_state_num()
return 0
def get_v1_account_state() -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_state()
raise _no_bainternal_error()
def get_v1_account_display_string(full: bool = True) -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_display_string(full=full)
raise _no_bainternal_error()
def get_v1_account_type() -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_type()
raise _no_bainternal_error()
def get_v1_account_name() -> str:
"""(internal)"""
if HAVE_INTERNAL:
return _bainternal.get_v1_account_name()
raise _no_bainternal_error()
def sign_out_v1(v2_embedded: bool = False) -> None:
"""(internal)
Category: General Utility Functions
"""
if HAVE_INTERNAL:
_bainternal.sign_out_v1(v2_embedded=v2_embedded)
return
raise _no_bainternal_error()
def sign_in_v1(account_type: str) -> None:
"""(internal)
Category: General Utility Functions
"""
if HAVE_INTERNAL:
_bainternal.sign_in_v1(account_type=account_type)
return
raise _no_bainternal_error()
def mark_config_dirty() -> None:
"""(internal)
Category: General Utility Functions
"""
if HAVE_INTERNAL:
_bainternal.mark_config_dirty()
return
# Note to self - need to fix config writing to not rely on
# internal lib.
_no_bainternal_warning()

View file

@ -101,22 +101,6 @@ def getmaps(playtype: str) -> list[str]:
if playtype in val.get_play_types())
def get_unowned_maps() -> list[str]:
"""Return the list of local maps not owned by the current account.
Category: **Asset Functions**
"""
from ba import _store
unowned_maps: set[str] = set()
if not _ba.app.headless_mode:
for map_section in _store.get_store_layout()['maps']:
for mapitem in map_section['items']:
if not _ba.get_purchased(mapitem):
m_info = _store.get_store_item(mapitem)
unowned_maps.add(m_info['map_type'].name)
return sorted(unowned_maps)
def get_map_class(name: str) -> type[ba.Map]:
"""Return a map type given a name.

View file

@ -6,33 +6,50 @@ from __future__ import annotations
import os
import time
import threading
import logging
from threading import Thread
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, TypeVar
from dataclasses import dataclass, field
from efro.call import tpartial
import _ba
if TYPE_CHECKING:
import ba
from typing import Callable
# 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 #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
# Meta export lines can use these names to represent these classes.
# This is purely a convenience; it is possible to use full class paths
# instead of these or to make the meta system aware of arbitrary classes.
EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = {
'plugin': 'ba.Plugin',
'keyboard': 'ba.Keyboard',
'game': 'ba.GameActivity',
}
T = TypeVar('T')
@dataclass
class ScanResults:
"""Final results from a metadata scan."""
games: list[str] = field(default_factory=list)
plugins: list[str] = field(default_factory=list)
keyboards: list[str] = field(default_factory=list)
errors: str = ''
warnings: str = ''
"""Final results from a meta-scan."""
exports: dict[str, list[str]] = field(default_factory=dict)
errors: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
def exports_of_class(self, cls: type) -> list[str]:
"""Return exports of a given class."""
return self.exports.get(f'{cls.__module__}.{cls.__qualname__}', [])
class MetadataSubsystem:
@ -44,99 +61,98 @@ class MetadataSubsystem:
"""
def __init__(self) -> None:
self.scanresults: ScanResults | None = None
self._scan: DirectoryScan | None = None
# Can be populated before starting the scan.
self.extra_scan_dirs: list[str] = []
def on_app_running(self) -> None:
"""Should be called when the app enters the running state."""
# Results populated once scan is complete.
self.scanresults: ScanResults | None = None
# Start scanning for things exposed via ba_meta.
self.start_scan()
self._scan_complete_cb: Callable[[], None] | None = None
def start_scan(self) -> None:
"""Begin scanning script directories for scripts containing metadata.
def start_scan(self, scan_complete_cb: Callable[[], None]) -> None:
"""Begin the overall scan.
Should be called only once at launch."""
app = _ba.app
if self.scanresults is not None:
print('WARNING: meta scan run more than once.')
pythondirs = ([app.python_directory_app, app.python_directory_user] +
self.extra_scan_dirs)
thread = ScanThread(pythondirs)
thread.start()
This will start scanning built in directories (which for vanilla
installs should be the vast majority of the work). This should only
be called once.
"""
assert self._scan_complete_cb is None
assert self._scan is None
def handle_scan_results(self, results: ScanResults) -> None:
"""Called in the game thread with results of a completed scan."""
self._scan_complete_cb = scan_complete_cb
self._scan = DirectoryScan(
[_ba.app.python_directory_app, _ba.app.python_directory_user])
from ba._language import Lstr
from ba._plugin import PotentialPlugin
Thread(target=self._run_scan_in_bg, daemon=True).start()
# Warnings generally only get printed locally for users' benefit
# (things like out-of-date scripts being ignored, etc.)
# Errors are more serious and will get included in the regular log
# warnings = results.get('warnings', '')
# errors = results.get('errors', '')
if results.warnings != '' or results.errors != '':
import textwrap
_ba.screenmessage(Lstr(resource='scanScriptsErrorText'),
color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
if results.warnings != '':
_ba.log(textwrap.indent(results.warnings,
'Warning (meta-scan): '),
to_server=False)
if results.errors != '':
_ba.log(textwrap.indent(results.errors, 'Error (meta-scan): '))
def start_extra_scan(self) -> None:
"""Proceed to the extra_scan_dirs portion of the scan.
# Handle plugins.
plugs = _ba.app.plugins
config_changed = False
found_new = False
plugstates: dict[str, dict] = _ba.app.config.setdefault('Plugins', {})
assert isinstance(plugstates, dict)
This is for parts of the scan that must be delayed until
workspace sync completion or other such events. This must be
called exactly once.
"""
assert self._scan is not None
self._scan.set_extras(self.extra_scan_dirs)
# Create a potential-plugin for each class we found in the scan.
for class_path in results.plugins:
plugs.potential_plugins.append(
PotentialPlugin(display_name=Lstr(value=class_path),
class_path=class_path,
available=True))
if class_path not in plugstates:
# 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
def load_exported_classes(
self,
cls: type[T],
completion_cb: Callable[[list[type[T]]], None],
completion_cb_in_bg_thread: bool = False,
) -> None:
"""High level function to load meta-exported classes.
# Also add a special one for any plugins set to load but *not* found
# in the scan (this way they will show up in the UI so we can disable
# them)
for class_path, plugstate in plugstates.items():
enabled = plugstate.get('enabled', False)
assert isinstance(enabled, bool)
if enabled and class_path not in results.plugins:
plugs.potential_plugins.append(
PotentialPlugin(display_name=Lstr(value=class_path),
class_path=class_path,
available=False))
Will wait for scanning to complete if necessary, and will load all
registered classes of a particular type in a background thread before
calling the passed callback in the logic thread. Errors may be logged
to messaged to the user in some way but the callback will be called
regardless.
To run the completion callback directly in the bg thread where the
loading work happens, pass completion_cb_in_bg_thread=True.
"""
Thread(
target=tpartial(self._load_exported_classes, cls, completion_cb,
completion_cb_in_bg_thread),
daemon=True,
).start()
plugs.potential_plugins.sort(key=lambda p: p.class_path)
def _load_exported_classes(
self,
cls: type[T],
completion_cb: Callable[[list[type[T]]], None],
completion_cb_in_bg_thread: bool,
) -> None:
from ba._general import getclass
classes: list[type[T]] = []
try:
classnames = self._wait_for_scan_results().exports_of_class(cls)
for classname in classnames:
try:
classes.append(getclass(classname, cls))
except Exception:
logging.exception('error importing %s', classname)
if found_new:
_ba.screenmessage(Lstr(resource='pluginsDetectedText'),
color=(0, 1, 0))
_ba.playsound(_ba.getsound('ding'))
except Exception:
logging.exception('Error loading exported classes.')
if config_changed:
_ba.app.config.commit()
completion_call = tpartial(completion_cb, classes)
if completion_cb_in_bg_thread:
completion_call()
else:
_ba.pushcall(completion_call, from_other_thread=True)
def get_scan_results(self) -> ScanResults:
"""Return meta scan results; block if the scan is not yet complete."""
def _wait_for_scan_results(self) -> ScanResults:
"""Return scan results, blocking if the scan is not yet complete."""
if self.scanresults is None:
print('WARNING: ba.meta.get_scan_results()'
' called before scan completed.'
' This can cause hitches.')
if _ba.in_logic_thread():
logging.warning(
'ba.meta._wait_for_scan_results()'
' called in logic thread before scan completed;'
' this can cause hitches.')
# Now wait a bit for the scan to complete.
# Eventually error though if it doesn't.
@ -148,69 +164,53 @@ class MetadataSubsystem:
'timeout waiting for meta scan to complete.')
return self.scanresults
def get_game_types(self) -> list[type[ba.GameActivity]]:
"""Return available game types."""
from ba._general import getclass
from ba._gameactivity import GameActivity
gameclassnames = self.get_scan_results().games
gameclasses = []
for gameclassname in gameclassnames:
try:
cls = getclass(gameclassname, GameActivity)
gameclasses.append(cls)
except Exception:
from ba import _error
_error.print_exception('error importing ' + str(gameclassname))
unowned = self.get_unowned_game_types()
return [cls for cls in gameclasses if cls not in unowned]
def get_unowned_game_types(self) -> set[type[ba.GameActivity]]:
"""Return present game types not owned by the current account."""
def _run_scan_in_bg(self) -> None:
"""Runs a scan (for use in background thread)."""
try:
from ba import _store
unowned_games: set[type[ba.GameActivity]] = set()
if not _ba.app.headless_mode:
for section in _store.get_store_layout()['minigames']:
for mname in section['items']:
if not _ba.get_purchased(mname):
m_info = _store.get_store_item(mname)
unowned_games.add(m_info['gametype'])
return unowned_games
except Exception:
from ba import _error
_error.print_exception('error calcing un-owned games')
return set()
class ScanThread(threading.Thread):
"""Thread to scan script dirs for metadata."""
def __init__(self, dirs: list[str]):
super().__init__()
self._dirs = dirs
def run(self) -> None:
from ba._general import Call
try:
scan = DirectoryScan(self._dirs)
scan.scan()
results = scan.results
assert self._scan is not None
self._scan.run()
results = self._scan.results
self._scan = None
except Exception as exc:
results = ScanResults(errors=f'Scan exception: {exc}')
results = ScanResults(errors=[f'Scan exception: {exc}'])
# Push a call to the game thread to print warnings/errors
# or otherwise deal with scan results.
_ba.pushcall(Call(_ba.app.meta.handle_scan_results, results),
from_other_thread=True)
# Place results and tell the logic thread they're ready.
self.scanresults = results
_ba.pushcall(self._handle_scan_results, from_other_thread=True)
# We also, however, immediately make results available.
# This is because the game thread may be blocked waiting
# for them so we can't push a call or we'd get deadlock.
_ba.app.meta.scanresults = results
def _handle_scan_results(self) -> None:
"""Called in the logic thread with results of a completed scan."""
from ba._language import Lstr
assert _ba.in_logic_thread()
results = self.scanresults
assert results is not None
# Spit out any warnings/errors that happened.
# Warnings generally only get printed locally for users' benefit
# (things like out-of-date scripts being ignored, etc.)
# Errors are more serious and will get included in the regular log.
if results.warnings or results.errors:
import textwrap
_ba.screenmessage(Lstr(resource='scanScriptsErrorText'),
color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
if results.warnings:
allwarnings = textwrap.indent('\n'.join(results.warnings),
'Warning (meta-scan): ')
logging.warning(allwarnings)
if results.errors:
allerrors = textwrap.indent('\n'.join(results.errors),
'Error (meta-scan): ')
logging.error(allerrors)
# Let the game know we're done.
assert self._scan_complete_cb is not None
self._scan_complete_cb()
class DirectoryScan:
"""Handles scanning directories for metadata."""
"""Scans directories for metadata."""
def __init__(self, paths: list[str]):
"""Given one or more paths, parses available meta information.
@ -220,9 +220,42 @@ class DirectoryScan:
"""
# Skip non-existent paths completely.
self.paths = [Path(p) for p in paths if os.path.isdir(p)]
self.base_paths = [Path(p) for p in paths if os.path.isdir(p)]
self.extra_paths: list[Path] = []
self.extra_paths_set = False
self.results = ScanResults()
def set_extras(self, paths: list[str]) -> None:
"""Set extra portion."""
# Skip non-existent paths completely.
self.extra_paths += [Path(p) for p in paths if os.path.isdir(p)]
self.extra_paths_set = True
def run(self) -> None:
"""Do the thing."""
for pathlist in [self.base_paths, self.extra_paths]:
# Spin and wait until extra paths are provided before doing them.
if pathlist is self.extra_paths:
while not self.extra_paths_set:
time.sleep(0.001)
modules: list[tuple[Path, Path]] = []
for path in pathlist:
self._get_path_module_entries(path, '', modules)
for moduledir, subpath in modules:
try:
self._scan_module(moduledir, subpath)
except Exception:
import traceback
self.results.warnings.append(
f"Error scanning '{subpath}': " +
traceback.format_exc())
# Sort our results
for exportlist in self.results.exports.values():
exportlist.sort()
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."""
@ -237,7 +270,7 @@ class DirectoryScan:
entries = []
except Exception as exc:
# Unexpected; report this.
self.results.errors += f'{exc}\n'
self.results.errors.append(str(exc))
entries = []
# Now identify python packages/modules out of what we found.
@ -248,24 +281,7 @@ class DirectoryScan:
and Path(entry[0], entry[1], '__init__.py').is_file()):
modules.append(entry)
def scan(self) -> None:
"""Scan provided paths."""
modules: list[tuple[Path, Path]] = []
for path in self.paths:
self._get_path_module_entries(path, '', modules)
for moduledir, subpath in modules:
try:
self.scan_module(moduledir, subpath)
except Exception:
import traceback
self.results.warnings += ("Error scanning '" + str(subpath) +
"': " + traceback.format_exc() +
'\n')
# Sort our results
self.results.games.sort()
self.results.plugins.sort()
def scan_module(self, moduledir: Path, subpath: 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 = Path(moduledir, subpath)
@ -279,19 +295,20 @@ class DirectoryScan:
lnum: l[1:].split()
for lnum, l in enumerate(flines) if '# ba_meta ' in l
}
toplevel = len(subpath.parts) <= 1
required_api = self.get_api_requirement(subpath, meta_lines, toplevel)
is_top_level = len(subpath.parts) <= 1
required_api = self._get_api_requirement(subpath, meta_lines,
is_top_level)
# Top level modules with no discernible api version get ignored.
if toplevel and required_api is None:
if is_top_level and required_api is None:
return
# 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')
f' we are running {CURRENT_API_VERSION}; ignoring module.')
return
# Ok; can proceed with a full scan of this module.
@ -304,11 +321,11 @@ class DirectoryScan:
self._get_path_module_entries(moduledir, subpath, submodules)
for submodule in submodules:
if submodule[1].name != '__init__.py':
self.scan_module(submodule[0], submodule[1])
self._scan_module(submodule[0], submodule[1])
except Exception:
import traceback
self.results.warnings += (
f"Error scanning '{subpath}': {traceback.format_exc()}\n")
self.results.warnings.append(
f"Error scanning '{subpath}': {traceback.format_exc()}")
def _process_module_meta_tags(self, subpath: Path, flines: list[str],
meta_lines: dict[int, list[str]]) -> None:
@ -317,10 +334,9 @@ class DirectoryScan:
# meta_lines is just anything containing '# ba_meta '; make sure
# the ba_meta is in the right place.
if mline[0] != 'ba_meta':
self.results.warnings += (
'Warning: ' + str(subpath) +
': malformed ba_meta statement on line ' +
str(lindex + 1) + '.\n')
self.results.warnings.append(
f'Warning: {subpath}:'
f' malformed ba_meta statement on line {lindex + 1}.')
elif (len(mline) == 4 and mline[1] == 'require'
and mline[2] == 'api'):
# Ignore 'require api X' lines in this pass.
@ -328,31 +344,28 @@ class DirectoryScan:
elif len(mline) != 3 or mline[1] != 'export':
# Currently we only support 'ba_meta export FOO';
# complain for anything else we see.
self.results.warnings += (
'Warning: ' + str(subpath) +
': unrecognized ba_meta statement on line ' +
str(lindex + 1) + '.\n')
self.results.warnings.append(
f'Warning: {subpath}'
f': unrecognized ba_meta statement on line {lindex + 1}.')
else:
# Looks like we've got a valid export line!
modulename = '.'.join(subpath.parts)
if subpath.name.endswith('.py'):
modulename = modulename[:-3]
exporttype = mline[2]
exporttypestr = mline[2]
export_class_name = self._get_export_class_name(
subpath, flines, lindex)
if export_class_name is not None:
classname = modulename + '.' + export_class_name
if exporttype == 'game':
self.results.games.append(classname)
elif exporttype == 'plugin':
self.results.plugins.append(classname)
elif exporttype == 'keyboard':
self.results.keyboards.append(classname)
else:
self.results.warnings += (
'Warning: ' + str(subpath) +
': unrecognized export type "' + exporttype +
'" on line ' + str(lindex + 1) + '.\n')
# If export type is one of our shortcuts, sub in the
# actual class path. Otherwise assume its a classpath
# itself.
exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr)
if exporttype is None:
exporttype = exporttypestr
self.results.exports.setdefault(exporttype,
[]).append(classname)
def _get_export_class_name(self, subpath: Path, lines: list[str],
lindex: int) -> str | None:
@ -374,13 +387,12 @@ class DirectoryScan:
classname = cbits[0]
break # Success!
if classname is None:
self.results.warnings += (
'Warning: ' + str(subpath) + ': class definition not found'
' below "ba_meta export" statement on line ' +
str(lindexorig + 1) + '.\n')
self.results.warnings.append(
f'Warning: {subpath}: class definition not found below'
f' "ba_meta export" statement on line {lindexorig + 1}.')
return classname
def get_api_requirement(
def _get_api_requirement(
self,
subpath: Path,
meta_lines: dict[int, list[str]],
@ -401,15 +413,15 @@ class DirectoryScan:
# Ok; not successful. lets issue warnings for a few error cases.
if len(lines) > 1:
self.results.warnings += (
'Warning: ' + str(subpath) +
': multiple "# ba_meta require api <NUM>" lines found;'
' ignoring module.\n')
self.results.warnings.append(
f'Warning: {subpath}: multiple'
' "# ba_meta require api <NUM>" lines found;'
' ignoring module.')
elif not lines and toplevel and meta_lines:
# If we're a top-level module containing meta lines but
# no valid "require api" line found, complain.
self.results.warnings += (
'Warning: ' + str(subpath) +
': no valid "# ba_meta require api <NUM>" line found;'
' ignoring module.\n')
self.results.warnings.append(
f'Warning: {subpath}:'
' no valid "# ba_meta require api <NUM>" line found;'
' ignoring module.')
return None

View file

@ -94,9 +94,11 @@ class MultiTeamSession(Session):
playlist = _playlist.get_default_free_for_all_playlist()
# Resolve types and whatnot to get our final playlist.
playlist_resolved = _playlist.filter_playlist(playlist,
sessiontype=type(self),
add_resolved_type=True)
playlist_resolved = _playlist.filter_playlist(
playlist,
sessiontype=type(self),
add_resolved_type=True,
name='default teams' if self.use_teams else 'default ffa')
if not playlist_resolved:
raise RuntimeError('Playlist contains no valid games.')

View file

@ -134,15 +134,16 @@ class MasterServerCallThread(threading.Thread):
import json
from efro.error import is_urllib_communication_error
from ba import _general
from ba._general import Call, utf8_all
from ba._internal import get_master_server_address
response_data: Any = None
url: str | None = None
try:
self._data = _general.utf8_all(self._data)
self._data = utf8_all(self._data)
_ba.set_thread_name('BA_ServerCallThread')
if self._request_type == 'get':
url = (_ba.get_master_server_address() + '/' + self._request +
url = (get_master_server_address() + '/' + self._request +
'?' + urllib.parse.urlencode(self._data))
response = urllib.request.urlopen(
urllib.request.Request(
@ -150,7 +151,7 @@ class MasterServerCallThread(threading.Thread):
context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
elif self._request_type == 'post':
url = _ba.get_master_server_address() + '/' + self._request
url = get_master_server_address() + '/' + self._request
response = urllib.request.urlopen(
urllib.request.Request(
url,
@ -189,7 +190,7 @@ class MasterServerCallThread(threading.Thread):
response_data = None
if self._callback is not None:
_ba.pushcall(_general.Call(self._run_callback, response_data),
_ba.pushcall(Call(self._run_callback, response_data),
from_other_thread=True)

View file

@ -5,6 +5,7 @@
from __future__ import annotations
import copy
import logging
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
@ -18,7 +19,8 @@ def filter_playlist(playlist: PlaylistType,
sessiontype: type[_session.Session],
add_resolved_type: bool = False,
remove_unowned: bool = True,
mark_unowned: bool = False) -> PlaylistType:
mark_unowned: bool = False,
name: str = '?') -> PlaylistType:
"""Return a filtered version of a playlist.
Strips out or replaces invalid or unowned game types, makes sure all
@ -28,15 +30,15 @@ def filter_playlist(playlist: PlaylistType,
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
import _ba
from ba import _map
from ba import _general
from ba import _gameactivity
from ba._map import get_filtered_map_name
from ba._store import get_unowned_maps, get_unowned_game_types
from ba._general import getclass
from ba._gameactivity import GameActivity
goodlist: list[dict] = []
unowned_maps: Sequence[str]
if remove_unowned or mark_unowned:
unowned_maps = _map.get_unowned_maps()
unowned_game_types = _ba.app.meta.get_unowned_game_types()
unowned_maps = get_unowned_maps()
unowned_game_types = get_unowned_game_types()
else:
unowned_maps = []
unowned_game_types = set()
@ -53,7 +55,7 @@ def filter_playlist(playlist: PlaylistType,
del entry['map']
# Update old map names to new ones.
entry['settings']['map'] = _map.get_filtered_map_name(
entry['settings']['map'] = get_filtered_map_name(
entry['settings']['map'])
if remove_unowned and entry['settings']['map'] in unowned_maps:
continue
@ -120,8 +122,7 @@ def filter_playlist(playlist: PlaylistType,
entry['type'] = (
'bastd.game.targetpractice.TargetPracticeGame')
gameclass = _general.getclass(entry['type'],
_gameactivity.GameActivity)
gameclass = getclass(entry['type'], GameActivity)
if remove_unowned and gameclass in unowned_game_types:
continue
@ -139,7 +140,8 @@ def filter_playlist(playlist: PlaylistType,
entry['settings'][setting.name] = setting.default
goodlist.append(entry)
except ImportError as exc:
print(f'Import failed while scanning playlist: {exc}')
logging.warning('Import failed while scanning playlist \'%s\': %s',
name, exc)
except Exception:
from ba import _error
_error.print_exception()

View file

@ -4,6 +4,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from dataclasses import dataclass
@ -25,6 +26,46 @@ class PluginSubsystem:
self.potential_plugins: list[ba.PotentialPlugin] = []
self.active_plugins: dict[str, ba.Plugin] = {}
def on_meta_scan_complete(self) -> None:
"""Should be called when meta-scanning is complete."""
from ba._language import Lstr
plugs = _ba.app.plugins
config_changed = False
found_new = False
plugstates: dict[str, dict] = _ba.app.config.setdefault('Plugins', {})
assert isinstance(plugstates, dict)
results = _ba.app.meta.scanresults
assert results is not None
# Create a potential-plugin for each class we found in the scan.
for class_path in results.exports_of_class(Plugin):
plugs.potential_plugins.append(
PotentialPlugin(display_name=Lstr(value=class_path),
class_path=class_path,
available=True))
if class_path not in plugstates:
# 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
plugs.potential_plugins.sort(key=lambda p: p.class_path)
# Note: these days we complete meta-scan and immediately activate
# plugins, so we don't need the message about 'restart to activate'
# anymore.
if found_new and bool(False):
_ba.screenmessage(Lstr(resource='pluginsDetectedText'),
color=(0, 1, 0))
_ba.playsound(_ba.getsound('ding'))
if config_changed:
_ba.app.config.commit()
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.
@ -69,10 +110,7 @@ class PluginSubsystem:
from ba._language import Lstr
# Note: the plugins we load is purely based on what's enabled
# in the app config. Our meta-scan gives us a list of available
# plugins, but that is only used to give the user a list of plugins
# that they can enable. (we wouldn't want to look at meta-scan here
# anyway because it may not be done yet at this point in the launch)
# in the app config. Its not our job to look at meta stuff here.
plugstates: dict[str, dict] = _ba.app.config.get('Plugins', {})
assert isinstance(plugstates, dict)
plugkeys: list[str] = sorted(key for key, val in plugstates.items()
@ -90,8 +128,7 @@ class PluginSubsystem:
subs=[('${PLUGIN}', plugkey),
('${ERROR}', str(exc))]),
color=(1, 0, 0))
_ba.log(f"Error loading plugin class '{plugkey}': {exc}",
to_server=False)
logging.exception("Error loading plugin class '%s'", plugkey)
continue
try:
plugin = cls()
@ -118,10 +155,8 @@ class PluginSubsystem:
color=(1, 1, 0),
)
plugnames = ', '.join(disappeared_plugs)
_ba.log(
f'{len(disappeared_plugs)} plugin(s) no longer found:'
f' {plugnames}.',
to_server=False)
logging.warning('%d plugin(s) no longer found: %s.',
len(disappeared_plugs), plugnames)
for goneplug in disappeared_plugs:
del _ba.app.config['Plugins'][goneplug]
_ba.app.config.commit()

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import sys
import time
import logging
from typing import TYPE_CHECKING
from efro.terminal import Clr
@ -13,6 +14,8 @@ from bacommon.servermanager import (ServerCommand, StartServerModeCommand,
ChatMessageCommand, ScreenMessageCommand,
ClientListCommand, KickCommand)
import _ba
from ba._internal import (add_transaction, run_transactions,
get_v1_account_state)
from ba._generated.enums import TimeType
from ba._freeforallsession import FreeForAllSession
from ba._dualteamsession import DualTeamSession
@ -227,7 +230,7 @@ class ServerController:
def _prepare_to_serve(self) -> None:
"""Run in a timer to do prep before beginning to serve."""
signed_in = _ba.get_v1_account_state() == 'signed_in'
signed_in = get_v1_account_state() == 'signed_in'
if not signed_in:
# Signing in to the local server account should not take long;
@ -247,14 +250,14 @@ class ServerController:
if not self._playlist_fetch_sent_request:
print(f'{Clr.SBLU}Requesting shared-playlist'
f' {self._config.playlist_code}...{Clr.RST}')
_ba.add_transaction(
add_transaction(
{
'type': 'IMPORT_PLAYLIST',
'code': str(self._config.playlist_code),
'overwrite': True
},
callback=self._on_playlist_fetch_response)
_ba.run_transactions()
run_transactions()
self._playlist_fetch_sent_request = True
if self._playlist_fetch_got_response:
@ -302,7 +305,7 @@ class ServerController:
appcfg = app.config
sessiontype = self._get_session_type()
if _ba.get_v1_account_state() != 'signed_in':
if get_v1_account_state() != 'signed_in':
print('WARNING: launch_server_session() expects to run '
'with a signed in server account')
@ -322,21 +325,21 @@ class ServerController:
# Need to add this in a transaction instead of just setting
# it directly or it will get overwritten by the master-server.
_ba.add_transaction({
add_transaction({
'type': 'ADD_PLAYLIST',
'playlistType': ptypename,
'playlistName': self._playlist_name,
'playlist': self._config.playlist_inline
})
_ba.run_transactions()
run_transactions()
if self._first_run:
curtimestr = time.strftime('%c')
_ba.log(
startupmsg = (
f'{Clr.BLD}{Clr.BLU}{_ba.appnameupper()} {app.version}'
f' ({app.build_number})'
f' entering server-mode {curtimestr}{Clr.RST}',
to_server=False)
f' entering server-mode {curtimestr}{Clr.RST}')
logging.info(startupmsg)
if sessiontype is FreeForAllSession:
appcfg['Free-for-All Playlist Selection'] = self._playlist_name

View file

@ -615,6 +615,7 @@ class Session:
def transitioning_out_activity_was_freed(
self, can_show_ad_on_death: bool) -> None:
"""(internal)"""
# pylint: disable=cyclic-import
from ba._apputils import garbage_collect
# Since things should be generally still right now, it's a good time

View file

@ -7,6 +7,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
from ba import _internal
if TYPE_CHECKING:
from typing import Any
@ -366,11 +367,11 @@ def get_store_layout() -> dict[str, list[dict[str, Any]]]:
'games.ninja_fight', 'games.meteor_shower', 'games.target_practice'
]
}]
if _ba.get_v1_account_misc_read_val('xmas', False):
if _internal.get_v1_account_misc_read_val('xmas', False):
store_layout['characters'][0]['items'].append('characters.santa')
store_layout['characters'][0]['items'].append('characters.wizard')
store_layout['characters'][0]['items'].append('characters.cyborg')
if _ba.get_v1_account_misc_read_val('easter', False):
if _internal.get_v1_account_misc_read_val('easter', False):
store_layout['characters'].append({
'title': 'store.holidaySpecialText',
'items': ['characters.bunny']
@ -401,10 +402,10 @@ def get_clean_price(price_string: str) -> str:
def get_available_purchase_count(tab: str | None = None) -> int:
"""(internal)"""
try:
if _ba.get_v1_account_state() != 'signed_in':
if _internal.get_v1_account_state() != 'signed_in':
return 0
count = 0
our_tickets = _ba.get_v1_account_ticket_count()
our_tickets = _internal.get_v1_account_ticket_count()
store_data = get_store_layout()
if tab is not None:
tabs = [(tab, store_data[tab])]
@ -425,11 +426,11 @@ def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int,
count: int) -> int:
for section in tabval:
for item in section['items']:
ticket_cost = _ba.get_v1_account_misc_read_val(
ticket_cost = _internal.get_v1_account_misc_read_val(
'price.' + item, None)
if ticket_cost is not None:
if (our_tickets >= ticket_cost
and not _ba.get_purchased(item)):
and not _internal.get_purchased(item)):
count += 1
return count
@ -463,7 +464,7 @@ def get_available_sale_time(tab: str) -> int | None:
# We start the timer once we get the duration from
# the server.
start_duration = _ba.get_v1_account_misc_read_val(
start_duration = _internal.get_v1_account_misc_read_val(
'proSaleDurationMinutes', None)
if start_duration is not None:
app.pro_sale_start_time = int(
@ -489,12 +490,12 @@ def get_available_sale_time(tab: str) -> int | None:
sale_times.append(val)
# Now look for sales in this tab.
sales_raw = _ba.get_v1_account_misc_read_val('sales', {})
sales_raw = _internal.get_v1_account_misc_read_val('sales', {})
store_layout = get_store_layout()
for section in store_layout[tab]:
for item in section['items']:
if item in sales_raw:
if not _ba.get_purchased(item):
if not _internal.get_purchased(item):
to_end = ((datetime.datetime.utcfromtimestamp(
sales_raw[item]['e']) -
datetime.datetime.utcnow()).total_seconds())
@ -509,3 +510,35 @@ def get_available_sale_time(tab: str) -> int | None:
from ba import _error
_error.print_exception('error calcing sale time')
return None
def get_unowned_maps() -> list[str]:
"""Return the list of local maps not owned by the current account.
Category: **Asset Functions**
"""
unowned_maps: set[str] = set()
if not _ba.app.headless_mode:
for map_section in get_store_layout()['maps']:
for mapitem in map_section['items']:
if not _internal.get_purchased(mapitem):
m_info = get_store_item(mapitem)
unowned_maps.add(m_info['map_type'].name)
return sorted(unowned_maps)
def get_unowned_game_types() -> set[type[ba.GameActivity]]:
"""Return present game types not owned by the current account."""
try:
unowned_games: set[type[ba.GameActivity]] = set()
if not _ba.app.headless_mode:
for section in get_store_layout()['minigames']:
for mname in section['items']:
if not _internal.get_purchased(mname):
m_info = get_store_item(mname)
unowned_games.add(m_info['gametype'])
return unowned_games
except Exception:
from ba import _error
_error.print_exception('error calcing un-owned games')
return set()

View file

@ -6,10 +6,35 @@ Classes and functions contained here, while technically 'public', may change
or disappear without warning, so should be avoided (or used sparingly and
defensively) in mods.
"""
from __future__ import annotations
from ba._map import (get_unowned_maps, get_map_class, register_map,
preload_map_preview_media, get_map_display_string,
get_filtered_map_name)
from _ba import (
show_online_score_ui, set_ui_input_device, is_party_icon_visible,
getinputdevice, add_clean_frame_callback, unlock_all_input,
increment_analytics_count, set_debug_speed_exponent, get_special_widget,
get_qrcode_texture, get_string_height, get_string_width, show_app_invite,
appnameupper, lock_all_input, open_file_externally, fade_screen, appname,
have_incentivized_ad, has_video_ads, workspaces_in_use,
set_party_icon_always_visible, connect_to_party, get_game_port,
end_host_scanning, host_scan_cycle, charstr, get_public_party_enabled,
get_public_party_max_size, set_public_party_name,
set_public_party_max_size, set_authenticate_clients,
set_public_party_enabled, reset_random_player_names, new_host_session,
get_foreground_host_session, get_local_active_input_devices_count,
get_ui_input_device, is_in_replay, set_replay_speed_exponent,
get_replay_speed_exponent, disconnect_from_host, set_party_window_open,
get_connection_to_host_info, get_chat_messages, get_game_roster,
disconnect_client, chatmessage, get_random_names, have_permission,
request_permission, have_touchscreen_input, is_xcode_build,
set_low_level_config_value, get_low_level_config_value,
capture_gamepad_input, release_gamepad_input, has_gamma_control,
get_max_graphics_quality, get_display_resolution, capture_keyboard_input,
release_keyboard_input, value_test, set_touchscreen_editing,
is_running_on_fire_tv, android_get_external_files_dir,
set_telnet_access_enabled, new_replay_session, get_replays_dir)
from ba._map import (get_map_class, register_map, preload_map_preview_media,
get_map_display_string, get_filtered_map_name)
from ba._appconfig import commit_app_config
from ba._input import (get_device_value, get_input_map_hash,
get_input_device_config)
@ -34,27 +59,174 @@ from ba._playlist import (get_default_free_for_all_playlist,
from ba._store import (get_available_sale_time, get_available_purchase_count,
get_store_item_name_translated,
get_store_item_display_size, get_store_layout,
get_store_item, get_clean_price)
get_store_item, get_clean_price, get_unowned_maps,
get_unowned_game_types)
from ba._tournament import get_tournament_prize_strings
from ba._gameutils import get_trophy_string
from ba._internal import (
get_v2_fleet, get_master_server_address, is_blessed, get_news_show,
game_service_has_leaderboard, report_achievement, submit_score,
tournament_query, power_ranking_query, restore_purchases, purchase,
get_purchases_state, get_purchased, get_price, in_game_purchase,
add_transaction, reset_achievements, get_public_login_id,
have_outstanding_transactions, run_transactions,
get_v1_account_misc_read_val, get_v1_account_misc_read_val_2,
get_v1_account_misc_val, get_v1_account_ticket_count,
get_v1_account_state_num, get_v1_account_state,
get_v1_account_display_string, get_v1_account_type, get_v1_account_name,
sign_out_v1, sign_in_v1, mark_config_dirty)
__all__ = [
'get_unowned_maps', 'get_map_class', 'register_map',
'preload_map_preview_media', 'get_map_display_string',
'get_filtered_map_name', 'commit_app_config', 'get_device_value',
'get_input_map_hash', 'get_input_device_config', 'getclass', 'json_prep',
'get_type_name', 'JoinActivity', 'ScoreScreenActivity',
'is_browser_likely_available', 'get_remote_app_name',
'should_submit_debug_info', 'run_gpu_benchmark', 'run_cpu_benchmark',
'run_media_reload_benchmark', 'run_stress_test', 'getcampaign',
'PlayerProfilesChangedMessage', 'DEFAULT_TEAM_COLORS',
'DEFAULT_TEAM_NAMES', 'do_play_music', 'master_server_get',
'master_server_post', 'get_ip_address_type',
'DEFAULT_REQUEST_TIMEOUT_SECONDS', 'get_default_powerup_distribution',
'get_player_profile_colors', 'get_player_profile_icon',
'get_player_colors', 'get_next_tip', 'get_default_free_for_all_playlist',
'get_default_teams_playlist', 'filter_playlist', 'get_available_sale_time',
'get_available_purchase_count', 'get_store_item_name_translated',
'get_store_item_display_size', 'get_store_layout', 'get_store_item',
'get_clean_price', 'get_tournament_prize_strings', 'get_trophy_string'
'show_online_score_ui',
'set_ui_input_device',
'is_party_icon_visible',
'getinputdevice',
'add_clean_frame_callback',
'unlock_all_input',
'increment_analytics_count',
'set_debug_speed_exponent',
'get_special_widget',
'get_qrcode_texture',
'get_string_height',
'get_string_width',
'show_app_invite',
'appnameupper',
'lock_all_input',
'open_file_externally',
'fade_screen',
'appname',
'have_incentivized_ad',
'has_video_ads',
'workspaces_in_use',
'set_party_icon_always_visible',
'connect_to_party',
'get_game_port',
'end_host_scanning',
'host_scan_cycle',
'charstr',
'get_public_party_enabled',
'get_public_party_max_size',
'set_public_party_name',
'set_public_party_max_size',
'set_authenticate_clients',
'set_public_party_enabled',
'reset_random_player_names',
'new_host_session',
'get_foreground_host_session',
'get_local_active_input_devices_count',
'get_ui_input_device',
'is_in_replay',
'set_replay_speed_exponent',
'get_replay_speed_exponent',
'disconnect_from_host',
'set_party_window_open',
'get_connection_to_host_info',
'get_chat_messages',
'get_game_roster',
'disconnect_client',
'chatmessage',
'get_random_names',
'have_permission',
'request_permission',
'have_touchscreen_input',
'is_xcode_build',
'set_low_level_config_value',
'get_low_level_config_value',
'capture_gamepad_input',
'release_gamepad_input',
'has_gamma_control',
'get_max_graphics_quality',
'get_display_resolution',
'capture_keyboard_input',
'release_keyboard_input',
'value_test',
'set_touchscreen_editing',
'is_running_on_fire_tv',
'android_get_external_files_dir',
'set_telnet_access_enabled',
'new_replay_session',
'get_replays_dir',
# DIVIDER
'get_unowned_maps',
'get_unowned_game_types',
'get_map_class',
'register_map',
'preload_map_preview_media',
'get_map_display_string',
'get_filtered_map_name',
'commit_app_config',
'get_device_value',
'get_input_map_hash',
'get_input_device_config',
'getclass',
'json_prep',
'get_type_name',
'JoinActivity',
'ScoreScreenActivity',
'is_browser_likely_available',
'get_remote_app_name',
'should_submit_debug_info',
'run_gpu_benchmark',
'run_cpu_benchmark',
'run_media_reload_benchmark',
'run_stress_test',
'getcampaign',
'PlayerProfilesChangedMessage',
'DEFAULT_TEAM_COLORS',
'DEFAULT_TEAM_NAMES',
'do_play_music',
'master_server_get',
'master_server_post',
'get_ip_address_type',
'DEFAULT_REQUEST_TIMEOUT_SECONDS',
'get_default_powerup_distribution',
'get_player_profile_colors',
'get_player_profile_icon',
'get_player_colors',
'get_next_tip',
'get_default_free_for_all_playlist',
'get_default_teams_playlist',
'filter_playlist',
'get_available_sale_time',
'get_available_purchase_count',
'get_store_item_name_translated',
'get_store_item_display_size',
'get_store_layout',
'get_store_item',
'get_clean_price',
'get_tournament_prize_strings',
'get_trophy_string',
'get_v2_fleet',
'get_master_server_address',
'is_blessed',
'get_news_show',
'game_service_has_leaderboard',
'report_achievement',
'submit_score',
'tournament_query',
'power_ranking_query',
'restore_purchases',
'purchase',
'get_purchases_state',
'get_purchased',
'get_price',
'in_game_purchase',
'add_transaction',
'reset_achievements',
'get_public_login_id',
'have_outstanding_transactions',
'run_transactions',
'get_v1_account_misc_read_val',
'get_v1_account_misc_read_val_2',
'get_v1_account_misc_val',
'get_v1_account_ticket_count',
'get_v1_account_state_num',
'get_v1_account_state',
'get_v1_account_display_string',
'get_v1_account_type',
'get_v1_account_name',
'sign_out_v1',
'sign_in_v1',
'mark_config_dirty',
]

View file

@ -14,7 +14,7 @@ if TYPE_CHECKING:
# 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 = 7
BACLOUD_VERSION = 8
@ioprepped

View file

@ -21,7 +21,7 @@ class LoginProxyRequestMessage(Message):
"""Request send to the cloud to ask for a login-proxy."""
@classmethod
def get_response_types(cls) -> list[type[Response]]:
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyRequestResponse]
@ -48,7 +48,7 @@ class LoginProxyStateQueryMessage(Message):
proxykey: Annotated[str, IOAttrs('k')]
@classmethod
def get_response_types(cls) -> list[type[Response]]:
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyStateQueryResponse]
@ -82,7 +82,7 @@ class PingMessage(Message):
"""Standard ping."""
@classmethod
def get_response_types(cls) -> list[type[Response]]:
def get_response_types(cls) -> list[type[Response] | None]:
return [PingResponse]
@ -99,7 +99,7 @@ class TestMessage(Message):
testfoo: Annotated[int, IOAttrs('f')]
@classmethod
def get_response_types(cls) -> list[type[Response]]:
def get_response_types(cls) -> list[type[Response] | None]:
return [TestResponse]
@ -130,7 +130,7 @@ class WorkspaceFetchMessage(Message):
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
@classmethod
def get_response_types(cls) -> list[type[Response]]:
def get_response_types(cls) -> list[type[Response] | None]:
return [WorkspaceFetchResponse]

View file

@ -6,12 +6,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
from ba.internal import JoinActivity
if TYPE_CHECKING:
from typing import Any, Sequence
pass
class CoopJoinActivity(JoinActivity):
@ -25,16 +24,6 @@ class CoopJoinActivity(JoinActivity):
session = self.session
assert isinstance(session, ba.CoopSession)
# Let's show a list of scores-to-beat for 1 player at least.
assert session.campaign is not None
level_name_full = (session.campaign.name + ':' +
session.campaign_level_name)
config_str = ('1p' + session.campaign.getlevel(
session.campaign_level_name).get_score_version_string().replace(
' ', '_'))
_ba.get_scores_to_beat(level_name_full, config_str,
ba.WeakCall(self._on_got_scores_to_beat))
def on_transition_in(self) -> None:
from bastd.actor.controlsguide import ControlsGuide
from bastd.actor.text import Text
@ -53,143 +42,61 @@ class CoopJoinActivity(JoinActivity):
position=(0, -95)).autoretain()
ControlsGuide(delay=1.0).autoretain()
def _on_got_scores_to_beat(self,
scores: list[dict[str, Any]] | None) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
from efro.util import asserttype
from bastd.actor.text import Text
ba.pushcall(self._show_remaining_achievements)
# Sort by originating date so that the most recent is first.
if scores is not None:
scores.sort(reverse=True,
key=lambda score: asserttype(score['time'], int))
def _show_remaining_achievements(self) -> None:
from bastd.actor.text import Text
# We only show achievements and challenges for CoopGameActivities.
session = self.session
assert isinstance(session, ba.CoopSession)
gameinstance = session.get_current_game_instance()
if isinstance(gameinstance, ba.CoopGameActivity):
score_type = gameinstance.get_score_type()
if scores is not None:
achievement_challenges = [
a for a in scores if a['type'] == 'achievement_challenge'
]
score_challenges = [
a for a in scores if a['type'] == 'score_challenge'
]
else:
achievement_challenges = score_challenges = []
if not isinstance(gameinstance, ba.CoopGameActivity):
return
delay = 1.0
vpos = -140.0
spacing = 25
delay_inc = 0.1
delay = 1.0
vpos = -140.0
def _add_t(
text: str | ba.Lstr,
h_offs: float = 0.0,
scale: float = 1.0,
color: Sequence[float] = (1.0, 1.0, 1.0, 0.46)
) -> None:
Text(text,
scale=scale * 0.76,
h_align=Text.HAlign.LEFT,
# Now list our remaining achievements for this level.
assert self.session.campaign is not None
assert isinstance(self.session, ba.CoopSession)
levelname = (self.session.campaign.name + ':' +
self.session.campaign_level_name)
ts_h_offs = 60
if not (ba.app.demo_mode or ba.app.arcade_mode):
achievements = [
a for a in ba.app.ach.achievements_for_coop_level(levelname)
if not a.complete
]
have_achievements = bool(achievements)
achievements = [a for a in achievements if not a.complete]
vrmode = ba.app.vr_mode
if have_achievements:
Text(ba.Lstr(resource='achievementsRemainingText'),
host_only=True,
position=(ts_h_offs - 10, vpos),
transition=Text.Transition.FADE_IN,
scale=1.1 * 0.76,
h_attach=Text.HAttach.LEFT,
v_attach=Text.VAttach.TOP,
transition=Text.Transition.FADE_IN,
transition_delay=delay,
color=color,
position=(60 + h_offs, vpos)).autoretain()
if score_challenges:
_add_t(ba.Lstr(value='${A}:',
subs=[('${A}',
ba.Lstr(resource='scoreChallengesText'))
]),
scale=1.1)
delay += delay_inc
vpos -= spacing
for chal in score_challenges:
_add_t(str(chal['value'] if score_type == 'points' else ba.
timestring(int(chal['value']) * 10,
timeformat=ba.TimeFormat.MILLISECONDS
).evaluate()) + ' (1 player)',
h_offs=30,
color=(0.9, 0.7, 1.0, 0.8))
delay += delay_inc
vpos -= 0.6 * spacing
_add_t(chal['player'],
h_offs=40,
color=(0.8, 1, 0.8, 0.6),
scale=0.8)
delay += delay_inc
vpos -= 1.2 * spacing
vpos -= 0.5 * spacing
if achievement_challenges:
_add_t(ba.Lstr(
value='${A}:',
subs=[('${A}',
ba.Lstr(resource='achievementChallengesText'))]),
scale=1.1)
delay += delay_inc
vpos -= spacing
for chal in achievement_challenges:
_add_t(str(chal['value']),
h_offs=30,
color=(0.9, 0.7, 1.0, 0.8))
delay += delay_inc
vpos -= 0.6 * spacing
_add_t(chal['player'],
h_offs=40,
color=(0.8, 1, 0.8, 0.6),
scale=0.8)
delay += delay_inc
vpos -= 1.2 * spacing
vpos -= 0.5 * spacing
# Now list our remaining achievements for this level.
assert self.session.campaign is not None
assert isinstance(self.session, ba.CoopSession)
levelname = (self.session.campaign.name + ':' +
self.session.campaign_level_name)
ts_h_offs = 60
if not (ba.app.demo_mode or ba.app.arcade_mode):
achievements = [
a
for a in ba.app.ach.achievements_for_coop_level(levelname)
if not a.complete
]
have_achievements = bool(achievements)
achievements = [a for a in achievements if not a.complete]
vrmode = ba.app.vr_mode
if have_achievements:
Text(ba.Lstr(resource='achievementsRemainingText'),
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1),
shadow=1.0,
flatness=1.0 if vrmode else 0.6,
transition_delay=delay).autoretain()
hval = ts_h_offs + 50
vpos -= 35
for ach in achievements:
delay += 0.05
ach.create_display(hval, vpos, delay, style='in_game')
vpos -= 55
if not achievements:
Text(ba.Lstr(resource='noAchievementsRemainingText'),
host_only=True,
position=(ts_h_offs - 10, vpos),
position=(ts_h_offs + 15, vpos + 10),
transition=Text.Transition.FADE_IN,
scale=1.1 * 0.76,
scale=0.7,
h_attach=Text.HAttach.LEFT,
v_attach=Text.VAttach.TOP,
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1),
shadow=1.0,
flatness=1.0 if vrmode else 0.6,
transition_delay=delay).autoretain()
hval = ts_h_offs + 50
vpos -= 35
for ach in achievements:
delay += 0.05
ach.create_display(hval, vpos, delay, style='in_game')
vpos -= 55
if not achievements:
Text(ba.Lstr(resource='noAchievementsRemainingText'),
host_only=True,
position=(ts_h_offs + 15, vpos + 10),
transition=Text.Transition.FADE_IN,
scale=0.7,
h_attach=Text.HAttach.LEFT,
v_attach=Text.VAttach.TOP,
color=(1, 1, 1, 0.5),
transition_delay=delay + 0.5).autoretain()
color=(1, 1, 1, 0.5),
transition_delay=delay + 0.5).autoretain()

View file

@ -8,8 +8,8 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
@ -52,9 +52,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.app.ach.achievements_for_coop_level(self._campaign.name + ':' +
settings['level']))
self._account_type = (_ba.get_v1_account_type()
if _ba.get_v1_account_state() == 'signed_in' else
None)
self._account_type = (ba.internal.get_v1_account_type()
if ba.internal.get_v1_account_state()
== 'signed_in' else None)
self._game_service_icon_color: Sequence[float] | None
self._game_service_achievements_texture: ba.Texture | None
@ -167,7 +167,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# If game-center/etc scores are available we show our friends'
# scores. Otherwise we show our local high scores.
self._show_friend_scores = _ba.game_service_has_leaderboard(
self._show_friend_scores = ba.internal.game_service_has_leaderboard(
self._game_name_str, self._game_config_str)
try:
@ -264,12 +264,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self.end({'outcome': 'next_level'})
def _ui_gc(self) -> None:
_ba.show_online_score_ui('leaderboard',
game=self._game_name_str,
game_version=self._game_config_str)
ba.internal.show_online_score_ui('leaderboard',
game=self._game_name_str,
game_version=self._game_config_str)
def _ui_show_achievements(self) -> None:
_ba.show_online_score_ui('achievements')
ba.internal.show_online_score_ui('achievements')
def _ui_worlds_best(self) -> None:
if self._score_link is None:
@ -331,7 +331,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# to the game (like on mac).
can_select_extra_buttons = ba.app.platform == 'android'
_ba.set_ui_input_device(None) # Menu is up for grabs.
ba.internal.set_ui_input_device(None) # Menu is up for grabs.
if self._show_friend_scores:
ba.buttonwidget(parent=rootc,
@ -483,7 +483,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
timetype=ba.TimeType.REAL)
def _update_corner_button_positions(self) -> None:
offs = -55 if _ba.is_party_icon_visible() else 0
offs = -55 if ba.internal.is_party_icon_visible() else 0
assert self._corner_button_offs is not None
pos_x = self._corner_button_offs[0] + offs
pos_y = self._corner_button_offs[1]
@ -497,9 +497,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# If this activity is a good 'end point', ask server-mode just once if
# it wants to do anything special like switch sessions or kill the app.
if (self._allow_server_transition and _ba.app.server is not None
if (self._allow_server_transition and ba.app.server is not None
and self._server_transitioning is None):
self._server_transitioning = _ba.app.server.handle_transition()
self._server_transitioning = ba.app.server.handle_transition()
assert isinstance(self._server_transitioning, bool)
# If server-mode is handling this, don't do anything ourself.
@ -528,7 +528,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if ba.app.server is not None:
# Host can't press retry button, so anyone can do it instead.
time_till_assign = max(
0, self._birth_time + self._min_view_time - _ba.time())
0, self._birth_time + self._min_view_time - ba.time())
ba.timer(time_till_assign, ba.WeakCall(self._safe_assign, player))
@ -552,7 +552,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# Any time we complete a level, set the next one as unlocked.
if self._is_complete and self._is_more_levels:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'COMPLETE_LEVEL',
'campaign': self._campaign.name,
'level': self._level_name
@ -632,7 +632,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if ba.app.server is None:
# If we're running in normal non-headless build, show this text
# because only host can continue the game.
adisp = _ba.get_v1_account_display_string()
adisp = ba.internal.get_v1_account_display_string()
txt = Text(ba.Lstr(resource='waitingForHostText',
subs=[('${HOST}', adisp)]),
maxwidth=300,
@ -726,14 +726,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if self._score is not None:
sver = (self._campaign.getlevel(
self._level_name).get_score_version_string())
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'SET_LEVEL_LOCAL_HIGH_SCORES',
'campaign': self._campaign.name,
'level': self._level_name,
'scoreVersion': sver,
'scores': our_high_scores_all
})
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
# We expect this only in kiosk mode; complain otherwise.
if not (ba.app.demo_mode or ba.app.arcade_mode):
print('got not-signed-in at score-submit; unexpected')
@ -743,21 +743,22 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
else:
assert self._game_name_str is not None
assert self._game_config_str is not None
_ba.submit_score(self._game_name_str,
self._game_config_str,
name_str,
self._score,
ba.WeakCall(self._got_score_results),
ba.WeakCall(self._got_friend_score_results)
if self._show_friend_scores else None,
order=self._score_order,
tournament_id=self.session.tournament_id,
score_type=self._score_type,
campaign=self._campaign.name,
level=self._level_name)
ba.internal.submit_score(
self._game_name_str,
self._game_config_str,
name_str,
self._score,
ba.WeakCall(self._got_score_results),
ba.WeakCall(self._got_friend_score_results)
if self._show_friend_scores else None,
order=self._score_order,
tournament_id=self.session.tournament_id,
score_type=self._score_type,
campaign=self._campaign.name,
level=self._level_name)
# Apply the transactions we've been adding locally.
_ba.run_transactions()
ba.internal.run_transactions()
# If we're not doing the world's-best button, just show a title
# instead.
@ -1074,9 +1075,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
else:
self._score_link = results['link']
assert self._score_link is not None
if not self._score_link.startswith('http://'):
self._score_link = (_ba.get_master_server_address() + '/' +
self._score_link)
# Prepend our master-server addr if its a relative addr.
if (not self._score_link.startswith('http://')
and not self._score_link.startswith('https://')):
self._score_link = (
ba.internal.get_master_server_address() + '/' +
self._score_link)
self._score_loading_status = None
if 'tournamentSecondsRemaining' in results:
secs_remaining = results['tournamentSecondsRemaining']

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Sequence
@ -317,9 +317,8 @@ 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: ba.InputDevice | None = _ba.getinputdevice('TouchScreen',
'#1',
doraise=False)
touchscreen: ba.InputDevice | None = ba.internal.getinputdevice(
'TouchScreen', '#1', doraise=False)
if touchscreen is not None:
# We look at the session's players; not the activity's.
@ -385,7 +384,7 @@ class ControlsGuide(ba.Actor):
# If there's no players with input devices yet, try to default to
# showing keyboard controls.
if not input_devices:
kbd = _ba.getinputdevice('Keyboard', '#1', doraise=False)
kbd = ba.internal.getinputdevice('Keyboard', '#1', doraise=False)
if kbd is not None:
input_devices.append(kbd)

View file

@ -36,9 +36,9 @@ class PopupText(ba.Actor):
if len(color) == 3:
color = (color[0], color[1], color[2], 1.0)
pos = (position[0] + offset[0] + random_offset *
(0.5 - random.random()), position[1] + offset[0] +
(0.5 - random.random()), position[1] + offset[1] +
random_offset * (0.5 - random.random()), position[2] +
offset[0] + random_offset * (0.5 - random.random()))
offset[2] + random_offset * (0.5 - random.random()))
self.node = ba.newnode('text',
attrs={

View file

@ -81,7 +81,7 @@ class Spaz(ba.Actor):
factory = SpazFactory.get()
# we need to behave slightly different in the tutorial
# We need to behave slightly different in the tutorial.
self._demo_mode = demo_mode
self.play_big_death_sound = False
@ -758,7 +758,7 @@ class Spaz(ba.Actor):
tex = PowerupBoxFactory.get().tex_punch
self._flash_billboard(tex)
self.equip_boxing_gloves()
if self.powerups_expire:
if self.powerups_expire and not self.default_boxing_gloves:
self.node.boxing_gloves_flashing = False
self.node.mini_billboard_3_texture = tex
t_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
@ -966,7 +966,7 @@ class Spaz(ba.Actor):
self.on_punched(damage)
# If damage was significant, lets show it.
if damage > 350:
if damage >= 350:
assert msg.force_direction is not None
ba.show_damage_count('-' + str(int(damage / 10)) + '%',
msg.pos, msg.force_direction)
@ -977,11 +977,13 @@ class Spaz(ba.Actor):
ba.playsound(SpazFactory.get().punch_sound_stronger,
1.0,
position=self.node.position)
if damage > 500:
if damage >= 500:
sounds = SpazFactory.get().punch_sound_strong
sound = sounds[random.randrange(len(sounds))]
else:
elif damage >= 100:
sound = SpazFactory.get().punch_sound
else:
sound = SpazFactory.get().punch_sound_weak
ba.playsound(sound, 1.0, position=self.node.position)
# Throw up some chunks.
@ -1075,7 +1077,7 @@ class Spaz(ba.Actor):
# us if its grown high enough.
if self.hitpoints <= 0:
damage_avg = self.node.damage_smoothed * damage_scale
if damage_avg > 1000:
if damage_avg >= 1000:
self.shatter()
elif isinstance(msg, BombDiedMessage):
@ -1341,9 +1343,9 @@ class Spaz(ba.Actor):
hit_type='impact'))
self.node.handlemessage('knockout', max(0.0, 50.0 * intensity))
sounds: Sequence[ba.Sound]
if intensity > 5.0:
if intensity >= 5.0:
sounds = SpazFactory.get().impact_sounds_harder
elif intensity > 3.0:
elif intensity >= 3.0:
sounds = SpazFactory.get().impact_sounds_hard
else:
sounds = SpazFactory.get().impact_sounds_medium

View file

@ -5,8 +5,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -16,66 +16,67 @@ def get_appearances(include_locked: bool = False) -> list[str]:
"""Get the list of available spaz appearances."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
get_purchased = ba.internal.get_purchased
disallowed = []
if not include_locked:
# hmm yeah this'll be tough to hack...
if not _ba.get_purchased('characters.santa'):
if not get_purchased('characters.santa'):
disallowed.append('Santa Claus')
if not _ba.get_purchased('characters.frosty'):
if not get_purchased('characters.frosty'):
disallowed.append('Frosty')
if not _ba.get_purchased('characters.bones'):
if not get_purchased('characters.bones'):
disallowed.append('Bones')
if not _ba.get_purchased('characters.bernard'):
if not get_purchased('characters.bernard'):
disallowed.append('Bernard')
if not _ba.get_purchased('characters.pixie'):
if not get_purchased('characters.pixie'):
disallowed.append('Pixel')
if not _ba.get_purchased('characters.pascal'):
if not get_purchased('characters.pascal'):
disallowed.append('Pascal')
if not _ba.get_purchased('characters.actionhero'):
if not get_purchased('characters.actionhero'):
disallowed.append('Todd McBurton')
if not _ba.get_purchased('characters.taobaomascot'):
if not get_purchased('characters.taobaomascot'):
disallowed.append('Taobao Mascot')
if not _ba.get_purchased('characters.agent'):
if not get_purchased('characters.agent'):
disallowed.append('Agent Johnson')
if not _ba.get_purchased('characters.jumpsuit'):
if not get_purchased('characters.jumpsuit'):
disallowed.append('Lee')
if not _ba.get_purchased('characters.assassin'):
if not get_purchased('characters.assassin'):
disallowed.append('Zola')
if not _ba.get_purchased('characters.wizard'):
if not get_purchased('characters.wizard'):
disallowed.append('Grumbledorf')
if not _ba.get_purchased('characters.cowboy'):
if not get_purchased('characters.cowboy'):
disallowed.append('Butch')
if not _ba.get_purchased('characters.witch'):
if not get_purchased('characters.witch'):
disallowed.append('Witch')
if not _ba.get_purchased('characters.warrior'):
if not get_purchased('characters.warrior'):
disallowed.append('Warrior')
if not _ba.get_purchased('characters.superhero'):
if not get_purchased('characters.superhero'):
disallowed.append('Middle-Man')
if not _ba.get_purchased('characters.alien'):
if not get_purchased('characters.alien'):
disallowed.append('Alien')
if not _ba.get_purchased('characters.oldlady'):
if not get_purchased('characters.oldlady'):
disallowed.append('OldLady')
if not _ba.get_purchased('characters.gladiator'):
if not get_purchased('characters.gladiator'):
disallowed.append('Gladiator')
if not _ba.get_purchased('characters.wrestler'):
if not get_purchased('characters.wrestler'):
disallowed.append('Wrestler')
if not _ba.get_purchased('characters.operasinger'):
if not get_purchased('characters.operasinger'):
disallowed.append('Gretel')
if not _ba.get_purchased('characters.robot'):
if not get_purchased('characters.robot'):
disallowed.append('Robot')
if not _ba.get_purchased('characters.cyborg'):
if not get_purchased('characters.cyborg'):
disallowed.append('B-9000')
if not _ba.get_purchased('characters.bunny'):
if not get_purchased('characters.bunny'):
disallowed.append('Easter Bunny')
if not _ba.get_purchased('characters.kronk'):
if not get_purchased('characters.kronk'):
disallowed.append('Kronk')
if not _ba.get_purchased('characters.zoe'):
if not get_purchased('characters.zoe'):
disallowed.append('Zoe')
if not _ba.get_purchased('characters.jackmorgan'):
if not get_purchased('characters.jackmorgan'):
disallowed.append('Jack Morgan')
if not _ba.get_purchased('characters.mel'):
if not get_purchased('characters.mel'):
disallowed.append('Mel')
if not _ba.get_purchased('characters.snakeshadow'):
if not get_purchased('characters.snakeshadow'):
disallowed.append('Snake Shadow')
return [
s for s in list(ba.app.spaz_appearances.keys()) if s not in disallowed

View file

@ -7,8 +7,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import ba.internal
from bastd.gameutils import SharedObjects
import _ba
if TYPE_CHECKING:
from typing import Any, Sequence
@ -38,6 +38,9 @@ class SpazFactory:
"""The sound that plays for an 'important' spaz death such as in
co-op games."""
punch_sound_weak: ba.Sound
"""A weak punch ba.Sound."""
punch_sound: ba.Sound
"""A standard punch ba.Sound."""
@ -98,6 +101,7 @@ class SpazFactory:
self.impact_sounds_harder = (ba.getsound('bigImpact'),
ba.getsound('bigImpact2'))
self.single_player_death_sound = ba.getsound('playerDeath')
self.punch_sound_weak = ba.getsound('punchWeak01')
self.punch_sound = ba.getsound('punch01')
self.punch_sound_strong = (ba.getsound('punchStrong01'),
ba.getsound('punchStrong02'))
@ -208,15 +212,18 @@ class SpazFactory:
# Lets load some basic rules.
# (allows them to be tweaked from the master server)
self.shield_decay_rate = _ba.get_v1_account_misc_read_val('rsdr', 10.0)
self.punch_cooldown = _ba.get_v1_account_misc_read_val('rpc', 400)
self.punch_cooldown_gloves = (_ba.get_v1_account_misc_read_val(
self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
'rsdr', 10.0)
self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
'rpc', 400)
self.punch_cooldown_gloves = (ba.internal.get_v1_account_misc_read_val(
'rpcg', 300))
self.punch_power_scale = _ba.get_v1_account_misc_read_val('rpp', 1.2)
self.punch_power_scale_gloves = (_ba.get_v1_account_misc_read_val(
'rppg', 1.4))
self.max_shield_spillover_damage = (_ba.get_v1_account_misc_read_val(
'rsms', 500))
self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
'rpp', 1.2)
self.punch_power_scale_gloves = (
ba.internal.get_v1_account_misc_read_val('rppg', 1.4))
self.max_shield_spillover_damage = (
ba.internal.get_v1_account_misc_read_val('rsms', 500))
def get_style(self, character: str) -> str:
"""Return the named style for this character.

View file

@ -44,7 +44,10 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
name = 'Easter Egg Hunt'
description = 'Gather eggs!'
available_settings = [ba.BoolSetting('Pro Mode', default=False)]
available_settings = [
ba.BoolSetting('Pro Mode', default=False),
ba.BoolSetting('Epic Mode', default=False),
]
scoreconfig = ba.ScoreConfig(label='Score', scoretype=ba.ScoreType.POINTS)
# We're currently hard-coded for one map.
@ -70,6 +73,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
self.egg_tex_3 = ba.gettexture('eggTex3')
self._collect_sound = ba.getsound('powerup01')
self._pro_mode = settings.get('Pro Mode', False)
self._epic_mode = settings.get('Epic Mode', False)
self._max_eggs = 1.0
self.egg_material = ba.Material()
self.egg_material.add_actions(
@ -81,7 +85,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
self._bots: SpazBotSet | None = None
# Base class overrides
self.default_music = ba.MusicType.FORWARD_MARCH
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.FORWARD_MARCH)
def on_team_join(self, team: Team) -> None:
if self.has_begun():

View file

@ -106,8 +106,8 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
default_music = ba.MusicType.FOOTBALL
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
@ -143,6 +143,10 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self._flag_respawn_light: ba.NodeActor | None = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.FOOTBALL)
def get_instance_description(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
@ -330,6 +334,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
tips = ['Use the pick-up button to grab the flag < ${PICKUP} >']
scoreconfig = ba.ScoreConfig(scoretype=ba.ScoreType.MILLISECONDS,
version='B')
default_music = ba.MusicType.FOOTBALL
# FIXME: Need to update co-op games to use getscoreconfig.

View file

@ -137,8 +137,8 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
default_music = ba.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
@ -203,6 +203,10 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self._puck: Puck | None = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.HOCKEY)
def get_instance_description(self) -> str | Sequence:
if self._score_to_win == 1:

View file

@ -76,9 +76,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
scoreconfig = ba.ScoreConfig(label='Time Held')
default_music = ba.MusicType.KEEP_AWAY
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
@ -115,6 +115,10 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
self._flag: Flag | None = None
self._hold_time = int(settings['Hold Time'])
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.KEEP_AWAY)
def get_instance_description(self) -> str | Sequence:
return 'Carry the flag for ${ARG1} seconds.', self._hold_time

View file

@ -79,6 +79,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
scoreconfig = ba.ScoreConfig(label='Time Held')
@ -115,6 +116,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
self._scoring_team: weakref.ref[Team] | None = None
self._hold_time = int(settings['Hold Time'])
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self._flag_region_material = ba.Material()
self._flag_region_material.add_actions(
conditions=('they_have_material', shared.player_material),
@ -128,7 +130,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
))
# Base class overrides.
self.default_music = ba.MusicType.SCARY
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SCARY)
def get_instance_description(self) -> str | Sequence:
return 'Secure the flag for ${ARG1} seconds.', self._hold_time

View file

@ -10,7 +10,7 @@ import weakref
from typing import TYPE_CHECKING
import ba
import _ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -67,7 +67,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# host is navigating menus while they're just staring at an
# empty-ish screen.
tval = ba.Lstr(resource='hostIsNavigatingMenusText',
subs=[('${HOST}', _ba.get_v1_account_display_string())])
subs=[('${HOST}',
ba.internal.get_v1_account_display_string())])
self._host_is_navigating_text = ba.NodeActor(
ba.newnode('text',
attrs={
@ -251,7 +252,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
self._update()
# Hopefully this won't hitch but lets space these out anyway.
_ba.add_clean_frame_callback(ba.WeakCall(self._start_preloads))
ba.internal.add_clean_frame_callback(ba.WeakCall(self._start_preloads))
random.seed()
@ -274,7 +275,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# We now want to wait until we're signed in before fetching news.
def _try_fetching_news(self) -> None:
if _ba.get_v1_account_state() == 'signed_in':
if ba.internal.get_v1_account_state() == 'signed_in':
self._fetch_news()
self._fetch_timer = None
@ -282,7 +283,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
ba.app.main_menu_last_news_fetch_time = time.time()
# UPDATE - We now just pull news from MRVs.
news = _ba.get_v1_account_misc_read_val('n', None)
news = ba.internal.get_v1_account_misc_read_val('n', None)
if news is not None:
self._got_news(news)
@ -453,6 +454,11 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
ba.app.ui.set_main_menu_window(
CoopBrowserWindow(
transition=None).get_root_widget())
elif main_menu_location == 'Benchmarks & Stress Tests':
# pylint: disable=cyclic-import
from bastd.ui.debug import DebugWindow
ba.app.ui.set_main_menu_window(
DebugWindow(transition=None).get_root_widget())
else:
# pylint: disable=cyclic-import
from bastd.ui.mainmenu import MainMenuWindow
@ -757,7 +763,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
})
def _get_custom_logo_tex_name(self) -> str | None:
if _ba.get_v1_account_misc_read_val('easter', False):
if ba.internal.get_v1_account_misc_read_val('easter', False):
return 'logoEaster'
return None
@ -930,7 +936,7 @@ class MainMenuSession(ba.Session):
def on_activity_end(self, activity: ba.Activity, results: Any) -> None:
if self._locked:
_ba.unlock_all_input()
ba.internal.unlock_all_input()
# Any ending activity leads us into the main menu one.
self.setactivity(ba.newactivity(MainMenuActivity))

View file

@ -18,8 +18,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.actor import spaz as basespaz
if TYPE_CHECKING:
@ -235,7 +235,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
super().on_begin()
ba.set_analytics_screen('Tutorial Start')
_ba.increment_analytics_count('Tutorial start')
ba.internal.increment_analytics_count('Tutorial start')
if bool(False):
# Buttons on top.
@ -461,7 +461,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
def run(self, a: TutorialActivity) -> None:
print('setting to', self._speed)
_ba.set_debug_speed_exponent(self._speed)
ba.internal.set_debug_speed_exponent(self._speed)
class RemoveGloves:
@ -609,7 +609,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
pass
def run(self, a: TutorialActivity) -> None:
_ba.increment_analytics_count('Tutorial finish')
ba.internal.increment_analytics_count('Tutorial finish')
a.end()
class Move:
@ -2328,7 +2328,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
('${TOTAL}', str(len(self.players)))]) if count > 0 else ''
if (count >= len(self.players) and self.players
and not self._have_skipped):
_ba.increment_analytics_count('Tutorial skip')
ba.internal.increment_analytics_count('Tutorial skip')
ba.set_analytics_screen('Tutorial Skip')
self._have_skipped = True
ba.playsound(ba.getsound('swish'))

View file

@ -4,7 +4,6 @@
from __future__ import annotations
import _ba
import ba
@ -12,10 +11,11 @@ def show_sign_in_prompt(account_type: str | None = None) -> None:
"""Bring up a prompt telling the user they must sign in."""
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.account import settings
from ba.internal import sign_in_v1
if account_type == 'Google Play':
ConfirmWindow(
ba.Lstr(resource='notSignedInGooglePlayErrorText'),
lambda: _ba.sign_in_v1('Google Play'),
lambda: sign_in_v1('Google Play'),
ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'),
width=460,
height=130)

View file

@ -8,8 +8,8 @@ import copy
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -50,7 +50,8 @@ class AccountLinkWindow(ba.Window):
autoselect=True,
icon=ba.gettexture('crossOut'),
iconscale=1.2)
maxlinks = _ba.get_v1_account_misc_read_val('maxLinkAccounts', 5)
maxlinks = ba.internal.get_v1_account_misc_read_val(
'maxLinkAccounts', 5)
ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.56),
@ -84,17 +85,17 @@ class AccountLinkWindow(ba.Window):
def _generate_press(self) -> None:
from bastd.ui import account
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
ba.screenmessage(
ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'),
color=(0, 1, 0))
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'ACCOUNT_LINK_CODE_REQUEST',
'expire_time': time.time() + 5
})
_ba.run_transactions()
ba.internal.run_transactions()
def _enter_code_press(self) -> None:
from bastd.ui import promocode

View file

@ -8,8 +8,8 @@ from __future__ import annotations
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -25,7 +25,6 @@ class AccountSettingsWindow(ba.Window):
close_once_signed_in: bool = False):
# pylint: disable=too-many-statements
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
@ -45,10 +44,10 @@ class AccountSettingsWindow(ba.Window):
self._r = 'accountSettingsWindow'
self._modal = modal
self._needs_refresh = False
self._signed_in = (_ba.get_v1_account_state() == 'signed_in')
self._account_state_num = _ba.get_v1_account_state_num()
self._signed_in = (ba.internal.get_v1_account_state() == 'signed_in')
self._account_state_num = ba.internal.get_v1_account_state_num()
self._show_linked = (self._signed_in
and _ba.get_v1_account_misc_read_val(
and ba.internal.get_v1_account_misc_read_val(
'allowAccountLinking2', False))
self._check_sign_in_timer = ba.Timer(1.0,
ba.WeakCall(self._update),
@ -58,7 +57,7 @@ class AccountSettingsWindow(ba.Window):
# Currently we can only reset achievements on game-center.
account_type: str | None
if self._signed_in:
account_type = _ba.get_v1_account_type()
account_type = ba.internal.get_v1_account_type()
else:
account_type = None
self._can_reset_achievements = (account_type == 'Game Center')
@ -84,9 +83,6 @@ class AccountSettingsWindow(ba.Window):
if app.platform == 'android' and app.subplatform == 'google':
self._show_sign_in_buttons.append('Google Play')
elif app.platform == 'android' and app.subplatform == 'amazon':
self._show_sign_in_buttons.append('Game Circle')
# Local accounts are generally always available with a few key
# exceptions.
self._show_sign_in_buttons.append('Local')
@ -159,11 +155,12 @@ class AccountSettingsWindow(ba.Window):
# Hmm should update this to use get_account_state_num.
# Theoretically if we switch from one signed-in account to another
# in the background this would break.
account_state_num = _ba.get_v1_account_state_num()
account_state = _ba.get_v1_account_state()
account_state_num = ba.internal.get_v1_account_state_num()
account_state = ba.internal.get_v1_account_state()
show_linked = (self._signed_in and _ba.get_v1_account_misc_read_val(
'allowAccountLinking2', False))
show_linked = (self._signed_in
and ba.internal.get_v1_account_misc_read_val(
'allowAccountLinking2', False))
if (account_state_num != self._account_state_num
or self._show_linked != show_linked or self._needs_refresh):
@ -191,8 +188,8 @@ class AccountSettingsWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui import confirm
account_state = _ba.get_v1_account_state()
account_type = (_ba.get_v1_account_type()
account_state = ba.internal.get_v1_account_state()
account_type = (ba.internal.get_v1_account_type()
if account_state == 'signed_in' else 'unknown')
is_google = account_type == 'Google Play'
@ -212,27 +209,24 @@ class AccountSettingsWindow(ba.Window):
show_google_play_sign_in_button = (account_state == 'signed_out'
and 'Google Play'
in self._show_sign_in_buttons)
show_game_circle_sign_in_button = (account_state == 'signed_out'
and 'Game Circle'
in self._show_sign_in_buttons)
show_device_sign_in_button = (account_state == 'signed_out' and 'Local'
in self._show_sign_in_buttons)
show_v2_sign_in_button = (account_state == 'signed_out'
and 'V2' in self._show_sign_in_buttons)
sign_in_button_space = 70.0
show_game_service_button = (self._signed_in and account_type
in ['Game Center', 'Game Circle'])
show_game_service_button = (self._signed_in
and account_type in ['Game Center'])
game_service_button_space = 60.0
show_linked_accounts_text = (self._signed_in
and _ba.get_v1_account_misc_read_val(
show_linked_accounts_text = (self._signed_in and
ba.internal.get_v1_account_misc_read_val(
'allowAccountLinking2', False))
linked_accounts_text_space = 60.0
show_achievements_button = (
self._signed_in
and account_type in ('Google Play', 'Alibaba', 'Local', 'OUYA'))
show_achievements_button = (self._signed_in and account_type
in ('Google Play', 'Alibaba', 'Local',
'OUYA', 'V2'))
achievements_button_space = 60.0
show_achievements_text = (self._signed_in
@ -251,11 +245,17 @@ class AccountSettingsWindow(ba.Window):
show_reset_progress_button = False
reset_progress_button_space = 70.0
show_player_profiles_button = self._signed_in
player_profiles_button_space = 100.0
show_manage_v2_account_button = (self._signed_in
and account_type == 'V2'
and bool(False)) # Disabled for now.
manage_v2_account_button_space = 100.0
show_link_accounts_button = (self._signed_in
and _ba.get_v1_account_misc_read_val(
show_player_profiles_button = self._signed_in
player_profiles_button_space = (70.0 if show_manage_v2_account_button
else 100.0)
show_link_accounts_button = (self._signed_in and
ba.internal.get_v1_account_misc_read_val(
'allowAccountLinking2', False))
link_accounts_button_space = 70.0
@ -282,8 +282,6 @@ class AccountSettingsWindow(ba.Window):
self._sub_height += signing_in_text_space
if show_google_play_sign_in_button:
self._sub_height += sign_in_button_space
if show_game_circle_sign_in_button:
self._sub_height += sign_in_button_space
if show_device_sign_in_button:
self._sub_height += sign_in_button_space
if show_v2_sign_in_button:
@ -306,6 +304,8 @@ class AccountSettingsWindow(ba.Window):
self._sub_height += sign_in_benefits_space
if show_reset_progress_button:
self._sub_height += reset_progress_button_space
if show_manage_v2_account_button:
self._sub_height += manage_v2_account_button_space
if show_player_profiles_button:
self._sub_height += player_profiles_button_space
if show_link_accounts_button:
@ -335,7 +335,8 @@ class AccountSettingsWindow(ba.Window):
size=(0, 0),
text=ba.Lstr(
resource='accountSettingsWindow.deviceSpecificAccountText',
subs=[('${NAME}', _ba.get_v1_account_display_string())]),
subs=[('${NAME}',
ba.internal.get_v1_account_display_string())]),
scale=0.7,
color=(0.5, 0.5, 0.6),
maxwidth=self._sub_width * 0.9,
@ -376,7 +377,7 @@ class AccountSettingsWindow(ba.Window):
self._account_name_text = None
if self._back_button is None:
bbtn = _ba.get_special_widget('back_button')
bbtn = ba.internal.get_special_widget('back_button')
else:
bbtn = self._back_button
@ -444,32 +445,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_game_circle_sign_in_button:
button_width = 350
v -= sign_in_button_space
self._sign_in_game_circle_button = btn = ba.buttonwidget(
parent=self._subcontainer,
position=((self._sub_width - button_width) * 0.5, v - 20),
autoselect=True,
size=(button_width, 60),
label=ba.Lstr(value='${A}${B}',
subs=[('${A}',
ba.charstr(
ba.SpecialChar.GAME_CIRCLE_LOGO)),
('${B}',
ba.Lstr(resource=self._r +
'.signInWithGameCircleText'))]),
on_activate_call=lambda: self._sign_in_press('Game Circle'))
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@ -514,7 +491,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@ -560,11 +538,34 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_manage_v2_account_button:
button_width = 300
v -= manage_v2_account_button_space
self._manage_v2_button = btn = ba.buttonwidget(
parent=self._subcontainer,
position=((self._sub_width - button_width) * 0.5, v + 30),
autoselect=True,
size=(button_width, 60),
label=ba.Lstr(resource=self._r + '.manageAccount'),
color=(0.55, 0.5, 0.6),
icon=ba.gettexture('settingsIcon'),
textcolor=(0.75, 0.7, 0.8),
on_activate_call=lambda: ba.open_url(
'https://ballistica.net/accountsettings'))
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
if show_player_profiles_button:
button_width = 300
v -= player_profiles_button_space
@ -582,18 +583,17 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0)
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
if show_game_service_button:
button_width = 300
v -= game_service_button_space * 0.85
account_type = _ba.get_v1_account_type()
account_type = ba.internal.get_v1_account_type()
if account_type == 'Game Center':
account_type_name = ba.Lstr(resource='gameCenterText')
elif account_type == 'Game Circle':
account_type_name = ba.Lstr(resource='gameCircleText')
else:
raise ValueError("unknown account type: '" +
str(account_type) + "'")
@ -603,14 +603,15 @@ class AccountSettingsWindow(ba.Window):
color=(0.55, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
on_activate_call=_ba.show_online_score_ui,
on_activate_call=ba.internal.show_online_score_ui,
size=(button_width, 50),
label=account_type_name)
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
v -= game_service_button_space * 0.15
else:
@ -652,7 +653,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
v -= achievements_button_space * 0.15
else:
@ -680,7 +682,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
v -= leaderboards_button_space * 0.15
else:
@ -750,7 +753,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn)
self._linked_accounts_text: ba.Widget | None
@ -805,7 +809,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._unlink_accounts_button: ba.Widget | None
@ -833,7 +838,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._update_unlink_accounts_button()
else:
@ -854,7 +860,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
if show_cancel_v2_sign_in_button:
@ -872,7 +879,8 @@ class AccountSettingsWindow(ba.Window):
first_selectable = btn
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
# Whatever the topmost selectable thing is, we want it to scroll all
@ -889,13 +897,13 @@ class AccountSettingsWindow(ba.Window):
def _on_achievements_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui import achievements
account_state = _ba.get_v1_account_state()
account_type = (_ba.get_v1_account_type()
account_state = ba.internal.get_v1_account_state()
account_type = (ba.internal.get_v1_account_type()
if account_state == 'signed_in' else 'unknown')
# for google play we use the built-in UI; otherwise pop up our own
if account_type == 'Google Play':
ba.timer(0.15,
ba.Call(_ba.show_online_score_ui, 'achievements'),
ba.Call(ba.internal.show_online_score_ui, 'achievements'),
timetype=ba.TimeType.REAL)
elif account_type != 'unknown':
assert self._achievements_button is not None
@ -907,15 +915,16 @@ class AccountSettingsWindow(ba.Window):
def _on_leaderboards_press(self) -> None:
ba.timer(0.15,
ba.Call(_ba.show_online_score_ui, 'leaderboards'),
ba.Call(ba.internal.show_online_score_ui, 'leaderboards'),
timetype=ba.TimeType.REAL)
def _have_unlinkable_accounts(self) -> bool:
# if this is not present, we haven't had contact from the server so
# let's not proceed..
if _ba.get_public_login_id() is None:
if ba.internal.get_public_login_id() is None:
return False
accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', [])
accounts = ba.internal.get_v1_account_misc_read_val_2(
'linkedAccounts', [])
return len(accounts) > 1
def _update_unlink_accounts_button(self) -> None:
@ -933,11 +942,12 @@ class AccountSettingsWindow(ba.Window):
# if this is not present, we haven't had contact from the server so
# let's not proceed..
if _ba.get_public_login_id() is None:
if ba.internal.get_public_login_id() is None:
num = int(time.time()) % 4
accounts_str = num * '.' + (4 - num) * ' '
else:
accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', [])
accounts = ba.internal.get_v1_account_misc_read_val_2(
'linkedAccounts', [])
# our_account = _bs.get_v1_account_display_string()
# accounts = [a for a in accounts if a != our_account]
# accounts_str = u', '.join(accounts) if accounts else
@ -977,7 +987,7 @@ class AccountSettingsWindow(ba.Window):
if self._tickets_text is None:
return
try:
tc_str = str(_ba.get_v1_account_ticket_count())
tc_str = str(ba.internal.get_v1_account_ticket_count())
except Exception:
ba.print_exception()
tc_str = '-'
@ -989,7 +999,7 @@ class AccountSettingsWindow(ba.Window):
if self._account_name_text is None:
return
try:
name_str = _ba.get_v1_account_display_string()
name_str = ba.internal.get_v1_account_display_string()
except Exception:
ba.print_exception()
name_str = '??'
@ -1043,7 +1053,7 @@ class AccountSettingsWindow(ba.Window):
if ba.app.accounts_v2.have_primary_credentials():
ba.app.accounts_v2.set_primary_credentials(None)
else:
_ba.sign_out_v1()
ba.internal.sign_out_v1()
cfg = ba.app.config
@ -1061,7 +1071,7 @@ class AccountSettingsWindow(ba.Window):
account_type: str,
show_test_warning: bool = True) -> None:
del show_test_warning # unused
_ba.sign_in_v1(account_type)
ba.internal.sign_in_v1(account_type)
# Make note of the type account we're *wanting* to be signed in with.
cfg = ba.app.config
@ -1082,7 +1092,7 @@ class AccountSettingsWindow(ba.Window):
# FIXME: This would need to happen server-side these days.
if self._can_reset_achievements:
ba.app.config['Achievements'] = {}
_ba.reset_achievements()
ba.internal.reset_achievements()
campaign = getcampaign('Default')
campaign.reset() # also writes the config..
campaign = getcampaign('Challenges')

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -77,11 +77,11 @@ class AccountUnlinkWindow(ba.Window):
margin=0,
left_border=10)
our_login_id = _ba.get_public_login_id()
our_login_id = ba.internal.get_public_login_id()
if our_login_id is None:
entries = []
else:
account_infos = _ba.get_v1_account_misc_read_val_2(
account_infos = ba.internal.get_v1_account_misc_read_val_2(
'linkedAccounts2', [])
entries = [{
'name': ai['d'],
@ -108,12 +108,12 @@ class AccountUnlinkWindow(ba.Window):
ba.screenmessage(ba.Lstr(resource='pleaseWaitText',
fallback_resource='requestingText'),
color=(0, 1, 0))
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'ACCOUNT_UNLINK_REQUEST',
'accountID': entry['id'],
'expire_time': time.time() + 5
})
_ba.run_transactions()
ba.internal.run_transactions()
ba.containerwidget(edit=self._root_widget,
transition=self._transition_out)

View file

@ -8,7 +8,7 @@ import logging
from typing import TYPE_CHECKING
import ba
import _ba
import ba.internal
from efro.error import CommunicationError
import bacommon.cloud
@ -81,7 +81,8 @@ class V2SignInWindow(ba.Window):
return
# Show link(s) the user can use to log in.
address = _ba.get_master_server_address(version=2) + response.url
address = ba.internal.get_master_server_address(
version=2) + response.url
address_pretty = address.removeprefix('https://')
ba.textwidget(
@ -123,7 +124,7 @@ class V2SignInWindow(ba.Window):
position=(self._width * 0.5 - qr_size * 0.5,
self._height * 0.36 + qroffs - qr_size * 0.5),
size=(qr_size, qr_size),
texture=_ba.get_qrcode_texture(address))
texture=ba.internal.get_qrcode_texture(address))
# Start querying for results.
self._proxyid = response.proxyid

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup
if TYPE_CHECKING:
@ -91,8 +91,9 @@ class AccountViewerWindow(popup.PopupWindow):
# In cases where the user most likely has a browser/email, lets
# offer a 'report this user' button.
if (is_browser_likely_available() and _ba.get_v1_account_misc_read_val(
'showAccountExtrasMenu', False)):
if (is_browser_likely_available()
and ba.internal.get_v1_account_misc_read_val(
'showAccountExtrasMenu', False)):
self._extras_menu_button = ba.buttonwidget(
parent=self.root_widget,
@ -154,11 +155,11 @@ class AccountViewerWindow(popup.PopupWindow):
delegate=self)
def _on_ban_press(self) -> None:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'BAN_ACCOUNT',
'account': self._account_id
})
_ba.run_transactions()
ba.internal.run_transactions()
def _on_report_press(self) -> None:
from bastd.ui import report
@ -166,8 +167,8 @@ class AccountViewerWindow(popup.PopupWindow):
origin_widget=self._extras_menu_button)
def _on_more_press(self) -> None:
ba.open_url(_ba.get_master_server_address() + '/highscores?profile=' +
self._account_id)
ba.open_url(ba.internal.get_master_server_address() +
'/highscores?profile=' + self._account_id)
def _on_query_response(self, data: dict[str, Any] | None) -> None:
# FIXME: Tidy this up.
@ -197,8 +198,8 @@ class AccountViewerWindow(popup.PopupWindow):
ba.print_exception('Error displaying trophies.')
account_name_spacing = 15
tscale = 0.65
ts_height = _ba.get_string_height(trophystr,
suppress_warning=True)
ts_height = ba.internal.get_string_height(
trophystr, suppress_warning=True)
sub_width = self._width - 80
sub_height = 200 + ts_height * tscale + \
account_name_spacing * len(data['accountDisplayStrings'])
@ -321,8 +322,8 @@ class AccountViewerWindow(popup.PopupWindow):
('${SUFFIX}', '')]).evaluate()
rank_str_width = min(
sub_width * maxwidth_scale,
_ba.get_string_width(rank_str, suppress_warning=True) *
0.55)
ba.internal.get_string_width(
rank_str, suppress_warning=True) * 0.55)
# Only tack our suffix on if its at the end and only for
# non-diamond leagues.
@ -374,8 +375,8 @@ class AccountViewerWindow(popup.PopupWindow):
]).evaluate()
rank_str_width = min(
sub_width * maxwidth_scale,
_ba.get_string_width(rank_str, suppress_warning=True) *
0.3)
ba.internal.get_string_width(
rank_str, suppress_warning=True) * 0.3)
# Only tack our suffix on if its at the end and only for
# non-diamond leagues.

View file

@ -8,8 +8,8 @@ import copy
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -62,11 +62,11 @@ class AppInviteWindow(ba.Window):
'gatherWindow.earnTicketsForRecommendingText'),
subs=[('${COUNT}',
str(
_ba.get_v1_account_misc_read_val(
ba.internal.get_v1_account_misc_read_val(
'friendTryTickets', 300))),
('${YOU_COUNT}',
str(
_ba.get_v1_account_misc_read_val(
ba.internal.get_v1_account_misc_read_val(
'friendTryAwardTickets', 100)))]))
or_text = ba.Lstr(resource='orText',
@ -104,14 +104,14 @@ class AppInviteWindow(ba.Window):
on_activate_call=ba.WeakCall(self._send_code))
# kick off a transaction to get our code
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'FRIEND_PROMO_CODE_REQUEST',
'ali': False,
'expire_time': time.time() + 20
},
callback=ba.WeakCall(self._on_code_result))
_ba.run_transactions()
ba.internal.run_transactions()
def _on_code_result(self, result: dict[str, Any] | None) -> None:
if result is not None:
@ -128,18 +128,18 @@ class AppInviteWindow(ba.Window):
ba.playsound(ba.getsound('error'))
return
if _ba.get_v1_account_state() == 'signed_in':
if ba.internal.get_v1_account_state() == 'signed_in':
ba.set_analytics_screen('App Invite UI')
_ba.show_app_invite(
ba.internal.show_app_invite(
ba.Lstr(resource='gatherWindow.appInviteTitleText',
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
]).evaluate(),
ba.Lstr(resource='gatherWindow.appInviteMessageText',
subs=[
('${COUNT}', str(self._data['tickets'])),
('${NAME}', _ba.get_v1_account_name().split()[0]),
('${APP_NAME}', ba.Lstr(resource='titleText'))
]).evaluate(), self._data['code'])
subs=[('${COUNT}', str(self._data['tickets'])),
('${NAME}',
ba.internal.get_v1_account_name().split()[0]),
('${APP_NAME}', ba.Lstr(resource='titleText'))
]).evaluate(), self._data['code'])
else:
ba.playsound(ba.getsound('error'))
@ -250,13 +250,14 @@ class ShowFriendCodeWindow(ba.Window):
def _google_invites(self) -> None:
ba.set_analytics_screen('App Invite UI')
_ba.show_app_invite(
ba.internal.show_app_invite(
ba.Lstr(resource='gatherWindow.appInviteTitleText',
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
]).evaluate(),
ba.Lstr(resource='gatherWindow.appInviteMessageText',
subs=[('${COUNT}', str(self._data['tickets'])),
('${NAME}', _ba.get_v1_account_name().split()[0]),
('${NAME}',
ba.internal.get_v1_account_name().split()[0]),
('${APP_NAME}', ba.Lstr(resource='titleText'))
]).evaluate(), self._data['code'])
@ -264,7 +265,7 @@ class ShowFriendCodeWindow(ba.Window):
import urllib.parse
# If somehow we got signed out.
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
@ -273,7 +274,7 @@ class ShowFriendCodeWindow(ba.Window):
ba.set_analytics_screen('Email Friend Code')
subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText').
evaluate().replace(
'${NAME}', _ba.get_v1_account_name()).replace(
'${NAME}', ba.internal.get_v1_account_name()).replace(
'${APP_NAME}',
ba.Lstr(resource='titleText').evaluate()).replace(
'${COUNT}', str(self._data['tickets'])))
@ -304,7 +305,7 @@ def handle_app_invites_press(force_code: bool = False) -> None:
"""(internal)"""
app = ba.app
do_app_invites = (app.platform == 'android' and app.subplatform == 'google'
and _ba.get_v1_account_misc_read_val(
and ba.internal.get_v1_account_misc_read_val(
'enableAppInvites', False) and not app.on_tv)
if force_code:
do_app_invites = False
@ -326,11 +327,11 @@ def handle_app_invites_press(force_code: bool = False) -> None:
else:
ShowFriendCodeWindow(result)
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'FRIEND_PROMO_CODE_REQUEST',
'ali': False,
'expire_time': time.time() + 10
},
callback=handle_result)
_ba.run_transactions()
ba.internal.run_transactions()

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup
if TYPE_CHECKING:
@ -156,7 +156,7 @@ class CharacterPicker(popup.PopupWindow):
def _on_store_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._transition_out()

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -29,7 +29,7 @@ class ConfigErrorWindow(ba.Window):
h_align='center',
v_align='top',
scale=0.73,
text=(f'Error reading {_ba.appnameupper()} config file'
text=(f'Error reading {ba.internal.appnameupper()} config file'
':\n\n\nCheck the console'
' (press ~ twice) for details.\n\nWould you like to quit and'
' try to fix it by hand\nor overwrite it with defaults?\n\n'
@ -58,10 +58,10 @@ class ConfigErrorWindow(ba.Window):
def _quit(self) -> None:
ba.timer(0.001, self._edit_and_quit, timetype=ba.TimeType.REAL)
_ba.lock_all_input()
ba.internal.lock_all_input()
def _edit_and_quit(self) -> None:
_ba.open_file_externally(self._config_file_path)
ba.internal.open_file_externally(self._config_file_path)
ba.timer(0.1, ba.quit, timetype=ba.TimeType.REAL)
def _defaults(self) -> None:

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -54,7 +54,7 @@ class ConfirmWindow:
size=(width, height),
transition=transition,
toolbar_visibility='menu_minimal_no_back',
parent=_ba.get_special_widget('overlay_stack'),
parent=ba.internal.get_special_widget('overlay_stack'),
scale=(2.1 if uiscale is ba.UIScale.SMALL else
1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
scale_origin_stack_offset=scale_origin)
@ -147,12 +147,13 @@ class QuitWindow:
origin_widget=origin_widget).root_widget)
def _fade_and_quit(self) -> None:
_ba.fade_screen(False,
time=0.2,
endcall=lambda: ba.quit(soft=True, back=self._back))
_ba.lock_all_input()
ba.internal.fade_screen(
False,
time=0.2,
endcall=lambda: ba.quit(soft=True, back=self._back))
ba.internal.lock_all_input()
# Unlock and fade back in shortly.. just in case something goes wrong
# (or on android where quit just backs out of our activity and
# we may come back)
ba.timer(0.3, _ba.unlock_all_input, timetype=ba.TimeType.REAL)
ba.timer(0.3, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -37,11 +37,14 @@ class ContinuesWindow(ba.Window):
txt = (ba.Lstr(
resource='continuePurchaseText').evaluate().split('${PRICE}'))
t_left = txt[0]
t_left_width = _ba.get_string_width(t_left, suppress_warning=True)
t_left_width = ba.internal.get_string_width(t_left,
suppress_warning=True)
t_price = ba.charstr(ba.SpecialChar.TICKET) + str(self._cost)
t_price_width = _ba.get_string_width(t_price, suppress_warning=True)
t_price_width = ba.internal.get_string_width(t_price,
suppress_warning=True)
t_right = txt[-1]
t_right_width = _ba.get_string_width(t_right, suppress_warning=True)
t_right_width = ba.internal.get_string_width(t_right,
suppress_warning=True)
width_total_half = (t_left_width + t_price_width + t_right_width) * 0.5
ba.textwidget(parent=self._root_widget,
@ -133,8 +136,15 @@ class ContinuesWindow(ba.Window):
ba.WeakCall(self._tick),
repeat=True,
timetype=ba.TimeType.REAL)
# If there is foreground activity, suspend it.
ba.app.pause()
self._tick()
def __del__(self) -> None:
# If there is suspended foreground activity, resume it.
ba.app.resume()
def _tick(self) -> None:
# if our target activity is gone or has ended, go away
activity = self._activity()
@ -142,9 +152,9 @@ class ContinuesWindow(ba.Window):
self._on_cancel()
return
if _ba.get_v1_account_state() == 'signed_in':
if ba.internal.get_v1_account_state() == 'signed_in':
sval = (ba.charstr(ba.SpecialChar.TICKET) +
str(_ba.get_v1_account_ticket_count()))
str(ba.internal.get_v1_account_ticket_count()))
else:
sval = '?'
if self._tickets_text is not None:
@ -176,14 +186,14 @@ class ContinuesWindow(ba.Window):
ba.playsound(ba.getsound('error'))
else:
# If somehow we got signed out...
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
# If it appears we don't have enough tickets, offer to buy more.
tickets = _ba.get_v1_account_ticket_count()
tickets = ba.internal.get_v1_account_ticket_count()
if tickets < self._cost:
# FIXME: Should we start the timer back up again after?
self._counting_down = False

View file

@ -8,8 +8,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui.store.button import StoreButton
from bastd.ui.league.rankbutton import LeagueRankButton
from bastd.ui.store.browser import StoreBrowserWindow
@ -26,7 +26,7 @@ class CoopBrowserWindow(ba.Window):
def _update_corner_button_positions(self) -> None:
uiscale = ba.app.ui.uiscale
offs = (-55 if uiscale is ba.UIScale.SMALL
and _ba.is_party_icon_visible() else 0)
and ba.internal.is_party_icon_visible() else 0)
if self._league_rank_button is not None:
self._league_rank_button.set_position(
(self._width - 282 + offs - self._x_inset, self._height - 85 -
@ -54,7 +54,7 @@ class CoopBrowserWindow(ba.Window):
# Quick note to players that tourneys won't work in ballistica
# core builds. (need to split the word so it won't get subbed out)
if 'ballistica' + 'core' == _ba.appname():
if 'ballistica' + 'core' == ba.internal.appname():
ba.timer(1.0,
lambda: ba.screenmessage(
ba.Lstr(resource='noTournamentsInTestBuildText'),
@ -93,7 +93,7 @@ class CoopBrowserWindow(ba.Window):
self._tourney_data_up_to_date = False
self._campaign_difficulty = _ba.get_v1_account_misc_val(
self._campaign_difficulty = ba.internal.get_v1_account_misc_val(
'campaignDifficulty', 'easy')
super().__init__(root_widget=ba.containerwidget(
@ -234,7 +234,7 @@ class CoopBrowserWindow(ba.Window):
self._subcontainer: ba.Widget | None = None
# Take note of our account state; we'll refresh later if this changes.
self._account_state_num = _ba.get_v1_account_state_num()
self._account_state_num = ba.internal.get_v1_account_state_num()
# Same for fg/bg state.
self._fg_state = app.fg_state
@ -252,7 +252,7 @@ class CoopBrowserWindow(ba.Window):
# starting point.
if (app.accounts_v1.account_tournament_list is not None
and app.accounts_v1.account_tournament_list[0]
== _ba.get_v1_account_state_num() and all(
== ba.internal.get_v1_account_state_num() and all(
t_id in app.accounts_v1.tournament_info
for t_id in app.accounts_v1.account_tournament_list[1])):
tourney_data = [
@ -300,7 +300,7 @@ class CoopBrowserWindow(ba.Window):
self._tourney_data_up_to_date = False
# If our account state has changed, do a full request.
account_state_num = _ba.get_v1_account_state_num()
account_state_num = ba.internal.get_v1_account_state_num()
if account_state_num != self._account_state_num:
self._account_state_num = account_state_num
self._save_state()
@ -324,7 +324,7 @@ class CoopBrowserWindow(ba.Window):
self._fg_state = ba.app.fg_state
self._last_tournament_query_time = cur_time
self._doing_tournament_query = True
_ba.tournament_query(
ba.internal.tournament_query(
args={
'source': 'coop window refresh',
'numScores': 1
@ -333,7 +333,7 @@ class CoopBrowserWindow(ba.Window):
)
# Decrement time on our tournament buttons.
ads_enabled = _ba.have_incentivized_ad()
ads_enabled = ba.internal.have_incentivized_ad()
for tbtn in self._tournament_buttons:
tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn.time_remaining_value_text is not None:
@ -346,7 +346,7 @@ class CoopBrowserWindow(ba.Window):
and self._tourney_data_up_to_date) else '-')
# Also adjust the ad icon visibility.
if tbtn.allow_ads and _ba.has_video_ads():
if tbtn.allow_ads and ba.internal.has_video_ads():
ba.imagewidget(edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
ba.textwidget(edit=tbtn.entry_fee_text_remaining,
@ -395,11 +395,9 @@ class CoopBrowserWindow(ba.Window):
accounts.cache_tournament_info(tournament_data)
# Also cache the current tourney list/order for this account.
accounts.account_tournament_list = (_ba.get_v1_account_state_num(),
[
e['tournamentID']
for e in tournament_data
])
accounts.account_tournament_list = (
ba.internal.get_v1_account_state_num(),
[e['tournamentID'] for e in tournament_data])
self._doing_tournament_query = False
self._update_for_data(tournament_data)
@ -417,7 +415,7 @@ class CoopBrowserWindow(ba.Window):
print('ERROR: invalid campaign difficulty:', difficulty)
difficulty = 'easy'
self._campaign_difficulty = difficulty
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'SET_MISC_VAL',
'name': 'campaignDifficulty',
'value': difficulty
@ -638,7 +636,7 @@ class CoopBrowserWindow(ba.Window):
# FIXME shouldn't use hard-coded strings here.
txt = ba.Lstr(resource='tournamentsText',
fallback_resource='tournamentText').evaluate()
t_width = _ba.get_string_width(txt, suppress_warning=True)
t_width = ba.internal.get_string_width(txt, suppress_warning=True)
ba.textwidget(parent=w_parent,
position=(h_base + 27, v + 30),
size=(0, 0),
@ -668,7 +666,7 @@ class CoopBrowserWindow(ba.Window):
# no tournaments).
if self._tournament_button_count == 0:
unavailable_text = ba.Lstr(resource='unavailableText')
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
unavailable_text = ba.Lstr(
value='${A} (${B})',
subs=[('${A}', unavailable_text),
@ -744,8 +742,9 @@ class CoopBrowserWindow(ba.Window):
]
# Show easter-egg-hunt either if its easter or we own it.
if _ba.get_v1_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
if ba.internal.get_v1_account_misc_read_val(
'easter',
False) or ba.internal.get_purchased('games.easter_egg_hunt'):
items = [
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
@ -838,7 +837,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.league.rankwindow import LeagueRankWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
@ -855,7 +854,7 @@ class CoopBrowserWindow(ba.Window):
) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
@ -893,7 +892,7 @@ class CoopBrowserWindow(ba.Window):
if game in ('Challenges:Infinite Runaround',
'Challenges:Infinite Onslaught'
) and not ba.app.accounts_v1.have_pro():
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
PurchaseWindow(items=['pro'])
@ -920,8 +919,8 @@ class CoopBrowserWindow(ba.Window):
required_purchase = None
if (required_purchase is not None
and not _ba.get_purchased(required_purchase)):
if _ba.get_v1_account_state() != 'signed_in':
and not ba.internal.get_purchased(required_purchase)):
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
PurchaseWindow(items=[required_purchase])
@ -937,10 +936,17 @@ class CoopBrowserWindow(ba.Window):
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.tournamententry import TournamentEntryWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
if ba.internal.workspaces_in_use():
ba.screenmessage(
ba.Lstr(resource='tournamentsDisabledWorkspaceText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if not self._tourney_data_up_to_date:
ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
color=(1, 1, 0))

View file

@ -7,7 +7,6 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
import _ba
import ba
if TYPE_CHECKING:
@ -200,17 +199,17 @@ class GameButton:
'Challenges:Infinite Onslaught')
and not ba.app.accounts_v1.have_pro())
or (game in ('Challenges:Meteor Shower', )
and not _ba.get_purchased('games.meteor_shower'))
and not ba.internal.get_purchased('games.meteor_shower'))
or (game in ('Challenges:Target Practice',
'Challenges:Target Practice B')
and not _ba.get_purchased('games.target_practice'))
and not ba.internal.get_purchased('games.target_practice'))
or (game in ('Challenges:Ninja Fight', )
and not _ba.get_purchased('games.ninja_fight'))
and not ba.internal.get_purchased('games.ninja_fight'))
or (game in ('Challenges:Pro Ninja Fight', )
and not _ba.get_purchased('games.ninja_fight'))
or (game in ('Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt')
and not _ba.get_purchased('games.easter_egg_hunt'))):
and not ba.internal.get_purchased('games.ninja_fight')) or
(game in ('Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt')
and not ba.internal.get_purchased('games.easter_egg_hunt'))):
unlocked = False
# Let's tint levels a slightly different color when easy mode

View file

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import copy
import ba
import _ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -499,7 +499,7 @@ class TournamentButton:
self.allow_ads = allow_ads = entry['allowAds']
final_fee: int | None = (None if fee_var is None else
_ba.get_v1_account_misc_read_val(
ba.internal.get_v1_account_misc_read_val(
fee_var, '?'))
final_fee_str: str | ba.Lstr
@ -519,8 +519,8 @@ class TournamentButton:
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
if allow_ads and ba.internal.has_video_ads():
ads_enabled = ba.internal.have_incentivized_ad()
ba.imagewidget(edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
or_text = ba.Lstr(resource='orText',

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Sequence
@ -91,17 +91,19 @@ class CreditsListWindow(ba.Window):
capture_arrows=True)
if ba.app.ui.use_toolbars:
ba.widget(edit=scroll,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=scroll,
right_widget=ba.internal.get_special_widget('party_button'))
if uiscale is ba.UIScale.SMALL:
ba.widget(edit=scroll,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(
edit=scroll,
left_widget=ba.internal.get_special_widget('back_button'))
def _format_names(names2: Sequence[str], inset: float) -> str:
sval = ''
# measure a series since there's overlaps and stuff..
space_width = _ba.get_string_width(' ' * 10,
suppress_warning=True) / 10.0
space_width = ba.internal.get_string_width(
' ' * 10, suppress_warning=True) / 10.0
spacing = 330.0
col1 = inset
col2 = col1 + spacing
@ -124,7 +126,8 @@ class CreditsListWindow(ba.Window):
spacingstr = ' ' * int((target - line_width) / space_width)
nline += spacingstr
nline += name
line_width = _ba.get_string_width(nline, suppress_warning=True)
line_width = ba.internal.get_string_width(
nline, suppress_warning=True)
if nline != '':
sval += nline + '\n'
return sval
@ -236,7 +239,7 @@ class CreditsListWindow(ba.Window):
'${NAME}', 'the Khronos Group') + '\n'
'\n'
' '
' www.froemling.net\n')
' www.ballistica.net\n')
txt = credits_text
lines = txt.splitlines()

View file

@ -15,11 +15,12 @@ if TYPE_CHECKING:
class DebugWindow(ba.Window):
"""Window for debugging internal values."""
def __init__(self, transition: str = 'in_right'):
def __init__(self, transition: str | None = 'in_right'):
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bastd.ui import popup
ba.app.ui.set_main_menu_location('Benchmarks & Stress Tests')
uiscale = ba.app.ui.uiscale
self._width = width = 580
self._height = height = (350 if uiscale is ba.UIScale.SMALL else

View file

@ -7,6 +7,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -54,13 +55,12 @@ def ask_for_rating() -> ba.Widget | None:
v_align='center')
def do_rating() -> None:
import _ba
if platform == 'android':
appname = _ba.appname()
appname = ba.internal.appname()
if subplatform == 'google':
url = f'market://details?id=net.froemling.{appname}'
else:
url = 'market://details?id=net.froemling.{appname}cb'
url = f'market://details?id=net.froemling.{appname}cb'
else:
url = 'macappstore://itunes.apple.com/app/id416482767?ls=1&mt=12'

View file

@ -9,8 +9,8 @@ import threading
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable, Sequence
@ -242,7 +242,7 @@ class FileSelectorWindow(ba.Window):
max_str_width = 300.0
str_width = min(
max_str_width,
_ba.get_string_width(folder_name, suppress_warning=True))
ba.internal.get_string_width(folder_name, suppress_warning=True))
ba.textwidget(edit=self._path_text,
text=folder_name,
maxwidth=max_str_width)

View file

@ -8,8 +8,8 @@ import weakref
from enum import Enum
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui.tabs import TabRow
if TYPE_CHECKING:
@ -88,7 +88,7 @@ class GatherWindow(ba.Window):
self._transition_out = 'out_right'
scale_origin = None
ba.app.ui.set_main_menu_location('Gather')
_ba.set_party_icon_always_visible(True)
ba.internal.set_party_icon_always_visible(True)
uiscale = ba.app.ui.uiscale
self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
x_offs = 100 if uiscale is ba.UIScale.SMALL else 0
@ -151,7 +151,8 @@ class GatherWindow(ba.Window):
tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [
(self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText'))
]
if _ba.get_v1_account_misc_read_val('enablePublicParties', True):
if ba.internal.get_v1_account_misc_read_val('enablePublicParties',
True):
tabdefs.append((self.TabID.INTERNET,
ba.Lstr(resource=self._r + '.publicText')))
tabdefs.append(
@ -186,11 +187,13 @@ class GatherWindow(ba.Window):
self._tabs[tab_id] = tabtype(self)
if ba.app.ui.use_toolbars:
ba.widget(edit=self._tab_row.tabs[tabdefs[-1][0]].button,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=self._tab_row.tabs[tabdefs[-1][0]].button,
right_widget=ba.internal.get_special_widget('party_button'))
if uiscale is ba.UIScale.SMALL:
ba.widget(edit=self._tab_row.tabs[tabdefs[0][0]].button,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(
edit=self._tab_row.tabs[tabdefs[0][0]].button,
left_widget=ba.internal.get_special_widget('back_button'))
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180.0 + tabs_top_extra
@ -214,7 +217,7 @@ class GatherWindow(ba.Window):
self._restore_state()
def __del__(self) -> None:
_ba.set_party_icon_always_visible(False)
ba.internal.set_party_icon_always_visible(False)
def playlist_select(self, origin_widget: ba.Widget) -> None:
"""Called by the private-hosting tab to select a playlist."""

View file

@ -7,7 +7,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import ba.internal
from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
@ -51,8 +51,8 @@ class AboutGatherTab(GatherTab):
include_invite = True
msc_scale = 1.1
c_height_2 = min(region_height, string_height * msc_scale + 100)
try_tickets = _ba.get_v1_account_misc_read_val('friendTryTickets',
None)
try_tickets = ba.internal.get_v1_account_misc_read_val(
'friendTryTickets', None)
if try_tickets is None:
include_invite = False
self._container = ba.containerwidget(
@ -106,7 +106,7 @@ class AboutGatherTab(GatherTab):
def _invite_to_try_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.appinvite import handle_app_invites_press
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
handle_app_invites_press()

View file

@ -11,8 +11,8 @@ from enum import Enum
from dataclasses import dataclass
from bastd.ui.gather import GatherTab
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -341,8 +341,9 @@ class ManualGatherTab(GatherTab):
label=ba.Lstr(resource='gatherWindow.manualConnectText'),
autoselect=True)
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.widget(edit=btn1,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(
edit=btn1,
left_widget=ba.internal.get_special_widget('back_button'))
btnv -= b_height + b_space_extra
ba.buttonwidget(parent=self._container,
size=(b_width, b_height),
@ -686,7 +687,7 @@ class ManualGatherTab(GatherTab):
config = ba.app.config
config['Last Manual Party Connect Address'] = resolved_address
config.commit()
_ba.connect_to_party(resolved_address, port=port)
ba.internal.connect_to_party(resolved_address, port=port)
def _run_addr_fetch(self) -> None:
try:
@ -894,9 +895,12 @@ class ManualGatherTab(GatherTab):
if t_accessible_extra:
ba.textwidget(
edit=t_accessible_extra,
text=ba.Lstr(resource='gatherWindow.'
'manualRouterForwardingText',
subs=[('${PORT}',
str(_ba.get_game_port()))]),
text=ba.Lstr(
resource='gatherWindow.'
'manualRouterForwardingText',
subs=[
('${PORT}', str(ba.internal.get_game_port())),
],
),
color=color_bad,
)

View file

@ -8,7 +8,7 @@ import weakref
from typing import TYPE_CHECKING
import ba
import _ba
import ba.internal
from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
@ -42,13 +42,13 @@ class NetScanner:
ba.timer(0.25, ba.WeakCall(self.update), timetype=ba.TimeType.REAL)
def __del__(self) -> None:
_ba.end_host_scanning()
ba.internal.end_host_scanning()
def _on_select(self, host: dict[str, Any]) -> None:
self._last_selected_host = host
def _on_activate(self, host: dict[str, Any]) -> None:
_ba.connect_to_party(host['address'])
ba.internal.connect_to_party(host['address'])
def update(self) -> None:
"""(internal)"""
@ -65,7 +65,7 @@ class NetScanner:
# Grab this now this since adding widgets will change it.
last_selected_host = self._last_selected_host
hosts = _ba.host_scan_cycle()
hosts = ba.internal.host_scan_cycle()
for i, host in enumerate(hosts):
txt3 = ba.textwidget(parent=self._columnwidget,
size=(self._width / t_scale, 30),

View file

@ -11,11 +11,11 @@ from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
import ba
import _ba
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
from bacommon.net import (PrivateHostingState, PrivateHostingConfig,
PrivatePartyConnectResult)
import ba
import ba.internal
from bastd.ui.gather import GatherTab
from bastd.ui import getcurrency
@ -184,7 +184,9 @@ class PrivateGatherTab(GatherTab):
if playlist is None:
playlist = pvars.get_default_list_call()
hcfg.playlist = filter_playlist(playlist, sessiontype)
hcfg.playlist = filter_playlist(playlist,
sessiontype,
name=playlist_name)
randomize = cfg.get(f'{pvars.config_name} Playlist Randomize')
if not isinstance(randomize, bool):
@ -225,7 +227,7 @@ class PrivateGatherTab(GatherTab):
def _update_currency_ui(self) -> None:
# Keep currency count up to date if applicable.
try:
t_str = str(_ba.get_v1_account_ticket_count())
t_str = str(ba.internal.get_v1_account_ticket_count())
except Exception:
t_str = '?'
if self._get_tickets_button:
@ -245,7 +247,7 @@ class PrivateGatherTab(GatherTab):
if self._state.sub_tab is SubTabType.HOST:
# If we're not signed in, just refresh to show that.
if (_ba.get_v1_account_state() != 'signed_in'
if (ba.internal.get_v1_account_state() != 'signed_in'
and self._showing_not_signed_in_screen):
self._refresh_sub_tab()
else:
@ -254,8 +256,8 @@ class PrivateGatherTab(GatherTab):
if (self._last_hosting_state_query_time is None
or now - self._last_hosting_state_query_time > 15.0):
self._debug_server_comm('querying private party state')
if _ba.get_v1_account_state() == 'signed_in':
_ba.add_transaction(
if ba.internal.get_v1_account_state() == 'signed_in':
ba.internal.add_transaction(
{
'type': 'PRIVATE_PARTY_QUERY',
'expire_time': time.time() + 20,
@ -263,7 +265,7 @@ class PrivateGatherTab(GatherTab):
callback=ba.WeakCall(
self._hosting_state_idle_response),
)
_ba.run_transactions()
ba.internal.run_transactions()
else:
self._hosting_state_idle_response(None)
self._last_hosting_state_query_time = now
@ -436,7 +438,7 @@ class PrivateGatherTab(GatherTab):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.textwidget(parent=self._container,
size=(0, 0),
h_align='center',
@ -702,7 +704,7 @@ class PrivateGatherTab(GatherTab):
btnlabel = ba.Lstr(
resource='gatherWindow.hostingUnavailableText')
elif self._hostingstate.party_code is None:
ticon = _ba.charstr(ba.SpecialChar.TICKET)
ticon = ba.internal.charstr(ba.SpecialChar.TICKET)
nowtickets = self._hostingstate.tickets_to_host_now
if nowtickets > 0:
btnlabel = ba.Lstr(
@ -760,7 +762,7 @@ class PrivateGatherTab(GatherTab):
self._connect_press_time = now
self._debug_server_comm('sending private party connect')
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'PRIVATE_PARTY_CONNECT',
'expire_time': time.time() + 20,
@ -768,14 +770,14 @@ class PrivateGatherTab(GatherTab):
},
callback=ba.WeakCall(self._connect_response),
)
_ba.run_transactions()
ba.internal.run_transactions()
def _start_stop_button_press(self) -> None:
if (self._waiting_for_start_stop_response
or self._waiting_for_initial_state):
return
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'))
ba.playsound(ba.getsound('error'))
self._refresh_sub_tab()
@ -794,7 +796,7 @@ class PrivateGatherTab(GatherTab):
if self._hostingstate.tickets_to_host_now > 0:
ticket_count: int | None
try:
ticket_count = _ba.get_v1_account_ticket_count()
ticket_count = ba.internal.get_v1_account_ticket_count()
except Exception:
# FIXME: should add a ba.NotSignedInError we can use here.
ticket_count = None
@ -804,7 +806,7 @@ class PrivateGatherTab(GatherTab):
ba.playsound(ba.getsound('error'))
return
self._last_action_send_time = time.time()
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'PRIVATE_PARTY_START',
'config': dataclass_to_dict(self._hostingconfig),
@ -812,17 +814,17 @@ class PrivateGatherTab(GatherTab):
'expire_time': time.time() + 20,
},
callback=ba.WeakCall(self._hosting_state_response))
_ba.run_transactions()
ba.internal.run_transactions()
else:
self._last_action_send_time = time.time()
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'PRIVATE_PARTY_STOP',
'expire_time': time.time() + 20,
},
callback=ba.WeakCall(self._hosting_state_response))
_ba.run_transactions()
ba.internal.run_transactions()
ba.playsound(ba.getsound('click01'))
self._waiting_for_start_stop_response = True
@ -860,7 +862,7 @@ class PrivateGatherTab(GatherTab):
return
self._debug_server_comm('got valid connect response')
assert cresult.addr is not None and cresult.port is not None
_ba.connect_to_party(cresult.addr, port=cresult.port)
ba.internal.connect_to_party(cresult.addr, port=cresult.port)
except Exception:
self._debug_server_comm('got connect response error')
ba.playsound(ba.getsound('error'))

View file

@ -12,8 +12,8 @@ from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
import _ba
import ba
import ba.internal
from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
@ -88,8 +88,8 @@ class UIRow:
if party.clean_display_index == index:
return
ping_good = _ba.get_v1_account_misc_read_val('pingGood', 100)
ping_med = _ba.get_v1_account_misc_read_val('pingMed', 500)
ping_good = ba.internal.get_v1_account_misc_read_val('pingGood', 100)
ping_med = ba.internal.get_v1_account_misc_read_val('pingMed', 500)
self._clear()
hpos = 20
@ -122,8 +122,8 @@ class UIRow:
if party.stats_addr:
url = party.stats_addr.replace(
'${ACCOUNT}',
_ba.get_v1_account_misc_read_val_2('resolvedAccountID',
'UNKNOWN'))
ba.internal.get_v1_account_misc_read_val_2(
'resolvedAccountID', 'UNKNOWN'))
self._stats_button = ba.buttonwidget(
color=(0.3, 0.6, 0.94),
textcolor=(1.0, 1.0, 1.0),
@ -582,7 +582,7 @@ class PublicGatherTab(GatherTab):
c_height = region_height - 20
v = c_height - 35
v -= 25
is_public_enabled = _ba.get_public_party_enabled()
is_public_enabled = ba.internal.get_public_party_enabled()
v -= 30
ba.textwidget(
@ -643,7 +643,7 @@ class PublicGatherTab(GatherTab):
scale=1.2,
color=(1, 1, 1),
position=(240, v - 9),
text=str(_ba.get_public_party_max_size()))
text=str(ba.internal.get_public_party_max_size()))
btn1 = self._host_max_party_size_minus_button = (ba.buttonwidget(
parent=self._container,
size=(40, 40),
@ -711,7 +711,7 @@ class PublicGatherTab(GatherTab):
# If public sharing is already on,
# launch a status-check immediately.
if _ba.get_public_party_enabled():
if ba.internal.get_public_party_enabled():
self._do_status_check()
def _on_public_party_query_result(self,
@ -793,7 +793,7 @@ class PublicGatherTab(GatherTab):
self._process_pending_party_infos()
# Anytime we sign in/out, make sure we refresh our list.
signed_in = _ba.get_v1_account_state() == 'signed_in'
signed_in = ba.internal.get_v1_account_state() == 'signed_in'
if self._signed_in != signed_in:
self._signed_in = signed_in
self._party_lists_dirty = True
@ -806,7 +806,7 @@ class PublicGatherTab(GatherTab):
text = self._host_name_text
if text:
name = cast(str, ba.textwidget(query=self._host_name_text))
_ba.set_public_party_name(name)
ba.internal.set_public_party_name(name)
# Update status text.
status_text = self._join_status_text
@ -986,7 +986,7 @@ class PublicGatherTab(GatherTab):
p[1].index))
# If signed out or errored, show no parties.
if (_ba.get_v1_account_state() != 'signed_in'
if (ba.internal.get_v1_account_state() != 'signed_in'
or not self._have_valid_server_list):
self._parties_displayed = {}
else:
@ -1022,20 +1022,21 @@ class PublicGatherTab(GatherTab):
# Fire off a new public-party query periodically.
if (self._last_server_list_query_time is None
or now - self._last_server_list_query_time > 0.001 *
_ba.get_v1_account_misc_read_val('pubPartyRefreshMS', 10000)):
or now - self._last_server_list_query_time >
0.001 * ba.internal.get_v1_account_misc_read_val(
'pubPartyRefreshMS', 10000)):
self._last_server_list_query_time = now
if DEBUG_SERVER_COMMUNICATION:
print('REQUESTING SERVER LIST')
if _ba.get_v1_account_state() == 'signed_in':
_ba.add_transaction(
if ba.internal.get_v1_account_state() == 'signed_in':
ba.internal.add_transaction(
{
'type': 'PUBLIC_PARTY_QUERY',
'proto': ba.app.protocol_version,
'lang': ba.app.lang.language
},
callback=ba.WeakCall(self._on_public_party_query_result))
_ba.run_transactions()
ba.internal.run_transactions()
else:
self._on_public_party_query_result(None)
@ -1128,15 +1129,18 @@ class PublicGatherTab(GatherTab):
edit=text,
text=ba.Lstr(
value='${A}\n${B}${C}',
subs=[('${A}',
ba.Lstr(resource='gatherWindow.'
'partyStatusNotJoinableText')),
('${B}',
ba.Lstr(resource='gatherWindow.'
'manualRouterForwardingText',
subs=[('${PORT}',
str(_ba.get_game_port()))])),
('${C}', ex_line)]),
subs=[
('${A}',
ba.Lstr(resource='gatherWindow.'
'partyStatusNotJoinableText')),
('${B}',
ba.Lstr(resource='gatherWindow.'
'manualRouterForwardingText',
subs=[
('${PORT}',
str(ba.internal.get_game_port()))
])), ('${C}', ex_line)
]),
color=(1, 0, 0))
else:
ba.textwidget(edit=text,
@ -1156,7 +1160,7 @@ class PublicGatherTab(GatherTab):
def _on_start_advertizing_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@ -1166,16 +1170,16 @@ class PublicGatherTab(GatherTab):
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
_ba.set_public_party_name(name)
ba.internal.set_public_party_name(name)
cfg = ba.app.config
cfg['Public Party Name'] = name
cfg.commit()
ba.playsound(ba.getsound('shieldUp'))
_ba.set_public_party_enabled(True)
ba.internal.set_public_party_enabled(True)
# In GUI builds we want to authenticate clients only when hosting
# public parties.
_ba.set_authenticate_clients(True)
ba.internal.set_authenticate_clients(True)
self._do_status_check()
ba.buttonwidget(
@ -1186,11 +1190,11 @@ class PublicGatherTab(GatherTab):
on_activate_call=self._on_stop_advertising_press)
def _on_stop_advertising_press(self) -> None:
_ba.set_public_party_enabled(False)
ba.internal.set_public_party_enabled(False)
# In GUI builds we want to authenticate clients only when hosting
# public parties.
_ba.set_authenticate_clients(False)
ba.internal.set_authenticate_clients(False)
ba.playsound(ba.getsound('shieldDown'))
text = self._host_status_text
if text:
@ -1222,7 +1226,7 @@ class PublicGatherTab(GatherTab):
now = time.time()
last_connect_time = self._last_connect_attempt_time
if last_connect_time is None or now - last_connect_time > 2.0:
_ba.connect_to_party(address, port=port)
ba.internal.connect_to_party(address, port=port)
self._last_connect_attempt_time = now
def set_public_party_selection(self, sel: Selection) -> None:
@ -1233,12 +1237,12 @@ class PublicGatherTab(GatherTab):
self._have_user_selected_row = True
def _on_max_public_party_size_minus_press(self) -> None:
val = max(1, _ba.get_public_party_max_size() - 1)
_ba.set_public_party_max_size(val)
val = max(1, ba.internal.get_public_party_max_size() - 1)
ba.internal.set_public_party_max_size(val)
ba.textwidget(edit=self._host_max_party_size_value, text=str(val))
def _on_max_public_party_size_plus_press(self) -> None:
val = _ba.get_public_party_max_size()
val = ba.internal.get_public_party_max_size()
val += 1
_ba.set_public_party_max_size(val)
ba.internal.set_public_party_max_size(val)
ba.textwidget(edit=self._host_max_party_size_value, text=str(val))

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -176,38 +176,34 @@ class GetCurrencyWindow(ba.Window):
rsrc = self._r + '.ticketsText'
c2txt = ba.Lstr(
resource=rsrc,
subs=[('${COUNT}',
str(_ba.get_v1_account_misc_read_val('tickets2Amount',
500)))])
c3txt = ba.Lstr(
resource=rsrc,
subs=[
('${COUNT}',
str(_ba.get_v1_account_misc_read_val('tickets3Amount', 1500)))
])
c4txt = ba.Lstr(
resource=rsrc,
subs=[
('${COUNT}',
str(_ba.get_v1_account_misc_read_val('tickets4Amount', 5000)))
])
c5txt = ba.Lstr(
resource=rsrc,
subs=[
('${COUNT}',
str(_ba.get_v1_account_misc_read_val('tickets5Amount',
15000)))
])
c2txt = ba.Lstr(resource=rsrc,
subs=[('${COUNT}',
str(
ba.internal.get_v1_account_misc_read_val(
'tickets2Amount', 500)))])
c3txt = ba.Lstr(resource=rsrc,
subs=[('${COUNT}',
str(
ba.internal.get_v1_account_misc_read_val(
'tickets3Amount', 1500)))])
c4txt = ba.Lstr(resource=rsrc,
subs=[('${COUNT}',
str(
ba.internal.get_v1_account_misc_read_val(
'tickets4Amount', 5000)))])
c5txt = ba.Lstr(resource=rsrc,
subs=[('${COUNT}',
str(
ba.internal.get_v1_account_misc_read_val(
'tickets5Amount', 15000)))])
h = 110.0
# enable buttons if we have prices..
tickets2_price = _ba.get_price('tickets2')
tickets3_price = _ba.get_price('tickets3')
tickets4_price = _ba.get_price('tickets4')
tickets5_price = _ba.get_price('tickets5')
tickets2_price = ba.internal.get_price('tickets2')
tickets3_price = ba.internal.get_price('tickets3')
tickets4_price = ba.internal.get_price('tickets4')
tickets5_price = ba.internal.get_price('tickets5')
# TEMP
# tickets1_price = '$0.99'
@ -252,7 +248,7 @@ class GetCurrencyWindow(ba.Window):
tex_name='ticketRolls',
tex_scale=1.2) # 19.99-ish
self._enable_ad_button = _ba.has_video_ads()
self._enable_ad_button = ba.internal.has_video_ads()
h = self._width * 0.5 + 110.0
v = self._height - b_size[1] - 115.0
@ -263,11 +259,12 @@ class GetCurrencyWindow(ba.Window):
'ad',
position=(h + h_offs, v),
size=b_size_3,
label=ba.Lstr(resource=self._r + '.ticketsFromASponsorText',
subs=[('${COUNT}',
str(
_ba.get_v1_account_misc_read_val(
'sponsorTickets', 5)))]),
label=ba.Lstr(
resource=self._r + '.ticketsFromASponsorText',
subs=[('${COUNT}',
str(
ba.internal.get_v1_account_misc_read_val(
'sponsorTickets', 5)))]),
tex_name='ticketsMore',
enabled=self._enable_ad_button,
tex_opacity=0.6,
@ -308,7 +305,7 @@ class GetCurrencyWindow(ba.Window):
resource='gatherWindow.earnTicketsForRecommendingText',
subs=[('${COUNT}',
str(
_ba.get_v1_account_misc_read_val(
ba.internal.get_v1_account_misc_read_val(
'sponsorTickets', 5)))]),
tex_name='ticketsMore',
enabled=True,
@ -431,22 +428,22 @@ class GetCurrencyWindow(ba.Window):
import datetime
# if we somehow get signed out, just die..
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
self._back()
return
self._ticket_count = _ba.get_v1_account_ticket_count()
self._ticket_count = ba.internal.get_v1_account_ticket_count()
# update our incentivized ad button depending on whether ads are
# available
if self._ad_button is not None:
next_reward_ad_time = _ba.get_v1_account_misc_read_val_2(
next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2(
'nextRewardAdTime', None)
if next_reward_ad_time is not None:
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
next_reward_ad_time)
now = datetime.datetime.utcnow()
if (_ba.have_incentivized_ad() and
if (ba.internal.have_incentivized_ad() and
(next_reward_ad_time is None or next_reward_ad_time <= now)):
self._ad_button_greyed = False
ba.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
@ -499,8 +496,8 @@ class GetCurrencyWindow(ba.Window):
if ((app.test_build or
(app.platform == 'android'
and app.subplatform in ['oculus', 'cardboard']))
and _ba.get_v1_account_misc_read_val('allowAccountLinking2',
False)):
and ba.internal.get_v1_account_misc_read_val(
'allowAccountLinking2', False)):
ba.screenmessage(ba.Lstr(resource=self._r +
'.unavailableLinkAccountText'),
color=(1, 0.5, 0))
@ -514,7 +511,7 @@ class GetCurrencyWindow(ba.Window):
from bastd.ui import appinvite
from ba.internal import master_server_get
if item == 'app_invite':
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
appinvite.handle_app_invites_press()
@ -559,7 +556,7 @@ class GetCurrencyWindow(ba.Window):
if item == 'ad':
import datetime
# if ads are disabled until some time, error..
next_reward_ad_time = _ba.get_v1_account_misc_read_val_2(
next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2(
'nextRewardAdTime', None)
if next_reward_ad_time is not None:
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
@ -572,9 +569,9 @@ class GetCurrencyWindow(ba.Window):
resource='getTicketsWindow.unavailableTemporarilyText'),
color=(1, 0, 0))
elif self._enable_ad_button:
_ba.app.ads.show_ad('tickets')
ba.app.ads.show_ad('tickets')
else:
_ba.purchase(item)
ba.internal.purchase(item)
def _back(self) -> None:
from bastd.ui.store import browser

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -76,8 +76,9 @@ class HelpWindow(ba.Window):
capture_arrows=True)
if ba.app.ui.use_toolbars:
ba.widget(edit=self._scrollwidget,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=self._scrollwidget,
right_widget=ba.internal.get_special_widget('party_button'))
ba.containerwidget(edit=self._root_widget,
selected_child=self._scrollwidget)
@ -86,8 +87,9 @@ class HelpWindow(ba.Window):
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.containerwidget(edit=self._root_widget,
on_cancel_call=self._close)
ba.widget(edit=self._scrollwidget,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(
edit=self._scrollwidget,
left_widget=ba.internal.get_special_widget('back_button'))
else:
btn = ba.buttonwidget(
parent=self._root_widget,
@ -150,7 +152,8 @@ class HelpWindow(ba.Window):
maxwidth=txt_maxwidth)
txt_width = min(
txt_maxwidth,
_ba.get_string_width(txt, suppress_warning=True) * txt_scale)
ba.internal.get_string_width(txt, suppress_warning=True) *
txt_scale)
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
@ -350,7 +353,8 @@ class HelpWindow(ba.Window):
maxwidth=txt_maxwidth)
txt_width = min(
txt_maxwidth,
_ba.get_string_width(txt, suppress_warning=True) * txt_scale)
ba.internal.get_string_width(txt, suppress_warning=True) *
txt_scale)
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
@ -496,7 +500,8 @@ class HelpWindow(ba.Window):
maxwidth=txt_maxwidth)
txt_width = min(
txt_maxwidth,
_ba.get_string_width(txt, suppress_warning=True) * txt_scale)
ba.internal.get_string_width(txt, suppress_warning=True) *
txt_scale)
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
ba.imagewidget(parent=self._subcontainer,

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup
if TYPE_CHECKING:
@ -137,7 +137,7 @@ class IconPicker(popup.PopupWindow):
def _on_store_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._transition_out()

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -37,7 +37,7 @@ class KioskWindow(ba.Window):
self._show_multiplayer = False
# Let's reset all random player names every time we hit the main menu.
_ba.reset_random_player_names()
ba.internal.reset_random_player_names()
# Reset achievements too (at least locally).
ba.app.config['Achievements'] = {}
@ -360,7 +360,7 @@ class KioskWindow(ba.Window):
def _update(self) -> None:
# Kiosk-mode is designed to be used signed-out... try for force
# the issue.
if _ba.get_v1_account_state() == 'signed_in':
if ba.internal.get_v1_account_state() == 'signed_in':
# _bs.sign_out()
# FIXME: Try to delete player profiles here too.
pass
@ -390,11 +390,12 @@ class KioskWindow(ba.Window):
'type': 'bs_elimination.EliminationGame'
}]
appconfig['Free-for-All Playlist Selection'] = 'Just Epic Elim'
_ba.fade_screen(False,
endcall=ba.Call(
ba.pushcall,
ba.Call(_ba.new_host_session,
ba.FreeForAllSession)))
ba.internal.fade_screen(False,
endcall=ba.Call(
ba.pushcall,
ba.Call(
ba.internal.new_host_session,
ba.FreeForAllSession)))
else:
if mode == 'ctf':
appconfig['Team Tournament Playlists']['Just CTF'] = [{
@ -423,11 +424,12 @@ class KioskWindow(ba.Window):
}]
appconfig['Team Tournament Playlist Selection'] = (
'Just Hockey')
_ba.fade_screen(False,
endcall=ba.Call(
ba.pushcall,
ba.Call(_ba.new_host_session,
ba.DualTeamSession)))
ba.internal.fade_screen(False,
endcall=ba.Call(
ba.pushcall,
ba.Call(
ba.internal.new_host_session,
ba.DualTeamSession)))
ba.containerwidget(edit=self._root_widget, transition='out_left')
return

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -94,7 +94,7 @@ class LeagueRankButton:
self._smooth_update_timer: ba.Timer | None = None
# Take note of our account state; we'll refresh later if this changes.
self._account_state_num = _ba.get_v1_account_state_num()
self._account_state_num = ba.internal.get_v1_account_state_num()
self._last_power_ranking_query_time: float | None = None
self._doing_power_ranking_query = False
self.set_position(position)
@ -111,7 +111,7 @@ class LeagueRankButton:
self._update_for_league_rank_data(data)
def _on_activate(self) -> None:
_ba.increment_analytics_count('League rank button press')
ba.internal.increment_analytics_count('League rank button press')
self._on_activate_call()
def __del__(self) -> None:
@ -224,7 +224,7 @@ class LeagueRankButton:
in_top = data is not None and data['rank'] is not None
do_percent = False
if data is None or _ba.get_v1_account_state() != 'signed_in':
if data is None or ba.internal.get_v1_account_state() != 'signed_in':
self._percent = self._rank = None
status_text = '-'
elif in_top:
@ -335,7 +335,7 @@ class LeagueRankButton:
cur_time = ba.time(ba.TimeType.REAL)
# If our account state has changed, refresh our UI.
account_state_num = _ba.get_v1_account_state_num()
account_state_num = ba.internal.get_v1_account_state_num()
if account_state_num != self._account_state_num:
self._account_state_num = account_state_num
@ -350,7 +350,7 @@ class LeagueRankButton:
or cur_time - self._last_power_ranking_query_time > 30.0):
self._last_power_ranking_query_time = cur_time
self._doing_power_ranking_query = True
_ba.power_ranking_query(
ba.internal.power_ranking_query(
callback=ba.WeakCall(self._on_power_ranking_query_response))
def _default_on_activate_call(self) -> None:

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import copy
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup as popup_ui
if TYPE_CHECKING:
@ -118,7 +118,7 @@ class LeagueRankWindow(ba.Window):
self._season: str | None = None
# take note of our account state; we'll refresh later if this changes
self._account_state = _ba.get_v1_account_state()
self._account_state = ba.internal.get_v1_account_state()
self._refresh()
self._restore_state()
@ -155,8 +155,9 @@ class LeagueRankWindow(ba.Window):
resource='coopSelectWindow.activenessAllTimeInfoText'
if self._season == 'a' else 'coopSelectWindow.activenessInfoText',
subs=[('${MAX}',
str(_ba.get_v1_account_misc_read_val('activenessMax',
1.0)))])
str(
ba.internal.get_v1_account_misc_read_val(
'activenessMax', 1.0)))])
confirm.ConfirmWindow(txt,
cancel_button=False,
width=460,
@ -168,7 +169,7 @@ class LeagueRankWindow(ba.Window):
txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText',
subs=[('${PERCENT}',
str(
_ba.get_v1_account_misc_read_val(
ba.internal.get_v1_account_misc_read_val(
'proPowerRankingBoost', 10))),
('${PRO}',
ba.Lstr(resource='store.bombSquadProNameText',
@ -208,7 +209,7 @@ class LeagueRankWindow(ba.Window):
cur_time = ba.time(ba.TimeType.REAL)
# if our account state has changed, refresh our UI
account_state = _ba.get_v1_account_state()
account_state = ba.internal.get_v1_account_state()
if account_state != self._account_state:
self._account_state = account_state
self._save_state()
@ -242,9 +243,9 @@ class LeagueRankWindow(ba.Window):
self._last_power_ranking_query_time = cur_time
self._doing_power_ranking_query = True
_ba.power_ranking_query(season=self._requested_season,
callback=ba.WeakCall(
self._on_power_ranking_query_response))
ba.internal.power_ranking_query(
season=self._requested_season,
callback=ba.WeakCall(self._on_power_ranking_query_response))
def _refresh(self) -> None:
# pylint: disable=too-many-statements
@ -352,7 +353,7 @@ class LeagueRankWindow(ba.Window):
maxwidth=200)
self._activity_mult_button: ba.Widget | None
if _ba.get_v1_account_misc_read_val('act', False):
if ba.internal.get_v1_account_misc_read_val('act', False):
self._activity_mult_button = ba.buttonwidget(
parent=w_parent,
position=(h2 - 60, v2 + 10),
@ -564,7 +565,7 @@ class LeagueRankWindow(ba.Window):
self._on_more_press))
def _on_more_press(self) -> None:
our_login_id = _ba.get_public_login_id()
our_login_id = ba.internal.get_public_login_id()
# our_login_id = _bs.get_account_misc_read_val_2(
# 'resolvedAccountID', None)
if not self._can_do_more_button or our_login_id is None:
@ -582,7 +583,7 @@ class LeagueRankWindow(ba.Window):
league_str = '&league=' + self._league_url_arg
else:
league_str = ''
ba.open_url(_ba.get_master_server_address() +
ba.open_url(ba.internal.get_master_server_address() +
'/highscores?list=powerRankings&v=2' + league_str +
season_str + '&player=' + our_login_id)
@ -602,7 +603,7 @@ class LeagueRankWindow(ba.Window):
finished_season_unranked = False
self._can_do_more_button = True
extra_text = ''
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
status_text = '(' + ba.Lstr(
resource='notSignedInText').evaluate() + ')'
elif in_top:
@ -746,7 +747,7 @@ class LeagueRankWindow(ba.Window):
ba.textwidget(edit=self._league_text, text=lname, color=lcolor)
l_text_width = min(
self._league_text_maxwidth,
_ba.get_string_width(lname, suppress_warning=True) *
ba.internal.get_string_width(lname, suppress_warning=True) *
self._league_text_scale)
ba.textwidget(
edit=self._league_number_text,
@ -789,8 +790,8 @@ class LeagueRankWindow(ba.Window):
have_pro = False if data is None else data['p']
pro_mult = 1.0 + float(
_ba.get_v1_account_misc_read_val('proPowerRankingBoost',
0.0)) * 0.01
ba.internal.get_v1_account_misc_read_val('proPowerRankingBoost',
0.0)) * 0.01
# pylint: disable=consider-using-f-string
ba.textwidget(edit=self._pro_mult_text,
text=' -' if

View file

@ -8,7 +8,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -21,8 +21,8 @@ class MainMenuWindow(ba.Window):
# pylint: disable=cyclic-import
import threading
from bastd.mainmenu import MainMenuSession
self._in_game = not isinstance(_ba.get_foreground_host_session(),
MainMenuSession)
self._in_game = not isinstance(
ba.internal.get_foreground_host_session(), MainMenuSession)
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
@ -67,9 +67,9 @@ class MainMenuWindow(ba.Window):
self._restore_state()
# Keep an eye on a few things and refresh if they change.
self._account_state = _ba.get_v1_account_state()
self._account_state_num = _ba.get_v1_account_state_num()
self._account_type = (_ba.get_v1_account_type()
self._account_state = ba.internal.get_v1_account_state()
self._account_state_num = ba.internal.get_v1_account_state_num()
self._account_type = (ba.internal.get_v1_account_type()
if self._account_state == 'signed_in' else None)
self._refresh_timer = ba.Timer(1.0,
ba.WeakCall(self._check_refresh),
@ -101,7 +101,7 @@ class MainMenuWindow(ba.Window):
try:
app = ba.app
force_test = False
_ba.get_local_active_input_devices_count()
ba.internal.get_local_active_input_devices_count()
if (((app.on_tv or app.platform == 'mac')
and ba.app.config.get('launchCount', 0) <= 1)
or force_test):
@ -122,10 +122,11 @@ class MainMenuWindow(ba.Window):
ba.print_exception('Error showing get-remote-app info')
def _get_store_char_tex(self) -> str:
return ('storeCharacterXmas' if _ba.get_v1_account_misc_read_val(
'xmas', False) else
'storeCharacterEaster' if _ba.get_v1_account_misc_read_val(
'easter', False) else 'storeCharacter')
return (
'storeCharacterXmas' if ba.internal.get_v1_account_misc_read_val(
'xmas', False) else
'storeCharacterEaster' if ba.internal.get_v1_account_misc_read_val(
'easter', False) else 'storeCharacter')
def _check_refresh(self) -> None:
if not self._root_widget:
@ -138,13 +139,14 @@ class MainMenuWindow(ba.Window):
return
store_char_tex = self._get_store_char_tex()
account_state_num = _ba.get_v1_account_state_num()
account_state_num = ba.internal.get_v1_account_state_num()
if (account_state_num != self._account_state_num
or store_char_tex != self._store_char_tex):
self._store_char_tex = store_char_tex
self._account_state_num = account_state_num
account_state = self._account_state = (_ba.get_v1_account_state())
self._account_type = (_ba.get_v1_account_type()
account_state = self._account_state = (
ba.internal.get_v1_account_state())
self._account_type = (ba.internal.get_v1_account_type()
if account_state == 'signed_in' else None)
self._save_state()
self._refresh()
@ -185,7 +187,7 @@ class MainMenuWindow(ba.Window):
(not self._in_game or not app.toolbar_test)
and not (self._is_demo or self._is_arcade or self._is_iircade))
self._input_device = input_device = _ba.get_ui_input_device()
self._input_device = input_device = ba.internal.get_ui_input_device()
self._input_player = input_device.player if input_device else None
self._connected_to_remote_player = (
input_device.is_connected_to_remote_player()
@ -213,8 +215,8 @@ class MainMenuWindow(ba.Window):
on_activate_call=self._settings)
# Scattered eggs on easter.
if _ba.get_v1_account_misc_read_val('easter',
False) and not self._in_game:
if ba.internal.get_v1_account_misc_read_val(
'easter', False) and not self._in_game:
icon_size = 34
ba.imagewidget(parent=self._root_widget,
position=(h - icon_size * 0.5 - 15,
@ -232,7 +234,7 @@ class MainMenuWindow(ba.Window):
self._p_index += 1
# If we're in a replay, we have a 'Leave Replay' button.
if _ba.is_in_replay():
if ba.internal.is_in_replay():
ba.buttonwidget(parent=self._root_widget,
position=(h - self._button_width * 0.5 * scale,
v),
@ -241,7 +243,7 @@ class MainMenuWindow(ba.Window):
autoselect=self._use_autoselect,
label=ba.Lstr(resource='replayEndText'),
on_activate_call=self._confirm_end_replay)
elif _ba.get_foreground_host_session() is not None:
elif ba.internal.get_foreground_host_session() is not None:
ba.buttonwidget(
parent=self._root_widget,
position=(h - self._button_width * 0.5 * scale, v),
@ -310,7 +312,7 @@ class MainMenuWindow(ba.Window):
transition_delay=self._tdelay)
# Scattered eggs on easter.
if _ba.get_v1_account_misc_read_val('easter', False):
if ba.internal.get_v1_account_misc_read_val('easter', False):
icon_size = 30
ba.imagewidget(parent=self._root_widget,
position=(h - icon_size * 0.5 + 25,
@ -341,7 +343,7 @@ class MainMenuWindow(ba.Window):
# Add speed-up/slow-down buttons for replays.
# (ideally this should be part of a fading-out playback bar like most
# media players but this works for now).
if _ba.is_in_replay():
if ba.internal.is_in_replay():
b_size = 50.0
b_buffer = 10.0
t_scale = 0.75
@ -427,8 +429,8 @@ class MainMenuWindow(ba.Window):
self._height = 200.0
enable_account_button = True
account_type_name: str | ba.Lstr
if _ba.get_v1_account_state() == 'signed_in':
account_type_name = _ba.get_v1_account_display_string()
if ba.internal.get_v1_account_state() == 'signed_in':
account_type_name = ba.internal.get_v1_account_display_string()
account_type_icon = None
account_textcolor = (1.0, 1.0, 1.0)
else:
@ -618,8 +620,8 @@ class MainMenuWindow(ba.Window):
enable_sound=account_type_enable_button_sound)
# Scattered eggs on easter.
if _ba.get_v1_account_misc_read_val('easter',
False) and not self._in_game:
if ba.internal.get_v1_account_misc_read_val(
'easter', False) and not self._in_game:
icon_size = 32
ba.imagewidget(parent=self._root_widget,
position=(h - icon_size * 0.5 + 35,
@ -648,8 +650,8 @@ class MainMenuWindow(ba.Window):
self._how_to_play_button = btn
# Scattered eggs on easter.
if _ba.get_v1_account_misc_read_val('easter',
False) and not self._in_game:
if ba.internal.get_v1_account_misc_read_val(
'easter', False) and not self._in_game:
icon_size = 28
ba.imagewidget(parent=self._root_widget,
position=(h - icon_size * 0.5 + 30,
@ -682,7 +684,7 @@ class MainMenuWindow(ba.Window):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
custom_menu_entries: list[dict[str, Any]] = []
session = _ba.get_foreground_host_session()
session = ba.internal.get_foreground_host_session()
if session is not None:
try:
custom_menu_entries = session.get_custom_menu_entries()
@ -819,8 +821,9 @@ class MainMenuWindow(ba.Window):
if ba.do_once():
print('_change_replay_speed called without widget')
return
_ba.set_replay_speed_exponent(_ba.get_replay_speed_exponent() + offs)
actual_speed = pow(2.0, _ba.get_replay_speed_exponent())
ba.internal.set_replay_speed_exponent(
ba.internal.get_replay_speed_exponent() + offs)
actual_speed = pow(2.0, ba.internal.get_replay_speed_exponent())
ba.textwidget(edit=self._replay_speed_text,
text=ba.Lstr(resource='watchWindow.playbackSpeedText',
subs=[('${SPEED}', str(actual_speed))]))
@ -851,7 +854,7 @@ class MainMenuWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.store.browser import StoreBrowserWindow
from bastd.ui.account import show_sign_in_prompt
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
@ -892,7 +895,7 @@ class MainMenuWindow(ba.Window):
cancel_is_selected=True)
def _leave_party(self) -> None:
_ba.disconnect_from_host()
ba.internal.disconnect_from_host()
def _end_game(self) -> None:
if not self._root_widget:
@ -942,7 +945,7 @@ class MainMenuWindow(ba.Window):
def _do_game_service_press(self) -> None:
self._save_state()
_ba.show_online_score_ui()
ba.internal.show_online_score_ui()
def _save_state(self) -> None:

View file

@ -6,10 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING, cast
import _ba
import ba
from ba import charstr
from ba import SpecialChar as SpCh
import ba.internal
if TYPE_CHECKING:
pass
@ -25,7 +23,7 @@ class OnScreenKeyboardWindow(ba.Window):
uiscale = ba.app.ui.uiscale
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
super().__init__(root_widget=ba.containerwidget(
parent=_ba.get_special_widget('overlay_stack'),
parent=ba.internal.get_special_widget('overlay_stack'),
size=(self._width, self._height + top_extra),
transition='in_scale',
scale_origin_stack_offset=self._target_text.
@ -129,7 +127,7 @@ class OnScreenKeyboardWindow(ba.Window):
autoselect=True,
textcolor=key_textcolor,
color=key_color_dark,
label=charstr(SpCh.SHIFT),
label=ba.charstr(ba.SpecialChar.SHIFT),
enable_sound=False,
extra_touch_border_scale=0.3,
button_type='square',
@ -165,7 +163,7 @@ class OnScreenKeyboardWindow(ba.Window):
repeat=True,
textcolor=key_textcolor,
color=key_color_dark,
label=charstr(SpCh.DELETE),
label=ba.charstr(ba.SpecialChar.DELETE),
button_type='square',
on_activate_call=self._del)
v -= (key_height + 9)
@ -193,7 +191,7 @@ class OnScreenKeyboardWindow(ba.Window):
enable_sound=False,
textcolor=key_textcolor,
color=key_color_dark,
label=charstr(SpCh.LOGO_FLAT),
label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
extra_touch_border_scale=0.3,
button_type='square',
)
@ -213,8 +211,10 @@ class OnScreenKeyboardWindow(ba.Window):
# Show change instructions only if we have more than one
# keyboard option.
if (ba.app.meta.scanresults is not None
and len(ba.app.meta.scanresults.keyboards) > 1):
keyboards = (ba.app.meta.scanresults.exports_of_class(
ba.Keyboard) if ba.app.meta.scanresults is not None
else [])
if len(keyboards) > 1:
ba.textwidget(
parent=self._root_widget,
h_align='center',
@ -239,7 +239,8 @@ class OnScreenKeyboardWindow(ba.Window):
def _get_keyboard(self) -> ba.Keyboard:
assert ba.app.meta.scanresults is not None
classname = ba.app.meta.scanresults.keyboards[self._keyboard_index]
classname = ba.app.meta.scanresults.exports_of_class(
ba.Keyboard)[self._keyboard_index]
kbclass = ba.getclass(classname, ba.Keyboard)
return kbclass()
@ -252,14 +253,14 @@ class OnScreenKeyboardWindow(ba.Window):
ba.buttonwidget(edit=self._shift_button,
color=self._key_color_lit
if self._mode == 'caps' else self._key_color_dark,
label=charstr(SpCh.SHIFT),
label=ba.charstr(ba.SpecialChar.SHIFT),
on_activate_call=self._shift)
ba.buttonwidget(edit=self._num_mode_button,
label='123#&*',
on_activate_call=self._num_mode)
ba.buttonwidget(edit=self._emoji_button,
color=self._key_color_dark,
label=charstr(SpCh.LOGO_FLAT),
label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
on_activate_call=self._next_mode)
else:
if self._mode == 'num':
@ -275,7 +276,7 @@ class OnScreenKeyboardWindow(ba.Window):
on_activate_call=self._abc_mode)
ba.buttonwidget(edit=self._emoji_button,
color=self._key_color_dark,
label=charstr(SpCh.LOGO_FLAT),
label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
on_activate_call=self._next_mode)
for i, btn in enumerate(self._char_keys):
@ -318,10 +319,11 @@ class OnScreenKeyboardWindow(ba.Window):
def _next_keyboard(self) -> None:
assert ba.app.meta.scanresults is not None
self._keyboard_index = (self._keyboard_index + 1) % len(
ba.app.meta.scanresults.keyboards)
kbexports = ba.app.meta.scanresults.exports_of_class(ba.Keyboard)
self._keyboard_index = (self._keyboard_index + 1) % len(kbexports)
self._load_keyboard()
if len(ba.app.meta.scanresults.keyboards) < 2:
if len(kbexports) < 2:
ba.playsound(ba.getsound('error'))
ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'),
color=(1, 0, 0))

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING, cast
import _ba
import ba
import ba.internal
from bastd.ui import popup
if TYPE_CHECKING:
@ -19,10 +19,10 @@ class PartyWindow(ba.Window):
"""Party list/chat window."""
def __del__(self) -> None:
_ba.set_party_window_open(False)
ba.internal.set_party_window_open(False)
def __init__(self, origin: Sequence[float] = (0, 0)):
_ba.set_party_window_open(True)
ba.internal.set_party_window_open(True)
self._r = 'partyWindow'
self._popup_type: str | None = None
self._popup_party_member_client_id: int | None = None
@ -35,7 +35,7 @@ class PartyWindow(ba.Window):
size=(self._width, self._height),
transition='in_scale',
color=(0.40, 0.55, 0.20),
parent=_ba.get_special_widget('overlay_stack'),
parent=ba.internal.get_special_widget('overlay_stack'),
on_outside_click_call=self.close_with_sound,
scale_origin_stack_offset=origin,
scale=(2.0 if uiscale is ba.UIScale.SMALL else
@ -68,7 +68,7 @@ class PartyWindow(ba.Window):
color=(0.55, 0.73, 0.25),
iconscale=1.2)
info = _ba.get_connection_to_host_info()
info = ba.internal.get_connection_to_host_info()
if info.get('name', '') != '':
title = ba.Lstr(value=info['name'])
else:
@ -116,7 +116,7 @@ class PartyWindow(ba.Window):
# add all existing messages if chat is not muted
if not ba.app.config.resolve('Chat Muted'):
msgs = _ba.get_chat_messages()
msgs = ba.internal.get_chat_messages()
for msg in msgs:
self._add_msg(msg)
@ -215,7 +215,7 @@ class PartyWindow(ba.Window):
ba.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.0))
# update roster section
roster = _ba.get_game_roster()
roster = ba.internal.get_game_roster()
if roster != self._roster:
self._roster = roster
@ -318,7 +318,7 @@ class PartyWindow(ba.Window):
if is_host:
twd = min(
c_width * 0.85,
_ba.get_string_width(
ba.internal.get_string_width(
p_str, suppress_warning=True) *
t_scale)
self._name_widgets.append(
@ -357,7 +357,7 @@ class PartyWindow(ba.Window):
assert self._popup_party_member_client_id is not None
# Ban for 5 minutes.
result = _ba.disconnect_client(
result = ba.internal.disconnect_client(
self._popup_party_member_client_id, ban_time=5 * 60)
if not result:
ba.playsound(ba.getsound('error'))
@ -379,12 +379,12 @@ class PartyWindow(ba.Window):
def _on_party_member_press(self, client_id: int, is_host: bool,
widget: ba.Widget) -> None:
# if we're the host, pop up 'kick' options for all non-host members
if _ba.get_foreground_host_session() is not None:
if ba.internal.get_foreground_host_session() is not None:
kick_str = ba.Lstr(resource='kickText')
else:
# kick-votes appeared in build 14248
if (_ba.get_connection_to_host_info().get('build_number', 0) <
14248):
if (ba.internal.get_connection_to_host_info().get(
'build_number', 0) < 14248):
return
kick_str = ba.Lstr(resource='kickVoteText')
uiscale = ba.app.ui.uiscale
@ -401,7 +401,8 @@ class PartyWindow(ba.Window):
self._popup_party_member_is_host = is_host
def _send_chat_message(self) -> None:
_ba.chatmessage(cast(str, ba.textwidget(query=self._text_field)))
ba.internal.chatmessage(
cast(str, ba.textwidget(query=self._text_field)))
ba.textwidget(edit=self._text_field, text='')
def close(self) -> None:

View file

@ -8,8 +8,8 @@ import random
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Sequence
@ -257,11 +257,11 @@ class PartyQueueWindow(ba.Window):
def __del__(self) -> None:
try:
ba.app.ui.have_party_queue_window = False
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'PARTY_QUEUE_REMOVE',
'q': self._queue_id
})
_ba.run_transactions()
ba.internal.run_transactions()
except Exception:
ba.print_exception('Error removing self from party queue.')
@ -320,8 +320,9 @@ class PartyQueueWindow(ba.Window):
if -1 not in self._dudes_by_id:
dude = self.Dude(
self, response['d'], self._initial_offset, True,
_ba.get_v1_account_misc_read_val_2('resolvedAccountID', None),
_ba.get_v1_account_display_string())
ba.internal.get_v1_account_misc_read_val_2(
'resolvedAccountID', None),
ba.internal.get_v1_account_display_string())
self._dudes_by_id[-1] = dude
self._dudes.append(dude)
else:
@ -448,26 +449,26 @@ class PartyQueueWindow(ba.Window):
now = time.time()
if (self._last_connect_attempt_time is None
or now - self._last_connect_attempt_time > 10.0):
_ba.connect_to_party(address=self._address,
port=self._port,
print_progress=False)
ba.internal.connect_to_party(address=self._address,
port=self._port,
print_progress=False)
self._last_connect_attempt_time = now
def on_boost_press(self) -> None:
"""Boost was pressed."""
from bastd.ui import account
from bastd.ui import getcurrency
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
if _ba.get_v1_account_ticket_count() < self._boost_tickets:
if ba.internal.get_v1_account_ticket_count() < self._boost_tickets:
ba.playsound(ba.getsound('error'))
getcurrency.show_get_tickets_prompt()
return
ba.playsound(ba.getsound('laserReverse'))
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'PARTY_QUEUE_BOOST',
't': self._boost_tickets,
@ -497,18 +498,18 @@ class PartyQueueWindow(ba.Window):
# Update boost button color based on if we have enough moola.
if self._boost_button is not None:
can_boost = (
(_ba.get_v1_account_state() == 'signed_in'
and _ba.get_v1_account_ticket_count() >= self._boost_tickets))
can_boost = ((ba.internal.get_v1_account_state() == 'signed_in'
and ba.internal.get_v1_account_ticket_count() >=
self._boost_tickets))
ba.buttonwidget(edit=self._boost_button,
color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7))
# Update ticket-count.
if self._tickets_text is not None:
if self._boost_button is not None:
if _ba.get_v1_account_state() == 'signed_in':
if ba.internal.get_v1_account_state() == 'signed_in':
val = ba.charstr(ba.SpecialChar.TICKET) + str(
_ba.get_v1_account_ticket_count())
ba.internal.get_v1_account_ticket_count())
else:
val = ba.charstr(ba.SpecialChar.TICKET) + '???'
ba.textwidget(edit=self._tickets_text, text=val)
@ -517,16 +518,16 @@ class PartyQueueWindow(ba.Window):
current_time = ba.time(ba.TimeType.REAL)
if (self._last_transaction_time is None
or current_time - self._last_transaction_time >
0.001 * _ba.get_v1_account_misc_read_val('pqInt', 5000)):
or current_time - self._last_transaction_time > 0.001 *
ba.internal.get_v1_account_misc_read_val('pqInt', 5000)):
self._last_transaction_time = current_time
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'PARTY_QUEUE_QUERY',
'q': self._queue_id
},
callback=ba.WeakCall(self.on_update_response))
_ba.run_transactions()
ba.internal.run_transactions()
# step our dudes
for dude in self._dudes:

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -129,13 +129,15 @@ class PlayWindow(ba.Window):
on_activate_call=self._coop)
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
ba.widget(edit=btn,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(edit=btn,
up_widget=_ba.get_special_widget('account_button'))
ba.widget(
edit=btn,
down_widget=_ba.get_special_widget('settings_button'))
left_widget=ba.internal.get_special_widget('back_button'))
ba.widget(
edit=btn,
up_widget=ba.internal.get_special_widget('account_button'))
ba.widget(edit=btn,
down_widget=ba.internal.get_special_widget(
'settings_button'))
self._draw_dude(0,
btn,
@ -216,9 +218,11 @@ class PlayWindow(ba.Window):
on_activate_call=self._team_tourney)
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
up_widget=_ba.get_special_widget('tickets_plus_button'),
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=btn,
up_widget=ba.internal.get_special_widget(
'tickets_plus_button'),
right_widget=ba.internal.get_special_widget('party_button'))
xxx = -14
self._draw_dude(2,
@ -447,7 +451,7 @@ class PlayWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.coop.browser import CoopBrowserWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from bastd.ui.playlist.editcontroller import PlaylistEditController
@ -57,8 +57,9 @@ class PlaylistAddGameWindow(ba.Window):
on_activate_call=self._add)
if ba.app.ui.use_toolbars:
ba.widget(edit=select_button,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=select_button,
right_widget=ba.internal.get_special_widget('party_button'))
ba.textwidget(parent=self._root_widget,
position=(self._width * 0.5, self._height - 28),
@ -116,9 +117,39 @@ class PlaylistAddGameWindow(ba.Window):
ba.containerwidget(edit=self._root_widget,
selected_child=self._scrollwidget)
self._game_types: list[type[ba.GameActivity]] = []
# Get actual games loading in the bg.
ba.app.meta.load_exported_classes(ba.GameActivity,
self._on_game_types_loaded,
completion_cb_in_bg_thread=True)
# Refresh with our initial empty list. We'll refresh again once
# game loading is complete.
self._refresh()
def _on_game_types_loaded(self,
gametypes: list[type[ba.GameActivity]]) -> None:
from ba.internal import get_unowned_game_types
# We asked for a bg thread completion cb so we can do some
# filtering here in the bg thread where its not gonna cause hitches.
assert not ba.in_logic_thread()
sessiontype = self._editcontroller.get_session_type()
unowned = get_unowned_game_types()
self._game_types = [
gt for gt in gametypes
if gt not in unowned and gt.supports_session_type(sessiontype)
]
# Sort in the current language.
self._game_types.sort(key=lambda g: g.get_display_string().evaluate())
# Tell ourself to refresh back in the logic thread.
ba.pushcall(self._refresh, from_other_thread=True)
def _refresh(self, select_get_more_games_button: bool = False) -> None:
# from ba.internal import get_game_types
if self._column is not None:
self._column.delete()
@ -127,15 +158,7 @@ class PlaylistAddGameWindow(ba.Window):
border=2,
margin=0)
gametypes = [
gt for gt in ba.app.meta.get_game_types() if
gt.supports_session_type(self._editcontroller.get_session_type())
]
# Sort in the current language.
gametypes.sort(key=lambda g: g.get_display_string().evaluate())
for i, gametype in enumerate(gametypes):
for i, gametype in enumerate(self._game_types):
def _doit() -> None:
if self._select_button:
@ -175,7 +198,7 @@ class PlaylistAddGameWindow(ba.Window):
def _on_get_more_games_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
StoreBrowserWindow(modal=True,
@ -187,8 +210,8 @@ class PlaylistAddGameWindow(ba.Window):
self._refresh(select_get_more_games_button=True)
def _add(self) -> None:
_ba.lock_all_input() # Make sure no more commands happen.
ba.timer(0.1, _ba.unlock_all_input, timetype=ba.TimeType.REAL)
ba.internal.lock_all_input() # Make sure no more commands happen.
ba.timer(0.1, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
assert self._selected_game_type is not None
self._editcontroller.add_game_type_selected(self._selected_game_type)

View file

@ -8,8 +8,8 @@ import copy
import math
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -140,8 +140,9 @@ class PlaylistBrowserWindow(ba.Window):
def _ensure_standard_playlists_exist(self) -> None:
# On new installations, go ahead and create a few playlists
# besides the hard-coded default one:
if not _ba.get_v1_account_misc_val('madeStandardPlaylists', False):
_ba.add_transaction({
if not ba.internal.get_v1_account_misc_val('madeStandardPlaylists',
False):
ba.internal.add_transaction({
'type':
'ADD_PLAYLIST',
'playlistType':
@ -175,7 +176,7 @@ class PlaylistBrowserWindow(ba.Window):
},
]
})
_ba.add_transaction({
ba.internal.add_transaction({
'type':
'ADD_PLAYLIST',
'playlistType':
@ -226,7 +227,7 @@ class PlaylistBrowserWindow(ba.Window):
},
]
})
_ba.add_transaction({
ba.internal.add_transaction({
'type':
'ADD_PLAYLIST',
'playlistType':
@ -255,7 +256,7 @@ class PlaylistBrowserWindow(ba.Window):
},
]
})
_ba.add_transaction({
ba.internal.add_transaction({
'type':
'ADD_PLAYLIST',
'playlistType':
@ -274,12 +275,12 @@ class PlaylistBrowserWindow(ba.Window):
}
}]
})
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'SET_MISC_VAL',
'name': 'madeStandardPlaylists',
'value': True
})
_ba.run_transactions()
ba.internal.run_transactions()
def _refresh(self) -> None:
# FIXME: Should tidy this up.
@ -367,14 +368,14 @@ class PlaylistBrowserWindow(ba.Window):
if (x == 0 and ba.app.ui.use_toolbars
and uiscale is ba.UIScale.SMALL):
ba.widget(
edit=btn,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(edit=btn,
left_widget=ba.internal.get_special_widget(
'back_button'))
if (x == columns - 1 and ba.app.ui.use_toolbars
and uiscale is ba.UIScale.SMALL):
ba.widget(
edit=btn,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(edit=btn,
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.buttonwidget(
edit=btn,
on_activate_call=ba.Call(self._on_playlist_press, btn,
@ -429,7 +430,8 @@ class PlaylistBrowserWindow(ba.Window):
playlist = filter_playlist(playlist,
self._sessiontype,
remove_unowned=False,
mark_unowned=True)
mark_unowned=True,
name=name)
for entry in playlist:
mapname = entry['settings']['map']
maptype: type[ba.Map] | None

View file

@ -8,8 +8,8 @@ import copy
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -225,7 +225,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
ba.widget(edit=btn, right_widget=scrollwidget)
ba.widget(edit=scrollwidget,
left_widget=new_button,
right_widget=_ba.get_special_widget('party_button')
right_widget=ba.internal.get_special_widget('party_button')
if ba.app.ui.use_toolbars else None)
# make sure config exists
@ -279,23 +279,23 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
def _run_selected_playlist(self) -> None:
# pylint: disable=cyclic-import
_ba.unlock_all_input()
ba.internal.unlock_all_input()
try:
_ba.new_host_session(self._sessiontype)
ba.internal.new_host_session(self._sessiontype)
except Exception:
from bastd import mainmenu
ba.print_exception(f'Error running session {self._sessiontype}.')
# Drop back into a main menu session.
_ba.new_host_session(mainmenu.MainMenuSession)
ba.internal.new_host_session(mainmenu.MainMenuSession)
def _choose_playlist(self) -> None:
if self._selected_playlist_name is None:
return
self._save_playlist_selection()
ba.containerwidget(edit=self._root_widget, transition='out_left')
_ba.fade_screen(False, endcall=self._run_selected_playlist)
_ba.lock_all_input()
ba.internal.fade_screen(False, endcall=self._run_selected_playlist)
ba.internal.lock_all_input()
def _refresh(self, select_playlist: str | None = None) -> None:
from efro.util import asserttype
@ -424,12 +424,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
def _do_delete_playlist(self) -> None:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'REMOVE_PLAYLIST',
'playlistType': self._pvars.config_name,
'playlistName': self._selected_playlist_name
})
_ba.run_transactions()
ba.internal.run_transactions()
ba.playsound(ba.getsound('shieldDown'))
# (we don't use len()-1 here because the default list adds one)
@ -445,7 +445,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
from bastd.ui.playlist import share
# Gotta be signed in for this to work.
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
@ -477,7 +477,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
return
# Gotta be signed in for this to work.
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
@ -492,7 +492,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
if self._selected_playlist_name is None:
return
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'SHARE_PLAYLIST',
'expire_time': time.time() + 5,
@ -501,7 +501,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
},
callback=ba.WeakCall(self._on_share_playlist_response,
self._selected_playlist_name))
_ba.run_transactions()
ba.internal.run_transactions()
ba.screenmessage(ba.Lstr(resource='sharingText'))
def _delete_playlist(self) -> None:
@ -582,13 +582,13 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
break
test_index += 1
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'ADD_PLAYLIST',
'playlistType': self._pvars.config_name,
'playlistName': test_name,
'playlist': copy.deepcopy(plst)
})
_ba.run_transactions()
ba.internal.run_transactions()
ba.playsound(ba.getsound('gunCocking'))
self._refresh(select_playlist=test_name)

View file

@ -7,7 +7,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, cast
import ba
import _ba
import ba.internal
if TYPE_CHECKING:
from bastd.ui.playlist.editcontroller import PlaylistEditController
@ -58,8 +58,9 @@ class PlaylistEditWindow(ba.Window):
text_scale=1.2)
if ba.app.ui.use_toolbars:
ba.widget(edit=btn,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=btn,
right_widget=ba.internal.get_special_widget('party_button'))
ba.widget(edit=cancel_button,
left_widget=cancel_button,
@ -283,7 +284,7 @@ class PlaylistEditWindow(ba.Window):
# If we had an old one, delete it.
if self._editcontroller.get_existing_playlist_name() is not None:
_ba.add_transaction({
ba.internal.add_transaction({
'type':
'REMOVE_PLAYLIST',
'playlistType':
@ -292,13 +293,13 @@ class PlaylistEditWindow(ba.Window):
self._editcontroller.get_existing_playlist_name()
})
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'ADD_PLAYLIST',
'playlistType': self._editcontroller.get_config_name(),
'playlistName': new_name,
'playlist': self._editcontroller.get_playlist()
})
_ba.run_transactions()
ba.internal.run_transactions()
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.playsound(ba.getsound('gunCocking'))

View file

@ -52,7 +52,8 @@ class PlaylistEditController:
appconfig[self._pvars.config_name +
' Playlists'][existing_playlist_name],
sessiontype=sessiontype,
remove_unowned=False)
remove_unowned=False,
name=existing_playlist_name)
self._edit_ui_selection = None
else:
if playlist is not None:

View file

@ -8,8 +8,8 @@ import copy
import random
from typing import TYPE_CHECKING, cast
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -134,7 +134,7 @@ class PlaylistEditGameWindow(ba.Window):
resource='doneText'))
if ba.app.ui.use_toolbars:
pbtn = _ba.get_special_widget('party_button')
pbtn = ba.internal.get_special_widget('party_button')
ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn)
ba.textwidget(parent=self._root_widget,

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Callable
@ -165,9 +165,9 @@ class PlaylistMapSelectWindow(ba.Window):
if y == 0:
ba.widget(edit=btn, up_widget=self._cancel_button)
if x == columns - 1 and ba.app.ui.use_toolbars:
ba.widget(
edit=btn,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(edit=btn,
right_widget=ba.internal.get_special_widget(
'party_button'))
ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
if self._maps[index][0] == self._previous_map:
@ -210,7 +210,7 @@ class PlaylistMapSelectWindow(ba.Window):
def _on_store_press(self) -> None:
from bastd.ui import account
from bastd.ui.store.browser import StoreBrowserWindow
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
StoreBrowserWindow(modal=True,
@ -236,8 +236,8 @@ class PlaylistMapSelectWindow(ba.Window):
edit_info=self._edit_info).get_root_widget())
def _select_with_delay(self, map_name: str) -> None:
_ba.lock_all_input()
ba.timer(0.1, _ba.unlock_all_input, timetype=ba.TimeType.REAL)
ba.internal.lock_all_input()
ba.timer(0.1, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
ba.timer(0.1,
ba.WeakCall(self._select, map_name),
timetype=ba.TimeType.REAL)

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import promocode
if TYPE_CHECKING:
@ -50,14 +50,14 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow):
transition=self._transition_out)
def _do_enter(self) -> None:
_ba.add_transaction(
ba.internal.add_transaction(
{
'type': 'IMPORT_PLAYLIST',
'expire_time': time.time() + 5,
'code': ba.textwidget(query=self._text_field)
},
callback=ba.WeakCall(self._on_import_response))
_ba.run_transactions()
ba.internal.run_transactions()
ba.screenmessage(ba.Lstr(resource='importingText'))

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup
if TYPE_CHECKING:
@ -86,7 +86,8 @@ class PlayOptionsWindow(popup.PopupWindow):
plst = filter_playlist(plst,
self._sessiontype,
remove_unowned=False,
mark_unowned=True)
mark_unowned=True,
name=name)
game_count = len(plst)
for entry in plst:
mapname = entry['settings']['map']
@ -349,7 +350,7 @@ class PlayOptionsWindow(popup.PopupWindow):
from bastd.ui.teamnamescolors import TeamNamesColorsWindow
from bastd.ui.purchase import PurchaseWindow
if not ba.app.accounts_v1.have_pro():
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
PurchaseWindow(items=['pro'])
@ -417,8 +418,8 @@ class PlayOptionsWindow(popup.PopupWindow):
if self._delegate is not None:
self._delegate.on_play_options_window_run_game()
else:
_ba.fade_screen(False, endcall=self._run_selected_playlist)
_ba.lock_all_input()
ba.internal.fade_screen(False, endcall=self._run_selected_playlist)
ba.internal.lock_all_input()
self._transition_out(transition='out_left')
if self._delegate is not None:
self._delegate.on_play_options_window_run_game()
@ -426,12 +427,12 @@ class PlayOptionsWindow(popup.PopupWindow):
cfg.commit()
def _run_selected_playlist(self) -> None:
_ba.unlock_all_input()
ba.internal.unlock_all_input()
try:
_ba.new_host_session(self._sessiontype)
ba.internal.new_host_session(self._sessiontype)
except Exception:
from bastd import mainmenu
ba.print_exception('exception running session', self._sessiontype)
# Drop back into a main menu session.
_ba.new_host_session(mainmenu.MainMenuSession)
ba.internal.new_host_session(mainmenu.MainMenuSession)

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any, Sequence, Callable
@ -81,7 +81,7 @@ class PopupWindow:
scale=scale,
toolbar_visibility=toolbar_visibility,
size=size,
parent=_ba.get_special_widget('overlay_stack'),
parent=ba.internal.get_special_widget('overlay_stack'),
stack_offset=(x_fin - x_offs, y_fin - y_offs),
scale_origin_stack_offset=(position[0], position[1]),
on_outside_click_call=self.on_popup_cancel,
@ -159,15 +159,15 @@ class PopupMenuWindow(PopupWindow):
self._width,
min(
maxwidth,
_ba.get_string_width(choice_display_name,
suppress_warning=True)) + 75)
ba.internal.get_string_width(
choice_display_name, suppress_warning=True)) + 75)
else:
self._width = max(
self._width,
min(
maxwidth,
_ba.get_string_width(choice_display_name,
suppress_warning=True)) + 60)
ba.internal.get_string_width(
choice_display_name, suppress_warning=True)) + 60)
# init parent class - this will rescale and reposition things as
# needed and create our root widget

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -174,7 +174,8 @@ class ProfileBrowserWindow(ba.Window):
from bastd.ui.purchase import PurchaseWindow
# Limit to a handful profiles if they don't have pro-options.
max_non_pro_profiles = _ba.get_v1_account_misc_read_val('mnpp', 5)
max_non_pro_profiles = ba.internal.get_v1_account_misc_read_val(
'mnpp', 5)
assert self._profiles is not None
if (not ba.app.accounts_v1.have_pro_options()
and len(self._profiles) >= max_non_pro_profiles):
@ -221,11 +222,11 @@ class ProfileBrowserWindow(ba.Window):
self._do_delete_profile, 350)
def _do_delete_profile(self) -> None:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'REMOVE_PLAYER_PROFILE',
'name': self._selected_profile
})
_ba.run_transactions()
ba.internal.run_transactions()
ba.playsound(ba.getsound('shieldDown'))
self._refresh()
@ -283,8 +284,8 @@ class ProfileBrowserWindow(ba.Window):
items.sort(key=lambda x: asserttype(x[0], str).lower())
index = 0
account_name: str | None
if _ba.get_v1_account_state() == 'signed_in':
account_name = _ba.get_v1_account_display_string()
if ba.internal.get_v1_account_state() == 'signed_in':
account_name = ba.internal.get_v1_account_display_string()
else:
account_name = None
widget_to_select = None
@ -330,7 +331,7 @@ class ProfileBrowserWindow(ba.Window):
# If there's a team-chooser in existence, tell it the profile-list
# has probably changed.
session = _ba.get_foreground_host_session()
session = ba.internal.get_foreground_host_session()
if session is not None:
session.handlemessage(PlayerProfilesChangedMessage())

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING, cast
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from bastd.ui.colorpicker import ColorPicker
@ -144,7 +144,7 @@ class EditProfileWindow(ba.Window):
# Assign a random name if they had none.
if self._name == '':
names = _ba.get_random_names()
names = ba.internal.get_random_names()
self._name = names[random.randrange(len(names))]
self._clipped_name_text = ba.textwidget(parent=self._root_widget,
@ -172,8 +172,8 @@ class EditProfileWindow(ba.Window):
self._upgrade_button = None
if self._is_account_profile:
if _ba.get_v1_account_state() == 'signed_in':
sval = _ba.get_v1_account_display_string()
if ba.internal.get_v1_account_state() == 'signed_in':
sval = ba.internal.get_v1_account_display_string()
else:
sval = '??'
ba.textwidget(parent=self._root_widget,
@ -188,7 +188,8 @@ class EditProfileWindow(ba.Window):
resource='editProfileWindow.accountProfileText').evaluate()
b_width = min(
270.0,
_ba.get_string_width(txtl, suppress_warning=True) * 0.6)
ba.internal.get_string_width(txtl, suppress_warning=True) *
0.6)
ba.textwidget(parent=self._root_widget,
position=(self._width * 0.5, v - 39),
size=(0, 0),
@ -258,7 +259,8 @@ class EditProfileWindow(ba.Window):
resource='editProfileWindow.globalProfileText').evaluate()
b_width = min(
240.0,
_ba.get_string_width(txtl, suppress_warning=True) * 0.6)
ba.internal.get_string_width(txtl, suppress_warning=True) *
0.6)
ba.textwidget(parent=self._root_widget,
position=(self._width * 0.5, v - 39),
size=(0, 0),
@ -299,7 +301,8 @@ class EditProfileWindow(ba.Window):
resource='editProfileWindow.localProfileText').evaluate()
b_width = min(
270.0,
_ba.get_string_width(txtl, suppress_warning=True) * 0.6)
ba.internal.get_string_width(txtl, suppress_warning=True) *
0.6)
ba.textwidget(parent=self._root_widget,
position=(self._width * 0.5, v - 43),
size=(0, 0),
@ -426,7 +429,7 @@ class EditProfileWindow(ba.Window):
"""Attempt to ugrade the profile to global."""
from bastd.ui import account
from bastd.ui.profile import upgrade as pupgrade
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
@ -592,8 +595,9 @@ class EditProfileWindow(ba.Window):
return
name = self.getname()
if name == '__account__':
name = (_ba.get_v1_account_name()
if _ba.get_v1_account_state() == 'signed_in' else '???')
name = (ba.internal.get_v1_account_name()
if ba.internal.get_v1_account_state() == 'signed_in' else
'???')
if len(name) > 10 and not (self._global or self._is_account_profile):
ba.textwidget(edit=self._clipped_name_text,
text=ba.Lstr(resource='inGameClippedNameText',
@ -640,7 +644,7 @@ class EditProfileWindow(ba.Window):
# Delete old in case we're renaming.
if self._existing_profile and self._existing_profile != new_name:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'REMOVE_PLAYER_PROFILE',
'name': self._existing_profile
})
@ -649,7 +653,7 @@ class EditProfileWindow(ba.Window):
# new name (will need to re-request it).
self._global = False
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'ADD_PLAYER_PROFILE',
'name': new_name,
'profile': {
@ -662,7 +666,7 @@ class EditProfileWindow(ba.Window):
})
if transition_out:
_ba.run_transactions()
ba.internal.run_transactions()
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
ProfileBrowserWindow(

View file

@ -8,8 +8,8 @@ import time
import weakref
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -126,8 +126,8 @@ class ProfileUpgradeWindow(ba.Window):
'b': ba.app.build_number
},
callback=ba.WeakCall(self._profile_check_result))
self._cost = _ba.get_v1_account_misc_read_val('price.global_profile',
500)
self._cost = ba.internal.get_v1_account_misc_read_val(
'price.global_profile', 500)
self._status: str | None = 'waiting'
self._update_timer = ba.Timer(1.0,
ba.WeakCall(self._update),
@ -170,7 +170,7 @@ class ProfileUpgradeWindow(ba.Window):
from bastd.ui import getcurrency
if self._status is None:
# If it appears we don't have enough tickets, offer to buy more.
tickets = _ba.get_v1_account_ticket_count()
tickets = ba.internal.get_v1_account_ticket_count()
if tickets < self._cost:
ba.playsound(ba.getsound('error'))
getcurrency.show_get_tickets_prompt()
@ -193,11 +193,11 @@ class ProfileUpgradeWindow(ba.Window):
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'UPGRADE_PROFILE',
'name': self._name
})
_ba.run_transactions()
ba.internal.run_transactions()
self._status = 'upgrading'
self._upgrade_start_time = time.time()
else:
@ -205,7 +205,7 @@ class ProfileUpgradeWindow(ba.Window):
def _update(self) -> None:
try:
t_str = str(_ba.get_v1_account_ticket_count())
t_str = str(ba.internal.get_v1_account_ticket_count())
except Exception:
t_str = '?'
if self._tickets_text is not None:
@ -219,7 +219,7 @@ class ProfileUpgradeWindow(ba.Window):
# Once we've kicked off an upgrade attempt and all transactions go
# through, we're done.
if (self._status == 'upgrading'
and not _ba.have_outstanding_transactions()):
and not ba.internal.have_outstanding_transactions()):
self._status = 'exiting'
ba.containerwidget(edit=self._root_widget, transition='out_right')
edit_profile_window = self._edit_profile_window()

View file

@ -7,8 +7,8 @@ from __future__ import annotations
import time
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -112,9 +112,9 @@ class PromoCodeWindow(ba.Window):
if not self._modal:
ba.app.ui.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget())
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
'code': ba.textwidget(query=self._text_field)
})
_ba.run_transactions()
ba.internal.run_transactions()

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
@ -68,11 +68,11 @@ class PurchaseWindow(ba.Window):
pass # not working
else:
if self._items == ['pro']:
price_str = _ba.get_price(self._items[0])
price_str = ba.internal.get_price(self._items[0])
pyoffs = -15
else:
pyoffs = 0
price = self._price = _ba.get_v1_account_misc_read_val(
price = self._price = ba.internal.get_v1_account_misc_read_val(
'price.' + str(items[0]), -1)
price_str = ba.charstr(ba.SpecialChar.TICKET) + str(price)
self._price_text = ba.textwidget(parent=self._root_widget,
@ -121,7 +121,7 @@ class PurchaseWindow(ba.Window):
if ba.app.accounts_v1.have_pro():
can_die = True
else:
if _ba.get_purchased(self._items[0]):
if ba.internal.get_purchased(self._items[0]):
can_die = True
if can_die:
@ -130,11 +130,11 @@ class PurchaseWindow(ba.Window):
def _purchase(self) -> None:
from bastd.ui import getcurrency
if self._items == ['pro']:
_ba.purchase('pro')
ba.internal.purchase('pro')
else:
ticket_count: int | None
try:
ticket_count = _ba.get_v1_account_ticket_count()
ticket_count = ba.internal.get_v1_account_ticket_count()
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._price:
@ -143,7 +143,7 @@ class PurchaseWindow(ba.Window):
return
def do_it() -> None:
_ba.in_game_purchase(self._items[0], self._price)
ba.internal.in_game_purchase(self._items[0], self._price)
ba.playsound(ba.getsound('swish'))
do_it()

View file

@ -4,8 +4,8 @@
from __future__ import annotations
import _ba
import ba
import ba.internal
class ReportPlayerWindow(ba.Window):
@ -18,7 +18,7 @@ class ReportPlayerWindow(ba.Window):
self._transition_out = 'out_scale'
scale_origin = origin_widget.get_screen_space_center()
overlay_stack = _ba.get_special_widget('overlay_stack')
overlay_stack = ba.internal.get_special_widget('overlay_stack')
uiscale = ba.app.ui.uiscale
super().__init__(root_widget=ba.containerwidget(
size=(self._width, self._height),
@ -63,27 +63,27 @@ class ReportPlayerWindow(ba.Window):
def _on_language_press(self) -> None:
from urllib import parse
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'REPORT_ACCOUNT',
'reason': 'language',
'account': self._account_id
})
body = ba.Lstr(resource='reportPlayerExplanationText').evaluate()
ba.open_url('mailto:support@froemling.net'
f'?subject={_ba.appnameupper()} Player Report: ' +
f'?subject={ba.internal.appnameupper()} Player Report: ' +
self._account_id + '&body=' + parse.quote(body))
self.close()
def _on_cheating_press(self) -> None:
from urllib import parse
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'REPORT_ACCOUNT',
'reason': 'cheating',
'account': self._account_id
})
body = ba.Lstr(resource='reportPlayerExplanationText').evaluate()
ba.open_url('mailto:support@froemling.net'
f'?subject={_ba.appnameupper()} Player Report: ' +
f'?subject={ba.internal.appnameupper()} Player Report: ' +
self._account_id + '&body=' + parse.quote(body))
self.close()

View file

@ -4,35 +4,52 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import logging
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated
from efro.dataclassio import ioprepped, IOAttrs
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
from typing import Any
pass
@ioprepped
@dataclass
class ServerDialogData:
"""Data for ServerDialog."""
dialog_id: Annotated[str, IOAttrs('dialogID')]
text: Annotated[str, IOAttrs('text')]
subs: Annotated[list[tuple[str, str]],
IOAttrs('subs')] = field(default_factory=list)
show_cancel: Annotated[bool, IOAttrs('showCancel')] = True
copy_text: Annotated[str | None, IOAttrs('copyText')] = None
class ServerDialogWindow(ba.Window):
"""A dialog window driven by the master-server."""
def __init__(self, data: dict[str, Any]):
self._dialog_id = data['dialogID']
txt = ba.Lstr(translate=('serverResponses', data['text']),
subs=data.get('subs', [])).evaluate()
def __init__(self, data: ServerDialogData):
self._data = data
txt = ba.Lstr(translate=('serverResponses', data.text),
subs=data.subs).evaluate()
txt = txt.strip()
txt_scale = 1.5
txt_height = (_ba.get_string_height(txt, suppress_warning=True) *
txt_scale)
txt_height = (
ba.internal.get_string_height(txt, suppress_warning=True) *
txt_scale)
self._width = 500
self._height = 130 + min(200, txt_height)
self._height = 160 + min(200, txt_height)
uiscale = ba.app.ui.uiscale
super().__init__(root_widget=ba.containerwidget(
size=(self._width, self._height),
transition='in_scale',
scale=(1.8 if uiscale is ba.UIScale.SMALL else
1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
self._starttime = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
self._starttime = ba.time(ba.TimeType.REAL)
ba.playsound(ba.getsound('swish'))
ba.textwidget(parent=self._root_widget,
@ -46,51 +63,72 @@ class ServerDialogWindow(ba.Window):
text=txt,
maxwidth=self._width * 0.85,
max_height=(self._height - 110))
show_cancel = data.get('showCancel', True)
self._cancel_button: ba.Widget | None
if show_cancel:
self._cancel_button = ba.buttonwidget(
parent=self._root_widget,
position=(30, 30),
size=(160, 60),
autoselect=True,
label=ba.Lstr(resource='cancelText'),
on_activate_call=self._cancel_press)
else:
self._cancel_button = None
show_copy = data.copy_text is not None and ba.clipboard_is_supported()
# Currently can't do both copy and cancel since they go in the same
# spot. Cancel wins in this case since it is important functionality
# and copy is just for convenience (and not even always available).
if show_copy and data.show_cancel:
logging.warning('serverdialog requesting both copy and cancel;'
' copy will not be shown.')
show_copy = False
self._cancel_button = (None
if not data.show_cancel else ba.buttonwidget(
parent=self._root_widget,
position=(30, 30),
size=(160, 60),
autoselect=True,
label=ba.Lstr(resource='cancelText'),
on_activate_call=self._cancel_press))
self._copy_button = None if not show_copy else ba.buttonwidget(
parent=self._root_widget,
position=(30, 30),
size=(160, 60),
autoselect=True,
label=ba.Lstr(resource='copyText'),
on_activate_call=self._copy_press)
self._ok_button = ba.buttonwidget(
parent=self._root_widget,
position=((self._width - 182) if show_cancel else
position=((self._width - 182) if
(data.show_cancel or show_copy) else
(self._width * 0.5 - 80), 30),
size=(160, 60),
autoselect=True,
label=ba.Lstr(resource='okText'),
on_activate_call=self._ok_press)
ba.containerwidget(edit=self._root_widget,
cancel_button=self._cancel_button,
start_button=self._ok_button,
selected_child=self._ok_button)
def _copy_press(self) -> None:
assert self._data.copy_text is not None
ba.clipboard_set_text(self._data.copy_text)
ba.screenmessage(ba.Lstr(resource='copyConfirmText'), color=(0, 1, 0))
def _ok_press(self) -> None:
if ba.time(ba.TimeType.REAL,
ba.TimeFormat.MILLISECONDS) - self._starttime < 1000:
if ba.time(ba.TimeType.REAL) - self._starttime < 1.0:
ba.playsound(ba.getsound('error'))
return
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'DIALOG_RESPONSE',
'dialogID': self._dialog_id,
'dialogID': self._data.dialog_id,
'response': 1
})
ba.containerwidget(edit=self._root_widget, transition='out_scale')
def _cancel_press(self) -> None:
if ba.time(ba.TimeType.REAL,
ba.TimeFormat.MILLISECONDS) - self._starttime < 1000:
if ba.time(ba.TimeType.REAL) - self._starttime < 1.0:
ba.playsound(ba.getsound('error'))
return
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'DIALOG_RESPONSE',
'dialogID': self._dialog_id,
'dialogID': self._data.dialog_id,
'response': 0
})
ba.containerwidget(edit=self._root_widget, transition='out_scale')

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
from bastd.ui import popup as popup_ui
if TYPE_CHECKING:
@ -200,10 +200,10 @@ class AdvancedSettingsWindow(ba.Window):
# menu based on the language so still need this. ...however we could
# make this more limited to it only rebuilds that one menu instead
# of everything.
if self._menu_open or (self._prev_lang == _ba.app.config.get(
if self._menu_open or (self._prev_lang == ba.app.config.get(
'Lang', None) and self._prev_lang_list == available_languages):
return
self._prev_lang = _ba.app.config.get('Lang', None)
self._prev_lang = ba.app.config.get('Lang', None)
self._prev_lang_list = available_languages
# Clear out our sub-container.
@ -251,8 +251,8 @@ class AdvancedSettingsWindow(ba.Window):
h_align='right',
v_align='center')
languages = _ba.app.lang.available_languages
cur_lang = _ba.app.config.get('Lang', None)
languages = ba.app.lang.available_languages
cur_lang = ba.app.config.get('Lang', None)
if cur_lang is None:
cur_lang = 'Auto'
@ -340,7 +340,7 @@ class AdvancedSettingsWindow(ba.Window):
self._update_lang_status()
v -= 40
lang_inform = _ba.get_v1_account_misc_val('langInform', False)
lang_inform = ba.internal.get_v1_account_misc_val('langInform', False)
self._language_inform_checkbox = cbw = ba.checkboxwidget(
parent=self._subcontainer,
@ -432,7 +432,7 @@ class AdvancedSettingsWindow(ba.Window):
label=ba.Lstr(resource=self._r + '.moddingGuideText'),
text_scale=1.0,
on_activate_call=ba.Call(
ba.open_url, 'http://ballistica.net/wiki/modding-guide'))
ba.open_url, 'https://ballistica.net/wiki/modding-guide'))
if self._show_always_use_internal_keyboard:
assert self._always_use_internal_keyboard_check_box is not None
ba.widget(edit=self._always_use_internal_keyboard_check_box.widget,
@ -512,11 +512,12 @@ class AdvancedSettingsWindow(ba.Window):
ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20)
if ba.app.ui.use_toolbars:
pbtn = _ba.get_special_widget('party_button')
pbtn = ba.internal.get_special_widget('party_button')
ba.widget(edit=self._scrollwidget, right_widget=pbtn)
if self._back_button is None:
ba.widget(edit=self._scrollwidget,
left_widget=_ba.get_special_widget('back_button'))
ba.widget(
edit=self._scrollwidget,
left_widget=ba.internal.get_special_widget('back_button'))
self._restore_state()
@ -526,12 +527,12 @@ class AdvancedSettingsWindow(ba.Window):
color=(1, 1, 0))
def _on_lang_inform_value_change(self, val: bool) -> None:
_ba.add_transaction({
ba.internal.add_transaction({
'type': 'SET_MISC_VAL',
'name': 'langInform',
'value': val
})
_ba.run_transactions()
ba.internal.run_transactions()
def _on_vr_test_press(self) -> None:
from bastd.ui.settings.vrtesting import VRTestingWindow
@ -544,7 +545,7 @@ class AdvancedSettingsWindow(ba.Window):
from bastd.ui.settings.nettesting import NetTestingWindow
# Net-testing requires a signed in v1 account.
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
@ -558,7 +559,7 @@ class AdvancedSettingsWindow(ba.Window):
def _on_friend_promo_code_press(self) -> None:
from bastd.ui import appinvite
from bastd.ui import account
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
appinvite.handle_app_invites_press()
@ -576,7 +577,7 @@ class AdvancedSettingsWindow(ba.Window):
from bastd.ui.account import show_sign_in_prompt
# We have to be logged in for promo-codes to work.
if _ba.get_v1_account_state() != 'signed_in':
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -118,7 +118,7 @@ class AllSettingsWindow(ba.Window):
label='',
on_activate_call=self._do_controllers)
if ba.app.ui.use_toolbars and self._back_button is None:
bbtn = _ba.get_special_widget('back_button')
bbtn = ba.internal.get_special_widget('back_button')
ba.widget(edit=ctb, left_widget=bbtn)
_b_title(x_offs2, v, ctb,
ba.Lstr(resource=self._r + '.controllersText'))
@ -138,7 +138,7 @@ class AllSettingsWindow(ba.Window):
label='',
on_activate_call=self._do_graphics)
if ba.app.ui.use_toolbars:
pbtn = _ba.get_special_widget('party_button')
pbtn = ba.internal.get_special_widget('party_button')
ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
_b_title(x_offs3, v, gfxb, ba.Lstr(resource=self._r + '.graphicsText'))
imgw = imgh = 110

View file

@ -6,8 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
import ba.internal
if TYPE_CHECKING:
pass
@ -105,8 +105,9 @@ class AudioSettingsWindow(ba.Window):
maxval=1.0,
increment=0.1)
if ba.app.ui.use_toolbars:
ba.widget(edit=svne.plusbutton,
right_widget=_ba.get_special_widget('party_button'))
ba.widget(
edit=svne.plusbutton,
right_widget=ba.internal.get_special_widget('party_button'))
v -= spacing
self._music_volume_numedit = ConfigNumberEdit(
parent=self._root_widget,
@ -208,12 +209,13 @@ class AudioSettingsWindow(ba.Window):
# We require disk access for soundtracks;
# if we don't have it, request it.
if not _ba.have_permission(ba.Permission.STORAGE):
if not ba.internal.have_permission(ba.Permission.STORAGE):
ba.playsound(ba.getsound('ding'))
ba.screenmessage(ba.Lstr(resource='storagePermissionAccessText'),
color=(0.5, 1, 0.5))
ba.timer(1.0,
ba.Call(_ba.request_permission, ba.Permission.STORAGE),
ba.Call(ba.internal.request_permission,
ba.Permission.STORAGE),
timetype=ba.TimeType.REAL)
return

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