mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-11-07 17:36:15 +00:00
1.7.32 ba_data update
This commit is contained in:
parent
bf2f252ee5
commit
15393d5461
144 changed files with 4296 additions and 2411 deletions
37
dist/ba_data/python/babase/__init__.py
vendored
37
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -27,7 +27,10 @@ from _babase import (
|
|||
apptime,
|
||||
apptimer,
|
||||
AppTimer,
|
||||
can_toggle_fullscreen,
|
||||
fullscreen_control_available,
|
||||
fullscreen_control_get,
|
||||
fullscreen_control_key_shortcut,
|
||||
fullscreen_control_set,
|
||||
charstr,
|
||||
clipboard_get_text,
|
||||
clipboard_has_text,
|
||||
|
|
@ -58,10 +61,8 @@ from _babase import (
|
|||
in_logic_thread,
|
||||
increment_analytics_count,
|
||||
is_os_playing_music,
|
||||
is_running_on_fire_tv,
|
||||
is_xcode_build,
|
||||
lock_all_input,
|
||||
mac_music_app_get_library_source,
|
||||
mac_music_app_get_playlists,
|
||||
mac_music_app_get_volume,
|
||||
mac_music_app_init,
|
||||
|
|
@ -72,7 +73,10 @@ from _babase import (
|
|||
music_player_set_volume,
|
||||
music_player_shutdown,
|
||||
music_player_stop,
|
||||
native_review_request,
|
||||
native_review_request_supported,
|
||||
native_stack_trace,
|
||||
open_file_externally,
|
||||
print_load_info,
|
||||
pushcall,
|
||||
quit,
|
||||
|
|
@ -82,7 +86,6 @@ from _babase import (
|
|||
screenmessage,
|
||||
set_analytics_screen,
|
||||
set_low_level_config_value,
|
||||
set_stress_testing,
|
||||
set_thread_name,
|
||||
set_ui_input_device,
|
||||
show_progress_bar,
|
||||
|
|
@ -114,6 +117,11 @@ from babase._apputils import (
|
|||
AppHealthMonitor,
|
||||
)
|
||||
from babase._cloud import CloudSubsystem
|
||||
from babase._devconsole import (
|
||||
DevConsoleTab,
|
||||
DevConsoleTabEntry,
|
||||
DevConsoleSubsystem,
|
||||
)
|
||||
from babase._emptyappmode import EmptyAppMode
|
||||
from babase._error import (
|
||||
print_exception,
|
||||
|
|
@ -146,9 +154,8 @@ from babase._general import (
|
|||
getclass,
|
||||
get_type_name,
|
||||
)
|
||||
from babase._keyboard import Keyboard
|
||||
from babase._language import Lstr, LanguageSubsystem
|
||||
from babase._login import LoginAdapter
|
||||
from babase._login import LoginAdapter, LoginInfo
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
# (PyCharm inspection bug?)
|
||||
|
|
@ -157,6 +164,7 @@ from babase._mgen.enums import (
|
|||
SpecialChar,
|
||||
InputType,
|
||||
UIScale,
|
||||
QuitType,
|
||||
)
|
||||
from babase._math import normalized_color, is_point_in_box, vec3validate
|
||||
from babase._meta import MetadataSubsystem
|
||||
|
|
@ -194,7 +202,10 @@ __all__ = [
|
|||
'apptimer',
|
||||
'AppTimer',
|
||||
'Call',
|
||||
'can_toggle_fullscreen',
|
||||
'fullscreen_control_available',
|
||||
'fullscreen_control_get',
|
||||
'fullscreen_control_key_shortcut',
|
||||
'fullscreen_control_set',
|
||||
'charstr',
|
||||
'clipboard_get_text',
|
||||
'clipboard_has_text',
|
||||
|
|
@ -206,6 +217,9 @@ __all__ = [
|
|||
'ContextError',
|
||||
'ContextRef',
|
||||
'DelegateNotFoundError',
|
||||
'DevConsoleTab',
|
||||
'DevConsoleTabEntry',
|
||||
'DevConsoleSubsystem',
|
||||
'DisplayTime',
|
||||
'displaytime',
|
||||
'displaytimer',
|
||||
|
|
@ -244,14 +258,12 @@ __all__ = [
|
|||
'is_browser_likely_available',
|
||||
'is_os_playing_music',
|
||||
'is_point_in_box',
|
||||
'is_running_on_fire_tv',
|
||||
'is_xcode_build',
|
||||
'Keyboard',
|
||||
'LanguageSubsystem',
|
||||
'lock_all_input',
|
||||
'LoginAdapter',
|
||||
'LoginInfo',
|
||||
'Lstr',
|
||||
'mac_music_app_get_library_source',
|
||||
'mac_music_app_get_playlists',
|
||||
'mac_music_app_get_volume',
|
||||
'mac_music_app_init',
|
||||
|
|
@ -264,10 +276,13 @@ __all__ = [
|
|||
'music_player_set_volume',
|
||||
'music_player_shutdown',
|
||||
'music_player_stop',
|
||||
'native_review_request',
|
||||
'native_review_request_supported',
|
||||
'native_stack_trace',
|
||||
'NodeNotFoundError',
|
||||
'normalized_color',
|
||||
'NotFoundError',
|
||||
'open_file_externally',
|
||||
'Permission',
|
||||
'PlayerNotFoundError',
|
||||
'Plugin',
|
||||
|
|
@ -278,6 +293,7 @@ __all__ = [
|
|||
'print_load_info',
|
||||
'pushcall',
|
||||
'quit',
|
||||
'QuitType',
|
||||
'reload_media',
|
||||
'request_permission',
|
||||
'safecolor',
|
||||
|
|
@ -287,7 +303,6 @@ __all__ = [
|
|||
'SessionTeamNotFoundError',
|
||||
'set_analytics_screen',
|
||||
'set_low_level_config_value',
|
||||
'set_stress_testing',
|
||||
'set_thread_name',
|
||||
'set_ui_input_device',
|
||||
'show_progress_bar',
|
||||
|
|
|
|||
89
dist/ba_data/python/babase/_accountv2.py
vendored
89
dist/ba_data/python/babase/_accountv2.py
vendored
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
from efro.call import tpartial
|
||||
from efro.error import CommunicationError
|
||||
|
|
@ -16,7 +16,7 @@ import _babase
|
|||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from babase._login import LoginAdapter
|
||||
from babase._login import LoginAdapter, LoginInfo
|
||||
|
||||
|
||||
DEBUG_LOG = False
|
||||
|
|
@ -27,10 +27,12 @@ class AccountV2Subsystem:
|
|||
|
||||
Category: **App Classes**
|
||||
|
||||
Access the single shared instance of this class at 'ba.app.accounts'.
|
||||
Access the single shared instance of this class at 'ba.app.plus.accounts'.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from babase._login import LoginAdapterGPGS, LoginAdapterGameCenter
|
||||
|
||||
# Whether or not everything related to an initial login
|
||||
# (or lack thereof) has completed. This includes things like
|
||||
# workspace syncing. Completion of this is what flips the app
|
||||
|
|
@ -45,16 +47,13 @@ class AccountV2Subsystem:
|
|||
self._implicit_state_changed = False
|
||||
self._can_do_auto_sign_in = True
|
||||
|
||||
if _babase.app.classic is None:
|
||||
raise RuntimeError('Needs updating for no-classic case.')
|
||||
|
||||
if (
|
||||
_babase.app.classic.platform == 'android'
|
||||
and _babase.app.classic.subplatform == 'google'
|
||||
):
|
||||
from babase._login import LoginAdapterGPGS
|
||||
|
||||
self.login_adapters[LoginType.GPGS] = LoginAdapterGPGS()
|
||||
adapter: LoginAdapter
|
||||
if _babase.using_google_play_game_services():
|
||||
adapter = LoginAdapterGPGS()
|
||||
self.login_adapters[adapter.login_type] = adapter
|
||||
if _babase.using_game_center():
|
||||
adapter = LoginAdapterGameCenter()
|
||||
self.login_adapters[adapter.login_type] = adapter
|
||||
|
||||
def on_app_loading(self) -> None:
|
||||
"""Should be called at standard on_app_loading time."""
|
||||
|
|
@ -62,10 +61,6 @@ class AccountV2Subsystem:
|
|||
for adapter in self.login_adapters.values():
|
||||
adapter.on_app_loading()
|
||||
|
||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def have_primary_credentials(self) -> bool:
|
||||
"""Are credentials currently set for the primary app account?
|
||||
|
||||
|
|
@ -80,10 +75,6 @@ class AccountV2Subsystem:
|
|||
"""The primary account for the app, or None if not logged in."""
|
||||
return self.do_get_primary()
|
||||
|
||||
def do_get_primary(self) -> AccountV2Handle | None:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
return None
|
||||
|
||||
def on_primary_account_changed(
|
||||
self, account: AccountV2Handle | None
|
||||
) -> None:
|
||||
|
|
@ -142,6 +133,8 @@ class AccountV2Subsystem:
|
|||
"""An implicit sign-in happened (called by native layer)."""
|
||||
from babase._login import LoginAdapter
|
||||
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
with _babase.ContextRef.empty():
|
||||
self.login_adapters[login_type].set_implicit_login_state(
|
||||
LoginAdapter.ImplicitLoginState(
|
||||
|
|
@ -151,6 +144,7 @@ class AccountV2Subsystem:
|
|||
|
||||
def on_implicit_sign_out(self, login_type: LoginType) -> None:
|
||||
"""An implicit sign-out happened (called by native layer)."""
|
||||
assert _babase.in_logic_thread()
|
||||
with _babase.ContextRef.empty():
|
||||
self.login_adapters[login_type].set_implicit_login_state(None)
|
||||
|
||||
|
|
@ -192,9 +186,10 @@ class AccountV2Subsystem:
|
|||
cfgkey = 'ImplicitLoginStates'
|
||||
cfgdict = _babase.app.config.setdefault(cfgkey, {})
|
||||
|
||||
# Store which (if any) adapter is currently implicitly signed in.
|
||||
# Making the assumption there will only ever be one implicit
|
||||
# adapter at a time; may need to update this if that changes.
|
||||
# Store which (if any) adapter is currently implicitly signed
|
||||
# in. Making the assumption there will only ever be one implicit
|
||||
# adapter at a time; may need to revisit this logic if that
|
||||
# changes.
|
||||
prev_state = cfgdict.get(login_type.value)
|
||||
if state is None:
|
||||
self._implicit_signed_in_adapter = None
|
||||
|
|
@ -205,18 +200,26 @@ class AccountV2Subsystem:
|
|||
state.login_id
|
||||
)
|
||||
|
||||
# Special case: if the user is already signed in but not with
|
||||
# this implicit login, we may want to let them know that the
|
||||
# 'Welcome back FOO' they likely just saw is not actually
|
||||
# accurate.
|
||||
# Special case: if the user is already signed in but not
|
||||
# with this implicit login, let them know that the 'Welcome
|
||||
# back FOO' they likely just saw is not actually accurate.
|
||||
if (
|
||||
self.primary is not None
|
||||
and not self.login_adapters[login_type].is_back_end_active()
|
||||
):
|
||||
service_str: Lstr | None
|
||||
if login_type is LoginType.GPGS:
|
||||
service_str = Lstr(resource='googlePlayText')
|
||||
else:
|
||||
elif login_type is LoginType.GAME_CENTER:
|
||||
# Note: Apparently Game Center is just called 'Game
|
||||
# Center' in all languages. Can revisit if not true.
|
||||
# https://developer.apple.com/forums/thread/725779
|
||||
service_str = Lstr(value='Game Center')
|
||||
elif login_type is LoginType.EMAIL:
|
||||
# Not possible; just here for exhaustive coverage.
|
||||
service_str = None
|
||||
else:
|
||||
assert_never(login_type)
|
||||
if service_str is not None:
|
||||
_babase.apptimer(
|
||||
2.0,
|
||||
|
|
@ -259,6 +262,14 @@ class AccountV2Subsystem:
|
|||
# We may want to auto-sign-in based on this new state.
|
||||
self._update_auto_sign_in()
|
||||
|
||||
def do_get_primary(self) -> AccountV2Handle | None:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def _update_auto_sign_in(self) -> None:
|
||||
plus = _babase.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -266,7 +277,7 @@ class AccountV2Subsystem:
|
|||
# If implicit state has changed, try to respond.
|
||||
if self._implicit_state_changed:
|
||||
if self._implicit_signed_in_adapter is None:
|
||||
# If implicit back-end is signed out, follow suit
|
||||
# If implicit back-end has signed out, we follow suit
|
||||
# immediately; no need to wait for network connectivity.
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
|
|
@ -286,9 +297,8 @@ class AccountV2Subsystem:
|
|||
# Consider this an 'explicit' sign in because the
|
||||
# implicit-login state change presumably was triggered
|
||||
# by some user action (signing in, signing out, or
|
||||
# switching accounts via the back-end).
|
||||
# NOTE: should test case where we don't have
|
||||
# connectivity here.
|
||||
# switching accounts via the back-end). NOTE: should
|
||||
# test case where we don't have connectivity here.
|
||||
if plus.cloud.is_connected():
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
|
|
@ -419,14 +429,11 @@ class AccountV2Handle:
|
|||
used with some operations such as cloud messaging.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.tag = '?'
|
||||
|
||||
self.workspacename: str | None = None
|
||||
self.workspaceid: str | None = None
|
||||
|
||||
# Login types and their display-names associated with this account.
|
||||
self.logins: dict[LoginType, str] = {}
|
||||
accountid: str
|
||||
tag: str
|
||||
workspacename: str | None
|
||||
workspaceid: str | None
|
||||
logins: dict[LoginType, LoginInfo]
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Support for "with" statement.
|
||||
|
|
|
|||
416
dist/ba_data/python/babase/_app.py
vendored
416
dist/ba_data/python/babase/_app.py
vendored
|
|
@ -1,12 +1,10 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the high level state of the app."""
|
||||
# pylint: disable=too-many-lines
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import logging
|
||||
import warnings
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
|
@ -24,6 +22,7 @@ from babase._appcomponent import AppComponentSubsystem
|
|||
from babase._appmodeselector import AppModeSelector
|
||||
from babase._appintent import AppIntentDefault, AppIntentExec
|
||||
from babase._stringedit import StringEditSubsystem
|
||||
from babase._devconsole import DevConsoleSubsystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import asyncio
|
||||
|
|
@ -57,6 +56,8 @@ class App:
|
|||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
# A few things defined as non-optional values but not actually
|
||||
# available until the app starts.
|
||||
plugins: PluginSubsystem
|
||||
lang: LanguageSubsystem
|
||||
health_monitor: AppHealthMonitor
|
||||
|
|
@ -71,7 +72,7 @@ class App:
|
|||
|
||||
# The app has not yet begun starting and should not be used in
|
||||
# any way.
|
||||
NOT_RUNNING = 0
|
||||
NOT_STARTED = 0
|
||||
|
||||
# The native layer is spinning up its machinery (screens,
|
||||
# renderers, etc.). Nothing should happen in the Python layer
|
||||
|
|
@ -91,13 +92,23 @@ class App:
|
|||
# All pieces are in place and the app is now doing its thing.
|
||||
RUNNING = 4
|
||||
|
||||
# The app is backgrounded or otherwise suspended.
|
||||
PAUSED = 5
|
||||
# Used on platforms such as mobile where the app basically needs
|
||||
# to shut down while backgrounded. In this state, all event
|
||||
# loops are suspended and all graphics and audio must cease
|
||||
# completely. Be aware that the suspended state can be entered
|
||||
# from any other state including NATIVE_BOOTSTRAPPING and
|
||||
# SHUTTING_DOWN.
|
||||
SUSPENDED = 5
|
||||
|
||||
# The app is shutting down.
|
||||
# The app is shutting down. This process may involve sending
|
||||
# network messages or other things that can take up to a few
|
||||
# seconds, so ideally graphics and audio should remain
|
||||
# functional (with fades or spinners or whatever to show
|
||||
# something is happening).
|
||||
SHUTTING_DOWN = 6
|
||||
|
||||
# The app has completed shutdown.
|
||||
# The app has completed shutdown. Any code running here should
|
||||
# be basically immediate.
|
||||
SHUTDOWN_COMPLETE = 7
|
||||
|
||||
class DefaultAppModeSelector(AppModeSelector):
|
||||
|
|
@ -140,9 +151,9 @@ class App:
|
|||
def __init__(self) -> None:
|
||||
"""(internal)
|
||||
|
||||
Do not instantiate this class; access the single shared instance
|
||||
of it as 'app' which is available in various Ballistica
|
||||
feature-set modules such as babase.
|
||||
Do not instantiate this class. You can access the single shared
|
||||
instance of it through various high level packages: 'babase.app',
|
||||
'bascenev1.app', 'bauiv1.app', etc.
|
||||
"""
|
||||
|
||||
# Hack for docs-generation: we can be imported with dummy modules
|
||||
|
|
@ -151,32 +162,35 @@ class App:
|
|||
return
|
||||
|
||||
self.env: babase.Env = _babase.Env()
|
||||
self.state = self.State.NOT_RUNNING
|
||||
self.state = self.State.NOT_STARTED
|
||||
|
||||
# Default executor which can be used for misc background
|
||||
# processing. It should also be passed to any additional asyncio
|
||||
# loops we create so that everything shares the same single set
|
||||
# of worker threads.
|
||||
self.threadpool = ThreadPoolExecutor(thread_name_prefix='baworker')
|
||||
self.threadpool = ThreadPoolExecutor(
|
||||
thread_name_prefix='baworker',
|
||||
initializer=self._thread_pool_thread_init,
|
||||
)
|
||||
|
||||
self.meta = MetadataSubsystem()
|
||||
self.net = NetworkSubsystem()
|
||||
self.workspaces = WorkspaceSubsystem()
|
||||
self.components = AppComponentSubsystem()
|
||||
self.stringedit = StringEditSubsystem()
|
||||
self.devconsole = DevConsoleSubsystem()
|
||||
|
||||
# This is incremented any time the app is backgrounded or
|
||||
# foregrounded; can be a simple way to determine if network data
|
||||
# should be refreshed/etc.
|
||||
self.fg_state = 0
|
||||
self.config_file_healthy: bool = False
|
||||
|
||||
self._subsystems: list[AppSubsystem] = []
|
||||
self._native_bootstrapping_completed = False
|
||||
self._init_completed = False
|
||||
self._meta_scan_completed = False
|
||||
self._native_start_called = False
|
||||
self._native_paused = False
|
||||
self._native_suspended = False
|
||||
self._native_shutdown_called = False
|
||||
self._native_shutdown_complete_called = False
|
||||
self._initial_sign_in_completed = False
|
||||
|
|
@ -194,8 +208,11 @@ class App:
|
|||
self._mode_selector: babase.AppModeSelector | None = None
|
||||
self._shutdown_task: asyncio.Task[None] | None = None
|
||||
self._shutdown_tasks: list[Coroutine[None, None, None]] = [
|
||||
self._wait_for_shutdown_suppressions()
|
||||
self._wait_for_shutdown_suppressions(),
|
||||
self._fade_and_shutdown_graphics(),
|
||||
self._fade_and_shutdown_audio(),
|
||||
]
|
||||
self._pool_thread_count = 0
|
||||
|
||||
def postinit(self) -> None:
|
||||
"""Called after we've been inited and assigned to babase.app.
|
||||
|
|
@ -212,6 +229,15 @@ class App:
|
|||
self.lang = LanguageSubsystem()
|
||||
self.plugins = PluginSubsystem()
|
||||
|
||||
@property
|
||||
def active(self) -> bool:
|
||||
"""Whether the app is currently front and center.
|
||||
|
||||
This will be False when the app is hidden, other activities
|
||||
are covering it, etc. (depending on the platform).
|
||||
"""
|
||||
return _babase.app_is_active()
|
||||
|
||||
@property
|
||||
def aioloop(self) -> asyncio.AbstractEventLoop:
|
||||
"""The logic thread's asyncio event loop.
|
||||
|
|
@ -311,7 +337,7 @@ class App:
|
|||
def add_shutdown_task(self, coro: Coroutine[None, None, None]) -> None:
|
||||
"""Add a task to be run on app shutdown.
|
||||
|
||||
Note that tasks will be killed after
|
||||
Note that shutdown tasks will be canceled after
|
||||
App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.
|
||||
"""
|
||||
if (
|
||||
|
|
@ -385,18 +411,18 @@ class App:
|
|||
self._native_bootstrapping_completed = True
|
||||
self._update_state()
|
||||
|
||||
def on_native_pause(self) -> None:
|
||||
"""Called by the native layer when the app pauses."""
|
||||
def on_native_suspend(self) -> None:
|
||||
"""Called by the native layer when the app is suspended."""
|
||||
assert _babase.in_logic_thread()
|
||||
assert not self._native_paused # Should avoid redundant calls.
|
||||
self._native_paused = True
|
||||
assert not self._native_suspended # Should avoid redundant calls.
|
||||
self._native_suspended = True
|
||||
self._update_state()
|
||||
|
||||
def on_native_resume(self) -> None:
|
||||
"""Called by the native layer when the app resumes."""
|
||||
def on_native_unsuspend(self) -> None:
|
||||
"""Called by the native layer when the app suspension ends."""
|
||||
assert _babase.in_logic_thread()
|
||||
assert self._native_paused # Should avoid redundant calls.
|
||||
self._native_paused = False
|
||||
assert self._native_suspended # Should avoid redundant calls.
|
||||
self._native_suspended = False
|
||||
self._update_state()
|
||||
|
||||
def on_native_shutdown(self) -> None:
|
||||
|
|
@ -415,7 +441,7 @@ class App:
|
|||
"""(internal)"""
|
||||
from babase._appconfig import read_app_config
|
||||
|
||||
self._config, self.config_file_healthy = read_app_config()
|
||||
self._config = read_app_config()
|
||||
|
||||
def handle_deep_link(self, url: str) -> None:
|
||||
"""Handle a deep link URL."""
|
||||
|
|
@ -493,7 +519,7 @@ class App:
|
|||
except Exception:
|
||||
logging.exception('Error setting app intent to %s.', intent)
|
||||
_babase.pushcall(
|
||||
tpartial(self._apply_intent_error, intent),
|
||||
tpartial(self._display_set_intent_error, intent),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
||||
|
|
@ -538,10 +564,11 @@ class App:
|
|||
'Error handling intent %s in app-mode %s.', intent, mode
|
||||
)
|
||||
|
||||
def _apply_intent_error(self, intent: AppIntent) -> None:
|
||||
def _display_set_intent_error(self, intent: AppIntent) -> None:
|
||||
"""Show the *user* something went wrong setting an intent."""
|
||||
from babase._language import Lstr
|
||||
|
||||
del intent # Unused.
|
||||
del intent
|
||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
_babase.getsimplesound('error').play()
|
||||
|
||||
|
|
@ -564,19 +591,6 @@ class App:
|
|||
self._aioloop = _asyncio.setup_asyncio()
|
||||
self.health_monitor = AppHealthMonitor()
|
||||
|
||||
# Only proceed if our config file is healthy so we don't
|
||||
# overwrite a broken one or whatnot and wipe out data.
|
||||
if not self.config_file_healthy:
|
||||
if self.classic is not None:
|
||||
handled = self.classic.show_config_error_window()
|
||||
if handled:
|
||||
return
|
||||
|
||||
# For now on other systems we just overwrite the bum config.
|
||||
# At this point settings are already set; lets just commit
|
||||
# them to disk.
|
||||
_appconfig.commit_app_config(force=True)
|
||||
|
||||
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
|
||||
# This section generated by batools.appmodule; do not edit.
|
||||
|
||||
|
|
@ -726,15 +740,15 @@ class App:
|
|||
_babase.lifecyclelog('app state shutting down')
|
||||
self._on_shutting_down()
|
||||
|
||||
elif self._native_paused:
|
||||
# Entering paused state:
|
||||
if self.state is not self.State.PAUSED:
|
||||
self.state = self.State.PAUSED
|
||||
self._on_pause()
|
||||
elif self._native_suspended:
|
||||
# Entering suspended state:
|
||||
if self.state is not self.State.SUSPENDED:
|
||||
self.state = self.State.SUSPENDED
|
||||
self._on_suspend()
|
||||
else:
|
||||
# Leaving paused state:
|
||||
if self.state is self.State.PAUSED:
|
||||
self._on_resume()
|
||||
# Leaving suspended state:
|
||||
if self.state is self.State.SUSPENDED:
|
||||
self._on_unsuspend()
|
||||
|
||||
# Entering or returning to running state
|
||||
if self._initial_sign_in_completed and self._meta_scan_completed:
|
||||
|
|
@ -768,7 +782,7 @@ class App:
|
|||
self.state = self.State.NATIVE_BOOTSTRAPPING
|
||||
_babase.lifecyclelog('app state native bootstrapping')
|
||||
else:
|
||||
# Only logical possibility left is NOT_RUNNING, in which
|
||||
# Only logical possibility left is NOT_STARTED, in which
|
||||
# case we should not be getting called.
|
||||
logging.warning(
|
||||
'App._update_state called while in %s state;'
|
||||
|
|
@ -780,6 +794,7 @@ class App:
|
|||
async def _shutdown(self) -> None:
|
||||
import asyncio
|
||||
|
||||
_babase.lock_all_input()
|
||||
try:
|
||||
async with asyncio.TaskGroup() as task_group:
|
||||
for task_coro in self._shutdown_tasks:
|
||||
|
|
@ -809,33 +824,33 @@ class App:
|
|||
try:
|
||||
await asyncio.wait_for(task, self.SHUTDOWN_TASK_TIMEOUT_SECONDS)
|
||||
except Exception:
|
||||
logging.exception('Error in shutdown task.')
|
||||
logging.exception('Error in shutdown task (%s).', coro)
|
||||
|
||||
def _on_pause(self) -> None:
|
||||
"""Called when the app goes to a paused state."""
|
||||
def _on_suspend(self) -> None:
|
||||
"""Called when the app goes to a suspended state."""
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
# Pause all app subsystems in the opposite order they were inited.
|
||||
# Suspend all app subsystems in the opposite order they were inited.
|
||||
for subsystem in reversed(self._subsystems):
|
||||
try:
|
||||
subsystem.on_app_pause()
|
||||
subsystem.on_app_suspend()
|
||||
except Exception:
|
||||
logging.exception(
|
||||
'Error in on_app_pause for subsystem %s.', subsystem
|
||||
'Error in on_app_suspend for subsystem %s.', subsystem
|
||||
)
|
||||
|
||||
def _on_resume(self) -> None:
|
||||
"""Called when resuming."""
|
||||
def _on_unsuspend(self) -> None:
|
||||
"""Called when unsuspending."""
|
||||
assert _babase.in_logic_thread()
|
||||
self.fg_state += 1
|
||||
|
||||
# Resume all app subsystems in the same order they were inited.
|
||||
# Unsuspend all app subsystems in the same order they were inited.
|
||||
for subsystem in self._subsystems:
|
||||
try:
|
||||
subsystem.on_app_resume()
|
||||
subsystem.on_app_unsuspend()
|
||||
except Exception:
|
||||
logging.exception(
|
||||
'Error in on_app_resume for subsystem %s.', subsystem
|
||||
'Error in on_app_unsuspend for subsystem %s.', subsystem
|
||||
)
|
||||
|
||||
def _on_shutting_down(self) -> None:
|
||||
|
|
@ -875,10 +890,45 @@ class App:
|
|||
import asyncio
|
||||
|
||||
# Spin and wait for anything blocking shutdown to complete.
|
||||
starttime = _babase.apptime()
|
||||
_babase.lifecyclelog('shutdown-suppress wait begin')
|
||||
while _babase.shutdown_suppress_count() > 0:
|
||||
await asyncio.sleep(0.001)
|
||||
_babase.lifecyclelog('shutdown-suppress wait end')
|
||||
duration = _babase.apptime() - starttime
|
||||
if duration > 1.0:
|
||||
logging.warning(
|
||||
'Shutdown-suppressions lasted longer than ideal '
|
||||
'(%.2f seconds).',
|
||||
duration,
|
||||
)
|
||||
|
||||
async def _fade_and_shutdown_graphics(self) -> None:
|
||||
import asyncio
|
||||
|
||||
# Kick off a short fade and give it time to complete.
|
||||
_babase.lifecyclelog('fade-and-shutdown-graphics begin')
|
||||
_babase.fade_screen(False, time=0.15)
|
||||
await asyncio.sleep(0.15)
|
||||
|
||||
# Now tell the graphics system to go down and wait until
|
||||
# it has done so.
|
||||
_babase.graphics_shutdown_begin()
|
||||
while not _babase.graphics_shutdown_is_complete():
|
||||
await asyncio.sleep(0.01)
|
||||
_babase.lifecyclelog('fade-and-shutdown-graphics end')
|
||||
|
||||
async def _fade_and_shutdown_audio(self) -> None:
|
||||
import asyncio
|
||||
|
||||
# Tell the audio system to go down and give it a bit of
|
||||
# time to do so gracefully.
|
||||
_babase.lifecyclelog('fade-and-shutdown-audio begin')
|
||||
_babase.audio_shutdown_begin()
|
||||
await asyncio.sleep(0.15)
|
||||
while not _babase.audio_shutdown_is_complete():
|
||||
await asyncio.sleep(0.01)
|
||||
_babase.lifecyclelog('fade-and-shutdown-audio end')
|
||||
|
||||
def _threadpool_no_wait_done(self, fut: Future) -> None:
|
||||
try:
|
||||
|
|
@ -888,243 +938,7 @@ class App:
|
|||
'Error in work submitted via threadpool_submit_no_wait()'
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# THE FOLLOWING ARE DEPRECATED AND WILL BE REMOVED IN A FUTURE UPDATE.
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def build_number(self) -> int:
|
||||
"""Integer build number.
|
||||
|
||||
This value increases by at least 1 with each release of the engine.
|
||||
It is independent of the human readable babase.App.version string.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.build_number is deprecated; use app.env.build_number',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.build_number
|
||||
|
||||
@property
|
||||
def device_name(self) -> str:
|
||||
"""Name of the device running the app."""
|
||||
warnings.warn(
|
||||
'app.device_name is deprecated; use app.env.device_name',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.device_name
|
||||
|
||||
@property
|
||||
def config_file_path(self) -> str:
|
||||
"""Where the app's config file is stored on disk."""
|
||||
warnings.warn(
|
||||
'app.config_file_path is deprecated;'
|
||||
' use app.env.config_file_path',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.config_file_path
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
"""Human-readable engine version string; something like '1.3.24'.
|
||||
|
||||
This should not be interpreted as a number; it may contain
|
||||
string elements such as 'alpha', 'beta', 'test', etc.
|
||||
If a numeric version is needed, use `build_number`.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.version is deprecated; use app.env.version',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.version
|
||||
|
||||
@property
|
||||
def debug_build(self) -> bool:
|
||||
"""Whether the app was compiled in debug mode.
|
||||
|
||||
Debug builds generally run substantially slower than non-debug
|
||||
builds due to compiler optimizations being disabled and extra
|
||||
checks being run.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.debug_build is deprecated; use app.env.debug',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.debug
|
||||
|
||||
@property
|
||||
def test_build(self) -> bool:
|
||||
"""Whether the app was compiled in test mode.
|
||||
|
||||
Test mode enables extra checks and features that are useful for
|
||||
release testing but which do not slow the game down significantly.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.test_build is deprecated; use app.env.test',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.test
|
||||
|
||||
@property
|
||||
def data_directory(self) -> str:
|
||||
"""Path where static app data lives."""
|
||||
warnings.warn(
|
||||
'app.data_directory is deprecated; use app.env.data_directory',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.data_directory
|
||||
|
||||
@property
|
||||
def python_directory_user(self) -> str | None:
|
||||
"""Path where the app expects its user scripts (mods) to live.
|
||||
|
||||
Be aware that this value may be None if ballistica is running in
|
||||
a non-standard environment, and that python-path modifications may
|
||||
cause modules to be loaded from other locations.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.python_directory_user is deprecated;'
|
||||
' use app.env.python_directory_user',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.python_directory_user
|
||||
|
||||
@property
|
||||
def python_directory_app(self) -> str | None:
|
||||
"""Path where the app expects its bundled modules to live.
|
||||
|
||||
Be aware that this value may be None if Ballistica is running in
|
||||
a non-standard environment, and that python-path modifications may
|
||||
cause modules to be loaded from other locations.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.python_directory_app is deprecated;'
|
||||
' use app.env.python_directory_app',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.python_directory_app
|
||||
|
||||
@property
|
||||
def python_directory_app_site(self) -> str | None:
|
||||
"""Path where the app expects its bundled pip modules to live.
|
||||
|
||||
Be aware that this value may be None if Ballistica is running in
|
||||
a non-standard environment, and that python-path modifications may
|
||||
cause modules to be loaded from other locations.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.python_directory_app_site is deprecated;'
|
||||
' use app.env.python_directory_app_site',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.python_directory_app_site
|
||||
|
||||
@property
|
||||
def api_version(self) -> int:
|
||||
"""The app's api version.
|
||||
|
||||
Only Python modules and packages associated with the current API
|
||||
version number will be detected by the game (see the ba_meta tag).
|
||||
This value will change whenever substantial backward-incompatible
|
||||
changes are introduced to ballistica APIs. When that happens,
|
||||
modules/packages should be updated accordingly and set to target
|
||||
the newer API version number.
|
||||
"""
|
||||
warnings.warn(
|
||||
'app.api_version is deprecated; use app.env.api_version',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.api_version
|
||||
|
||||
@property
|
||||
def on_tv(self) -> bool:
|
||||
"""Whether the app is currently running on a TV."""
|
||||
warnings.warn(
|
||||
'app.on_tv is deprecated; use app.env.tv',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.tv
|
||||
|
||||
@property
|
||||
def vr_mode(self) -> bool:
|
||||
"""Whether the app is currently running in VR."""
|
||||
warnings.warn(
|
||||
'app.vr_mode is deprecated; use app.env.vr',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.vr
|
||||
|
||||
# __SPINOFF_REQUIRE_UI_V1_BEGIN__
|
||||
|
||||
@property
|
||||
def toolbar_test(self) -> bool:
|
||||
"""(internal)."""
|
||||
warnings.warn(
|
||||
'app.toolbar_test is deprecated; use app.ui_v1.use_toolbars',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.ui_v1.use_toolbars
|
||||
|
||||
# __SPINOFF_REQUIRE_UI_V1_END__
|
||||
|
||||
@property
|
||||
def arcade_mode(self) -> bool:
|
||||
"""Whether the app is currently running on arcade hardware."""
|
||||
warnings.warn(
|
||||
'app.arcade_mode is deprecated; use app.env.arcade',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.arcade
|
||||
|
||||
@property
|
||||
def headless_mode(self) -> bool:
|
||||
"""Whether the app is running headlessly."""
|
||||
warnings.warn(
|
||||
'app.headless_mode is deprecated; use app.env.headless',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.headless
|
||||
|
||||
@property
|
||||
def demo_mode(self) -> bool:
|
||||
"""Whether the app is targeting a demo experience."""
|
||||
warnings.warn(
|
||||
'app.demo_mode is deprecated; use app.env.demo',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.env.demo
|
||||
|
||||
# __SPINOFF_REQUIRE_SCENE_V1_BEGIN__
|
||||
|
||||
@property
|
||||
def protocol_version(self) -> int:
|
||||
"""(internal)."""
|
||||
# pylint: disable=cyclic-import
|
||||
import bascenev1
|
||||
|
||||
warnings.warn(
|
||||
'app.protocol_version is deprecated;'
|
||||
' use bascenev1.protocol_version()',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return bascenev1.protocol_version()
|
||||
|
||||
# __SPINOFF_REQUIRE_SCENE_V1_END__
|
||||
def _thread_pool_thread_init(self) -> None:
|
||||
# Help keep things clear in profiling tools/etc.
|
||||
self._pool_thread_count += 1
|
||||
_babase.set_thread_name(f'ballistica worker-{self._pool_thread_count}')
|
||||
|
|
|
|||
41
dist/ba_data/python/babase/_appconfig.py
vendored
41
dist/ba_data/python/babase/_appconfig.py
vendored
|
|
@ -101,15 +101,13 @@ class AppConfig(dict):
|
|||
self.commit()
|
||||
|
||||
|
||||
def read_app_config() -> tuple[AppConfig, bool]:
|
||||
def read_app_config() -> AppConfig:
|
||||
"""Read the app config."""
|
||||
import os
|
||||
import json
|
||||
|
||||
config_file_healthy = False
|
||||
|
||||
# NOTE: it is assumed that this only gets called once and the
|
||||
# config object will not change from here on out
|
||||
# NOTE: it is assumed that this only gets called once and the config
|
||||
# object will not change from here on out
|
||||
config_file_path = _babase.app.env.config_file_path
|
||||
config_contents = ''
|
||||
try:
|
||||
|
|
@ -119,20 +117,16 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||
config = AppConfig(json.loads(config_contents))
|
||||
else:
|
||||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Error reading config file at time %.3f: '%s'.",
|
||||
"Error reading config file '%s' at time %.3f.\n"
|
||||
"Backing up broken config to'%s.broken'.",
|
||||
config_file_path,
|
||||
_babase.apptime(),
|
||||
config_file_path,
|
||||
)
|
||||
|
||||
# Whenever this happens lets back up the broken one just in case it
|
||||
# gets overwritten accidentally.
|
||||
logging.info(
|
||||
"Backing up current config file to '%s.broken'", config_file_path
|
||||
)
|
||||
try:
|
||||
import shutil
|
||||
|
||||
|
|
@ -141,23 +135,10 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||
logging.exception('Error copying broken config.')
|
||||
config = AppConfig()
|
||||
|
||||
# Now attempt to read one of our 'prev' backup copies.
|
||||
prev_path = config_file_path + '.prev'
|
||||
try:
|
||||
if os.path.exists(prev_path):
|
||||
with open(prev_path, encoding='utf-8') as infile:
|
||||
config_contents = infile.read()
|
||||
config = AppConfig(json.loads(config_contents))
|
||||
else:
|
||||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
logging.info('Successfully read backup config.')
|
||||
except Exception:
|
||||
logging.exception('Error reading prev backup config.')
|
||||
return config, config_file_healthy
|
||||
return config
|
||||
|
||||
|
||||
def commit_app_config(force: bool = False) -> None:
|
||||
def commit_app_config() -> None:
|
||||
"""Commit the config to persistent storage.
|
||||
|
||||
Category: **General Utility Functions**
|
||||
|
|
@ -167,10 +148,4 @@ def commit_app_config(force: bool = False) -> None:
|
|||
plus = _babase.app.plus
|
||||
assert plus is not None
|
||||
|
||||
if not _babase.app.config_file_healthy and not force:
|
||||
logging.warning(
|
||||
'Current config file is broken; '
|
||||
'skipping write to avoid losing settings.'
|
||||
)
|
||||
return
|
||||
plus.mark_config_dirty()
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_appmode.py
vendored
1
dist/ba_data/python/babase/_appmode.py
vendored
|
|
@ -31,6 +31,7 @@ class AppMode:
|
|||
AppExperience associated with the AppMode must be supported by
|
||||
the current app and runtime environment.
|
||||
"""
|
||||
# FIXME: check AppExperience.
|
||||
return cls._supports_intent(intent)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
4
dist/ba_data/python/babase/_appsubsystem.py
vendored
4
dist/ba_data/python/babase/_appsubsystem.py
vendored
|
|
@ -39,10 +39,10 @@ class AppSubsystem:
|
|||
def on_app_running(self) -> None:
|
||||
"""Called when the app reaches the running state."""
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
def on_app_suspend(self) -> None:
|
||||
"""Called when the app enters the paused state."""
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
def on_app_unsuspend(self) -> None:
|
||||
"""Called when the app exits the paused state."""
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
|
|
|
|||
20
dist/ba_data/python/babase/_apputils.py
vendored
20
dist/ba_data/python/babase/_apputils.py
vendored
|
|
@ -64,7 +64,9 @@ def get_remote_app_name() -> babase.Lstr:
|
|||
|
||||
def should_submit_debug_info() -> bool:
|
||||
"""(internal)"""
|
||||
return _babase.app.config.get('Submit Debug Info', True)
|
||||
val = _babase.app.config.get('Submit Debug Info', True)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
|
||||
def handle_v1_cloud_log() -> None:
|
||||
|
|
@ -323,7 +325,7 @@ def dump_app_state(
|
|||
)
|
||||
|
||||
|
||||
def log_dumped_app_state() -> None:
|
||||
def log_dumped_app_state(from_previous_run: bool = False) -> None:
|
||||
"""If an app-state dump exists, log it and clear it. No-op otherwise."""
|
||||
|
||||
try:
|
||||
|
|
@ -350,8 +352,13 @@ def log_dumped_app_state() -> None:
|
|||
|
||||
metadata = dataclass_from_json(DumpedAppStateMetadata, appstatedata)
|
||||
|
||||
header = (
|
||||
'Found app state dump from previous app run'
|
||||
if from_previous_run
|
||||
else 'App state dump'
|
||||
)
|
||||
out += (
|
||||
f'App state dump:\nReason: {metadata.reason}\n'
|
||||
f'{header}:\nReason: {metadata.reason}\n'
|
||||
f'Time: {metadata.app_time:.2f}'
|
||||
)
|
||||
tbpath = os.path.join(
|
||||
|
|
@ -381,9 +388,10 @@ class AppHealthMonitor(AppSubsystem):
|
|||
|
||||
def on_app_loading(self) -> None:
|
||||
# If any traceback dumps happened last run, log and clear them.
|
||||
log_dumped_app_state()
|
||||
log_dumped_app_state(from_previous_run=True)
|
||||
|
||||
def _app_monitor_thread_main(self) -> None:
|
||||
_babase.set_thread_name('ballistica app-monitor')
|
||||
try:
|
||||
self._monitor_app()
|
||||
except Exception:
|
||||
|
|
@ -441,10 +449,10 @@ class AppHealthMonitor(AppSubsystem):
|
|||
|
||||
self._first_check = False
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
def on_app_suspend(self) -> None:
|
||||
assert _babase.in_logic_thread()
|
||||
self._running = False
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
def on_app_unsuspend(self) -> None:
|
||||
assert _babase.in_logic_thread()
|
||||
self._running = True
|
||||
|
|
|
|||
5
dist/ba_data/python/babase/_cloud.py
vendored
5
dist/ba_data/python/babase/_cloud.py
vendored
|
|
@ -26,6 +26,11 @@ DEBUG_LOG = False
|
|||
class CloudSubsystem(AppSubsystem):
|
||||
"""Manages communication with cloud components."""
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
"""Property equivalent of CloudSubsystem.is_connected()."""
|
||||
return self.is_connected()
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Return whether a connection to the cloud is present.
|
||||
|
||||
|
|
|
|||
188
dist/ba_data/python/babase/_devconsole.py
vendored
Normal file
188
dist/ba_data/python/babase/_devconsole.py
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Dev-Console functionality."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any, Literal
|
||||
|
||||
|
||||
class DevConsoleTab:
|
||||
"""Defines behavior for a tab in the dev-console."""
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Called when the tab should refresh itself."""
|
||||
|
||||
def request_refresh(self) -> None:
|
||||
"""The tab can call this to request that it be refreshed."""
|
||||
_babase.dev_console_request_refresh()
|
||||
|
||||
def button(
|
||||
self,
|
||||
label: str,
|
||||
pos: tuple[float, float],
|
||||
size: tuple[float, float],
|
||||
call: Callable[[], Any] | None = None,
|
||||
h_anchor: Literal['left', 'center', 'right'] = 'center',
|
||||
label_scale: float = 1.0,
|
||||
corner_radius: float = 8.0,
|
||||
style: Literal['normal', 'dark'] = 'normal',
|
||||
) -> None:
|
||||
"""Add a button to the tab being refreshed."""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
_babase.dev_console_add_button(
|
||||
label,
|
||||
pos[0],
|
||||
pos[1],
|
||||
size[0],
|
||||
size[1],
|
||||
call,
|
||||
h_anchor,
|
||||
label_scale,
|
||||
corner_radius,
|
||||
style,
|
||||
)
|
||||
|
||||
def text(
|
||||
self,
|
||||
text: str,
|
||||
pos: tuple[float, float],
|
||||
h_anchor: Literal['left', 'center', 'right'] = 'center',
|
||||
h_align: Literal['left', 'center', 'right'] = 'center',
|
||||
v_align: Literal['top', 'center', 'bottom', 'none'] = 'center',
|
||||
scale: float = 1.0,
|
||||
) -> None:
|
||||
"""Add a button to the tab being refreshed."""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
_babase.dev_console_add_text(
|
||||
text, pos[0], pos[1], h_anchor, h_align, v_align, scale
|
||||
)
|
||||
|
||||
def python_terminal(self) -> None:
|
||||
"""Add a Python Terminal to the tab being refreshed."""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
_babase.dev_console_add_python_terminal()
|
||||
|
||||
@property
|
||||
def width(self) -> float:
|
||||
"""Return the current tab width. Only call during refreshes."""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
return _babase.dev_console_tab_width()
|
||||
|
||||
@property
|
||||
def height(self) -> float:
|
||||
"""Return the current tab height. Only call during refreshes."""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
return _babase.dev_console_tab_height()
|
||||
|
||||
@property
|
||||
def base_scale(self) -> float:
|
||||
"""A scale value set depending on the app's UI scale.
|
||||
|
||||
Dev-console tabs can incorporate this into their UI sizes and
|
||||
positions if they desire. This must be done manually however.
|
||||
"""
|
||||
assert _babase.app.devconsole.is_refreshing
|
||||
return _babase.dev_console_base_scale()
|
||||
|
||||
|
||||
class DevConsoleTabPython(DevConsoleTab):
|
||||
"""The Python dev-console tab."""
|
||||
|
||||
def refresh(self) -> None:
|
||||
self.python_terminal()
|
||||
|
||||
|
||||
class DevConsoleTabTest(DevConsoleTab):
|
||||
"""Test dev-console tab."""
|
||||
|
||||
def refresh(self) -> None:
|
||||
import random
|
||||
|
||||
self.button(
|
||||
f'FLOOP-{random.randrange(200)}',
|
||||
pos=(10, 10),
|
||||
size=(100, 30),
|
||||
h_anchor='left',
|
||||
label_scale=0.6,
|
||||
call=self.request_refresh,
|
||||
)
|
||||
self.button(
|
||||
f'FLOOP2-{random.randrange(200)}',
|
||||
pos=(120, 10),
|
||||
size=(100, 30),
|
||||
h_anchor='left',
|
||||
label_scale=0.6,
|
||||
style='dark',
|
||||
)
|
||||
self.text(
|
||||
'TestText',
|
||||
scale=0.8,
|
||||
pos=(15, 50),
|
||||
h_anchor='left',
|
||||
h_align='left',
|
||||
v_align='none',
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DevConsoleTabEntry:
|
||||
"""Represents a distinct tab in the dev-console."""
|
||||
|
||||
name: str
|
||||
factory: Callable[[], DevConsoleTab]
|
||||
|
||||
|
||||
class DevConsoleSubsystem:
|
||||
"""Subsystem for wrangling the dev console.
|
||||
|
||||
The single instance of this class can be found at
|
||||
babase.app.devconsole. The dev-console is a simple always-available
|
||||
UI intended for use by developers; not end users. Traditionally it
|
||||
is available by typing a backtick (`) key on a keyboard, but now can
|
||||
be accessed via an on-screen button (see settings/advanced to enable
|
||||
said button).
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# All tabs in the dev-console. Add your own stuff here via
|
||||
# plugins or whatnot.
|
||||
self.tabs: list[DevConsoleTabEntry] = [
|
||||
DevConsoleTabEntry('Python', DevConsoleTabPython)
|
||||
]
|
||||
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
|
||||
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
|
||||
self.is_refreshing = False
|
||||
|
||||
def do_refresh_tab(self, tabname: str) -> None:
|
||||
"""Called by the C++ layer when a tab should be filled out."""
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
# FIXME: We currently won't handle multiple tabs with the same
|
||||
# name. We should give a clean error or something in that case.
|
||||
tab: DevConsoleTab | None = None
|
||||
for tabentry in self.tabs:
|
||||
if tabentry.name == tabname:
|
||||
tab = tabentry.factory()
|
||||
break
|
||||
|
||||
if tab is None:
|
||||
logging.error(
|
||||
'DevConsole got refresh request for tab'
|
||||
" '%s' which does not exist.",
|
||||
tabname,
|
||||
)
|
||||
return
|
||||
|
||||
self.is_refreshing = True
|
||||
try:
|
||||
tab.refresh()
|
||||
finally:
|
||||
self.is_refreshing = False
|
||||
11
dist/ba_data/python/babase/_env.py
vendored
11
dist/ba_data/python/babase/_env.py
vendored
|
|
@ -40,6 +40,11 @@ def on_native_module_import() -> None:
|
|||
if envconfig.log_handler is not None:
|
||||
_feed_logs_to_babase(envconfig.log_handler)
|
||||
|
||||
# Also let's name the log-handler thread to help in profiling.
|
||||
envconfig.log_handler.call_in_thread(
|
||||
lambda: _babase.set_thread_name('ballistica logging')
|
||||
)
|
||||
|
||||
env = _babase.pre_env()
|
||||
|
||||
# Give a soft warning if we're being used with a different binary
|
||||
|
|
@ -180,10 +185,8 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None:
|
|||
def _on_log(entry: LogEntry) -> None:
|
||||
# Forward this along to the engine to display in the in-app
|
||||
# console, in the Android log, etc.
|
||||
_babase.display_log(
|
||||
name=entry.name,
|
||||
level=entry.level.name,
|
||||
message=entry.message,
|
||||
_babase.emit_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.
|
||||
|
|
|
|||
61
dist/ba_data/python/babase/_hooks.py
vendored
61
dist/ba_data/python/babase/_hooks.py
vendored
|
|
@ -33,18 +33,47 @@ def reset_to_main_menu() -> None:
|
|||
logging.warning('reset_to_main_menu: no-op due to classic not present.')
|
||||
|
||||
|
||||
def set_config_fullscreen_on() -> None:
|
||||
def get_v2_account_id() -> str | None:
|
||||
"""Return the current V2 account id if signed in, or None if not."""
|
||||
try:
|
||||
plus = _babase.app.plus
|
||||
if plus is not None:
|
||||
account = plus.accounts.primary
|
||||
if account is not None:
|
||||
accountid = account.accountid
|
||||
# (Avoids mypy complaints when plus is not present)
|
||||
assert isinstance(accountid, (str, type(None)))
|
||||
return accountid
|
||||
return None
|
||||
except Exception:
|
||||
logging.exception('Error fetching v2 account id.')
|
||||
return None
|
||||
|
||||
|
||||
def store_config_fullscreen_on() -> None:
|
||||
"""The OS has changed our fullscreen state and we should take note."""
|
||||
_babase.app.config['Fullscreen'] = True
|
||||
_babase.app.config.commit()
|
||||
|
||||
|
||||
def set_config_fullscreen_off() -> None:
|
||||
def store_config_fullscreen_off() -> None:
|
||||
"""The OS has changed our fullscreen state and we should take note."""
|
||||
_babase.app.config['Fullscreen'] = False
|
||||
_babase.app.config.commit()
|
||||
|
||||
|
||||
def set_config_fullscreen_on() -> None:
|
||||
"""Set and store fullscreen state"""
|
||||
_babase.app.config['Fullscreen'] = True
|
||||
_babase.app.config.apply_and_commit()
|
||||
|
||||
|
||||
def set_config_fullscreen_off() -> None:
|
||||
"""The OS has changed our fullscreen state and we should take note."""
|
||||
_babase.app.config['Fullscreen'] = False
|
||||
_babase.app.config.apply_and_commit()
|
||||
|
||||
|
||||
def not_signed_in_screen_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
|
|
@ -111,6 +140,14 @@ def error_message() -> None:
|
|||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
|
||||
|
||||
def success_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('dingSmall').play()
|
||||
_babase.screenmessage(Lstr(resource='successText'), color=(0, 1, 0))
|
||||
|
||||
|
||||
def purchase_not_valid_error() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
|
|
@ -300,6 +337,7 @@ def implicit_sign_in(
|
|||
from bacommon.login import LoginType
|
||||
|
||||
assert _babase.app.plus is not None
|
||||
|
||||
_babase.app.plus.accounts.on_implicit_sign_in(
|
||||
login_type=LoginType(login_type_str),
|
||||
login_id=login_id,
|
||||
|
|
@ -372,3 +410,22 @@ def string_edit_adapter_can_be_replaced(adapter: StringEditAdapter) -> bool:
|
|||
|
||||
assert isinstance(adapter, StringEditAdapter)
|
||||
return adapter.can_be_replaced()
|
||||
|
||||
|
||||
def get_dev_console_tab_names() -> list[str]:
|
||||
"""Return the current set of dev-console tab names."""
|
||||
return [t.name for t in _babase.app.devconsole.tabs]
|
||||
|
||||
|
||||
def unsupported_controller_message(name: str) -> None:
|
||||
"""Print a message when an unsupported controller is connected."""
|
||||
from babase._language import Lstr
|
||||
|
||||
# Ick; this can get called early in the bootstrapping process
|
||||
# before we're allowed to load assets. Guard against that.
|
||||
if _babase.asset_loads_allowed():
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
Lstr(resource='unsupportedControllerText', subs=[('${NAME}', name)]),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
|
|
|||
59
dist/ba_data/python/babase/_login.py
vendored
59
dist/ba_data/python/babase/_login.py
vendored
|
|
@ -20,6 +20,13 @@ if TYPE_CHECKING:
|
|||
DEBUG_LOG = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoginInfo:
|
||||
"""Basic info about a login available in the app.plus.accounts section."""
|
||||
|
||||
name: str
|
||||
|
||||
|
||||
class LoginAdapter:
|
||||
"""Allows using implicit login types in an explicit way.
|
||||
|
||||
|
|
@ -138,7 +145,7 @@ class LoginAdapter:
|
|||
is actually being used by the app. It should therefore register
|
||||
unlocked achievements, leaderboard scores, allow viewing native
|
||||
UIs, etc. When not active it should ignore everything and behave
|
||||
as if logged out, even if it technically is still logged in.
|
||||
as if signed out, even if it technically is still signed in.
|
||||
"""
|
||||
assert _babase.in_logic_thread()
|
||||
del active # Unused.
|
||||
|
|
@ -149,7 +156,7 @@ class LoginAdapter:
|
|||
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
|
||||
description: str,
|
||||
) -> None:
|
||||
"""Attempt an explicit sign in via this adapter.
|
||||
"""Attempt to sign in via this adapter.
|
||||
|
||||
This can be called even if the back-end is not implicitly signed in;
|
||||
the adapter will attempt to sign in if possible. An exception will
|
||||
|
|
@ -161,7 +168,7 @@ class LoginAdapter:
|
|||
# Have been seeing multiple sign-in attempts come through
|
||||
# nearly simultaneously which can be problematic server-side.
|
||||
# Let's error if a sign-in attempt is made within a few seconds
|
||||
# of the last one to address this.
|
||||
# of the last one to try and address this.
|
||||
now = time.monotonic()
|
||||
appnow = _babase.apptime()
|
||||
if self._last_sign_in_time is not None:
|
||||
|
|
@ -229,6 +236,7 @@ class LoginAdapter:
|
|||
def _got_sign_in_response(
|
||||
response: bacommon.cloud.SignInResponse | Exception,
|
||||
) -> None:
|
||||
# This likely means we couldn't communicate with the server.
|
||||
if isinstance(response, Exception):
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
|
|
@ -239,20 +247,18 @@ class LoginAdapter:
|
|||
)
|
||||
_babase.pushcall(Call(result_cb, self, response))
|
||||
else:
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
'LoginAdapter: %s adapter got successful'
|
||||
' sign-in response',
|
||||
self.login_type.name,
|
||||
)
|
||||
# This means our credentials were explicitly rejected.
|
||||
if response.credentials is None:
|
||||
result2: LoginAdapter.SignInResult | Exception = (
|
||||
RuntimeError(
|
||||
'No credentials returned after'
|
||||
' submitting sign-in-token.'
|
||||
)
|
||||
RuntimeError('Sign-in-token was rejected.')
|
||||
)
|
||||
else:
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
'LoginAdapter: %s adapter got successful'
|
||||
' sign-in response',
|
||||
self.login_type.name,
|
||||
)
|
||||
result2 = self.SignInResult(
|
||||
credentials=response.credentials
|
||||
)
|
||||
|
|
@ -269,7 +275,7 @@ class LoginAdapter:
|
|||
on_response=_got_sign_in_response,
|
||||
)
|
||||
|
||||
# Kick off the process by fetching a sign-in token.
|
||||
# Kick off the sign-in process by fetching a sign-in token.
|
||||
self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
|
||||
|
||||
def is_back_end_active(self) -> bool:
|
||||
|
|
@ -282,11 +288,10 @@ class LoginAdapter:
|
|||
"""Get a sign-in token from the adapter back end.
|
||||
|
||||
This token is then passed to the master-server to complete the
|
||||
login process.
|
||||
The adapter can use this opportunity to bring up account creation
|
||||
UI, call its internal sign_in function, etc. as needed.
|
||||
The provided completion_cb should then be called with either a token
|
||||
or None if sign in failed or was cancelled.
|
||||
sign-in process. The adapter can use this opportunity to bring
|
||||
up account creation UI, call its internal sign_in function, etc.
|
||||
as needed. The provided completion_cb should then be called with
|
||||
either a token or None if sign in failed or was cancelled.
|
||||
"""
|
||||
from babase._general import Call
|
||||
|
||||
|
|
@ -295,7 +300,7 @@ class LoginAdapter:
|
|||
|
||||
def _update_implicit_login_state(self) -> None:
|
||||
# If we've received an implicit login state, schedule it to be
|
||||
# sent along to the app. We wait until on-app-launch has been
|
||||
# sent along to the app. We wait until on-app-loading has been
|
||||
# called so that account-client-v2 has had a chance to load
|
||||
# any existing state so it can properly respond to this.
|
||||
if self._implicit_login_state_dirty and self._on_app_loading_called:
|
||||
|
|
@ -340,8 +345,8 @@ class LoginAdapter:
|
|||
class LoginAdapterNative(LoginAdapter):
|
||||
"""A login adapter that does its work in the native layer."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GPGS)
|
||||
def __init__(self, login_type: LoginType) -> None:
|
||||
super().__init__(login_type)
|
||||
|
||||
# Store int ids for in-flight attempts since they may go through
|
||||
# various platform layers and back.
|
||||
|
|
@ -375,3 +380,13 @@ class LoginAdapterNative(LoginAdapter):
|
|||
|
||||
class LoginAdapterGPGS(LoginAdapterNative):
|
||||
"""Google Play Game Services adapter."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GPGS)
|
||||
|
||||
|
||||
class LoginAdapterGameCenter(LoginAdapterNative):
|
||||
"""Apple Game Center adapter."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GAME_CENTER)
|
||||
|
|
|
|||
35
dist/ba_data/python/babase/_meta.py
vendored
35
dist/ba_data/python/babase/_meta.py
vendored
|
|
@ -24,6 +24,8 @@ if TYPE_CHECKING:
|
|||
# instead of these or to make the meta system aware of arbitrary classes.
|
||||
EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = {
|
||||
'plugin': 'babase.Plugin',
|
||||
# DEPRECATED as of 12/2023. Currently am warning if finding these
|
||||
# but should take this out eventually.
|
||||
'keyboard': 'babase.Keyboard',
|
||||
}
|
||||
|
||||
|
|
@ -414,30 +416,27 @@ class DirectoryScan:
|
|||
if export_class_name is not None:
|
||||
classname = modulename + '.' + export_class_name
|
||||
|
||||
# Since we'll soon have multiple versions of 'game'
|
||||
# classes we need to migrate people to using base
|
||||
# class names for them.
|
||||
if exporttypestr == 'game':
|
||||
# Migrating away from the 'keyboard' name shortcut
|
||||
# since it's specific to bauiv1; warn if we find it.
|
||||
if exporttypestr == 'keyboard':
|
||||
logging.warning(
|
||||
"metascan: %s:%d: '# ba_meta export"
|
||||
" game' tag should be replaced by '# ba_meta"
|
||||
" export bascenev1.GameActivity'.",
|
||||
" keyboard' tag should be replaced by '# ba_meta"
|
||||
" export bauiv1.Keyboard'.",
|
||||
subpath,
|
||||
lindex + 1,
|
||||
)
|
||||
self.results.announce_errors_occurred = True
|
||||
else:
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
|
|
|
|||
21
dist/ba_data/python/babase/_mgen/enums.py
vendored
21
dist/ba_data/python/babase/_mgen/enums.py
vendored
|
|
@ -38,6 +38,27 @@ class InputType(Enum):
|
|||
DOWN_RELEASE = 26
|
||||
|
||||
|
||||
class QuitType(Enum):
|
||||
"""Types of input a controller can send to the game.
|
||||
|
||||
Category: Enums
|
||||
|
||||
'soft' may hide/reset the app but keep the process running, depending
|
||||
on the platform.
|
||||
|
||||
'back' is a variant of 'soft' which may give 'back-button-pressed'
|
||||
behavior depending on the platform. (returning to some previous
|
||||
activity instead of dumping to the home screen, etc.)
|
||||
|
||||
'hard' leads to the process exiting. This generally should be avoided
|
||||
on platforms such as mobile.
|
||||
"""
|
||||
|
||||
SOFT = 0
|
||||
BACK = 1
|
||||
HARD = 2
|
||||
|
||||
|
||||
class UIScale(Enum):
|
||||
"""The overall scale the UI is being rendered for. Note that this is
|
||||
independent of pixel resolution. For example, a phone and a desktop PC
|
||||
|
|
|
|||
20
dist/ba_data/python/babase/_plugin.py
vendored
20
dist/ba_data/python/babase/_plugin.py
vendored
|
|
@ -170,23 +170,23 @@ class PluginSubsystem(AppSubsystem):
|
|||
|
||||
_error.print_exception('Error in plugin on_app_running()')
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
def on_app_suspend(self) -> None:
|
||||
for plugin in self.active_plugins:
|
||||
try:
|
||||
plugin.on_app_pause()
|
||||
plugin.on_app_suspend()
|
||||
except Exception:
|
||||
from babase import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_pause()')
|
||||
_error.print_exception('Error in plugin on_app_suspend()')
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
def on_app_unsuspend(self) -> None:
|
||||
for plugin in self.active_plugins:
|
||||
try:
|
||||
plugin.on_app_resume()
|
||||
plugin.on_app_unsuspend()
|
||||
except Exception:
|
||||
from babase import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_resume()')
|
||||
_error.print_exception('Error in plugin on_app_unsuspend()')
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
for plugin in self.active_plugins:
|
||||
|
|
@ -327,11 +327,11 @@ class Plugin:
|
|||
def on_app_running(self) -> None:
|
||||
"""Called when the app reaches the running state."""
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
"""Called when the app is switching to a paused state."""
|
||||
def on_app_suspend(self) -> None:
|
||||
"""Called when the app enters the suspended state."""
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
"""Called when the app is resuming from a paused state."""
|
||||
def on_app_unsuspend(self) -> None:
|
||||
"""Called when the app exits the suspended state."""
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
"""Called when the app is beginning the shutdown process."""
|
||||
|
|
|
|||
4
dist/ba_data/python/babase/modutils.py
vendored
4
dist/ba_data/python/babase/modutils.py
vendored
|
|
@ -104,8 +104,8 @@ def show_user_scripts() -> None:
|
|||
|
||||
_error.print_exception('error writing about_this_folder stuff')
|
||||
|
||||
# On a few platforms we try to open the dir in the UI.
|
||||
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
|
||||
# On platforms that support it, open the dir in the UI.
|
||||
if _babase.supports_open_dir_externally():
|
||||
_babase.open_dir_externally(env.python_directory_user)
|
||||
|
||||
# Otherwise we just print a pretty version of it.
|
||||
|
|
|
|||
9
dist/ba_data/python/baclassic/_accountv1.py
vendored
9
dist/ba_data/python/baclassic/_accountv1.py
vendored
|
|
@ -49,10 +49,10 @@ class AccountV1Subsystem:
|
|||
|
||||
babase.pushcall(do_auto_sign_in)
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
def on_app_suspend(self) -> None:
|
||||
"""Should be called when app is pausing."""
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
def on_app_unsuspend(self) -> None:
|
||||
"""Should be called when the app is resumed."""
|
||||
|
||||
# Mark our cached tourneys as invalid so anyone using them knows
|
||||
|
|
@ -302,6 +302,11 @@ class AccountV1Subsystem:
|
|||
"""(internal)"""
|
||||
plus = babase.app.plus
|
||||
if plus is None:
|
||||
import logging
|
||||
|
||||
logging.warning(
|
||||
'Error adding pending promo code; plus not present.'
|
||||
)
|
||||
babase.screenmessage(
|
||||
babase.Lstr(resource='errorText'), color=(1, 0, 0)
|
||||
)
|
||||
|
|
|
|||
59
dist/ba_data/python/baclassic/_ads.py
vendored
59
dist/ba_data/python/baclassic/_ads.py
vendored
|
|
@ -4,10 +4,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
import bauiv1
|
||||
import bascenev1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -31,6 +32,7 @@ class AdsSubsystem:
|
|||
self.last_in_game_ad_remove_message_show_time: float | None = None
|
||||
self.last_ad_completion_time: float | None = None
|
||||
self.last_ad_was_short = False
|
||||
self._fallback_task: asyncio.Task | None = None
|
||||
|
||||
def do_remove_in_game_ads_message(self) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -69,7 +71,8 @@ class AdsSubsystem:
|
|||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
bauiv1.show_ad(purpose, on_completion_call)
|
||||
assert babase.app.plus is not None
|
||||
babase.app.plus.show_ad(purpose, on_completion_call)
|
||||
|
||||
def show_ad_2(
|
||||
self,
|
||||
|
|
@ -78,7 +81,8 @@ class AdsSubsystem:
|
|||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
bauiv1.show_ad_2(purpose, on_completion_call)
|
||||
assert babase.app.plus is not None
|
||||
babase.app.plus.show_ad_2(purpose, on_completion_call)
|
||||
|
||||
def call_after_ad(self, call: Callable[[], Any]) -> None:
|
||||
"""Run a call after potentially showing an ad."""
|
||||
|
|
@ -94,7 +98,7 @@ class AdsSubsystem:
|
|||
show = True
|
||||
|
||||
# No ads without net-connections, etc.
|
||||
if not bauiv1.can_show_ad():
|
||||
if not plus.can_show_ad():
|
||||
show = False
|
||||
if classic.accounts.have_pro():
|
||||
show = False # Pro disables interstitials.
|
||||
|
|
@ -132,7 +136,7 @@ class AdsSubsystem:
|
|||
# ad-show-threshold and see if we should *actually* show
|
||||
# (we reach our threshold faster the longer we've been
|
||||
# playing).
|
||||
base = 'ads' if bauiv1.has_video_ads() else 'ads2'
|
||||
base = 'ads' if plus.has_video_ads() else 'ads2'
|
||||
min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0)
|
||||
max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
|
||||
min_lc_scale = plus.get_v1_account_misc_read_val(
|
||||
|
|
@ -181,36 +185,53 @@ class AdsSubsystem:
|
|||
|
||||
# If we're *still* cleared to show, actually tell the system to show.
|
||||
if show:
|
||||
# As a safety-check, set up an object that will run
|
||||
# the completion callback if we've returned and sat for 10 seconds
|
||||
# (in case some random ad network doesn't properly deliver its
|
||||
# completion callback).
|
||||
# As a safety-check, we set up an object that will run the
|
||||
# completion callback if we've returned and sat for several
|
||||
# seconds (in case some random ad network doesn't properly
|
||||
# deliver its completion callback).
|
||||
class _Payload:
|
||||
def __init__(self, pcall: Callable[[], Any]):
|
||||
self._call = pcall
|
||||
self._ran = False
|
||||
|
||||
def run(self, fallback: bool = False) -> None:
|
||||
"""Run fallback call (and issue a warning about it)."""
|
||||
"""Run the payload."""
|
||||
assert app.classic is not None
|
||||
if not self._ran:
|
||||
if fallback:
|
||||
lanst = app.classic.ads.last_ad_network_set_time
|
||||
print(
|
||||
'ERROR: relying on fallback ad-callback! '
|
||||
'last network: '
|
||||
+ app.classic.ads.last_ad_network
|
||||
+ ' (set '
|
||||
+ str(int(time.time() - lanst))
|
||||
+ 's ago); purpose='
|
||||
+ app.classic.ads.last_ad_purpose
|
||||
logging.error(
|
||||
'Relying on fallback ad-callback! '
|
||||
'last network: %s (set %s seconds ago);'
|
||||
' purpose=%s.',
|
||||
app.classic.ads.last_ad_network,
|
||||
time.time() - lanst,
|
||||
app.classic.ads.last_ad_purpose,
|
||||
)
|
||||
babase.pushcall(self._call)
|
||||
self._ran = True
|
||||
|
||||
payload = _Payload(call)
|
||||
|
||||
# Set up our backup.
|
||||
with babase.ContextRef.empty():
|
||||
babase.apptimer(5.0, lambda: payload.run(fallback=True))
|
||||
# Note to self: Previously this was a simple 5 second
|
||||
# timer because the app got totally suspended while ads
|
||||
# were showing (which delayed the timer), but these days
|
||||
# the app may continue to run, so we need to be more
|
||||
# careful and only fire the fallback after we see that
|
||||
# the app has been front-and-center for several seconds.
|
||||
async def add_fallback_task() -> None:
|
||||
activesecs = 5
|
||||
while activesecs > 0:
|
||||
if babase.app.active:
|
||||
activesecs -= 1
|
||||
await asyncio.sleep(1.0)
|
||||
payload.run(fallback=True)
|
||||
|
||||
_fallback_task = babase.app.aioloop.create_task(
|
||||
add_fallback_task()
|
||||
)
|
||||
self.show_ad('between_game', on_completion_call=payload.run)
|
||||
else:
|
||||
babase.pushcall(call) # Just run the callback without the ad.
|
||||
|
|
|
|||
|
|
@ -41,5 +41,6 @@ class AppDelegate:
|
|||
sessiontype,
|
||||
settings,
|
||||
completion_call=completion_call,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=False, # Disable check since we don't know.
|
||||
)
|
||||
|
|
|
|||
20
dist/ba_data/python/baclassic/_benchmark.py
vendored
20
dist/ba_data/python/baclassic/_benchmark.py
vendored
|
|
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
import babase
|
||||
import bascenev1
|
||||
import _baclassic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
|
@ -54,7 +55,6 @@ def run_stress_test(
|
|||
round_duration: int = 30,
|
||||
) -> None:
|
||||
"""Run a stress test."""
|
||||
from babase import modutils
|
||||
|
||||
babase.screenmessage(
|
||||
"Beginning stress test.. use 'End Test' to stop testing.",
|
||||
|
|
@ -69,22 +69,12 @@ def run_stress_test(
|
|||
'round_duration': round_duration,
|
||||
}
|
||||
)
|
||||
babase.apptimer(
|
||||
7.0,
|
||||
babase.Call(
|
||||
babase.screenmessage,
|
||||
(
|
||||
'stats will be written to '
|
||||
+ modutils.get_human_readable_user_scripts_path()
|
||||
+ '/stress_test_stats.csv'
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def stop_stress_test() -> None:
|
||||
"""End a running stress test."""
|
||||
babase.set_stress_testing(False, 0)
|
||||
|
||||
_baclassic.set_stress_testing(False, 0)
|
||||
assert babase.app.classic is not None
|
||||
try:
|
||||
if babase.app.classic.stress_test_reset_timer is not None:
|
||||
|
|
@ -134,14 +124,14 @@ def start_stress_test(args: dict[str, Any]) -> None:
|
|||
babase.Call(bascenev1.new_host_session, FreeForAllSession),
|
||||
),
|
||||
)
|
||||
babase.set_stress_testing(True, args['player_count'])
|
||||
_baclassic.set_stress_testing(True, args['player_count'])
|
||||
babase.app.classic.stress_test_reset_timer = babase.AppTimer(
|
||||
args['round_duration'], babase.Call(_reset_stress_test, args)
|
||||
)
|
||||
|
||||
|
||||
def _reset_stress_test(args: dict[str, Any]) -> None:
|
||||
babase.set_stress_testing(False, args['player_count'])
|
||||
_baclassic.set_stress_testing(False, args['player_count'])
|
||||
babase.screenmessage('Resetting stress test...')
|
||||
session = bascenev1.get_foreground_host_session()
|
||||
assert session is not None
|
||||
|
|
|
|||
329
dist/ba_data/python/baclassic/_input.py
vendored
329
dist/ba_data/python/baclassic/_input.py
vendored
|
|
@ -20,7 +20,6 @@ def get_input_device_mapped_value(
|
|||
This checks the user config and falls back to default values
|
||||
where available.
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
|
|
@ -40,7 +39,14 @@ def get_input_device_mapped_value(
|
|||
mapping = ccfgs[devicename][unique_id]
|
||||
elif 'default' in ccfgs[devicename]:
|
||||
mapping = ccfgs[devicename]['default']
|
||||
if mapping is not None:
|
||||
|
||||
# We now use the config mapping *only* if it is not empty.
|
||||
# There have been cases of config writing code messing up
|
||||
# and leaving empty dicts in the app config, which currently
|
||||
# leaves the device unusable. Alternatively, we'd perhaps
|
||||
# want to fall back to defaults for individual missing
|
||||
# values, but that is a bigger change we can make later.
|
||||
if isinstance(mapping, dict) and mapping:
|
||||
return mapping.get(name, -1)
|
||||
|
||||
if platform == 'windows':
|
||||
|
|
@ -76,91 +82,6 @@ def get_input_device_mapped_value(
|
|||
'triggerRun1': 5,
|
||||
}.get(name, -1)
|
||||
|
||||
# Look for some exact types.
|
||||
if babase.is_running_on_fire_tv():
|
||||
if devicename in ['Thunder', 'Amazon Fire Game Controller']:
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'unassignedButtonsRun': False,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'analogStickDeadZone': 0.0,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonRun2': 103,
|
||||
'buttonRun1': 104,
|
||||
'triggerRun1': 24,
|
||||
}.get(name, -1)
|
||||
if devicename == 'NYKO PLAYPAD PRO':
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'buttonUp': 20,
|
||||
'buttonLeft': 22,
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Logitech Dual Action':
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 98,
|
||||
'buttonBomb': 101,
|
||||
'buttonJump': 100,
|
||||
'buttonStart': 109,
|
||||
'buttonPunch': 97,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Xbox 360 Wireless Receiver':
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'buttonUp': 20,
|
||||
'buttonLeft': 22,
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Microsoft X-Box 360 pad':
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
}.get(name, -1)
|
||||
if devicename in [
|
||||
'Amazon Remote',
|
||||
'Amazon Bluetooth Dev',
|
||||
'Amazon Fire TV Remote',
|
||||
]:
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 24,
|
||||
'buttonBomb': 91,
|
||||
'buttonJump': 86,
|
||||
'buttonUp': 20,
|
||||
'buttonLeft': 22,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 90,
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
|
||||
elif 'NVIDIA SHIELD;' in useragentstring:
|
||||
if 'NVIDIA Controller' in devicename:
|
||||
return {
|
||||
|
|
@ -175,112 +96,6 @@ def get_input_device_mapped_value(
|
|||
'buttonIgnored': 184,
|
||||
'buttonIgnored2': 86,
|
||||
}.get(name, -1)
|
||||
elif platform == 'mac':
|
||||
if devicename == 'PLAYSTATION(R)3 Controller':
|
||||
return {
|
||||
'buttonLeft': 8,
|
||||
'buttonUp': 5,
|
||||
'buttonRight': 6,
|
||||
'buttonDown': 7,
|
||||
'buttonJump': 15,
|
||||
'buttonPunch': 16,
|
||||
'buttonBomb': 14,
|
||||
'buttonPickUp': 13,
|
||||
'buttonStart': 4,
|
||||
'buttonIgnored': 17,
|
||||
}.get(name, -1)
|
||||
if devicename in ['Wireless 360 Controller', 'Controller']:
|
||||
# Xbox360 gamepads
|
||||
return {
|
||||
'analogStickDeadZone': 1.2,
|
||||
'buttonBomb': 13,
|
||||
'buttonDown': 2,
|
||||
'buttonJump': 12,
|
||||
'buttonLeft': 3,
|
||||
'buttonPickUp': 15,
|
||||
'buttonPunch': 14,
|
||||
'buttonRight': 4,
|
||||
'buttonStart': 5,
|
||||
'buttonUp': 1,
|
||||
'triggerRun1': 5,
|
||||
'triggerRun2': 6,
|
||||
'buttonIgnored': 11,
|
||||
}.get(name, -1)
|
||||
if devicename in [
|
||||
'Logitech Dual Action',
|
||||
'Logitech Cordless RumblePad 2',
|
||||
]:
|
||||
return {
|
||||
'buttonJump': 2,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
# Old gravis gamepad.
|
||||
if devicename == 'GamePad Pro USB ':
|
||||
return {
|
||||
'buttonJump': 2,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
if devicename == 'Microsoft SideWinder Plug & Play Game Pad':
|
||||
return {
|
||||
'buttonJump': 1,
|
||||
'buttonPunch': 3,
|
||||
'buttonBomb': 2,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 6,
|
||||
}.get(name, -1)
|
||||
|
||||
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
|
||||
if devicename == 'Saitek P2500 Rumble Force Pad':
|
||||
return {
|
||||
'buttonJump': 3,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 4,
|
||||
'buttonPickUp': 2,
|
||||
'buttonStart': 11,
|
||||
}.get(name, -1)
|
||||
|
||||
# Some crazy 'Senze' dual gamepad.
|
||||
if devicename == 'Twin USB Joystick':
|
||||
return {
|
||||
'analogStickLR': 3,
|
||||
'analogStickLR_B': 7,
|
||||
'analogStickUD': 4,
|
||||
'analogStickUD_B': 8,
|
||||
'buttonBomb': 2,
|
||||
'buttonBomb_B': 14,
|
||||
'buttonJump': 3,
|
||||
'buttonJump_B': 15,
|
||||
'buttonPickUp': 1,
|
||||
'buttonPickUp_B': 13,
|
||||
'buttonPunch': 4,
|
||||
'buttonPunch_B': 16,
|
||||
'buttonRun1': 7,
|
||||
'buttonRun1_B': 19,
|
||||
'buttonRun2': 8,
|
||||
'buttonRun2_B': 20,
|
||||
'buttonStart': 10,
|
||||
'buttonStart_B': 22,
|
||||
'enableSecondary': 1,
|
||||
'unassignedButtonsRun': False,
|
||||
}.get(name, -1)
|
||||
if devicename == 'USB Gamepad ': # some weird 'JITE' gamepad
|
||||
return {
|
||||
'analogStickLR': 4,
|
||||
'analogStickUD': 5,
|
||||
'buttonJump': 3,
|
||||
'buttonPunch': 4,
|
||||
'buttonBomb': 2,
|
||||
'buttonPickUp': 1,
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
default_android_mapping = {
|
||||
'triggerRun2': 19,
|
||||
|
|
@ -303,6 +118,41 @@ def get_input_device_mapped_value(
|
|||
|
||||
# Generic android...
|
||||
if platform == 'android':
|
||||
if devicename in ['Amazon Fire Game Controller']:
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'unassignedButtonsRun': False,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'analogStickDeadZone': 0.0,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonRun2': 103,
|
||||
'buttonRun1': 104,
|
||||
'triggerRun1': 24,
|
||||
}.get(name, -1)
|
||||
if devicename in [
|
||||
'Amazon Remote',
|
||||
'Amazon Bluetooth Dev',
|
||||
'Amazon Fire TV Remote',
|
||||
]:
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 24,
|
||||
'buttonBomb': 91,
|
||||
'buttonJump': 86,
|
||||
'buttonUp': 20,
|
||||
'buttonLeft': 22,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 90,
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
|
||||
# Steelseries stratus xl.
|
||||
if devicename == 'SteelSeries Stratus XL':
|
||||
return {
|
||||
|
|
@ -380,14 +230,6 @@ def get_input_device_mapped_value(
|
|||
'uiOnly': True,
|
||||
}.get(name, -1)
|
||||
|
||||
# flag particular gamepads to use exact android defaults..
|
||||
# (so they don't even ask to configure themselves)
|
||||
if devicename in [
|
||||
'Samsung Game Pad EI-GP20',
|
||||
'ASUS Gamepad',
|
||||
] or devicename.startswith('Freefly VR Glide'):
|
||||
return default_android_mapping.get(name, -1)
|
||||
|
||||
# Nvidia controller is default, but gets some strange
|
||||
# keypresses we want to ignore.. touching the touchpad,
|
||||
# so lets ignore those.
|
||||
|
|
@ -445,76 +287,11 @@ def get_input_device_mapped_value(
|
|||
'buttonRight': 100,
|
||||
}.get(name, -1)
|
||||
|
||||
# Ok, this gamepad's not in our specific preset list;
|
||||
# fall back to some (hopefully) reasonable defaults.
|
||||
|
||||
# Leaving these in here for now but not gonna add any more now that we have
|
||||
# fancy-pants config sharing across the internet.
|
||||
if platform == 'mac':
|
||||
if 'PLAYSTATION' in devicename: # ps3 gamepad?..
|
||||
return {
|
||||
'buttonLeft': 8,
|
||||
'buttonUp': 5,
|
||||
'buttonRight': 6,
|
||||
'buttonDown': 7,
|
||||
'buttonJump': 15,
|
||||
'buttonPunch': 16,
|
||||
'buttonBomb': 14,
|
||||
'buttonPickUp': 13,
|
||||
'buttonStart': 4,
|
||||
}.get(name, -1)
|
||||
|
||||
# Dual Action Config - hopefully applies to more...
|
||||
if 'Logitech' in devicename:
|
||||
return {
|
||||
'buttonJump': 2,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
|
||||
if 'Saitek' in devicename:
|
||||
return {
|
||||
'buttonJump': 3,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 4,
|
||||
'buttonPickUp': 2,
|
||||
'buttonStart': 11,
|
||||
}.get(name, -1)
|
||||
|
||||
# Gravis stuff?...
|
||||
if 'GamePad' in devicename:
|
||||
return {
|
||||
'buttonJump': 2,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
# Ok, this gamepad's not in our specific preset list; fall back to
|
||||
# some (hopefully) reasonable defaults.
|
||||
|
||||
# Reasonable defaults.
|
||||
if platform == 'android':
|
||||
if babase.is_running_on_fire_tv():
|
||||
# Mostly same as default firetv controller.
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
'triggerRun1': 24,
|
||||
'buttonPickUp': 101,
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonDown': 21,
|
||||
'buttonUp': 20,
|
||||
'buttonLeft': 22,
|
||||
'buttonRight': 23,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
}.get(name, -1)
|
||||
|
||||
# Mostly same as 'Gamepad' except with 'menu' for default start
|
||||
# button instead of 'mode'.
|
||||
return default_android_mapping.get(name, -1)
|
||||
|
||||
# Is there a point to any sort of fallbacks here?.. should check.
|
||||
|
|
@ -533,9 +310,9 @@ def _gen_android_input_hash() -> str:
|
|||
|
||||
md5 = hashlib.md5()
|
||||
|
||||
# Currently we just do a single hash of *all* inputs on android
|
||||
# and that's it.. good enough.
|
||||
# (grabbing mappings for a specific device looks to be non-trivial)
|
||||
# Currently we just do a single hash of *all* inputs on android and
|
||||
# that's it. Good enough. (grabbing mappings for a specific device
|
||||
# looks to be non-trivial)
|
||||
for dirname in [
|
||||
'/system/usr/keylayout',
|
||||
'/data/usr/keylayout',
|
||||
|
|
@ -544,9 +321,9 @@ def _gen_android_input_hash() -> str:
|
|||
try:
|
||||
if os.path.isdir(dirname):
|
||||
for f_name in os.listdir(dirname):
|
||||
# This is usually volume keys and stuff;
|
||||
# assume we can skip it?..
|
||||
# (since it'll vary a lot across devices)
|
||||
# This is usually volume keys and stuff; assume we
|
||||
# can skip it?.. (since it'll vary a lot across
|
||||
# devices)
|
||||
if f_name == 'gpio-keys.kl':
|
||||
continue
|
||||
try:
|
||||
|
|
@ -569,8 +346,8 @@ def get_input_device_map_hash() -> str:
|
|||
"""
|
||||
app = babase.app
|
||||
|
||||
# Currently only using this when classic is present.
|
||||
# Need to replace with a modern equivalent.
|
||||
# Currently only using this when classic is present. Need to replace
|
||||
# with a modern equivalent.
|
||||
if app.classic is not None:
|
||||
try:
|
||||
if app.classic.input_map_hash is None:
|
||||
|
|
|
|||
15
dist/ba_data/python/baclassic/_music.py
vendored
15
dist/ba_data/python/baclassic/_music.py
vendored
|
|
@ -165,15 +165,16 @@ class MusicSubsystem:
|
|||
|
||||
def supports_soundtrack_entry_type(self, entry_type: str) -> bool:
|
||||
"""Return whether provided soundtrack entry type is supported here."""
|
||||
uas = babase.env()['legacy_user_agent_string']
|
||||
assert isinstance(uas, str)
|
||||
|
||||
# FIXME: Generalize this.
|
||||
# Note to self; can't access babase.app.classic here because
|
||||
# we are called during its construction.
|
||||
env = babase.env()
|
||||
platform = env.get('platform')
|
||||
assert isinstance(platform, str)
|
||||
if entry_type == 'iTunesPlaylist':
|
||||
return 'Mac' in uas
|
||||
return platform == 'mac' and babase.is_xcode_build()
|
||||
if entry_type in ('musicFile', 'musicFolder'):
|
||||
return (
|
||||
'android' in uas
|
||||
platform == 'android'
|
||||
and babase.android_get_external_files_dir() is not None
|
||||
)
|
||||
if entry_type == 'default':
|
||||
|
|
@ -239,7 +240,7 @@ class MusicSubsystem:
|
|||
logging.exception('Error in get_soundtrack_entry_name.')
|
||||
return 'default'
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
def on_app_unsuspend(self) -> None:
|
||||
"""Should be run when the app resumes from a suspended state."""
|
||||
if babase.is_os_playing_music():
|
||||
self.do_play_music(None)
|
||||
|
|
|
|||
4
dist/ba_data/python/baclassic/_servermode.py
vendored
4
dist/ba_data/python/baclassic/_servermode.py
vendored
|
|
@ -423,6 +423,10 @@ class ServerController:
|
|||
bascenev1.set_public_party_stats_url(self._config.stats_url)
|
||||
bascenev1.set_public_party_enabled(self._config.party_is_public)
|
||||
|
||||
bascenev1.set_player_rejoin_cooldown(
|
||||
self._config.player_rejoin_cooldown
|
||||
)
|
||||
|
||||
# And here.. we.. go.
|
||||
if self._config.stress_test_players is not None:
|
||||
# Special case: run a stress test.
|
||||
|
|
|
|||
35
dist/ba_data/python/baclassic/_subsystem.py
vendored
35
dist/ba_data/python/baclassic/_subsystem.py
vendored
|
|
@ -229,12 +229,12 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||
|
||||
self.accounts.on_app_loading()
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
self.accounts.on_app_pause()
|
||||
def on_app_suspend(self) -> None:
|
||||
self.accounts.on_app_suspend()
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
self.accounts.on_app_resume()
|
||||
self.music.on_app_resume()
|
||||
def on_app_unsuspend(self) -> None:
|
||||
self.accounts.on_app_unsuspend()
|
||||
self.music.on_app_unsuspend()
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
self.music.on_app_shutdown()
|
||||
|
|
@ -451,15 +451,6 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||
if playtype in val.get_play_types()
|
||||
)
|
||||
|
||||
def show_online_score_ui(
|
||||
self,
|
||||
show: str = 'general',
|
||||
game: str | None = None,
|
||||
game_version: str | None = None,
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
bauiv1.show_online_score_ui(show, game, game_version)
|
||||
|
||||
def game_begin_analytics(self) -> None:
|
||||
"""(internal)"""
|
||||
from baclassic import _analytics
|
||||
|
|
@ -627,15 +618,6 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||
"""(internal)"""
|
||||
return bascenev1.get_foreground_host_activity()
|
||||
|
||||
def show_config_error_window(self) -> bool:
|
||||
"""(internal)"""
|
||||
if self.platform in ('mac', 'linux', 'windows'):
|
||||
from bauiv1lib.configerror import ConfigErrorWindow
|
||||
|
||||
babase.pushcall(ConfigErrorWindow)
|
||||
return True
|
||||
return False
|
||||
|
||||
def value_test(
|
||||
self,
|
||||
arg: str,
|
||||
|
|
@ -701,11 +683,11 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||
|
||||
ShowURLWindow(address)
|
||||
|
||||
def quit_window(self) -> None:
|
||||
def quit_window(self, quit_type: babase.QuitType) -> None:
|
||||
"""(internal)"""
|
||||
from bauiv1lib.confirm import QuitWindow
|
||||
|
||||
QuitWindow()
|
||||
QuitWindow(quit_type)
|
||||
|
||||
def tournament_entry_window(
|
||||
self,
|
||||
|
|
@ -809,5 +791,6 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||
bauiv1.getsound('swish').play()
|
||||
|
||||
babase.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow().get_root_widget()
|
||||
MainMenuWindow().get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
|
|
|
|||
8
dist/ba_data/python/baclassic/macmusicapp.py
vendored
8
dist/ba_data/python/baclassic/macmusicapp.py
vendored
|
|
@ -80,14 +80,13 @@ class _MacMusicAppThread(threading.Thread):
|
|||
def run(self) -> None:
|
||||
"""Run the Music.app thread."""
|
||||
babase.set_thread_name('BA_MacMusicAppThread')
|
||||
babase.mac_music_app_init()
|
||||
|
||||
# Let's mention to the user we're launching Music.app in case
|
||||
# it causes any funny business (this used to background the app
|
||||
# sometimes, though I think that is fixed now)
|
||||
def do_print() -> None:
|
||||
babase.apptimer(
|
||||
1.0,
|
||||
0.5,
|
||||
babase.Call(
|
||||
babase.screenmessage,
|
||||
babase.Lstr(resource='usingItunesText'),
|
||||
|
|
@ -97,9 +96,8 @@ class _MacMusicAppThread(threading.Thread):
|
|||
|
||||
babase.pushcall(do_print, from_other_thread=True)
|
||||
|
||||
# Here we grab this to force the actual launch.
|
||||
babase.mac_music_app_get_volume()
|
||||
babase.mac_music_app_get_library_source()
|
||||
babase.mac_music_app_init()
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
self._commands_available.wait()
|
||||
|
|
|
|||
119
dist/ba_data/python/bacommon/app.py
vendored
119
dist/ba_data/python/bacommon/app.py
vendored
|
|
@ -5,22 +5,49 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class AppExperience(Enum):
|
||||
"""Overall experience that can be provided by a Ballistica app.
|
||||
class AppInterfaceIdiom(Enum):
|
||||
"""A general form-factor or way of experiencing a Ballistica app.
|
||||
|
||||
This corresponds generally, but not exactly, to distinct apps built
|
||||
with Ballistica. However, a single app may support multiple experiences,
|
||||
or there may be multiple apps targeting one experience. Cloud components
|
||||
such as leagues are generally associated with an AppExperience.
|
||||
Note that it is possible for a running app to switch idioms (for
|
||||
instance if a mobile device or computer is connected to a TV).
|
||||
"""
|
||||
|
||||
# A special experience category that is supported everywhere. Used
|
||||
PHONE = 'phone'
|
||||
TABLET = 'tablet'
|
||||
DESKTOP = 'desktop'
|
||||
TV = 'tv'
|
||||
XR = 'xr'
|
||||
|
||||
|
||||
class AppExperience(Enum):
|
||||
"""A particular experience that can be provided by a Ballistica app.
|
||||
|
||||
This is one metric used to isolate different playerbases from
|
||||
eachother where there might be no technical barriers doing so.
|
||||
For example, a casual one-hand-playable phone game and an augmented
|
||||
reality tabletop game may both use the same scene-versions and
|
||||
networking-protocols and whatnot, but it would make no sense to
|
||||
allow players of one join servers for the other. AppExperience can
|
||||
be used to keep these player bases separate.
|
||||
|
||||
Generally a single Ballistica app targets a single AppExperience.
|
||||
This is not a technical requirement, however. A single app may
|
||||
support multiple experiences, or there may be multiple apps
|
||||
targeting one experience. Cloud components such as leagues are
|
||||
generally associated with an AppExperience so that they are only
|
||||
visible to client apps designed for that play style.
|
||||
"""
|
||||
|
||||
# An experience that is supported everywhere. Used
|
||||
# for the default empty AppMode when starting the app, etc.
|
||||
EMPTY = 'empty'
|
||||
|
||||
|
|
@ -33,3 +60,79 @@ class AppExperience(Enum):
|
|||
# touch-screen allowing a mobile device to be used as a game
|
||||
# controller.
|
||||
REMOTE = 'remote'
|
||||
|
||||
|
||||
class AppArchitecture(Enum):
|
||||
"""Processor architecture the App is running on."""
|
||||
|
||||
ARM = 'arm'
|
||||
ARM64 = 'arm64'
|
||||
X86 = 'x86'
|
||||
X86_64 = 'x86_64'
|
||||
|
||||
|
||||
class AppPlatform(Enum):
|
||||
"""Overall platform a Ballistica build can be targeting.
|
||||
|
||||
Each distinct flavor of an app has a unique combination
|
||||
of AppPlatform and AppVariant. Generally platform describes
|
||||
a set of hardware, while variant describes a destination or
|
||||
purpose for the build.
|
||||
"""
|
||||
|
||||
MAC = 'mac'
|
||||
WINDOWS = 'windows'
|
||||
LINUX = 'linux'
|
||||
ANDROID = 'android'
|
||||
IOS = 'ios'
|
||||
TVOS = 'tvos'
|
||||
|
||||
|
||||
class AppVariant(Enum):
|
||||
"""A unique Ballistica build type within a single platform.
|
||||
|
||||
Each distinct flavor of an app has a unique combination
|
||||
of AppPlatform and AppVariant. Generally platform describes
|
||||
a set of hardware, while variant describes a destination or
|
||||
purpose for the build.
|
||||
"""
|
||||
|
||||
# Default builds.
|
||||
GENERIC = 'generic'
|
||||
|
||||
# Builds intended for public testing (may have some extra checks
|
||||
# or logging enabled).
|
||||
TEST = 'test'
|
||||
|
||||
# Various stores.
|
||||
AMAZON_APPSTORE = 'amazon_appstore'
|
||||
GOOGLE_PLAY = 'google_play'
|
||||
APP_STORE = 'app_store'
|
||||
WINDOWS_STORE = 'windows_store'
|
||||
STEAM = 'steam'
|
||||
META = 'meta'
|
||||
EPIC_GAMES_STORE = 'epic_games_store'
|
||||
|
||||
# Other.
|
||||
ARCADE = 'arcade'
|
||||
DEMO = 'demo'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AppInstanceInfo:
|
||||
"""General info about an individual running app."""
|
||||
|
||||
name = Annotated[str, IOAttrs('n')]
|
||||
version = Annotated[str, IOAttrs('v')]
|
||||
build = Annotated[int, IOAttrs('b')]
|
||||
|
||||
platform = Annotated[AppPlatform, IOAttrs('p')]
|
||||
variant = Annotated[AppVariant, IOAttrs('va')]
|
||||
architecture = Annotated[AppArchitecture, IOAttrs('a')]
|
||||
os_version = Annotated[str | None, IOAttrs('o')]
|
||||
|
||||
interface_idiom: Annotated[AppInterfaceIdiom, IOAttrs('i')]
|
||||
locale: Annotated[str, IOAttrs('l')]
|
||||
|
||||
device: Annotated[str | None, IOAttrs('d')]
|
||||
|
|
|
|||
11
dist/ba_data/python/bacommon/login.py
vendored
11
dist/ba_data/python/bacommon/login.py
vendored
|
|
@ -11,6 +11,12 @@ if TYPE_CHECKING:
|
|||
pass
|
||||
|
||||
|
||||
# NOTE TO SELF:
|
||||
# Whenever adding login types here, make sure to update all
|
||||
# basn nodes before trying to send values through to bamaster,
|
||||
# as they need to be extractable by basn en route.
|
||||
|
||||
|
||||
class LoginType(Enum):
|
||||
"""Types of logins available."""
|
||||
|
||||
|
|
@ -20,6 +26,9 @@ class LoginType(Enum):
|
|||
# Google Play Game Services
|
||||
GPGS = 'gpgs'
|
||||
|
||||
# Apple's Game Center
|
||||
GAME_CENTER = 'game_center'
|
||||
|
||||
@property
|
||||
def displayname(self) -> str:
|
||||
"""Human readable name for this value."""
|
||||
|
|
@ -29,3 +38,5 @@ class LoginType(Enum):
|
|||
return 'Email/Password'
|
||||
case cls.GPGS:
|
||||
return 'Google Play Games'
|
||||
case cls.GAME_CENTER:
|
||||
return 'Game Center'
|
||||
|
|
|
|||
11
dist/ba_data/python/bacommon/servermanager.py
vendored
11
dist/ba_data/python/bacommon/servermanager.py
vendored
|
|
@ -143,9 +143,20 @@ class ServerConfig:
|
|||
# queue spamming attacks.
|
||||
enable_queue: bool = True
|
||||
|
||||
# Protocol version we host with. Currently the default is 33 which
|
||||
# still allows older 1.4 game clients to connect. Explicitly setting
|
||||
# to 35 no longer allows those clients but adds/fixes a few things
|
||||
# such as making camera shake properly work in net games.
|
||||
protocol_version: int | None = None
|
||||
|
||||
# (internal) stress-testing mode.
|
||||
stress_test_players: int | None = None
|
||||
|
||||
# How many seconds individual players from a given account must wait
|
||||
# before rejoining the game. This can help suppress exploits
|
||||
# involving leaving and rejoining or switching teams rapidly.
|
||||
player_rejoin_cooldown: float = 10.0
|
||||
|
||||
|
||||
# NOTE: as much as possible, communication from the server-manager to the
|
||||
# child-process should go through these and not ad-hoc Python string commands
|
||||
|
|
|
|||
8
dist/ba_data/python/baenv.py
vendored
8
dist/ba_data/python/baenv.py
vendored
|
|
@ -40,7 +40,7 @@ if TYPE_CHECKING:
|
|||
# the last load. Either way, however, multiple execs will happen in some
|
||||
# form.
|
||||
#
|
||||
# So we need to do a few things to handle that situation gracefully.
|
||||
# To handle that situation gracefully, we need to do a few things:
|
||||
#
|
||||
# - First, we need to store any mutable global state in the __main__
|
||||
# module; not in ourself. This way, alternate versions of ourself will
|
||||
|
|
@ -48,12 +48,12 @@ if TYPE_CHECKING:
|
|||
#
|
||||
# - Second, we should avoid the use of isinstance and similar calls for
|
||||
# our types. An EnvConfig we create would technically be a different
|
||||
# type than that created by an alternate baenv.
|
||||
# type than an EnvConfig created by an alternate baenv.
|
||||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21397
|
||||
TARGET_BALLISTICA_VERSION = '1.7.28'
|
||||
TARGET_BALLISTICA_BUILD = 21739
|
||||
TARGET_BALLISTICA_VERSION = '1.7.32'
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
40
dist/ba_data/python/baplus/_subsystem.py
vendored
40
dist/ba_data/python/baplus/_subsystem.py
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides classic app subsystem."""
|
||||
"""Provides plus app subsystem."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -249,3 +249,41 @@ class PlusSubsystem(AppSubsystem):
|
|||
) -> None:
|
||||
"""(internal)"""
|
||||
return _baplus.tournament_query(callback, args)
|
||||
|
||||
@staticmethod
|
||||
def have_incentivized_ad() -> bool:
|
||||
"""Is an incentivized ad available?"""
|
||||
return _baplus.have_incentivized_ad()
|
||||
|
||||
@staticmethod
|
||||
def has_video_ads() -> bool:
|
||||
"""Are video ads available?"""
|
||||
return _baplus.has_video_ads()
|
||||
|
||||
@staticmethod
|
||||
def can_show_ad() -> bool:
|
||||
"""Can we show an ad?"""
|
||||
return _baplus.can_show_ad()
|
||||
|
||||
@staticmethod
|
||||
def show_ad(
|
||||
purpose: str, on_completion_call: Callable[[], None] | None = None
|
||||
) -> None:
|
||||
"""Show an ad."""
|
||||
_baplus.show_ad(purpose, on_completion_call)
|
||||
|
||||
@staticmethod
|
||||
def show_ad_2(
|
||||
purpose: str, on_completion_call: Callable[[bool], None] | None = None
|
||||
) -> None:
|
||||
"""Show an ad."""
|
||||
_baplus.show_ad_2(purpose, on_completion_call)
|
||||
|
||||
@staticmethod
|
||||
def show_game_service_ui(
|
||||
show: str = 'general',
|
||||
game: str | None = None,
|
||||
game_version: str | None = None,
|
||||
) -> None:
|
||||
"""Show game-service provided UI."""
|
||||
_baplus.show_game_service_ui(show, game, game_version)
|
||||
|
|
|
|||
7
dist/ba_data/python/bascenev1/__init__.py
vendored
7
dist/ba_data/python/bascenev1/__init__.py
vendored
|
|
@ -78,6 +78,7 @@ from _bascenev1 import (
|
|||
end_host_scanning,
|
||||
get_chat_messages,
|
||||
get_connection_to_host_info,
|
||||
get_connection_to_host_info_2,
|
||||
get_foreground_host_activity,
|
||||
get_foreground_host_session,
|
||||
get_game_port,
|
||||
|
|
@ -202,6 +203,7 @@ from bascenev1._multiteamsession import (
|
|||
DEFAULT_TEAM_NAMES,
|
||||
)
|
||||
from bascenev1._music import MusicType, setmusic
|
||||
from bascenev1._net import HostInfo
|
||||
from bascenev1._nodeactor import NodeActor
|
||||
from bascenev1._powerup import get_default_powerup_distribution
|
||||
from bascenev1._profile import (
|
||||
|
|
@ -226,7 +228,7 @@ from bascenev1._settings import (
|
|||
IntSetting,
|
||||
Setting,
|
||||
)
|
||||
from bascenev1._session import Session
|
||||
from bascenev1._session import Session, set_player_rejoin_cooldown
|
||||
from bascenev1._stats import PlayerScoredMessage, PlayerRecord, Stats
|
||||
from bascenev1._team import SessionTeam, Team, EmptyTeam
|
||||
from bascenev1._teamgame import TeamGameActivity
|
||||
|
|
@ -303,6 +305,7 @@ __all__ = [
|
|||
'GameTip',
|
||||
'get_chat_messages',
|
||||
'get_connection_to_host_info',
|
||||
'get_connection_to_host_info_2',
|
||||
'get_default_free_for_all_playlist',
|
||||
'get_default_teams_playlist',
|
||||
'get_default_powerup_distribution',
|
||||
|
|
@ -338,6 +341,7 @@ __all__ = [
|
|||
'have_connected_clients',
|
||||
'have_touchscreen_input',
|
||||
'HitMessage',
|
||||
'HostInfo',
|
||||
'host_scan_cycle',
|
||||
'ImpactDamageMessage',
|
||||
'increment_analytics_count',
|
||||
|
|
@ -415,6 +419,7 @@ __all__ = [
|
|||
'set_public_party_name',
|
||||
'set_public_party_queue_enabled',
|
||||
'set_public_party_stats_url',
|
||||
'set_player_rejoin_cooldown',
|
||||
'set_replay_speed_exponent',
|
||||
'set_touchscreen_editing',
|
||||
'setmusic',
|
||||
|
|
|
|||
4
dist/ba_data/python/bascenev1/_campaign.py
vendored
4
dist/ba_data/python/bascenev1/_campaign.py
vendored
|
|
@ -87,7 +87,9 @@ class Campaign:
|
|||
|
||||
def get_selected_level(self) -> str:
|
||||
"""Return the name of the Level currently selected in the UI."""
|
||||
return self.configdict.get('Selection', self._levels[0].name)
|
||||
val = self.configdict.get('Selection', self._levels[0].name)
|
||||
assert isinstance(val, str)
|
||||
return val
|
||||
|
||||
@property
|
||||
def configdict(self) -> dict[str, Any]:
|
||||
|
|
|
|||
10
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
10
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
|
|
@ -438,10 +438,16 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
|||
assert classic is not None
|
||||
continues_window = classic.continues_window
|
||||
|
||||
# Turning these off. I want to migrate towards monetization that
|
||||
# feels less pay-to-win-ish.
|
||||
allow_continues = False
|
||||
|
||||
plus = babase.app.plus
|
||||
try:
|
||||
if plus is not None and plus.get_v1_account_misc_read_val(
|
||||
'enableContinues', False
|
||||
if (
|
||||
plus is not None
|
||||
and plus.get_v1_account_misc_read_val('enableContinues', False)
|
||||
and allow_continues
|
||||
):
|
||||
session = self.session
|
||||
|
||||
|
|
|
|||
8
dist/ba_data/python/bascenev1/_level.py
vendored
8
dist/ba_data/python/bascenev1/_level.py
vendored
|
|
@ -105,7 +105,9 @@ class Level:
|
|||
def complete(self) -> bool:
|
||||
"""Whether this Level has been completed."""
|
||||
config = self._get_config_dict()
|
||||
return config.get('Complete', False)
|
||||
val = config.get('Complete', False)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_complete(self, val: bool) -> None:
|
||||
"""Set whether or not this level is complete."""
|
||||
|
|
@ -147,7 +149,9 @@ class Level:
|
|||
@property
|
||||
def rating(self) -> float:
|
||||
"""The current rating for this Level."""
|
||||
return self._get_config_dict().get('Rating', 0.0)
|
||||
val = self._get_config_dict().get('Rating', 0.0)
|
||||
assert isinstance(val, float)
|
||||
return val
|
||||
|
||||
def set_rating(self, rating: float) -> None:
|
||||
"""Set a rating for this Level, replacing the old ONLY IF higher."""
|
||||
|
|
|
|||
|
|
@ -170,8 +170,11 @@ class MultiTeamSession(Session):
|
|||
def get_max_players(self) -> int:
|
||||
"""Return max number of Players allowed to join the game at once."""
|
||||
if self.use_teams:
|
||||
return babase.app.config.get('Team Game Max Players', 8)
|
||||
return babase.app.config.get('Free-for-All Max Players', 8)
|
||||
val = babase.app.config.get('Team Game Max Players', 8)
|
||||
else:
|
||||
val = babase.app.config.get('Free-for-All Max Players', 8)
|
||||
assert isinstance(val, int)
|
||||
return val
|
||||
|
||||
def _instantiate_next_game(self) -> None:
|
||||
self._next_game_instance = _bascenev1.newactivity(
|
||||
|
|
|
|||
24
dist/ba_data/python/bascenev1/_net.py
vendored
Normal file
24
dist/ba_data/python/bascenev1/_net.py
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to net play."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class HostInfo:
|
||||
"""Info about a host."""
|
||||
|
||||
name: str
|
||||
build_number: int
|
||||
|
||||
# Note this can be None for non-ip hosts such as bluetooth.
|
||||
address: str | None
|
||||
|
||||
# Note this can be None for non-ip hosts such as bluetooth.
|
||||
port: int | None
|
||||
60
dist/ba_data/python/bascenev1/_session.py
vendored
60
dist/ba_data/python/bascenev1/_session.py
vendored
|
|
@ -3,6 +3,7 @@
|
|||
"""Defines base session class."""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import weakref
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -17,6 +18,17 @@ if TYPE_CHECKING:
|
|||
|
||||
import bascenev1
|
||||
|
||||
# How many seconds someone who left the session (but not the party) must
|
||||
# wait to rejoin the session again. Intended to prevent game exploits
|
||||
# such as skipping respawn waits.
|
||||
_g_player_rejoin_cooldown: float = 0.0
|
||||
|
||||
|
||||
def set_player_rejoin_cooldown(cooldown: float) -> None:
|
||||
"""Set the cooldown for individual players rejoining after leaving."""
|
||||
global _g_player_rejoin_cooldown # pylint: disable=global-statement
|
||||
_g_player_rejoin_cooldown = max(0.0, cooldown)
|
||||
|
||||
|
||||
class Session:
|
||||
"""Defines a high level series of bascenev1.Activity-es.
|
||||
|
|
@ -203,6 +215,11 @@ class Session:
|
|||
# Instantiate our session globals node which will apply its settings.
|
||||
self._sessionglobalsnode = _bascenev1.newnode('sessionglobals')
|
||||
|
||||
# Rejoin cooldown stuff.
|
||||
self._players_on_wait: dict = {}
|
||||
self._player_requested_identifiers: dict = {}
|
||||
self._waitlist_timers: dict = {}
|
||||
|
||||
@property
|
||||
def context(self) -> bascenev1.ContextRef:
|
||||
"""A context-ref pointing at this activity."""
|
||||
|
|
@ -253,6 +270,33 @@ class Session:
|
|||
)
|
||||
return False
|
||||
|
||||
# Rejoin cooldown.
|
||||
identifier = player.get_v1_account_id()
|
||||
if identifier:
|
||||
leave_time = self._players_on_wait.get(identifier)
|
||||
if leave_time:
|
||||
diff = str(
|
||||
math.ceil(
|
||||
_g_player_rejoin_cooldown
|
||||
- babase.apptime()
|
||||
+ leave_time
|
||||
)
|
||||
)
|
||||
_bascenev1.broadcastmessage(
|
||||
babase.Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'You can join in ${COUNT} seconds.',
|
||||
),
|
||||
subs=[('${COUNT}', diff)],
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
clients=[player.inputdevice.client_id],
|
||||
transient=True,
|
||||
)
|
||||
return False
|
||||
self._player_requested_identifiers[player.id] = identifier
|
||||
|
||||
_bascenev1.getsound('dripity').play()
|
||||
return True
|
||||
|
||||
|
|
@ -270,6 +314,16 @@ class Session:
|
|||
|
||||
activity = self._activity_weak()
|
||||
|
||||
# Rejoin cooldown.
|
||||
identifier = self._player_requested_identifiers.get(sessionplayer.id)
|
||||
if identifier:
|
||||
self._players_on_wait[identifier] = babase.apptime()
|
||||
with babase.ContextRef.empty():
|
||||
self._waitlist_timers[identifier] = babase.AppTimer(
|
||||
_g_player_rejoin_cooldown,
|
||||
babase.Call(self._remove_player_from_waitlist, identifier),
|
||||
)
|
||||
|
||||
if not sessionplayer.in_game:
|
||||
# Ok, the player is still in the lobby; simply remove them.
|
||||
with self.context:
|
||||
|
|
@ -770,3 +824,9 @@ class Session:
|
|||
if pass_to_activity:
|
||||
activity.add_player(sessionplayer)
|
||||
return sessionplayer
|
||||
|
||||
def _remove_player_from_waitlist(self, identifier: str) -> None:
|
||||
try:
|
||||
self._players_on_wait.pop(identifier)
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import random
|
|||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bacommon.login import LoginType
|
||||
import bascenev1 as bs
|
||||
import bauiv1 as bui
|
||||
|
||||
|
|
@ -59,29 +60,25 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
|
|||
)
|
||||
)
|
||||
|
||||
self._account_type = (
|
||||
plus.get_v1_account_type()
|
||||
if plus.get_v1_account_state() == 'signed_in'
|
||||
else None
|
||||
)
|
||||
|
||||
self._game_service_icon_color: Sequence[float] | None
|
||||
self._game_service_achievements_texture: bui.Texture | None
|
||||
self._game_service_leaderboards_texture: bui.Texture | None
|
||||
|
||||
if self._account_type == 'Game Center':
|
||||
# Tie in to specific game services if they are active.
|
||||
adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
|
||||
gpgs_active = adapter is not None and adapter.is_back_end_active()
|
||||
adapter = plus.accounts.login_adapters.get(LoginType.GAME_CENTER)
|
||||
game_center_active = (
|
||||
adapter is not None and adapter.is_back_end_active()
|
||||
)
|
||||
|
||||
if game_center_active:
|
||||
self._game_service_icon_color = (1.0, 1.0, 1.0)
|
||||
icon = bui.gettexture('gameCenterIcon')
|
||||
self._game_service_achievements_texture = icon
|
||||
self._game_service_leaderboards_texture = icon
|
||||
self._account_has_achievements = True
|
||||
elif self._account_type == 'Game Circle':
|
||||
icon = bui.gettexture('gameCircleIcon')
|
||||
self._game_service_icon_color = (1, 1, 1)
|
||||
self._game_service_achievements_texture = icon
|
||||
self._game_service_leaderboards_texture = icon
|
||||
self._account_has_achievements = True
|
||||
elif self._account_type == 'Google Play':
|
||||
elif gpgs_active:
|
||||
self._game_service_icon_color = (0.8, 1.0, 0.6)
|
||||
self._game_service_achievements_texture = bui.gettexture(
|
||||
'googlePlayAchievementsIcon'
|
||||
|
|
@ -193,7 +190,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
|
|||
super().__del__()
|
||||
|
||||
# If our UI is still up, kill it.
|
||||
if self._root_ui:
|
||||
if self._root_ui and not self._root_ui.transitioning_out:
|
||||
with bui.ContextRef.empty():
|
||||
bui.containerwidget(edit=self._root_ui, transition='out_left')
|
||||
|
||||
|
|
@ -287,20 +284,20 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
|
|||
self.end({'outcome': 'next_level'})
|
||||
|
||||
def _ui_gc(self) -> None:
|
||||
if bs.app.classic is not None:
|
||||
bs.app.classic.show_online_score_ui(
|
||||
if bs.app.plus is not None:
|
||||
bs.app.plus.show_game_service_ui(
|
||||
'leaderboard',
|
||||
game=self._game_name_str,
|
||||
game_version=self._game_config_str,
|
||||
)
|
||||
else:
|
||||
logging.warning('show_online_score_ui requires classic')
|
||||
logging.warning('show_game_service_ui requires plus feature-set')
|
||||
|
||||
def _ui_show_achievements(self) -> None:
|
||||
if bs.app.classic is not None:
|
||||
bs.app.classic.show_online_score_ui('achievements')
|
||||
if bs.app.plus is not None:
|
||||
bs.app.plus.show_game_service_ui('achievements')
|
||||
else:
|
||||
logging.warning('show_online_score_ui requires classic')
|
||||
logging.warning('show_game_service_ui requires plus feature-set')
|
||||
|
||||
def _ui_worlds_best(self) -> None:
|
||||
if self._score_link is None:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class ControlsGuide(bs.Actor):
|
|||
delay: is the time in seconds before the overlay fades in.
|
||||
|
||||
lifespan: if not None, the overlay will fade back out and die after
|
||||
that long (in milliseconds).
|
||||
that long (in seconds).
|
||||
|
||||
bright: if True, brighter colors will be used; handy when showing
|
||||
over gameplay but may be too bright for join-screens, etc.
|
||||
|
|
@ -50,6 +50,7 @@ class ControlsGuide(bs.Actor):
|
|||
offs5 = 43.0 * scale
|
||||
ouya = False
|
||||
maxw = 50
|
||||
xtweak = -2.8 * scale
|
||||
self._lifespan = lifespan
|
||||
self._dead = False
|
||||
self._bright = bright
|
||||
|
|
@ -117,7 +118,7 @@ class ControlsGuide(bs.Actor):
|
|||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
|
@ -145,7 +146,7 @@ class ControlsGuide(bs.Actor):
|
|||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
|
@ -173,7 +174,7 @@ class ControlsGuide(bs.Actor):
|
|||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
|
@ -201,7 +202,7 @@ class ControlsGuide(bs.Actor):
|
|||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
|
@ -264,10 +265,19 @@ class ControlsGuide(bs.Actor):
|
|||
bs.timer(delay, bs.WeakCall(self._start_updating))
|
||||
|
||||
@staticmethod
|
||||
def _meaningful_button_name(device: bs.InputDevice, button: int) -> str:
|
||||
def _meaningful_button_name(
|
||||
device: bs.InputDevice, button_name: str
|
||||
) -> str:
|
||||
"""Return a flattened string button name; empty for non-meaningful."""
|
||||
if not device.has_meaningful_button_names:
|
||||
return ''
|
||||
assert bs.app.classic is not None
|
||||
button = bs.app.classic.get_input_device_mapped_value(
|
||||
device, button_name
|
||||
)
|
||||
# -1 means unset; let's show that.
|
||||
if button == -1:
|
||||
return bs.Lstr(resource='configGamepadWindow.unsetText').evaluate()
|
||||
return device.get_button_name(button).evaluate()
|
||||
|
||||
def _start_updating(self) -> None:
|
||||
|
|
@ -289,10 +299,10 @@ class ControlsGuide(bs.Actor):
|
|||
def _check_fade_in(self) -> None:
|
||||
assert bs.app.classic is not None
|
||||
|
||||
# If we have a touchscreen, we only fade in if we have a player with
|
||||
# an input device that is *not* the touchscreen.
|
||||
# (otherwise it is confusing to see the touchscreen buttons right
|
||||
# next to our display buttons)
|
||||
# If we have a touchscreen, we only fade in if we have a player
|
||||
# with an input device that is *not* the touchscreen. Otherwise
|
||||
# it is confusing to see the touchscreen buttons right next to
|
||||
# our display buttons.
|
||||
touchscreen: bs.InputDevice | None = bs.getinputdevice(
|
||||
'TouchScreen', '#1', doraise=False
|
||||
)
|
||||
|
|
@ -318,15 +328,7 @@ class ControlsGuide(bs.Actor):
|
|||
'buttonBomb',
|
||||
'buttonPickUp',
|
||||
):
|
||||
if (
|
||||
self._meaningful_button_name(
|
||||
device,
|
||||
bs.app.classic.get_input_device_mapped_value(
|
||||
device, name
|
||||
),
|
||||
)
|
||||
!= ''
|
||||
):
|
||||
if self._meaningful_button_name(device, name) != '':
|
||||
fade_in = True
|
||||
break
|
||||
if fade_in:
|
||||
|
|
@ -401,58 +403,30 @@ class ControlsGuide(bs.Actor):
|
|||
# We only care about movement buttons in the case of keyboards.
|
||||
if all_keyboards:
|
||||
right_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonRight'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonRight')
|
||||
)
|
||||
left_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonLeft'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonLeft')
|
||||
)
|
||||
down_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonDown'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonDown')
|
||||
)
|
||||
up_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonUp'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonUp')
|
||||
)
|
||||
|
||||
# Ignore empty values; things like the remote app or
|
||||
# wiimotes can return these.
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonPunch'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonPunch')
|
||||
if bname != '':
|
||||
punch_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonJump'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonJump')
|
||||
if bname != '':
|
||||
jump_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonBomb'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonBomb')
|
||||
if bname != '':
|
||||
bomb_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonPickUp'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonPickUp')
|
||||
if bname != '':
|
||||
pickup_button_names.add(bname)
|
||||
|
||||
|
|
@ -582,8 +556,8 @@ class ControlsGuide(bs.Actor):
|
|||
if msg.immediate:
|
||||
self._die()
|
||||
else:
|
||||
# If they don't need immediate,
|
||||
# fade out our nodes and die later.
|
||||
# If they don't need immediate, fade out our nodes and
|
||||
# die later.
|
||||
for node in self._nodes:
|
||||
bs.animate(node, 'opacity', {0: node.opacity, 3.0: 0.0})
|
||||
bs.timer(3.1, bs.WeakCall(self._die))
|
||||
|
|
|
|||
|
|
@ -41,17 +41,17 @@ class Spawner:
|
|||
self,
|
||||
spawner: Spawner,
|
||||
data: Any,
|
||||
pt: Sequence[float], # pylint: disable=invalid-name
|
||||
pt: Sequence[float],
|
||||
):
|
||||
"""Instantiate with the given values."""
|
||||
self.spawner = spawner
|
||||
self.data = data
|
||||
self.pt = pt # pylint: disable=invalid-name
|
||||
self.pt = pt
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: Any = None,
|
||||
pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name
|
||||
pt: Sequence[float] = (0, 0, 0),
|
||||
spawn_time: float = 1.0,
|
||||
send_spawn_message: bool = True,
|
||||
spawn_callback: Callable[[], Any] | None = None,
|
||||
|
|
|
|||
|
|
@ -624,7 +624,7 @@ class Spaz(bs.Actor):
|
|||
1000.0 * (tval + self.curse_time)
|
||||
)
|
||||
self._curse_timer = bs.Timer(
|
||||
5.0, bs.WeakCall(self.curse_explode)
|
||||
5.0, bs.WeakCall(self.handlemessage, CurseExplodeMessage())
|
||||
)
|
||||
|
||||
def equip_boxing_gloves(self) -> None:
|
||||
|
|
@ -1136,7 +1136,7 @@ class Spaz(bs.Actor):
|
|||
if self.hitpoints > 0:
|
||||
# It's kinda crappy to die from impacts, so lets reduce
|
||||
# impact damage by a reasonable amount *if* it'll keep us alive.
|
||||
if msg.hit_type == 'impact' and damage > self.hitpoints:
|
||||
if msg.hit_type == 'impact' and damage >= self.hitpoints:
|
||||
# Drop damage to whatever puts us at 10 hit points,
|
||||
# or 200 less than it used to be whichever is greater
|
||||
# (so it *can* still kill us if its high enough).
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ def register_appearances() -> None:
|
|||
"""Register our builtin spaz appearances."""
|
||||
|
||||
# This is quite ugly but will be going away so not worth cleaning up.
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
|
|
|
|||
|
|
@ -14,13 +14,12 @@ from bascenev1lib.actor.flag import Flag
|
|||
from bascenev1lib.actor.scoreboard import Scoreboard
|
||||
from bascenev1lib.actor.playerspaz import PlayerSpaz
|
||||
from bascenev1lib.gameutils import SharedObjects
|
||||
from bascenev1lib.actor.respawnicon import RespawnIcon
|
||||
import bascenev1 as bs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
from bascenev1lib.actor.respawnicon import RespawnIcon
|
||||
|
||||
|
||||
class ConquestFlag(Flag):
|
||||
"""A custom flag for use with Conquest games."""
|
||||
|
|
@ -49,7 +48,9 @@ class Player(bs.Player['Team']):
|
|||
@property
|
||||
def respawn_timer(self) -> bs.Timer | None:
|
||||
"""Type safe access to standard respawn timer."""
|
||||
return self.customdata.get('respawn_timer', None)
|
||||
val = self.customdata.get('respawn_timer', None)
|
||||
assert isinstance(val, (bs.Timer, type(None)))
|
||||
return val
|
||||
|
||||
@respawn_timer.setter
|
||||
def respawn_timer(self, value: bs.Timer | None) -> None:
|
||||
|
|
@ -58,7 +59,9 @@ class Player(bs.Player['Team']):
|
|||
@property
|
||||
def respawn_icon(self) -> RespawnIcon | None:
|
||||
"""Type safe access to standard respawn icon."""
|
||||
return self.customdata.get('respawn_icon', None)
|
||||
val = self.customdata.get('respawn_icon', None)
|
||||
assert isinstance(val, (RespawnIcon, type(None)))
|
||||
return val
|
||||
|
||||
@respawn_icon.setter
|
||||
def respawn_icon(self, value: RespawnIcon | None) -> None:
|
||||
|
|
|
|||
31
dist/ba_data/python/bascenev1lib/mainmenu.py
vendored
31
dist/ba_data/python/bascenev1lib/mainmenu.py
vendored
|
|
@ -300,7 +300,10 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||
from bauiv1lib import specialoffer
|
||||
|
||||
assert bs.app.classic is not None
|
||||
if bool(False):
|
||||
if bui.app.env.headless:
|
||||
# UI stuff fails now in headless builds; avoid it.
|
||||
pass
|
||||
elif bool(False):
|
||||
uicontroller = bs.app.ui_v1.controller
|
||||
assert uicontroller is not None
|
||||
uicontroller.show_main_menu()
|
||||
|
|
@ -314,7 +317,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||
from bauiv1lib.kiosk import KioskWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
KioskWindow().get_root_widget()
|
||||
KioskWindow().get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
# ..or in normal cases go back to the main menu
|
||||
else:
|
||||
|
|
@ -323,14 +327,16 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||
from bauiv1lib.gather import GatherWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
GatherWindow(transition=None).get_root_widget()
|
||||
GatherWindow(transition=None).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
elif main_menu_location == 'Watch':
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.watch import WatchWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
WatchWindow(transition=None).get_root_widget()
|
||||
WatchWindow(transition=None).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
elif main_menu_location == 'Team Game Select':
|
||||
# pylint: disable=cyclic-import
|
||||
|
|
@ -341,7 +347,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||
bs.app.ui_v1.set_main_menu_window(
|
||||
PlaylistBrowserWindow(
|
||||
sessiontype=bs.DualTeamSession, transition=None
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
elif main_menu_location == 'Free-for-All Game Select':
|
||||
# pylint: disable=cyclic-import
|
||||
|
|
@ -353,28 +360,34 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||
PlaylistBrowserWindow(
|
||||
sessiontype=bs.FreeForAllSession,
|
||||
transition=None,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
elif main_menu_location == 'Coop Select':
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.coop.browser import CoopBrowserWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
CoopBrowserWindow(transition=None).get_root_widget()
|
||||
CoopBrowserWindow(
|
||||
transition=None
|
||||
).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
elif main_menu_location == 'Benchmarks & Stress Tests':
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.debug import DebugWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
DebugWindow(transition=None).get_root_widget()
|
||||
DebugWindow(transition=None).get_root_widget(),
|
||||
from_window=False, # Disable check here.
|
||||
)
|
||||
else:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
bs.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition=None).get_root_widget()
|
||||
MainMenuWindow(transition=None).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
|
||||
# attempt to show any pending offers immediately.
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
1
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
|
|
@ -8,7 +8,6 @@
|
|||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-lines
|
||||
# pylint: disable=missing-function-docstring, missing-class-docstring
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=unused-variable
|
||||
|
|
|
|||
36
dist/ba_data/python/bauiv1/__init__.py
vendored
36
dist/ba_data/python/bauiv1/__init__.py
vendored
|
|
@ -31,7 +31,10 @@ from babase import (
|
|||
apptimer,
|
||||
AppTimer,
|
||||
Call,
|
||||
can_toggle_fullscreen,
|
||||
fullscreen_control_available,
|
||||
fullscreen_control_get,
|
||||
fullscreen_control_key_shortcut,
|
||||
fullscreen_control_set,
|
||||
charstr,
|
||||
clipboard_is_supported,
|
||||
clipboard_set_text,
|
||||
|
|
@ -57,18 +60,21 @@ from babase import (
|
|||
in_logic_thread,
|
||||
increment_analytics_count,
|
||||
is_browser_likely_available,
|
||||
is_running_on_fire_tv,
|
||||
is_xcode_build,
|
||||
Keyboard,
|
||||
lock_all_input,
|
||||
LoginAdapter,
|
||||
LoginInfo,
|
||||
Lstr,
|
||||
native_review_request,
|
||||
native_review_request_supported,
|
||||
NotFoundError,
|
||||
open_file_externally,
|
||||
Permission,
|
||||
Plugin,
|
||||
PluginSpec,
|
||||
pushcall,
|
||||
quit,
|
||||
QuitType,
|
||||
request_permission,
|
||||
safecolor,
|
||||
screenmessage,
|
||||
|
|
@ -87,7 +93,6 @@ from babase import (
|
|||
|
||||
from _bauiv1 import (
|
||||
buttonwidget,
|
||||
can_show_ad,
|
||||
checkboxwidget,
|
||||
columnwidget,
|
||||
containerwidget,
|
||||
|
|
@ -96,21 +101,15 @@ from _bauiv1 import (
|
|||
getmesh,
|
||||
getsound,
|
||||
gettexture,
|
||||
has_video_ads,
|
||||
have_incentivized_ad,
|
||||
hscrollwidget,
|
||||
imagewidget,
|
||||
is_party_icon_visible,
|
||||
Mesh,
|
||||
open_file_externally,
|
||||
open_url,
|
||||
rowwidget,
|
||||
scrollwidget,
|
||||
set_party_icon_always_visible,
|
||||
set_party_window_open,
|
||||
show_ad,
|
||||
show_ad_2,
|
||||
show_online_score_ui,
|
||||
Sound,
|
||||
Texture,
|
||||
textwidget,
|
||||
|
|
@ -118,6 +117,7 @@ from _bauiv1 import (
|
|||
Widget,
|
||||
widget,
|
||||
)
|
||||
from bauiv1._keyboard import Keyboard
|
||||
from bauiv1._uitypes import Window, uicleanupcheck
|
||||
from bauiv1._subsystem import UIV1Subsystem
|
||||
|
||||
|
|
@ -137,8 +137,10 @@ __all__ = [
|
|||
'AppTimer',
|
||||
'buttonwidget',
|
||||
'Call',
|
||||
'can_show_ad',
|
||||
'can_toggle_fullscreen',
|
||||
'fullscreen_control_available',
|
||||
'fullscreen_control_get',
|
||||
'fullscreen_control_key_shortcut',
|
||||
'fullscreen_control_set',
|
||||
'charstr',
|
||||
'checkboxwidget',
|
||||
'clipboard_is_supported',
|
||||
|
|
@ -168,8 +170,6 @@ __all__ = [
|
|||
'getmesh',
|
||||
'getsound',
|
||||
'gettexture',
|
||||
'has_video_ads',
|
||||
'have_incentivized_ad',
|
||||
'have_permission',
|
||||
'hscrollwidget',
|
||||
'imagewidget',
|
||||
|
|
@ -177,13 +177,15 @@ __all__ = [
|
|||
'increment_analytics_count',
|
||||
'is_browser_likely_available',
|
||||
'is_party_icon_visible',
|
||||
'is_running_on_fire_tv',
|
||||
'is_xcode_build',
|
||||
'Keyboard',
|
||||
'lock_all_input',
|
||||
'LoginAdapter',
|
||||
'LoginInfo',
|
||||
'Lstr',
|
||||
'Mesh',
|
||||
'native_review_request',
|
||||
'native_review_request_supported',
|
||||
'NotFoundError',
|
||||
'open_file_externally',
|
||||
'open_url',
|
||||
|
|
@ -192,6 +194,7 @@ __all__ = [
|
|||
'PluginSpec',
|
||||
'pushcall',
|
||||
'quit',
|
||||
'QuitType',
|
||||
'request_permission',
|
||||
'rowwidget',
|
||||
'safecolor',
|
||||
|
|
@ -202,9 +205,6 @@ __all__ = [
|
|||
'set_party_icon_always_visible',
|
||||
'set_party_window_open',
|
||||
'set_ui_input_device',
|
||||
'show_ad',
|
||||
'show_ad_2',
|
||||
'show_online_score_ui',
|
||||
'Sound',
|
||||
'SpecialChar',
|
||||
'supports_max_fps',
|
||||
|
|
|
|||
23
dist/ba_data/python/bauiv1/_hooks.py
vendored
23
dist/ba_data/python/bauiv1/_hooks.py
vendored
|
|
@ -6,6 +6,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import _bauiv1
|
||||
|
|
@ -13,6 +14,8 @@ import _bauiv1
|
|||
if TYPE_CHECKING:
|
||||
from typing import Sequence
|
||||
|
||||
import babase
|
||||
|
||||
|
||||
def ticket_icon_press() -> None:
|
||||
from babase import app
|
||||
|
|
@ -57,14 +60,14 @@ def party_icon_activate(origin: Sequence[float]) -> None:
|
|||
logging.warning('party_icon_activate: no classic.')
|
||||
|
||||
|
||||
def quit_window() -> None:
|
||||
def quit_window(quit_type: babase.QuitType) -> None:
|
||||
from babase import app
|
||||
|
||||
if app.classic is None:
|
||||
logging.exception('Classic not present.')
|
||||
return
|
||||
|
||||
app.classic.quit_window()
|
||||
app.classic.quit_window(quit_type)
|
||||
|
||||
|
||||
def device_menu_press(device_id: int | None) -> None:
|
||||
|
|
@ -85,3 +88,19 @@ def show_url_window(address: str) -> None:
|
|||
return
|
||||
|
||||
app.classic.show_url_window(address)
|
||||
|
||||
|
||||
def double_transition_out_warning() -> None:
|
||||
"""Called if a widget is set to transition out twice."""
|
||||
caller_frame = inspect.stack()[1]
|
||||
caller_filename = caller_frame.filename
|
||||
caller_line_number = caller_frame.lineno
|
||||
logging.warning(
|
||||
'ContainerWidget was set to transition out twice;'
|
||||
' this often implies buggy code (%s line %s).\n'
|
||||
' Generally you should check the value of'
|
||||
' _root_widget.transitioning_out and perform no actions if that'
|
||||
' is True.',
|
||||
caller_filename,
|
||||
caller_line_number,
|
||||
)
|
||||
|
|
|
|||
33
dist/ba_data/python/bauiv1/_keyboard.py
vendored
Normal file
33
dist/ba_data/python/bauiv1/_keyboard.py
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""On-screen Keyboard related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class Keyboard:
|
||||
"""Chars definitions for on-screen keyboard.
|
||||
|
||||
Category: **App Classes**
|
||||
|
||||
Keyboards are discoverable by the meta-tag system
|
||||
and the user can select which one they want to use.
|
||||
On-screen keyboard uses chars from active babase.Keyboard.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""Displays when user selecting this keyboard."""
|
||||
|
||||
chars: list[tuple[str, ...]]
|
||||
"""Used for row/column lengths."""
|
||||
|
||||
pages: dict[str, tuple[str, ...]]
|
||||
"""Extra chars like emojis."""
|
||||
|
||||
nums: tuple[str, ...]
|
||||
"""The 'num' page."""
|
||||
76
dist/ba_data/python/bauiv1/_subsystem.py
vendored
76
dist/ba_data/python/bauiv1/_subsystem.py
vendored
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
|
|
@ -66,6 +67,16 @@ class UIV1Subsystem(babase.AppSubsystem):
|
|||
# a more elegant way once we revamp high level UI stuff a bit.
|
||||
self.selecting_private_party_playlist: bool = False
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Can uiv1 currently be used?
|
||||
|
||||
Code that may run in headless mode, before the UI has been spun up,
|
||||
while other ui systems are active, etc. can check this to avoid
|
||||
likely erroring.
|
||||
"""
|
||||
return _bauiv1.is_available()
|
||||
|
||||
@property
|
||||
def uiscale(self) -> babase.UIScale:
|
||||
"""Current ui scale for the app."""
|
||||
|
|
@ -106,21 +117,69 @@ class UIV1Subsystem(babase.AppSubsystem):
|
|||
# FIXME: Can probably kill this if we do immediate UI death checks.
|
||||
self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
|
||||
|
||||
def set_main_menu_window(self, window: bauiv1.Widget) -> None:
|
||||
"""Set the current 'main' window, replacing any existing."""
|
||||
def set_main_menu_window(
|
||||
self,
|
||||
window: bauiv1.Widget,
|
||||
from_window: bauiv1.Widget | None | bool = True,
|
||||
) -> None:
|
||||
"""Set the current 'main' window, replacing any existing.
|
||||
|
||||
If 'from_window' is passed as a bauiv1.Widget or None, a warning
|
||||
will be issued if it that value does not match the current main
|
||||
window. This can help clean up flawed code that can lead to bad
|
||||
UI states. A value of False will disable the check.
|
||||
"""
|
||||
|
||||
existing = self._main_menu_window
|
||||
from inspect import currentframe, getframeinfo
|
||||
|
||||
try:
|
||||
if isinstance(from_window, bool):
|
||||
# For default val True we warn that the arg wasn't
|
||||
# passed. False can be explicitly passed to disable this
|
||||
# check.
|
||||
if from_window is True:
|
||||
caller_frame = inspect.stack()[1]
|
||||
caller_filename = caller_frame.filename
|
||||
caller_line_number = caller_frame.lineno
|
||||
logging.warning(
|
||||
'set_main_menu_window() should be passed a'
|
||||
" 'from_window' value to help ensure proper UI behavior"
|
||||
' (%s line %i).',
|
||||
caller_filename,
|
||||
caller_line_number,
|
||||
)
|
||||
else:
|
||||
# For everything else, warn if what they passed wasn't
|
||||
# the previous main menu widget.
|
||||
if from_window is not existing:
|
||||
caller_frame = inspect.stack()[1]
|
||||
caller_filename = caller_frame.filename
|
||||
caller_line_number = caller_frame.lineno
|
||||
logging.warning(
|
||||
"set_main_menu_window() was passed 'from_window' %s"
|
||||
' but existing main-menu-window is %s. (%s line %i).',
|
||||
from_window,
|
||||
existing,
|
||||
caller_filename,
|
||||
caller_line_number,
|
||||
)
|
||||
except Exception:
|
||||
# Prevent any bugs in these checks from causing problems.
|
||||
logging.exception('Error checking from_window')
|
||||
|
||||
# Once the above code leads to us fixing all leftover window bugs
|
||||
# at the source, we can kill the code below.
|
||||
|
||||
# Let's grab the location where we were called from to report
|
||||
# if we have to force-kill the existing window (which normally
|
||||
# should not happen).
|
||||
frameline = None
|
||||
try:
|
||||
frame = currentframe()
|
||||
frame = inspect.currentframe()
|
||||
if frame is not None:
|
||||
frame = frame.f_back
|
||||
if frame is not None:
|
||||
frameinfo = getframeinfo(frame)
|
||||
frameinfo = inspect.getframeinfo(frame)
|
||||
frameline = f'{frameinfo.filename} {frameinfo.lineno}'
|
||||
except Exception:
|
||||
logging.exception('Error calcing line for set_main_menu_window')
|
||||
|
|
@ -150,13 +209,18 @@ class UIV1Subsystem(babase.AppSubsystem):
|
|||
|
||||
def clear_main_menu_window(self, transition: str | None = None) -> None:
|
||||
"""Clear any existing 'main' window with the provided transition."""
|
||||
assert transition is None or not transition.endswith('_in')
|
||||
if self._main_menu_window:
|
||||
if transition is not None:
|
||||
if (
|
||||
transition is not None
|
||||
and not self._main_menu_window.transitioning_out
|
||||
):
|
||||
_bauiv1.containerwidget(
|
||||
edit=self._main_menu_window, transition=transition
|
||||
)
|
||||
else:
|
||||
self._main_menu_window.delete()
|
||||
self._main_menu_window = None
|
||||
|
||||
def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
|
||||
"""(internal)"""
|
||||
|
|
|
|||
30
dist/ba_data/python/bauiv1/onscreenkeyboard.py
vendored
30
dist/ba_data/python/bauiv1/onscreenkeyboard.py
vendored
|
|
@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
|
|||
import babase
|
||||
|
||||
import _bauiv1
|
||||
from bauiv1._keyboard import Keyboard
|
||||
from bauiv1._uitypes import Window
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -51,6 +52,19 @@ class OnScreenKeyboardWindow(Window):
|
|||
else (0, 0),
|
||||
)
|
||||
)
|
||||
self._cancel_button = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
scale=0.5,
|
||||
position=(30, self._height - 55),
|
||||
size=(60, 60),
|
||||
label='',
|
||||
enable_sound=False,
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
color=(0.55, 0.5, 0.6),
|
||||
icon=_bauiv1.gettexture('crossOut'),
|
||||
iconscale=1.2,
|
||||
)
|
||||
self._done_button = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 200, 44),
|
||||
|
|
@ -240,9 +254,7 @@ class OnScreenKeyboardWindow(Window):
|
|||
# Show change instructions only if we have more than one
|
||||
# keyboard option.
|
||||
keyboards = (
|
||||
babase.app.meta.scanresults.exports_of_class(
|
||||
babase.Keyboard
|
||||
)
|
||||
babase.app.meta.scanresults.exports_of_class(Keyboard)
|
||||
if babase.app.meta.scanresults is not None
|
||||
else []
|
||||
)
|
||||
|
|
@ -274,10 +286,10 @@ class OnScreenKeyboardWindow(Window):
|
|||
|
||||
def _get_keyboard(self) -> bui.Keyboard:
|
||||
assert babase.app.meta.scanresults is not None
|
||||
classname = babase.app.meta.scanresults.exports_of_class(
|
||||
babase.Keyboard
|
||||
)[self._keyboard_index]
|
||||
kbclass = babase.getclass(classname, babase.Keyboard)
|
||||
classname = babase.app.meta.scanresults.exports_of_class(Keyboard)[
|
||||
self._keyboard_index
|
||||
]
|
||||
kbclass = babase.getclass(classname, Keyboard)
|
||||
return kbclass()
|
||||
|
||||
def _refresh(self) -> None:
|
||||
|
|
@ -372,9 +384,7 @@ class OnScreenKeyboardWindow(Window):
|
|||
|
||||
def _next_keyboard(self) -> None:
|
||||
assert babase.app.meta.scanresults is not None
|
||||
kbexports = babase.app.meta.scanresults.exports_of_class(
|
||||
babase.Keyboard
|
||||
)
|
||||
kbexports = babase.app.meta.scanresults.exports_of_class(Keyboard)
|
||||
self._keyboard_index = (self._keyboard_index + 1) % len(kbexports)
|
||||
|
||||
self._load_keyboard()
|
||||
|
|
|
|||
308
dist/ba_data/python/bauiv1lib/account/settings.py
vendored
308
dist/ba_data/python/bauiv1lib/account/settings.py
vendored
|
|
@ -63,20 +63,14 @@ class AccountSettingsWindow(bui.Window):
|
|||
1.0, bui.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
# Currently we can only reset achievements on game-center.
|
||||
v1_account_type: str | None
|
||||
if self._v1_signed_in:
|
||||
v1_account_type = plus.get_v1_account_type()
|
||||
else:
|
||||
v1_account_type = None
|
||||
self._can_reset_achievements = v1_account_type == 'Game Center'
|
||||
self._can_reset_achievements = False
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
uiscale = app.ui_v1.uiscale
|
||||
|
||||
self._width = 760 if uiscale is bui.UIScale.SMALL else 660
|
||||
x_offs = 50 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 860 if uiscale is bui.UIScale.SMALL else 660
|
||||
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
390
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -98,6 +92,9 @@ class AccountSettingsWindow(bui.Window):
|
|||
if LoginType.GPGS in plus.accounts.login_adapters:
|
||||
self._show_sign_in_buttons.append('Google Play')
|
||||
|
||||
if LoginType.GAME_CENTER in plus.accounts.login_adapters:
|
||||
self._show_sign_in_buttons.append('Game Center')
|
||||
|
||||
# Always want to show our web-based v2 login option.
|
||||
self._show_sign_in_buttons.append('V2Proxy')
|
||||
|
||||
|
|
@ -227,6 +224,8 @@ class AccountSettingsWindow(bui.Window):
|
|||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
via_lines: list[str] = []
|
||||
|
||||
primary_v2_account = plus.accounts.primary
|
||||
|
||||
v1_state = plus.get_v1_account_state()
|
||||
|
|
@ -237,14 +236,55 @@ class AccountSettingsWindow(bui.Window):
|
|||
# We expose GPGS-specific functionality only if it is 'active'
|
||||
# (meaning the current GPGS player matches one of our account's
|
||||
# logins).
|
||||
gpgs_adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
|
||||
is_gpgs = (
|
||||
False if gpgs_adapter is None else gpgs_adapter.is_back_end_active()
|
||||
adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
|
||||
gpgs_active = adapter is not None and adapter.is_back_end_active()
|
||||
|
||||
# Ditto for Game Center.
|
||||
adapter = plus.accounts.login_adapters.get(LoginType.GAME_CENTER)
|
||||
game_center_active = (
|
||||
adapter is not None and adapter.is_back_end_active()
|
||||
)
|
||||
|
||||
show_signed_in_as = self._v1_signed_in
|
||||
signed_in_as_space = 95.0
|
||||
|
||||
# To reduce confusion about the whole V2 account situation for
|
||||
# people used to seeing their Google Play Games or Game Center
|
||||
# account name and icon and whatnot, let's show those underneath
|
||||
# the V2 tag to help communicate that they are in fact logged in
|
||||
# through that account.
|
||||
via_space = 25.0
|
||||
if show_signed_in_as and bui.app.plus is not None:
|
||||
accounts = bui.app.plus.accounts
|
||||
if accounts.primary is not None:
|
||||
# For these login types, we show 'via' IF there is a
|
||||
# login of that type attached to our account AND it is
|
||||
# currently active (We don't want to show 'via Game
|
||||
# Center' if we're signed out of Game Center or
|
||||
# currently running on Steam, even if there is a Game
|
||||
# Center login attached to our account).
|
||||
for ltype, lchar in [
|
||||
(LoginType.GPGS, bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO),
|
||||
(LoginType.GAME_CENTER, bui.SpecialChar.GAME_CENTER_LOGO),
|
||||
]:
|
||||
linfo = accounts.primary.logins.get(ltype)
|
||||
ladapter = accounts.login_adapters.get(ltype)
|
||||
if (
|
||||
linfo is not None
|
||||
and ladapter is not None
|
||||
and ladapter.is_back_end_active()
|
||||
):
|
||||
via_lines.append(f'{bui.charstr(lchar)}{linfo.name}')
|
||||
|
||||
# TEMP TESTING
|
||||
if bool(False):
|
||||
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
||||
via_lines.append(f'{icontxt}FloofDibble')
|
||||
icontxt = bui.charstr(
|
||||
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
|
||||
)
|
||||
via_lines.append(f'{icontxt}StinkBobble')
|
||||
|
||||
show_sign_in_benefits = not self._v1_signed_in
|
||||
sign_in_benefits_space = 80.0
|
||||
|
||||
|
|
@ -258,6 +298,11 @@ class AccountSettingsWindow(bui.Window):
|
|||
and self._signing_in_adapter is None
|
||||
and 'Google Play' in self._show_sign_in_buttons
|
||||
)
|
||||
show_game_center_sign_in_button = (
|
||||
v1_state == 'signed_out'
|
||||
and self._signing_in_adapter is None
|
||||
and 'Game Center' in self._show_sign_in_buttons
|
||||
)
|
||||
show_v2_proxy_sign_in_button = (
|
||||
v1_state == 'signed_out'
|
||||
and self._signing_in_adapter is None
|
||||
|
|
@ -271,9 +316,8 @@ class AccountSettingsWindow(bui.Window):
|
|||
sign_in_button_space = 70.0
|
||||
deprecated_space = 60
|
||||
|
||||
show_game_service_button = self._v1_signed_in and v1_account_type in [
|
||||
'Game Center'
|
||||
]
|
||||
# Game Center currently has a single UI for everything.
|
||||
show_game_service_button = game_center_active
|
||||
game_service_button_space = 60.0
|
||||
|
||||
show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2'
|
||||
|
|
@ -281,11 +325,9 @@ class AccountSettingsWindow(bui.Window):
|
|||
show_linked_accounts_text = self._v1_signed_in
|
||||
linked_accounts_text_space = 60.0
|
||||
|
||||
show_achievements_button = self._v1_signed_in and v1_account_type in (
|
||||
'Google Play',
|
||||
'Local',
|
||||
'V2',
|
||||
)
|
||||
# Always show achievements except in the game-center case where
|
||||
# its unified UI covers them.
|
||||
show_achievements_button = self._v1_signed_in and not game_center_active
|
||||
achievements_button_space = 60.0
|
||||
|
||||
show_achievements_text = (
|
||||
|
|
@ -293,7 +335,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
)
|
||||
achievements_text_space = 27.0
|
||||
|
||||
show_leaderboards_button = self._v1_signed_in and is_gpgs
|
||||
show_leaderboards_button = self._v1_signed_in and gpgs_active
|
||||
leaderboards_button_space = 60.0
|
||||
|
||||
show_campaign_progress = self._v1_signed_in
|
||||
|
|
@ -330,7 +372,6 @@ class AccountSettingsWindow(bui.Window):
|
|||
|
||||
show_sign_out_button = self._v1_signed_in and v1_account_type in [
|
||||
'Local',
|
||||
'Google Play',
|
||||
'V2',
|
||||
]
|
||||
sign_out_button_space = 70.0
|
||||
|
|
@ -349,10 +390,13 @@ class AccountSettingsWindow(bui.Window):
|
|||
self._sub_height = 60.0
|
||||
if show_signed_in_as:
|
||||
self._sub_height += signed_in_as_space
|
||||
self._sub_height += via_space * len(via_lines)
|
||||
if show_signing_in_text:
|
||||
self._sub_height += signing_in_text_space
|
||||
if show_google_play_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_game_center_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_v2_proxy_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_device_sign_in_button:
|
||||
|
|
@ -442,20 +486,21 @@ class AccountSettingsWindow(bui.Window):
|
|||
self._account_name_what_is_text = bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(0.0, self._account_name_what_is_y),
|
||||
size=(200.0, 60),
|
||||
size=(220.0, 60),
|
||||
text=bui.Lstr(
|
||||
value='${WHAT} -->',
|
||||
subs=[('${WHAT}', bui.Lstr(resource='whatIsThisText'))],
|
||||
),
|
||||
scale=0.6,
|
||||
color=(0.3, 0.7, 0.05),
|
||||
maxwidth=200.0,
|
||||
maxwidth=130.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
autoselect=True,
|
||||
selectable=True,
|
||||
on_activate_call=show_what_is_v2_page,
|
||||
click_activate=True,
|
||||
glow_type='uniform',
|
||||
)
|
||||
if first_selectable is None:
|
||||
first_selectable = self._account_name_what_is_text
|
||||
|
|
@ -466,6 +511,54 @@ class AccountSettingsWindow(bui.Window):
|
|||
|
||||
v -= signed_in_as_space * 0.4
|
||||
|
||||
for via in via_lines:
|
||||
v -= via_space * 0.1
|
||||
sscale = 0.7
|
||||
swidth = (
|
||||
bui.get_string_width(via, suppress_warning=True) * sscale
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v),
|
||||
size=(0, 0),
|
||||
text=via,
|
||||
scale=sscale,
|
||||
color=(0.6, 0.6, 0.6),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 - swidth * 0.5 - 5, v),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
value='(${VIA}',
|
||||
subs=[('${VIA}', bui.Lstr(resource='viaText'))],
|
||||
),
|
||||
scale=0.5,
|
||||
color=(0.4, 0.6, 0.4, 0.5),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 + swidth * 0.5 + 10, v),
|
||||
size=(0, 0),
|
||||
text=')',
|
||||
scale=0.5,
|
||||
color=(0.4, 0.6, 0.4, 0.5),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= via_space * 0.9
|
||||
|
||||
else:
|
||||
self._account_name_text = None
|
||||
self._account_name_what_is_text = None
|
||||
|
|
@ -477,22 +570,6 @@ class AccountSettingsWindow(bui.Window):
|
|||
|
||||
if show_sign_in_benefits:
|
||||
v -= sign_in_benefits_space
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
extra: str | bui.Lstr | None
|
||||
if (
|
||||
app.classic.platform in ['mac', 'ios']
|
||||
and app.classic.subplatform == 'appstore'
|
||||
):
|
||||
extra = bui.Lstr(
|
||||
value='\n${S}',
|
||||
subs=[
|
||||
('${S}', bui.Lstr(resource='signInWithGameCenterText'))
|
||||
],
|
||||
)
|
||||
else:
|
||||
extra = ''
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(
|
||||
|
|
@ -500,16 +577,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v + sign_in_benefits_space * 0.4,
|
||||
),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
value='${A}${B}',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
bui.Lstr(resource=self._r + '.signInInfoText'),
|
||||
),
|
||||
('${B}', extra),
|
||||
],
|
||||
),
|
||||
text=bui.Lstr(resource=self._r + '.signInInfoText'),
|
||||
max_height=sign_in_benefits_space * 0.9,
|
||||
scale=0.9,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
|
|
@ -554,7 +622,13 @@ class AccountSettingsWindow(bui.Window):
|
|||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithGooglePlayText'
|
||||
resource=self._r + '.signInWithText',
|
||||
subs=[
|
||||
(
|
||||
'${SERVICE}',
|
||||
bui.Lstr(resource='googlePlayText'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -572,6 +646,48 @@ class AccountSettingsWindow(bui.Window):
|
|||
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
||||
self._sign_in_text = None
|
||||
|
||||
if show_game_center_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
self._sign_in_google_play_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
# Note: Apparently Game Center is just called 'Game Center'
|
||||
# in all languages. Can revisit if not true.
|
||||
# https://developer.apple.com/forums/thread/725779
|
||||
label=bui.Lstr(
|
||||
value='${A}${B}',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO),
|
||||
),
|
||||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithText',
|
||||
subs=[('${SERVICE}', 'Game Center')],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
on_activate_call=lambda: self._sign_in_press(
|
||||
LoginType.GAME_CENTER
|
||||
),
|
||||
)
|
||||
if first_selectable is None:
|
||||
first_selectable = btn
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
edit=btn,
|
||||
right_widget=bui.get_special_widget('party_button'),
|
||||
)
|
||||
bui.widget(edit=btn, left_widget=bbtn)
|
||||
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
||||
self._sign_in_text = None
|
||||
|
||||
if show_v2_proxy_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
|
|
@ -704,7 +820,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
position=((self._sub_width - button_width) * 0.5, v + 30),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=self._r + '.manageAccountText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.manageAccountText'),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
icon=bui.gettexture('settingsIcon'),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
|
|
@ -745,10 +861,15 @@ class AccountSettingsWindow(bui.Window):
|
|||
# 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
|
||||
v1_account_type = plus.get_v1_account_type()
|
||||
if v1_account_type == 'Game Center':
|
||||
v1_account_type_name = bui.Lstr(resource='gameCenterText')
|
||||
v -= game_service_button_space * 0.6
|
||||
if game_center_active:
|
||||
# Note: Apparently Game Center is just called 'Game Center'
|
||||
# in all languages. Can revisit if not true.
|
||||
# https://developer.apple.com/forums/thread/725779
|
||||
game_service_button_label = bui.Lstr(
|
||||
value=bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
||||
+ 'Game Center'
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"unknown account type: '" + str(v1_account_type) + "'"
|
||||
|
|
@ -761,7 +882,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
on_activate_call=self._on_game_service_button_press,
|
||||
size=(button_width, 50),
|
||||
label=v1_account_type_name,
|
||||
label=game_service_button_label,
|
||||
)
|
||||
if first_selectable is None:
|
||||
first_selectable = btn
|
||||
|
|
@ -771,7 +892,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
right_widget=bui.get_special_widget('party_button'),
|
||||
)
|
||||
bui.widget(edit=btn, left_widget=bbtn)
|
||||
v -= game_service_button_space * 0.15
|
||||
v -= game_service_button_space * 0.4
|
||||
else:
|
||||
self.game_service_button = None
|
||||
|
||||
|
|
@ -804,13 +925,15 @@ class AccountSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
icon=bui.gettexture(
|
||||
'googlePlayAchievementsIcon'
|
||||
if is_gpgs
|
||||
if gpgs_active
|
||||
else 'achievementsIcon'
|
||||
),
|
||||
icon_color=(0.8, 0.95, 0.7) if is_gpgs else (0.85, 0.8, 0.9),
|
||||
icon_color=(0.8, 0.95, 0.7)
|
||||
if gpgs_active
|
||||
else (0.85, 0.8, 0.9),
|
||||
on_activate_call=(
|
||||
self._on_custom_achievements_press
|
||||
if is_gpgs
|
||||
if gpgs_active
|
||||
else self._on_achievements_press
|
||||
),
|
||||
size=(button_width, 50),
|
||||
|
|
@ -1135,19 +1258,21 @@ class AccountSettingsWindow(bui.Window):
|
|||
self._needs_refresh = False
|
||||
|
||||
def _on_game_service_button_press(self) -> None:
|
||||
if bui.app.classic is not None:
|
||||
bui.app.classic.show_online_score_ui()
|
||||
if bui.app.plus is not None:
|
||||
bui.app.plus.show_game_service_ui()
|
||||
else:
|
||||
logging.warning('game service ui not available without classic.')
|
||||
logging.warning(
|
||||
'game-service-ui not available without plus feature-set.'
|
||||
)
|
||||
|
||||
def _on_custom_achievements_press(self) -> None:
|
||||
if bui.app.classic is not None:
|
||||
if bui.app.plus is not None:
|
||||
bui.apptimer(
|
||||
0.15,
|
||||
bui.Call(bui.app.classic.show_online_score_ui, 'achievements'),
|
||||
bui.Call(bui.app.plus.show_game_service_ui, 'achievements'),
|
||||
)
|
||||
else:
|
||||
logging.warning('show_online_score_ui requires classic')
|
||||
logging.warning('show_game_service_ui requires plus feature-set.')
|
||||
|
||||
def _on_achievements_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
|
|
@ -1162,11 +1287,21 @@ class AccountSettingsWindow(bui.Window):
|
|||
show_what_is_v2_page()
|
||||
|
||||
def _on_manage_account_press(self) -> None:
|
||||
bui.screenmessage(bui.Lstr(resource='oneMomentText'))
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# Preemptively fail if it looks like we won't be able to talk to
|
||||
# the server anyway.
|
||||
if not plus.cloud.connected:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.unavailableNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
bui.screenmessage(bui.Lstr(resource='oneMomentText'))
|
||||
|
||||
# We expect to have a v2 account signed in if we get here.
|
||||
if plus.accounts.primary is None:
|
||||
logging.exception(
|
||||
|
|
@ -1184,6 +1319,9 @@ class AccountSettingsWindow(bui.Window):
|
|||
self, response: bacommon.cloud.ManageAccountResponse | Exception
|
||||
) -> None:
|
||||
if isinstance(response, Exception) or response.url is None:
|
||||
logging.warning(
|
||||
'Got error in manage-account-response: %s.', response
|
||||
)
|
||||
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
@ -1191,13 +1329,13 @@ class AccountSettingsWindow(bui.Window):
|
|||
bui.open_url(response.url)
|
||||
|
||||
def _on_leaderboards_press(self) -> None:
|
||||
if bui.app.classic is not None:
|
||||
if bui.app.plus is not None:
|
||||
bui.apptimer(
|
||||
0.15,
|
||||
bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'),
|
||||
bui.Call(bui.app.plus.show_game_service_ui, 'leaderboards'),
|
||||
)
|
||||
else:
|
||||
logging.warning('show_online_score_ui requires classic')
|
||||
logging.warning('show_game_service_ui requires classic')
|
||||
|
||||
def _have_unlinkable_v1_accounts(self) -> bool:
|
||||
plus = bui.app.plus
|
||||
|
|
@ -1323,7 +1461,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
swidth = bui.get_string_width(name_str, suppress_warning=True)
|
||||
# Eww; number-fudging. Need to recalibrate this if
|
||||
# account name scaling changes.
|
||||
x = self._sub_width * 0.5 - swidth * 0.75 - 170
|
||||
x = self._sub_width * 0.5 - swidth * 0.75 - 190
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._account_name_what_is_text,
|
||||
|
|
@ -1371,9 +1509,18 @@ class AccountSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ProfileBrowserWindow(origin_widget=self._player_profiles_button)
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ProfileBrowserWindow(
|
||||
origin_widget=self._player_profiles_button
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _cancel_sign_in_press(self) -> None:
|
||||
# If we're waiting on an adapter to give us credentials, abort.
|
||||
|
|
@ -1466,7 +1613,11 @@ class AccountSettingsWindow(bui.Window):
|
|||
if isinstance(result, Exception):
|
||||
# For now just make a bit of noise if anything went wrong;
|
||||
# can get more specific as needed later.
|
||||
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
logging.warning('Got error in v2 sign-in result: %s', result)
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.signInNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
else:
|
||||
# Success! Plug in these credentials which will begin
|
||||
|
|
@ -1530,6 +1681,10 @@ class AccountSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
|
|
@ -1538,7 +1693,8 @@ class AccountSettingsWindow(bui.Window):
|
|||
if not self._modal:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
|
|||
26
dist/ba_data/python/bauiv1lib/account/v2proxy.py
vendored
26
dist/ba_data/python/bauiv1lib/account/v2proxy.py
vendored
|
|
@ -62,14 +62,11 @@ class V2ProxySignInWindow(bui.Window):
|
|||
label=bui.Lstr(resource='cancelText'),
|
||||
on_activate_call=self._done,
|
||||
autoselect=True,
|
||||
color=(0.55, 0.5, 0.6),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
)
|
||||
|
||||
if bool(False):
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._cancel_button
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._cancel_button
|
||||
)
|
||||
|
||||
self._update_timer: bui.AppTimer | None = None
|
||||
|
||||
|
|
@ -131,14 +128,17 @@ class V2ProxySignInWindow(bui.Window):
|
|||
else:
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 145),
|
||||
size=(0, 0),
|
||||
position=(self._width * 0.5 - 200, self._height - 180),
|
||||
size=(button_width - 50, 50),
|
||||
text=bui.Lstr(value=address_pretty),
|
||||
flatness=1.0,
|
||||
maxwidth=self._width,
|
||||
scale=0.75,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
autoselect=True,
|
||||
on_activate_call=bui.Call(self._copy_link, address_pretty),
|
||||
selectable=True,
|
||||
)
|
||||
qroffs = 20.0
|
||||
|
||||
|
|
@ -231,5 +231,15 @@ class V2ProxySignInWindow(bui.Window):
|
|||
# We could do something smart like retry on exceptions here, but
|
||||
# this isn't critical so we'll just let anything slide.
|
||||
|
||||
def _copy_link(self, link: str) -> None:
|
||||
if bui.clipboard_is_supported():
|
||||
bui.clipboard_set_text(link)
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='copyConfirmText'), color=(0, 1, 0)
|
||||
)
|
||||
|
||||
def _done(self) -> None:
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
|
|
|||
8
dist/ba_data/python/bauiv1lib/config.py
vendored
8
dist/ba_data/python/bauiv1lib/config.py
vendored
|
|
@ -93,6 +93,7 @@ class ConfigNumberEdit:
|
|||
displayname: str | bui.Lstr | None = None,
|
||||
changesound: bool = True,
|
||||
textscale: float = 1.0,
|
||||
as_percent: bool = False,
|
||||
):
|
||||
if displayname is None:
|
||||
displayname = configkey
|
||||
|
|
@ -103,6 +104,7 @@ class ConfigNumberEdit:
|
|||
self._increment = increment
|
||||
self._callback = callback
|
||||
self._value = bui.app.config.resolve(configkey)
|
||||
self._as_percent = as_percent
|
||||
|
||||
self.nametext = bui.textwidget(
|
||||
parent=parent,
|
||||
|
|
@ -166,4 +168,8 @@ class ConfigNumberEdit:
|
|||
bui.app.config.apply_and_commit()
|
||||
|
||||
def _update_display(self) -> None:
|
||||
bui.textwidget(edit=self.valuetext, text=f'{self._value:.1f}')
|
||||
if self._as_percent:
|
||||
val = f'{round(self._value*100.0)}%'
|
||||
else:
|
||||
val = f'{self._value:.1f}'
|
||||
bui.textwidget(edit=self.valuetext, text=val)
|
||||
|
|
|
|||
31
dist/ba_data/python/bauiv1lib/confirm.py
vendored
31
dist/ba_data/python/bauiv1lib/confirm.py
vendored
|
|
@ -153,15 +153,15 @@ class QuitWindow:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
quit_type: bui.QuitType | None = None,
|
||||
swish: bool = False,
|
||||
back: bool = False,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
ui = bui.app.ui_v1
|
||||
app = bui.app
|
||||
self._back = back
|
||||
self._quit_type = quit_type
|
||||
|
||||
# If there's already one of us up somewhere, kill it.
|
||||
if ui.quit_window is not None:
|
||||
|
|
@ -187,29 +187,8 @@ class QuitWindow:
|
|||
resource=quit_resource,
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
),
|
||||
self._fade_and_quit,
|
||||
lambda: bui.quit(confirm=False, quit_type=self._quit_type)
|
||||
if self._quit_type is not None
|
||||
else bui.quit(confirm=False),
|
||||
origin_widget=origin_widget,
|
||||
).root_widget
|
||||
|
||||
def _fade_and_quit(self) -> None:
|
||||
bui.fade_screen(
|
||||
False,
|
||||
time=0.2,
|
||||
endcall=lambda: bui.quit(soft=True, back=self._back),
|
||||
)
|
||||
|
||||
# Prevent the user from doing anything else while we're on our
|
||||
# way out.
|
||||
bui.lock_all_input()
|
||||
|
||||
# On systems supporting soft-quit, unlock and fade back in shortly
|
||||
# (soft-quit basically just backgrounds/hides the app).
|
||||
if bui.app.env.supports_soft_quit:
|
||||
# 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 after).
|
||||
def _come_back() -> None:
|
||||
bui.unlock_all_input()
|
||||
bui.fade_screen(True)
|
||||
|
||||
bui.apptimer(0.5, _come_back)
|
||||
|
|
|
|||
29
dist/ba_data/python/bauiv1lib/coop/browser.py
vendored
29
dist/ba_data/python/bauiv1lib/coop/browser.py
vendored
|
|
@ -85,8 +85,8 @@ class CoopBrowserWindow(bui.Window):
|
|||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 1320 if uiscale is bui.UIScale.SMALL else 1120
|
||||
self._x_inset = x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 1520 if uiscale is bui.UIScale.SMALL else 1120
|
||||
self._x_inset = x_inset = 200 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
657
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -415,7 +415,7 @@ class CoopBrowserWindow(bui.Window):
|
|||
)
|
||||
|
||||
# Decrement time on our tournament buttons.
|
||||
ads_enabled = bui.have_incentivized_ad()
|
||||
ads_enabled = plus.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:
|
||||
|
|
@ -430,7 +430,7 @@ class CoopBrowserWindow(bui.Window):
|
|||
)
|
||||
|
||||
# Also adjust the ad icon visibility.
|
||||
if tbtn.allow_ads and bui.has_video_ads():
|
||||
if tbtn.allow_ads and plus.has_video_ads():
|
||||
bui.imagewidget(
|
||||
edit=tbtn.entry_fee_ad_image,
|
||||
opacity=1.0 if ads_enabled else 0.25,
|
||||
|
|
@ -1019,6 +1019,10 @@ class CoopBrowserWindow(bui.Window):
|
|||
from bauiv1lib.account import show_sign_in_prompt
|
||||
from bauiv1lib.league.rankwindow import LeagueRankWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -1032,7 +1036,8 @@ class CoopBrowserWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
LeagueRankWindow(
|
||||
origin_widget=self._league_rank_button.get_button()
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _switch_to_score(
|
||||
|
|
@ -1043,6 +1048,10 @@ class CoopBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.account import show_sign_in_prompt
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -1058,7 +1067,8 @@ class CoopBrowserWindow(bui.Window):
|
|||
origin_widget=self._store_button.get_button(),
|
||||
show_tab=show_tab,
|
||||
back_location='CoopBrowserWindow',
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def is_tourney_data_up_to_date(self) -> bool:
|
||||
|
|
@ -1218,6 +1228,10 @@ class CoopBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# If something is selected, store it.
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
|
|
@ -1225,7 +1239,8 @@ class CoopBrowserWindow(bui.Window):
|
|||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlayWindow(transition='in_left').get_root_widget()
|
||||
PlayWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
|
|||
|
|
@ -638,8 +638,8 @@ class TournamentButton:
|
|||
|
||||
# Now, if this fee allows ads and we support video ads, show
|
||||
# the 'or ad' version.
|
||||
if allow_ads and bui.has_video_ads():
|
||||
ads_enabled = bui.have_incentivized_ad()
|
||||
if allow_ads and plus.has_video_ads():
|
||||
ads_enabled = plus.have_incentivized_ad()
|
||||
bui.imagewidget(
|
||||
edit=self.entry_fee_ad_image,
|
||||
opacity=1.0 if ads_enabled else 0.25,
|
||||
|
|
|
|||
7
dist/ba_data/python/bauiv1lib/creditslist.py
vendored
7
dist/ba_data/python/bauiv1lib/creditslist.py
vendored
|
|
@ -359,10 +359,15 @@ class CreditsListWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
7
dist/ba_data/python/bauiv1lib/debug.py
vendored
7
dist/ba_data/python/bauiv1lib/debug.py
vendored
|
|
@ -379,8 +379,13 @@ class DebugWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
133
dist/ba_data/python/bauiv1lib/discord.py
vendored
Normal file
133
dist/ba_data/python/bauiv1lib/discord.py
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for the Discord window."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
class DiscordWindow(bui.Window):
|
||||
"""Window for joining the Discord."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
if bui.app.classic is None:
|
||||
raise RuntimeError('This requires classic support.')
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 800
|
||||
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = 320
|
||||
top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
1.6
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.3
|
||||
if uiscale is bui.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._do_back
|
||||
)
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(53 + x_inset, self._height - 60),
|
||||
size=(140, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
# Do we need to translate 'Discord'? Or is that always the name?
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, self._height - 52),
|
||||
size=(self._width, 25),
|
||||
text='Discord',
|
||||
color=app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
min_size = min(self._width - 25, self._height - 25)
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, -15),
|
||||
size=(min_size, min_size),
|
||||
texture=bui.gettexture('discordServer'),
|
||||
)
|
||||
|
||||
# Hmm should we translate this? The discord server is mostly
|
||||
# English so being able to read this might be a good screening
|
||||
# process?..
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width / 2 - 60, self._height - 100),
|
||||
text='We have our own Discord server where you can:\n- Find new'
|
||||
' friends and people to play with\n- Participate in Office'
|
||||
' Hours/Coffee with Eric\n- Share mods, plugins, art, and'
|
||||
' memes\n- Report bugs and make feature suggestions\n'
|
||||
'- Troubleshoot issues',
|
||||
maxwidth=(self._width - 10) / 2,
|
||||
color=(1, 1, 1, 1),
|
||||
h_align='left',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width / 2 - 30, 20),
|
||||
size=(self._width / 2 - 60, 60),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='discordJoinText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=bui.Call(
|
||||
bui.open_url, 'https://ballistica.net/discord'
|
||||
),
|
||||
)
|
||||
|
||||
if self._back_button is not None:
|
||||
bui.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
def _do_back(self) -> None:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
18
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
18
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
|
|
@ -94,8 +94,8 @@ class GatherWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_location('Gather')
|
||||
bui.set_party_icon_always_visible(True)
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040
|
||||
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
|
||||
x_offs = 200 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
582
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -270,12 +270,17 @@ class GatherWindow(bui.Window):
|
|||
"""Called by the private-hosting tab to select a playlist."""
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.selecting_private_party_playlist = True
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlayWindow(origin_widget=origin_widget).get_root_widget()
|
||||
PlayWindow(origin_widget=origin_widget).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _set_tab(self, tab_id: TabID) -> None:
|
||||
|
|
@ -383,11 +388,16 @@ class GatherWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
167
dist/ba_data/python/bauiv1lib/gather/abouttab.py
vendored
167
dist/ba_data/python/bauiv1lib/gather/abouttab.py
vendored
|
|
@ -16,10 +16,6 @@ if TYPE_CHECKING:
|
|||
class AboutGatherTab(GatherTab):
|
||||
"""The about tab in the gather UI"""
|
||||
|
||||
def __init__(self, window: GatherWindow) -> None:
|
||||
super().__init__(window)
|
||||
self._container: bui.Widget | None = None
|
||||
|
||||
def on_activate(
|
||||
self,
|
||||
parent_widget: bui.Widget,
|
||||
|
|
@ -29,9 +25,45 @@ class AboutGatherTab(GatherTab):
|
|||
region_left: float,
|
||||
region_bottom: float,
|
||||
) -> bui.Widget:
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
try_tickets = plus.get_v1_account_misc_read_val(
|
||||
'friendTryTickets', None
|
||||
)
|
||||
|
||||
show_message = True
|
||||
# Squish message as needed to get things to fit nicely at
|
||||
# various scales.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
message_height = (
|
||||
210
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 305
|
||||
if uiscale is bui.UIScale.MEDIUM
|
||||
else 370
|
||||
)
|
||||
# Let's not talk about sharing in vr-mode; its tricky to fit more
|
||||
# than one head in a VR-headset.
|
||||
show_message_extra = not bui.app.env.vr
|
||||
message_extra_height = 60
|
||||
show_invite = try_tickets is not None
|
||||
invite_height = 80
|
||||
show_discord = True
|
||||
discord_height = 80
|
||||
|
||||
c_height = 0
|
||||
if show_message:
|
||||
c_height += message_height
|
||||
if show_message_extra:
|
||||
c_height += message_extra_height
|
||||
if show_invite:
|
||||
c_height += invite_height
|
||||
if show_discord:
|
||||
c_height += discord_height
|
||||
|
||||
party_button_label = bui.charstr(bui.SpecialChar.TOP_BUTTON)
|
||||
message = bui.Lstr(
|
||||
resource='gatherWindow.aboutDescriptionText',
|
||||
|
|
@ -41,9 +73,7 @@ class AboutGatherTab(GatherTab):
|
|||
],
|
||||
)
|
||||
|
||||
# Let's not talk about sharing in vr-mode; its tricky to fit more
|
||||
# than one head in a VR-headset ;-)
|
||||
if not bui.app.env.vr:
|
||||
if show_message_extra:
|
||||
message = bui.Lstr(
|
||||
value='${A}\n\n${B}',
|
||||
subs=[
|
||||
|
|
@ -57,47 +87,52 @@ class AboutGatherTab(GatherTab):
|
|||
),
|
||||
],
|
||||
)
|
||||
string_height = 400
|
||||
include_invite = True
|
||||
msc_scale = 1.1
|
||||
c_height_2 = min(region_height, string_height * msc_scale + 100)
|
||||
try_tickets = plus.get_v1_account_misc_read_val(
|
||||
'friendTryTickets', None
|
||||
)
|
||||
if try_tickets is None:
|
||||
include_invite = False
|
||||
self._container = bui.containerwidget(
|
||||
|
||||
scroll_widget = bui.scrollwidget(
|
||||
parent=parent_widget,
|
||||
position=(region_left, region_bottom),
|
||||
size=(region_width, region_height),
|
||||
highlight=False,
|
||||
border_opacity=0,
|
||||
)
|
||||
msc_scale = 1.1
|
||||
|
||||
container = bui.containerwidget(
|
||||
parent=scroll_widget,
|
||||
position=(
|
||||
region_left,
|
||||
region_bottom + (region_height - c_height_2) * 0.5,
|
||||
region_bottom + (region_height - c_height) * 0.5,
|
||||
),
|
||||
size=(region_width, c_height_2),
|
||||
size=(region_width, c_height),
|
||||
background=False,
|
||||
selectable=include_invite,
|
||||
selectable=show_invite or show_discord,
|
||||
)
|
||||
bui.widget(edit=self._container, up_widget=tab_button)
|
||||
# Allows escaping if we select the container somehow (though
|
||||
# shouldn't be possible when buttons are present).
|
||||
bui.widget(edit=container, up_widget=tab_button)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
position=(
|
||||
region_width * 0.5,
|
||||
c_height_2 * (0.58 if include_invite else 0.5),
|
||||
),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=msc_scale,
|
||||
size=(0, 0),
|
||||
maxwidth=region_width * 0.9,
|
||||
max_height=c_height_2 * (0.7 if include_invite else 0.9),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=message,
|
||||
)
|
||||
|
||||
if include_invite:
|
||||
y = c_height - 30
|
||||
if show_message:
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
position=(region_width * 0.57, 35),
|
||||
parent=container,
|
||||
position=(region_width * 0.5, y),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=msc_scale,
|
||||
size=(0, 0),
|
||||
maxwidth=region_width * 0.9,
|
||||
max_height=message_height,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
text=message,
|
||||
)
|
||||
y -= message_height
|
||||
if show_message_extra:
|
||||
y -= message_extra_height
|
||||
|
||||
if show_invite:
|
||||
bui.textwidget(
|
||||
parent=container,
|
||||
position=(region_width * 0.57, y),
|
||||
color=(0, 1, 0),
|
||||
scale=0.6,
|
||||
size=(0, 0),
|
||||
|
|
@ -110,9 +145,9 @@ class AboutGatherTab(GatherTab):
|
|||
subs=[('${COUNT}', str(try_tickets))],
|
||||
),
|
||||
)
|
||||
bui.buttonwidget(
|
||||
parent=self._container,
|
||||
position=(region_width * 0.59, 10),
|
||||
invite_button = bui.buttonwidget(
|
||||
parent=container,
|
||||
position=(region_width * 0.59, y - 25),
|
||||
size=(230, 50),
|
||||
color=(0.54, 0.42, 0.56),
|
||||
textcolor=(0, 1, 0),
|
||||
|
|
@ -124,7 +159,44 @@ class AboutGatherTab(GatherTab):
|
|||
on_activate_call=bui.WeakCall(self._invite_to_try_press),
|
||||
up_widget=tab_button,
|
||||
)
|
||||
return self._container
|
||||
y -= invite_height
|
||||
else:
|
||||
invite_button = None
|
||||
|
||||
if show_discord:
|
||||
bui.textwidget(
|
||||
parent=container,
|
||||
position=(region_width * 0.57, y),
|
||||
color=(0.6, 0.6, 1),
|
||||
scale=0.6,
|
||||
size=(0, 0),
|
||||
maxwidth=region_width * 0.5,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
flatness=1.0,
|
||||
text=bui.Lstr(resource='discordFriendsText'),
|
||||
)
|
||||
discord_button = bui.buttonwidget(
|
||||
parent=container,
|
||||
position=(region_width * 0.59, y - 25),
|
||||
size=(230, 50),
|
||||
color=(0.54, 0.42, 0.56),
|
||||
textcolor=(0.6, 0.6, 1),
|
||||
label=bui.Lstr(resource='discordJoinText'),
|
||||
autoselect=True,
|
||||
on_activate_call=bui.WeakCall(self._join_the_discord_press),
|
||||
up_widget=(
|
||||
invite_button if invite_button is not None else tab_button
|
||||
),
|
||||
)
|
||||
y -= discord_height
|
||||
else:
|
||||
discord_button = None
|
||||
|
||||
if discord_button is not None:
|
||||
pass
|
||||
|
||||
return scroll_widget
|
||||
|
||||
def _invite_to_try_press(self) -> None:
|
||||
from bauiv1lib.account import show_sign_in_prompt
|
||||
|
|
@ -137,3 +209,10 @@ class AboutGatherTab(GatherTab):
|
|||
show_sign_in_prompt()
|
||||
return
|
||||
handle_app_invites_press()
|
||||
|
||||
def _join_the_discord_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.discord import DiscordWindow
|
||||
|
||||
assert bui.app.classic is not None
|
||||
DiscordWindow().get_root_widget()
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class ManualGatherTab(GatherTab):
|
|||
self._party_edit_name_text: bui.Widget | None = None
|
||||
self._party_edit_addr_text: bui.Widget | None = None
|
||||
self._party_edit_port_text: bui.Widget | None = None
|
||||
self._no_parties_added_text: bui.Widget | None = None
|
||||
|
||||
def on_activate(
|
||||
self,
|
||||
|
|
@ -142,6 +143,7 @@ class ManualGatherTab(GatherTab):
|
|||
playsound=True,
|
||||
),
|
||||
text=bui.Lstr(resource='gatherWindow.manualJoinSectionText'),
|
||||
glow_type='uniform',
|
||||
)
|
||||
self._favorites_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
|
|
@ -162,6 +164,7 @@ class ManualGatherTab(GatherTab):
|
|||
playsound=True,
|
||||
),
|
||||
text=bui.Lstr(resource='gatherWindow.favoritesText'),
|
||||
glow_type='uniform',
|
||||
)
|
||||
bui.widget(edit=self._join_by_address_text, up_widget=tab_button)
|
||||
bui.widget(
|
||||
|
|
@ -316,7 +319,7 @@ class ManualGatherTab(GatherTab):
|
|||
self._check_button = bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(250, 60),
|
||||
text=bui.Lstr(resource='gatherWindow.' 'showMyAddressText'),
|
||||
text=bui.Lstr(resource='gatherWindow.showMyAddressText'),
|
||||
v_align='center',
|
||||
h_align='center',
|
||||
click_activate=True,
|
||||
|
|
@ -331,6 +334,7 @@ class ManualGatherTab(GatherTab):
|
|||
self._container,
|
||||
c_width,
|
||||
),
|
||||
glow_type='uniform',
|
||||
)
|
||||
bui.widget(edit=self._check_button, up_widget=btn)
|
||||
|
||||
|
|
@ -453,6 +457,24 @@ class ManualGatherTab(GatherTab):
|
|||
claims_left_right=True,
|
||||
)
|
||||
|
||||
self._no_parties_added_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text='',
|
||||
color=(0.6, 0.6, 0.6),
|
||||
scale=1.2,
|
||||
position=(
|
||||
(
|
||||
(190 if uiscale is bui.UIScale.SMALL else 225)
|
||||
+ sub_scroll_width * 0.5
|
||||
),
|
||||
v + sub_scroll_height * 0.5,
|
||||
),
|
||||
glow_type='uniform',
|
||||
)
|
||||
|
||||
self._favorite_selected = None
|
||||
self._refresh_favorites()
|
||||
|
||||
|
|
@ -695,6 +717,12 @@ class ManualGatherTab(GatherTab):
|
|||
|
||||
assert self._favorites_scroll_width is not None
|
||||
assert self._favorites_connect_button is not None
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._no_parties_added_text,
|
||||
text='',
|
||||
)
|
||||
num_of_fav = 0
|
||||
for i, server in enumerate(servers):
|
||||
txt = bui.textwidget(
|
||||
parent=self._columnwidget,
|
||||
|
|
@ -718,11 +746,13 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
if i == 0:
|
||||
bui.widget(edit=txt, up_widget=self._favorites_text)
|
||||
self._favorite_selected = server
|
||||
bui.widget(
|
||||
edit=txt,
|
||||
left_widget=self._favorites_connect_button,
|
||||
right_widget=txt,
|
||||
)
|
||||
num_of_fav = num_of_fav + 1
|
||||
|
||||
# If there's no servers, allow selecting out of the scroll area
|
||||
bui.containerwidget(
|
||||
|
|
@ -735,6 +765,11 @@ class ManualGatherTab(GatherTab):
|
|||
up_widget=self._favorites_text,
|
||||
left_widget=self._favorites_connect_button,
|
||||
)
|
||||
if num_of_fav == 0:
|
||||
bui.textwidget(
|
||||
edit=self._no_parties_added_text,
|
||||
text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'),
|
||||
)
|
||||
|
||||
def on_deactivate(self) -> None:
|
||||
self._access_check_timer = None
|
||||
|
|
@ -800,8 +835,17 @@ class ManualGatherTab(GatherTab):
|
|||
}
|
||||
config.commit()
|
||||
bui.getsound('gunCocking').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource='addedToFavoritesText', subs=[('${NAME}', addr)]
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
else:
|
||||
bui.screenmessage('Invalid Address', color=(1, 0, 0))
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidAddressErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
||||
def _host_lookup_result(
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ class PrivateGatherTab(GatherTab):
|
|||
playsound=True,
|
||||
),
|
||||
text=bui.Lstr(resource='gatherWindow.privatePartyJoinText'),
|
||||
glow_type='uniform',
|
||||
)
|
||||
self._host_sub_tab_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
|
|
@ -138,6 +139,7 @@ class PrivateGatherTab(GatherTab):
|
|||
playsound=True,
|
||||
),
|
||||
text=bui.Lstr(resource='gatherWindow.privatePartyHostText'),
|
||||
glow_type='uniform',
|
||||
)
|
||||
bui.widget(edit=self._join_sub_tab_text, up_widget=tab_button)
|
||||
bui.widget(
|
||||
|
|
@ -458,9 +460,9 @@ class PrivateGatherTab(GatherTab):
|
|||
scale=1.5,
|
||||
size=(300, 50),
|
||||
editable=True,
|
||||
max_chars=20,
|
||||
description=bui.Lstr(resource='gatherWindow.partyCodeText'),
|
||||
autoselect=True,
|
||||
maxwidth=250,
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
text='',
|
||||
|
|
@ -962,7 +964,7 @@ class PrivateGatherTab(GatherTab):
|
|||
code = cast(str, bui.textwidget(query=self._join_party_code_text))
|
||||
if not code:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidAddressErrorText'),
|
||||
bui.Lstr(translate=('serverResponses', 'Invalid code.')),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class UIRow:
|
|||
self._name_widget = bui.textwidget(
|
||||
text=bui.Lstr(value=party.name),
|
||||
parent=columnwidget,
|
||||
size=(sub_scroll_width * 0.63, 20),
|
||||
size=(sub_scroll_width * 0.46, 20),
|
||||
position=(0 + hpos, 4 + vpos),
|
||||
selectable=True,
|
||||
on_select_call=bui.WeakCall(
|
||||
|
|
@ -248,6 +248,7 @@ class AddrFetchThread(Thread):
|
|||
self._call = call
|
||||
|
||||
def run(self) -> None:
|
||||
sock: socket.socket | None = None
|
||||
try:
|
||||
# FIXME: Update this to work with IPv6 at some point.
|
||||
import socket
|
||||
|
|
@ -255,7 +256,6 @@ class AddrFetchThread(Thread):
|
|||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.connect(('8.8.8.8', 80))
|
||||
val = sock.getsockname()[0]
|
||||
sock.close()
|
||||
bui.pushcall(bui.Call(self._call, val), from_other_thread=True)
|
||||
except Exception as exc:
|
||||
from efro.error import is_udp_communication_error
|
||||
|
|
@ -265,6 +265,9 @@ class AddrFetchThread(Thread):
|
|||
pass
|
||||
else:
|
||||
logging.exception('Error in addr-fetch-thread')
|
||||
finally:
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
|
||||
class PingThread(Thread):
|
||||
|
|
@ -361,6 +364,7 @@ class PublicGatherTab(GatherTab):
|
|||
self._last_server_list_query_time: float | None = None
|
||||
self._join_list_column: bui.Widget | None = None
|
||||
self._join_status_text: bui.Widget | None = None
|
||||
self._no_servers_found_text: bui.Widget | None = None
|
||||
self._host_max_party_size_value: bui.Widget | None = None
|
||||
self._host_max_party_size_minus_button: (bui.Widget | None) = None
|
||||
self._host_max_party_size_plus_button: (bui.Widget | None) = None
|
||||
|
|
@ -431,6 +435,7 @@ class PublicGatherTab(GatherTab):
|
|||
text=bui.Lstr(
|
||||
resource='gatherWindow.' 'joinPublicPartyDescriptionText'
|
||||
),
|
||||
glow_type='uniform',
|
||||
)
|
||||
self._host_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
|
|
@ -453,6 +458,7 @@ class PublicGatherTab(GatherTab):
|
|||
text=bui.Lstr(
|
||||
resource='gatherWindow.' 'hostPublicPartyDescriptionText'
|
||||
),
|
||||
glow_type='uniform',
|
||||
)
|
||||
bui.widget(edit=self._join_text, up_widget=tab_button)
|
||||
bui.widget(
|
||||
|
|
@ -658,6 +664,18 @@ class PublicGatherTab(GatherTab):
|
|||
color=(0.6, 0.6, 0.6),
|
||||
position=(c_width * 0.5, c_height * 0.5),
|
||||
)
|
||||
self._no_servers_found_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
text='',
|
||||
size=(0, 0),
|
||||
scale=0.9,
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
color=(0.6, 0.6, 0.6),
|
||||
position=(c_width * 0.5, c_height * 0.5),
|
||||
)
|
||||
|
||||
def _build_host_tab(
|
||||
self, region_width: float, region_height: float
|
||||
|
|
@ -950,6 +968,9 @@ class PublicGatherTab(GatherTab):
|
|||
self._update_party_rows()
|
||||
|
||||
def _update_party_rows(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
columnwidget = self._join_list_column
|
||||
if not columnwidget:
|
||||
return
|
||||
|
|
@ -963,6 +984,7 @@ class PublicGatherTab(GatherTab):
|
|||
edit=self._host_scrollwidget,
|
||||
claims_up_down=(len(self._parties_displayed) > 0),
|
||||
)
|
||||
bui.textwidget(edit=self._no_servers_found_text, text='')
|
||||
|
||||
# Clip if we have more UI rows than parties to show.
|
||||
clipcount = len(self._ui_rows) - len(self._parties_displayed)
|
||||
|
|
@ -972,6 +994,15 @@ class PublicGatherTab(GatherTab):
|
|||
|
||||
# If we have no parties to show, we're done.
|
||||
if not self._parties_displayed:
|
||||
text = self._join_status_text
|
||||
if (
|
||||
plus.get_v1_account_state() == 'signed_in'
|
||||
and cast(str, bui.textwidget(query=text)) == ''
|
||||
):
|
||||
bui.textwidget(
|
||||
edit=self._no_servers_found_text,
|
||||
text=bui.Lstr(resource='noServersFoundText'),
|
||||
)
|
||||
return
|
||||
|
||||
sub_scroll_width = 830
|
||||
|
|
|
|||
13
dist/ba_data/python/bauiv1lib/getcurrency.py
vendored
13
dist/ba_data/python/bauiv1lib/getcurrency.py
vendored
|
|
@ -334,7 +334,7 @@ class GetCurrencyWindow(bui.Window):
|
|||
tex_scale=1.2,
|
||||
) # 19.99-ish
|
||||
|
||||
self._enable_ad_button = bui.has_video_ads()
|
||||
self._enable_ad_button = plus.has_video_ads()
|
||||
h = self._width * 0.5 + 110.0
|
||||
v = self._height - b_size[1] - 115.0
|
||||
|
||||
|
|
@ -561,7 +561,7 @@ class GetCurrencyWindow(bui.Window):
|
|||
next_reward_ad_time
|
||||
)
|
||||
now = datetime.datetime.utcnow()
|
||||
if bui.have_incentivized_ad() and (
|
||||
if plus.have_incentivized_ad() and (
|
||||
next_reward_ad_time is None or next_reward_ad_time <= now
|
||||
):
|
||||
self._ad_button_greyed = False
|
||||
|
|
@ -732,8 +732,13 @@ class GetCurrencyWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.store import browser
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
if self._transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
|
@ -745,7 +750,9 @@ class GetCurrencyWindow(bui.Window):
|
|||
).get_root_widget()
|
||||
if not self._from_modal_store:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(window)
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
window, from_window=self._root_widget
|
||||
)
|
||||
self._transitioning_out = True
|
||||
|
||||
|
||||
|
|
|
|||
11
dist/ba_data/python/bauiv1lib/helpui.py
vendored
11
dist/ba_data/python/bauiv1lib/helpui.py
vendored
|
|
@ -36,8 +36,8 @@ class HelpWindow(bui.Window):
|
|||
self._main_menu = main_menu
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
width = 950 if uiscale is bui.UIScale.SMALL else 750
|
||||
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
width = 1050 if uiscale is bui.UIScale.SMALL else 750
|
||||
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
|
||||
height = (
|
||||
460
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -645,11 +645,16 @@ class HelpWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
if self._main_menu:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Iterable
|
||||
|
|
@ -33,15 +33,15 @@ def split(chars: Iterable[str], maxlen: int) -> list[list[str]]:
|
|||
|
||||
|
||||
def generate_emojis(maxlen: int) -> list[list[str]]:
|
||||
"""Generates a lot of UTF8 emojis prepared for babase.Keyboard pages"""
|
||||
"""Generates a lot of UTF8 emojis prepared for bui.Keyboard pages"""
|
||||
all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen)
|
||||
all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen)
|
||||
all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen)
|
||||
return all_emojis
|
||||
|
||||
|
||||
# ba_meta export keyboard
|
||||
class EnglishKeyboard(babase.Keyboard):
|
||||
# ba_meta export bauiv1.Keyboard
|
||||
class EnglishKeyboard(bui.Keyboard):
|
||||
"""Default English keyboard."""
|
||||
|
||||
name = 'English'
|
||||
|
|
|
|||
10
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
10
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
|
|
@ -21,7 +21,7 @@ class KioskWindow(bui.Window):
|
|||
self._height = 340.0
|
||||
|
||||
def _do_cancel() -> None:
|
||||
QuitWindow(swish=True, back=True)
|
||||
QuitWindow(swish=True, quit_type=bui.QuitType.BACK)
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
|
|
@ -501,9 +501,15 @@ class KioskWindow(bui.Window):
|
|||
def _do_full_menu(self) -> None:
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
bui.app.classic.did_menu_intro = True # prevent delayed transition-in
|
||||
bui.app.ui_v1.set_main_menu_window(MainMenuWindow().get_root_widget())
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow().get_root_widget(), from_window=self._root_widget
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1142,6 +1142,10 @@ class LeagueRankWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.coop.browser import CoopBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
|
|
@ -1149,5 +1153,6 @@ class LeagueRankWindow(bui.Window):
|
|||
if not self._modal:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
CoopBrowserWindow(transition='in_left').get_root_widget()
|
||||
CoopBrowserWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
91
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
91
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
|
|
@ -190,7 +190,6 @@ class MainMenuWindow(bui.Window):
|
|||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
from bauiv1lib.confirm import QuitWindow
|
||||
from bauiv1lib.store.button import StoreButton
|
||||
|
||||
plus = bui.app.plus
|
||||
|
|
@ -312,8 +311,8 @@ class MainMenuWindow(bui.Window):
|
|||
else self._confirm_end_game
|
||||
),
|
||||
)
|
||||
# Assume we're in a client-session.
|
||||
else:
|
||||
# Assume we're in a client-session.
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h - self._button_width * 0.5 * scale, v),
|
||||
|
|
@ -361,7 +360,6 @@ class MainMenuWindow(bui.Window):
|
|||
tilt_scale=0.0,
|
||||
draw_controller=store_button,
|
||||
)
|
||||
|
||||
self._tdelay += self._t_delay_inc
|
||||
else:
|
||||
self._store_button = None
|
||||
|
|
@ -422,7 +420,7 @@ class MainMenuWindow(bui.Window):
|
|||
):
|
||||
|
||||
def _do_quit() -> None:
|
||||
QuitWindow(swish=True, back=True)
|
||||
bui.quit(confirm=True, quit_type=bui.QuitType.BACK)
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=_do_quit
|
||||
|
|
@ -1040,30 +1038,47 @@ class MainMenuWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.confirm import QuitWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Note: Normally we should go through bui.quit(confirm=True) but
|
||||
# invoking the window directly lets us scale it up from the
|
||||
# button.
|
||||
QuitWindow(origin_widget=self._quit_button)
|
||||
|
||||
def _demo_menu_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.kiosk import KioskWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
KioskWindow(transition='in_left').get_root_widget()
|
||||
KioskWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _show_account_window(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.account.settings import AccountSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AccountSettingsWindow(
|
||||
origin_widget=self._account_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_store_pressed(self) -> None:
|
||||
|
|
@ -1071,6 +1086,10 @@ class MainMenuWindow(bui.Window):
|
|||
from bauiv1lib.store.browser import StoreBrowserWindow
|
||||
from bauiv1lib.account import show_sign_in_prompt
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -1083,7 +1102,8 @@ class MainMenuWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
StoreBrowserWindow(
|
||||
origin_widget=self._store_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _is_benchmark(self) -> bool:
|
||||
|
|
@ -1148,8 +1168,11 @@ class MainMenuWindow(bui.Window):
|
|||
|
||||
def _end_game(self) -> None:
|
||||
assert bui.app.classic is not None
|
||||
if not self._root_widget:
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
|
||||
|
||||
|
|
@ -1165,39 +1188,54 @@ class MainMenuWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.creditslist import CreditsListWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
CreditsListWindow(
|
||||
origin_widget=self._credits_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _howtoplay(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.helpui import HelpWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
HelpWindow(
|
||||
main_menu=True, origin_widget=self._how_to_play_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _settings(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AllSettingsWindow(
|
||||
origin_widget=self._settings_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _resume_and_call(self, call: Callable[[], Any]) -> None:
|
||||
|
|
@ -1206,10 +1244,12 @@ class MainMenuWindow(bui.Window):
|
|||
|
||||
def _do_game_service_press(self) -> None:
|
||||
self._save_state()
|
||||
if bui.app.classic is not None:
|
||||
bui.app.classic.show_online_score_ui()
|
||||
if bui.app.plus is not None:
|
||||
bui.app.plus.show_game_service_ui()
|
||||
else:
|
||||
logging.warning('classic is required to show game service ui')
|
||||
logging.warning(
|
||||
'plus feature-set is required to show game service ui'
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
# Don't do this for the in-game menu.
|
||||
|
|
@ -1280,35 +1320,50 @@ class MainMenuWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.gather import GatherWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
GatherWindow(origin_widget=self._gather_button).get_root_widget()
|
||||
GatherWindow(origin_widget=self._gather_button).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _watch_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.watch import WatchWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
WatchWindow(origin_widget=self._watch_button).get_root_widget()
|
||||
WatchWindow(origin_widget=self._watch_button).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _play_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.selecting_private_party_playlist = False
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlayWindow(origin_widget=self._start_button).get_root_widget()
|
||||
PlayWindow(origin_widget=self._start_button).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _resume(self) -> None:
|
||||
|
|
@ -1316,7 +1371,7 @@ class MainMenuWindow(bui.Window):
|
|||
bui.app.classic.resume()
|
||||
if self._root_widget:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
bui.app.ui_v1.clear_main_menu_window()
|
||||
bui.app.ui_v1.clear_main_menu_window(transition='out_right')
|
||||
|
||||
# If there's callbacks waiting for this window to go away, call them.
|
||||
for call in bui.app.ui_v1.main_menu_resume_callbacks:
|
||||
|
|
|
|||
121
dist/ba_data/python/bauiv1lib/party.py
vendored
121
dist/ba_data/python/bauiv1lib/party.py
vendored
|
|
@ -40,6 +40,7 @@ class PartyWindow(bui.Window):
|
|||
if uiscale is bui.UIScale.MEDIUM
|
||||
else 600
|
||||
)
|
||||
self._display_old_msgs = True
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
|
|
@ -92,9 +93,10 @@ class PartyWindow(bui.Window):
|
|||
iconscale=1.2,
|
||||
)
|
||||
|
||||
info = bs.get_connection_to_host_info()
|
||||
if info.get('name', '') != '':
|
||||
title = bui.Lstr(value=info['name'])
|
||||
info = bs.get_connection_to_host_info_2()
|
||||
|
||||
if info is not None and info.name != '':
|
||||
title = bui.Lstr(value=info.name)
|
||||
else:
|
||||
title = bui.Lstr(resource=self._r + '.titleText')
|
||||
|
||||
|
|
@ -142,12 +144,6 @@ class PartyWindow(bui.Window):
|
|||
)
|
||||
self._chat_texts: list[bui.Widget] = []
|
||||
|
||||
# add all existing messages if chat is not muted
|
||||
if not bui.app.config.resolve('Chat Muted'):
|
||||
msgs = bs.get_chat_messages()
|
||||
for msg in msgs:
|
||||
self._add_msg(msg)
|
||||
|
||||
self._text_field = txt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
editable=True,
|
||||
|
|
@ -233,6 +229,23 @@ class PartyWindow(bui.Window):
|
|||
is_muted = bui.app.config.resolve('Chat Muted')
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
|
||||
choices: list[str] = ['unmute' if is_muted else 'mute']
|
||||
choices_display: list[bui.Lstr] = [
|
||||
bui.Lstr(resource='chatUnMuteText' if is_muted else 'chatMuteText')
|
||||
]
|
||||
|
||||
# Allow the 'Add to Favorites' option only if we're actually
|
||||
# connected to a party and if it doesn't seem to be a private
|
||||
# party (those are dynamically assigned addresses and ports so
|
||||
# it makes no sense to save them).
|
||||
server_info = bs.get_connection_to_host_info_2()
|
||||
if server_info is not None and not server_info.name.startswith(
|
||||
'Private Party '
|
||||
):
|
||||
choices.append('add_to_favorites')
|
||||
choices_display.append(bui.Lstr(resource='addToFavoritesText'))
|
||||
|
||||
PopupMenuWindow(
|
||||
position=self._menu_button.get_screen_space_center(),
|
||||
scale=(
|
||||
|
|
@ -242,12 +255,8 @@ class PartyWindow(bui.Window):
|
|||
if uiscale is bui.UIScale.MEDIUM
|
||||
else 1.23
|
||||
),
|
||||
choices=['unmute' if is_muted else 'mute'],
|
||||
choices_display=[
|
||||
bui.Lstr(
|
||||
resource='chatUnMuteText' if is_muted else 'chatMuteText'
|
||||
)
|
||||
],
|
||||
choices=choices,
|
||||
choices_display=choices_display,
|
||||
current_choice='unmute' if is_muted else 'mute',
|
||||
delegate=self,
|
||||
)
|
||||
|
|
@ -269,6 +278,12 @@ class PartyWindow(bui.Window):
|
|||
first.delete()
|
||||
else:
|
||||
bui.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.0))
|
||||
# add all existing messages if chat is not muted
|
||||
if self._display_old_msgs:
|
||||
msgs = bs.get_chat_messages()
|
||||
for msg in msgs:
|
||||
self._add_msg(msg)
|
||||
self._display_old_msgs = False
|
||||
|
||||
# update roster section
|
||||
roster = bs.get_game_roster()
|
||||
|
|
@ -466,10 +481,75 @@ class PartyWindow(bui.Window):
|
|||
cfg = bui.app.config
|
||||
cfg['Chat Muted'] = choice == 'mute'
|
||||
cfg.apply_and_commit()
|
||||
self._display_old_msgs = True
|
||||
self._update()
|
||||
if choice == 'add_to_favorites':
|
||||
info = bs.get_connection_to_host_info_2()
|
||||
if info is not None:
|
||||
self._add_to_favorites(
|
||||
name=info.name,
|
||||
address=info.address,
|
||||
port_num=info.port,
|
||||
)
|
||||
else:
|
||||
# We should not allow the user to see this option
|
||||
# if they aren't in a server; this is our bad.
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='errorText'), color=(1, 0, 0)
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
else:
|
||||
print(f'unhandled popup type: {self._popup_type}')
|
||||
|
||||
def _add_to_favorites(
|
||||
self, name: str, address: str | None, port_num: int | None
|
||||
) -> None:
|
||||
addr = address
|
||||
if addr == '':
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidAddressErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
port = port_num if port_num is not None else -1
|
||||
if port > 65535 or port < 0:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidPortErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
# Avoid empty names.
|
||||
if not name:
|
||||
name = f'{addr}@{port}'
|
||||
|
||||
config = bui.app.config
|
||||
|
||||
if addr:
|
||||
if not isinstance(config.get('Saved Servers'), dict):
|
||||
config['Saved Servers'] = {}
|
||||
config['Saved Servers'][f'{addr}@{port}'] = {
|
||||
'addr': addr,
|
||||
'port': port,
|
||||
'name': name,
|
||||
}
|
||||
config.commit()
|
||||
bui.getsound('gunCocking').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource='addedToFavoritesText', subs=[('${NAME}', name)]
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
else:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidAddressErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
||||
def popup_menu_closing(self, popup_window: PopupWindow) -> None:
|
||||
"""Called when the popup is closing."""
|
||||
|
||||
|
|
@ -481,7 +561,8 @@ class PartyWindow(bui.Window):
|
|||
kick_str = bui.Lstr(resource='kickText')
|
||||
else:
|
||||
# kick-votes appeared in build 14248
|
||||
if bs.get_connection_to_host_info().get('build_number', 0) < 14248:
|
||||
info = bs.get_connection_to_host_info_2()
|
||||
if info is None or info.build_number < 14248:
|
||||
return
|
||||
kick_str = bui.Lstr(resource='kickVoteText')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -510,9 +591,17 @@ class PartyWindow(bui.Window):
|
|||
|
||||
def close(self) -> None:
|
||||
"""Close the window."""
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
||||
def close_with_sound(self) -> None:
|
||||
"""Close the window and make a lovely sound."""
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.getsound('swish').play()
|
||||
self.close()
|
||||
|
|
|
|||
1
dist/ba_data/python/bauiv1lib/partyqueue.py
vendored
1
dist/ba_data/python/bauiv1lib/partyqueue.py
vendored
|
|
@ -20,7 +20,6 @@ class PartyQueueWindow(bui.Window):
|
|||
"""Window showing players waiting to join a server."""
|
||||
|
||||
# Ewww this needs quite a bit of de-linting if/when i revisit it..
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=consider-using-dict-comprehension
|
||||
class Dude:
|
||||
"""Represents a single dude waiting in a server line."""
|
||||
|
|
|
|||
38
dist/ba_data/python/bauiv1lib/play.py
vendored
38
dist/ba_data/python/bauiv1lib/play.py
vendored
|
|
@ -32,8 +32,8 @@ class PlayWindow(bui.Window):
|
|||
self._is_main_menu = not bui.app.ui_v1.selecting_private_party_playlist
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
width = 1000 if uiscale is bui.UIScale.SMALL else 800
|
||||
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
width = 1100 if uiscale is bui.UIScale.SMALL else 800
|
||||
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
|
||||
height = 550
|
||||
button_width = 400
|
||||
|
||||
|
|
@ -521,13 +521,19 @@ class PlayWindow(bui.Window):
|
|||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
if self._is_main_menu:
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
self._save_state()
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
|
|
@ -538,7 +544,8 @@ class PlayWindow(bui.Window):
|
|||
self._save_state()
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
GatherWindow(transition='in_left').get_root_widget()
|
||||
GatherWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
|
|
@ -549,6 +556,10 @@ class PlayWindow(bui.Window):
|
|||
from bauiv1lib.account import show_sign_in_prompt
|
||||
from bauiv1lib.coop.browser import CoopBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -559,26 +570,38 @@ class PlayWindow(bui.Window):
|
|||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget()
|
||||
CoopBrowserWindow(
|
||||
origin_widget=self._coop_button
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _team_tourney(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistBrowserWindow(
|
||||
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _free_for_all(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -586,7 +609,8 @@ class PlayWindow(bui.Window):
|
|||
PlaylistBrowserWindow(
|
||||
origin_widget=self._free_for_all_button,
|
||||
sessiontype=bs.FreeForAllSession,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _draw_dude(
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ class PlaylistAddGameWindow(bui.Window):
|
|||
txt = bui.textwidget(
|
||||
parent=self._column,
|
||||
position=(0, 0),
|
||||
size=(self._width - 88, 24),
|
||||
size=(self._scroll_width * 1.1, 24),
|
||||
text=gametype.get_display_string(),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ class PlaylistBrowserWindow(bui.Window):
|
|||
)
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 900.0 if uiscale is bui.UIScale.SMALL else 800.0
|
||||
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 1100.0 if uiscale is bui.UIScale.SMALL else 800.0
|
||||
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
480
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -684,6 +684,10 @@ class PlaylistBrowserWindow(bui.Window):
|
|||
PlaylistCustomizeBrowserWindow,
|
||||
)
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -691,13 +695,18 @@ class PlaylistBrowserWindow(bui.Window):
|
|||
PlaylistCustomizeBrowserWindow(
|
||||
origin_widget=self._customize_button,
|
||||
sessiontype=self._sessiontype,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_back_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Store our selected playlist if that's changed.
|
||||
if self._selected_playlist is not None:
|
||||
prev_sel = bui.app.config.get(
|
||||
|
|
@ -716,7 +725,8 @@ class PlaylistBrowserWindow(bui.Window):
|
|||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlayWindow(transition='in_left').get_root_widget()
|
||||
PlayWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
self._r = 'gameListWindow'
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 750.0 if uiscale is bui.UIScale.SMALL else 650.0
|
||||
x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._width = 850.0 if uiscale is bui.UIScale.SMALL else 650.0
|
||||
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = (
|
||||
380.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -323,6 +323,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist import browser
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
if self._selected_playlist_name is not None:
|
||||
cfg = bui.app.config
|
||||
cfg[
|
||||
|
|
@ -337,7 +341,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
browser.PlaylistBrowserWindow(
|
||||
transition='in_left', sessiontype=self._sessiontype
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _select(self, name: str, index: int) -> None:
|
||||
|
|
|
|||
18
dist/ba_data/python/bauiv1lib/playlist/edit.py
vendored
18
dist/ba_data/python/bauiv1lib/playlist/edit.py
vendored
|
|
@ -31,8 +31,8 @@ class PlaylistEditWindow(bui.Window):
|
|||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 770 if uiscale is bui.UIScale.SMALL else 670
|
||||
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 870 if uiscale is bui.UIScale.SMALL else 670
|
||||
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
400
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -283,6 +283,10 @@ class PlaylistEditWindow(bui.Window):
|
|||
PlaylistCustomizeBrowserWindow,
|
||||
)
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.getsound('powerdown01').play()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -293,7 +297,8 @@ class PlaylistEditWindow(bui.Window):
|
|||
select_playlist=(
|
||||
self._editcontroller.get_existing_playlist_name()
|
||||
),
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _add(self) -> None:
|
||||
|
|
@ -315,6 +320,10 @@ class PlaylistEditWindow(bui.Window):
|
|||
PlaylistCustomizeBrowserWindow,
|
||||
)
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -380,7 +389,8 @@ class PlaylistEditWindow(bui.Window):
|
|||
transition='in_left',
|
||||
sessiontype=self._editcontroller.get_session_type(),
|
||||
select_playlist=new_name,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_press_with_sound(self) -> None:
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ class PlaylistEditController:
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistEditWindow(
|
||||
editcontroller=self, transition=transition
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=False, # Disable this check.
|
||||
)
|
||||
|
||||
def get_config_name(self) -> str:
|
||||
|
|
@ -150,7 +151,8 @@ class PlaylistEditController:
|
|||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.clear_main_menu_window(transition='out_left')
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistAddGameWindow(editcontroller=self).get_root_widget()
|
||||
PlaylistAddGameWindow(editcontroller=self).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
|
||||
def edit_game_pressed(self) -> None:
|
||||
|
|
@ -175,7 +177,8 @@ class PlaylistEditController:
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistEditWindow(
|
||||
editcontroller=self, transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
|
||||
def _show_edit_ui(
|
||||
|
|
@ -205,7 +208,8 @@ class PlaylistEditController:
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistEditWindow(
|
||||
editcontroller=self, transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
|
||||
# Otherwise we were adding; go back to the add type choice list.
|
||||
|
|
@ -214,7 +218,8 @@ class PlaylistEditController:
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistAddGameWindow(
|
||||
editcontroller=self, transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
else:
|
||||
# Make sure type is in there.
|
||||
|
|
@ -236,5 +241,6 @@ class PlaylistEditController:
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
PlaylistEditWindow(
|
||||
editcontroller=self, transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ class PlaylistEditGameWindow(bui.Window):
|
|||
self._choice_selections: dict[str, int] = {}
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
width = 720 if uiscale is bui.UIScale.SMALL else 620
|
||||
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
|
||||
width = 820 if uiscale is bui.UIScale.SMALL else 620
|
||||
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
height = (
|
||||
365
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -514,6 +514,10 @@ class PlaylistEditGameWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Replace ourself with the map-select UI.
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -524,7 +528,8 @@ class PlaylistEditGameWindow(bui.Window):
|
|||
copy.deepcopy(self._getconfig()),
|
||||
self._edit_info,
|
||||
self._completion_call,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _choice_inc(
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ class PlaylistMapSelectWindow(bui.Window):
|
|||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
width = 715 if uiscale is bui.UIScale.SMALL else 615
|
||||
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
|
||||
width = 815 if uiscale is bui.UIScale.SMALL else 615
|
||||
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
height = (
|
||||
400
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -273,6 +273,10 @@ class PlaylistMapSelectWindow(bui.Window):
|
|||
def _select(self, map_name: str) -> None:
|
||||
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._config['settings']['map'] = map_name
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -285,7 +289,8 @@ class PlaylistMapSelectWindow(bui.Window):
|
|||
default_selection='map',
|
||||
transition='in_left',
|
||||
edit_info=self._edit_info,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _select_with_delay(self, map_name: str) -> None:
|
||||
|
|
@ -296,6 +301,10 @@ class PlaylistMapSelectWindow(bui.Window):
|
|||
def _cancel(self) -> None:
|
||||
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
|
|
@ -307,5 +316,6 @@ class PlaylistMapSelectWindow(bui.Window):
|
|||
default_selection='map',
|
||||
transition='in_left',
|
||||
edit_info=self._edit_info,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
12
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
12
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
|
|
@ -140,7 +140,6 @@ class PlayOptionsWindow(PopupWindow):
|
|||
if show_shuffle_check_box:
|
||||
self._height += 40
|
||||
|
||||
# Creates our _root_widget.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
scale = (
|
||||
1.69
|
||||
|
|
@ -149,6 +148,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
if uiscale is bui.UIScale.MEDIUM
|
||||
else 0.85
|
||||
)
|
||||
# Creates our _root_widget.
|
||||
super().__init__(
|
||||
position=scale_origin, size=(self._width, self._height), scale=scale
|
||||
)
|
||||
|
|
@ -448,6 +448,10 @@ class PlayOptionsWindow(PopupWindow):
|
|||
self._transition_out()
|
||||
|
||||
def _on_ok_press(self) -> None:
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self.root_widget or self.root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Disallow if our playlist has disappeared.
|
||||
if not self._does_target_playlist_exist():
|
||||
return
|
||||
|
|
@ -478,8 +482,12 @@ class PlayOptionsWindow(PopupWindow):
|
|||
cfg['Private Party Host Session Type'] = typename
|
||||
bui.getsound('gunCocking').play()
|
||||
assert bui.app.classic is not None
|
||||
# Note: this is a wonky situation where we aren't actually
|
||||
# the main window but we set it on behalf of the main window
|
||||
# that popped us up.
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
GatherWindow(transition='in_right').get_root_widget()
|
||||
GatherWindow(transition='in_right').get_root_widget(),
|
||||
from_window=False, # Disable this test.
|
||||
)
|
||||
self._transition_out(transition='out_left')
|
||||
if self._delegate is not None:
|
||||
|
|
|
|||
87
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
87
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
|
|
@ -33,8 +33,8 @@ class ProfileBrowserWindow(bui.Window):
|
|||
back_label = bui.Lstr(resource='doneText')
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 700.0 if uiscale is bui.UIScale.SMALL else 600.0
|
||||
x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0
|
||||
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = (
|
||||
360.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -197,8 +197,10 @@ class ProfileBrowserWindow(bui.Window):
|
|||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
self._columnwidget = bui.columnwidget(
|
||||
parent=self._scrollwidget, border=2, margin=0
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._scroll_width, 32),
|
||||
background=False,
|
||||
)
|
||||
v -= 255
|
||||
self._profiles: dict[str, dict[str, Any]] | None = None
|
||||
|
|
@ -212,6 +214,10 @@ class ProfileBrowserWindow(bui.Window):
|
|||
from bauiv1lib.profile.edit import EditProfileWindow
|
||||
from bauiv1lib.purchase import PurchaseWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -252,7 +258,8 @@ class ProfileBrowserWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
EditProfileWindow(
|
||||
existing_profile=None, in_main_menu=self._in_main_menu
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget if self._in_main_menu else False,
|
||||
)
|
||||
|
||||
def _delete_profile(self) -> None:
|
||||
|
|
@ -301,6 +308,10 @@ class ProfileBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.profile.edit import EditProfileWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
if self._selected_profile is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
|
|
@ -313,7 +324,8 @@ class ProfileBrowserWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
EditProfileWindow(
|
||||
self._selected_profile, in_main_menu=self._in_main_menu
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget if self._in_main_menu else False,
|
||||
)
|
||||
|
||||
def _select(self, name: str, index: int) -> None:
|
||||
|
|
@ -324,6 +336,10 @@ class ProfileBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.account.settings import AccountSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
self._save_state()
|
||||
|
|
@ -333,7 +349,8 @@ class ProfileBrowserWindow(bui.Window):
|
|||
if self._in_main_menu:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AccountSettingsWindow(transition='in_left').get_root_widget()
|
||||
AccountSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
# If we're being called up standalone, handle pause/resume ourself.
|
||||
|
|
@ -342,8 +359,10 @@ class ProfileBrowserWindow(bui.Window):
|
|||
|
||||
def _refresh(self) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
from efro.util import asserttype
|
||||
from bascenev1 import PlayerProfilesChangedMessage
|
||||
from bascenev1lib.actor import spazappearance
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
|
|
@ -359,14 +378,27 @@ class ProfileBrowserWindow(bui.Window):
|
|||
assert self._profiles is not None
|
||||
items = list(self._profiles.items())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
spazzes = spazappearance.get_appearances()
|
||||
spazzes.sort()
|
||||
icon_textures = [
|
||||
bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture)
|
||||
for s in spazzes
|
||||
]
|
||||
icon_tint_textures = [
|
||||
bui.gettexture(
|
||||
bui.app.classic.spaz_appearances[s].icon_mask_texture
|
||||
)
|
||||
for s in spazzes
|
||||
]
|
||||
index = 0
|
||||
y_val = 35 * (len(self._profiles) - 1)
|
||||
account_name: str | None
|
||||
if plus.get_v1_account_state() == 'signed_in':
|
||||
account_name = plus.get_v1_account_display_string()
|
||||
else:
|
||||
account_name = None
|
||||
widget_to_select = None
|
||||
for p_name, _ in items:
|
||||
for p_name, p_info in items:
|
||||
if p_name == '__account__' and account_name is None:
|
||||
continue
|
||||
color, _highlight = bui.app.classic.get_player_profile_colors(
|
||||
|
|
@ -378,16 +410,35 @@ class ProfileBrowserWindow(bui.Window):
|
|||
if p_name == '__account__'
|
||||
else bui.app.classic.get_player_profile_icon(p_name) + p_name
|
||||
)
|
||||
|
||||
try:
|
||||
char_index = spazzes.index(p_info['character'])
|
||||
except Exception:
|
||||
char_index = spazzes.index('Spaz')
|
||||
|
||||
assert isinstance(tval, str)
|
||||
character = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(0, y_val),
|
||||
size=(28, 28),
|
||||
label='',
|
||||
color=(1, 1, 1),
|
||||
mask_texture=bui.gettexture('characterIconMask'),
|
||||
tint_color=color,
|
||||
tint2_color=_highlight,
|
||||
texture=icon_textures[char_index],
|
||||
tint_texture=icon_tint_textures[char_index],
|
||||
selectable=False,
|
||||
)
|
||||
txtw = bui.textwidget(
|
||||
parent=self._columnwidget,
|
||||
position=(0, 32),
|
||||
size=((self._width - 40) / scl, 28),
|
||||
parent=self._subcontainer,
|
||||
position=(35, y_val),
|
||||
size=((self._width - 210) / scl, 28),
|
||||
text=bui.Lstr(value=tval),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
on_select_call=bui.WeakCall(self._select, p_name, index),
|
||||
maxwidth=self._scroll_width * 0.92,
|
||||
maxwidth=self._scroll_width * 0.86,
|
||||
corner_scale=scl,
|
||||
color=bui.safecolor(color, 0.4),
|
||||
always_highlight=True,
|
||||
|
|
@ -396,8 +447,11 @@ class ProfileBrowserWindow(bui.Window):
|
|||
)
|
||||
if index == 0:
|
||||
bui.widget(edit=txtw, up_widget=self._back_button)
|
||||
if self._selected_profile is None:
|
||||
self._selected_profile = p_name
|
||||
bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40)
|
||||
self._profile_widgets.append(txtw)
|
||||
self._profile_widgets.append(character)
|
||||
|
||||
# Select/show this one if it was previously selected
|
||||
# (but defer till after this loop since our height is
|
||||
|
|
@ -406,10 +460,15 @@ class ProfileBrowserWindow(bui.Window):
|
|||
widget_to_select = txtw
|
||||
|
||||
index += 1
|
||||
y_val -= 35
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
size=(self._scroll_width, index * 35),
|
||||
)
|
||||
if widget_to_select is not None:
|
||||
bui.columnwidget(
|
||||
edit=self._columnwidget,
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
selected_child=widget_to_select,
|
||||
visible_child=widget_to_select,
|
||||
)
|
||||
|
|
|
|||
49
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
49
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
|
|
@ -18,12 +18,18 @@ class EditProfileWindow(bui.Window):
|
|||
# FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
|
||||
def reload_window(self) -> None:
|
||||
"""Transitions out and recreates ourself."""
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
EditProfileWindow(
|
||||
self.getname(), self._in_main_menu
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
|
@ -54,8 +60,8 @@ class EditProfileWindow(bui.Window):
|
|||
self._highlight,
|
||||
) = bui.app.classic.get_player_profile_colors(existing_profile)
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = width = 780.0 if uiscale is bui.UIScale.SMALL else 680.0
|
||||
self._x_inset = x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._width = width = 880.0 if uiscale is bui.UIScale.SMALL else 680.0
|
||||
self._x_inset = x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = height = (
|
||||
350.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -184,7 +190,7 @@ class EditProfileWindow(bui.Window):
|
|||
self._clipped_name_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text='',
|
||||
position=(540 + x_inset, v - 8),
|
||||
position=(580 + x_inset, v - 8),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
scale=0.55,
|
||||
|
|
@ -390,6 +396,16 @@ class EditProfileWindow(bui.Window):
|
|||
autoselect=True,
|
||||
on_activate_call=self.upgrade_profile,
|
||||
)
|
||||
self._random_name_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
label=bui.Lstr(resource='randomText'),
|
||||
size=(30, 20),
|
||||
position=(495 + x_inset, v - 20),
|
||||
button_type='square',
|
||||
color=(0.6, 0.5, 0.65),
|
||||
autoselect=True,
|
||||
on_activate_call=self.assign_random_name,
|
||||
)
|
||||
|
||||
self._update_clipped_name()
|
||||
self._clipped_name_timer = bui.AppTimer(
|
||||
|
|
@ -498,8 +514,17 @@ class EditProfileWindow(bui.Window):
|
|||
)
|
||||
self._update_character()
|
||||
|
||||
def assign_random_name(self) -> None:
|
||||
"""Assigning a random name to the player."""
|
||||
names = bs.get_random_names()
|
||||
name = names[random.randrange(len(names))]
|
||||
bui.textwidget(
|
||||
edit=self._text_field,
|
||||
text=name,
|
||||
)
|
||||
|
||||
def upgrade_profile(self) -> None:
|
||||
"""Attempt to ugrade the profile to global."""
|
||||
"""Attempt to upgrade the profile to global."""
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib.profile import upgrade as pupgrade
|
||||
|
||||
|
|
@ -653,6 +678,10 @@ class EditProfileWindow(bui.Window):
|
|||
def _cancel(self) -> None:
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
|
|
@ -660,7 +689,8 @@ class EditProfileWindow(bui.Window):
|
|||
'in_left',
|
||||
selected_profile=self._existing_profile,
|
||||
in_main_menu=self._in_main_menu,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _set_color(self, color: tuple[float, float, float]) -> None:
|
||||
|
|
@ -759,6 +789,10 @@ class EditProfileWindow(bui.Window):
|
|||
"""Save has been selected."""
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return False
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -808,6 +842,7 @@ class EditProfileWindow(bui.Window):
|
|||
'in_left',
|
||||
selected_profile=new_name,
|
||||
in_main_menu=self._in_main_menu,
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
return True
|
||||
|
|
|
|||
56
dist/ba_data/python/bauiv1lib/promocode.py
vendored
56
dist/ba_data/python/bauiv1lib/promocode.py
vendored
|
|
@ -26,7 +26,7 @@ class PromoCodeWindow(bui.Window):
|
|||
transition = 'in_right'
|
||||
|
||||
width = 450
|
||||
height = 230
|
||||
height = 330
|
||||
|
||||
self._modal = modal
|
||||
self._r = 'promoCodeWindow'
|
||||
|
|
@ -62,17 +62,50 @@ class PromoCodeWindow(bui.Window):
|
|||
iconscale=1.2,
|
||||
)
|
||||
|
||||
v = height - 74
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource='codesExplainText'),
|
||||
maxwidth=width * 0.9,
|
||||
position=(width * 0.5, v),
|
||||
color=(0.7, 0.7, 0.7, 1.0),
|
||||
size=(0, 0),
|
||||
scale=0.8,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 60
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(
|
||||
resource='supportEmailText',
|
||||
subs=[('${EMAIL}', 'support@froemling.net')],
|
||||
),
|
||||
maxwidth=width * 0.9,
|
||||
position=(width * 0.5, v),
|
||||
color=(0.7, 0.7, 0.7, 1.0),
|
||||
size=(0, 0),
|
||||
scale=0.65,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 80
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=self._r + '.codeText'),
|
||||
position=(22, height - 113),
|
||||
position=(22, v),
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
size=(90, 30),
|
||||
h_align='right',
|
||||
)
|
||||
v -= 8
|
||||
|
||||
self._text_field = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(125, height - 121),
|
||||
position=(125, v),
|
||||
size=(280, 46),
|
||||
text='',
|
||||
h_align='left',
|
||||
|
|
@ -86,10 +119,11 @@ class PromoCodeWindow(bui.Window):
|
|||
)
|
||||
bui.widget(edit=btn, down_widget=self._text_field)
|
||||
|
||||
v -= 79
|
||||
b_width = 200
|
||||
self._enter_button = btn2 = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5 - b_width * 0.5, height - 200),
|
||||
position=(width * 0.5 - b_width * 0.5, v),
|
||||
size=(b_width, 60),
|
||||
scale=1.0,
|
||||
label=bui.Lstr(
|
||||
|
|
@ -108,13 +142,18 @@ class PromoCodeWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
if not self._modal:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _activate_enter_button(self) -> None:
|
||||
|
|
@ -124,6 +163,10 @@ class PromoCodeWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -133,7 +176,8 @@ class PromoCodeWindow(bui.Window):
|
|||
if not self._modal:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
plus.add_v1_account_transaction(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
scale_origin = None
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
|
||||
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._width = 970.0 if uiscale is bui.UIScale.SMALL else 670.0
|
||||
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
390.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -682,11 +682,16 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
def _on_vr_test_press(self) -> None:
|
||||
from bauiv1lib.settings.vrtesting import VRTestingWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
VRTestingWindow(transition='in_right').get_root_widget()
|
||||
VRTestingWindow(transition='in_right').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_net_test_press(self) -> None:
|
||||
|
|
@ -694,6 +699,10 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
assert plus is not None
|
||||
from bauiv1lib.settings.nettesting import NetTestingWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Net-testing requires a signed in v1 account.
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
bui.screenmessage(
|
||||
|
|
@ -706,7 +715,8 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
NetTestingWindow(transition='in_right').get_root_widget()
|
||||
NetTestingWindow(transition='in_right').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_friend_promo_code_press(self) -> None:
|
||||
|
|
@ -724,17 +734,26 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
def _on_plugins_button_press(self) -> None:
|
||||
from bauiv1lib.settings.plugins import PluginWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PluginWindow(origin_widget=self._plugins_button).get_root_widget()
|
||||
PluginWindow(origin_widget=self._plugins_button).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_promo_code_press(self) -> None:
|
||||
from bauiv1lib.promocode import PromoCodeWindow
|
||||
from bauiv1lib.account import show_sign_in_prompt
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -742,23 +761,30 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
if plus.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PromoCodeWindow(
|
||||
origin_widget=self._promo_code_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _on_benchmark_press(self) -> None:
|
||||
from bauiv1lib.debug import DebugWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
DebugWindow(transition='in_right').get_root_widget()
|
||||
DebugWindow(transition='in_right').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
@ -807,6 +833,8 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
sel_name = 'ModdingGuide'
|
||||
elif sel == self._language_inform_checkbox:
|
||||
sel_name = 'LangInform'
|
||||
elif sel == self._show_dev_console_button_check_box.widget:
|
||||
sel_name = 'ShowDevConsole'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
elif sel == self._back_button:
|
||||
|
|
@ -870,6 +898,8 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
sel = self._modding_guide_button
|
||||
elif sel_name == 'LangInform':
|
||||
sel = self._language_inform_checkbox
|
||||
elif sel_name == 'ShowDevConsole':
|
||||
sel = self._show_dev_console_button_check_box.widget
|
||||
else:
|
||||
sel = None
|
||||
if sel is not None:
|
||||
|
|
@ -904,11 +934,16 @@ class AdvancedSettingsWindow(bui.Window):
|
|||
def _do_back(self) -> None:
|
||||
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AllSettingsWindow(transition='in_left').get_root_widget()
|
||||
AllSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class AllSettingsWindow(bui.Window):
|
|||
scale_origin = None
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
width = 900 if uiscale is bui.UIScale.SMALL else 580
|
||||
x_inset = 75 if uiscale is bui.UIScale.SMALL else 0
|
||||
width = 1000 if uiscale is bui.UIScale.SMALL else 580
|
||||
x_inset = 125 if uiscale is bui.UIScale.SMALL else 0
|
||||
height = 435
|
||||
self._r = 'settingsWindow'
|
||||
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
||||
|
|
@ -235,65 +235,90 @@ class AllSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
MainMenuWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_controllers(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.controls import ControlsSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ControlsSettingsWindow(
|
||||
origin_widget=self._controllers_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_graphics(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.graphics import GraphicsSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
GraphicsSettingsWindow(
|
||||
origin_widget=self._graphics_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_audio(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.audio import AudioSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AudioSettingsWindow(
|
||||
origin_widget=self._audio_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_advanced(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(
|
||||
origin_widget=self._advanced_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
|
|||
20
dist/ba_data/python/bauiv1lib/settings/audio.py
vendored
20
dist/ba_data/python/bauiv1lib/settings/audio.py
vendored
|
|
@ -121,7 +121,8 @@ class AudioSettingsWindow(bui.Window):
|
|||
displayname=bui.Lstr(resource=self._r + '.soundVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
increment=0.05,
|
||||
as_percent=True,
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
|
|
@ -137,9 +138,10 @@ class AudioSettingsWindow(bui.Window):
|
|||
displayname=bui.Lstr(resource=self._r + '.musicVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
increment=0.05,
|
||||
callback=music.music_volume_changed,
|
||||
changesound=False,
|
||||
as_percent=True,
|
||||
)
|
||||
|
||||
v -= 0.5 * spacing
|
||||
|
|
@ -235,6 +237,10 @@ class AudioSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.soundtrack import browser as stb
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# We require disk access for soundtracks;
|
||||
# if we don't have it, request it.
|
||||
if not bui.have_permission(bui.Permission.STORAGE):
|
||||
|
|
@ -254,13 +260,18 @@ class AudioSettingsWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
stb.SoundtrackBrowserWindow(
|
||||
origin_widget=self._soundtrack_button
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings import allsettings
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
|
|
@ -269,7 +280,8 @@ class AudioSettingsWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
allsettings.AllSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
|
|||
|
|
@ -98,9 +98,11 @@ class ControlsSettingsWindow(bui.Window):
|
|||
# made-for-iOS/Mac systems
|
||||
# (we can run into problems where devices register as one of each
|
||||
# type otherwise)..
|
||||
# UPDATE: We always use the apple system these days (which should
|
||||
# support older controllers). So no need for a switch.
|
||||
show_mac_controller_subsystem = False
|
||||
if platform == 'mac' and bui.is_xcode_build():
|
||||
show_mac_controller_subsystem = True
|
||||
# if platform == 'mac' and bui.is_xcode_build():
|
||||
# show_mac_controller_subsystem = True
|
||||
|
||||
if show_mac_controller_subsystem:
|
||||
height += spacing * 1.5
|
||||
|
|
@ -311,6 +313,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||
maxwidth=width * 0.8,
|
||||
)
|
||||
v -= spacing
|
||||
|
||||
if show_mac_controller_subsystem:
|
||||
PopupMenu(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -364,59 +367,84 @@ class ControlsSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ConfigKeyboardWindow(
|
||||
bs.getinputdevice('Keyboard', '#1')
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _config_keyboard2(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ConfigKeyboardWindow(
|
||||
bs.getinputdevice('Keyboard', '#2')
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_mobile_devices(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
RemoteAppSettingsWindow().get_root_widget()
|
||||
RemoteAppSettingsWindow().get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_gamepads(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
GamepadSelectWindow().get_root_widget()
|
||||
GamepadSelectWindow().get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _do_touchscreen(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
TouchscreenSettingsWindow().get_root_widget()
|
||||
TouchscreenSettingsWindow().get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
|
|
@ -463,11 +491,16 @@ class ControlsSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AllSettingsWindow(transition='in_left').get_root_widget()
|
||||
AllSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
110
dist/ba_data/python/bauiv1lib/settings/gamepad.py
vendored
110
dist/ba_data/python/bauiv1lib/settings/gamepad.py
vendored
|
|
@ -431,7 +431,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
def get_unassigned_buttons_run_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('unassignedButtonsRun', True)
|
||||
val = self._settings.get('unassignedButtonsRun', True)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_unassigned_buttons_run_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -446,7 +448,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
def get_start_button_activates_default_widget_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('startButtonActivatesDefaultWidget', True)
|
||||
val = self._settings.get('startButtonActivatesDefaultWidget', True)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_start_button_activates_default_widget_value(
|
||||
self, value: bool
|
||||
|
|
@ -463,7 +467,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
def get_ui_only_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('uiOnly', False)
|
||||
val = self._settings.get('uiOnly', False)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_ui_only_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -478,7 +484,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
def get_ignore_completely_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('ignoreCompletely', False)
|
||||
val = self._settings.get('ignoreCompletely', False)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_ignore_completely_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -493,7 +501,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
def get_auto_recalibrate_analog_stick_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('autoRecalibrateAnalogStick', False)
|
||||
val = self._settings.get('autoRecalibrateAnalogStick', False)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def set_auto_recalibrate_analog_stick_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -510,7 +520,9 @@ class GamepadSettingsWindow(bui.Window):
|
|||
assert self._settings is not None
|
||||
if not self._is_secondary:
|
||||
raise RuntimeError('Enable value only applies to secondary editor.')
|
||||
return self._settings.get('enableSecondary', False)
|
||||
val = self._settings.get('enableSecondary', False)
|
||||
assert isinstance(val, bool)
|
||||
return val
|
||||
|
||||
def show_secondary_editor(self) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -533,20 +545,24 @@ class GamepadSettingsWindow(bui.Window):
|
|||
if 'analogStickLR' + self._ext in self._settings
|
||||
else 5
|
||||
if self._is_secondary
|
||||
else 1
|
||||
else None
|
||||
)
|
||||
sval2 = (
|
||||
self._settings['analogStickUD' + self._ext]
|
||||
if 'analogStickUD' + self._ext in self._settings
|
||||
else 6
|
||||
if self._is_secondary
|
||||
else 2
|
||||
)
|
||||
return (
|
||||
self._input.get_axis_name(sval1)
|
||||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
else None
|
||||
)
|
||||
assert isinstance(sval1, (int, type(None)))
|
||||
assert isinstance(sval2, (int, type(None)))
|
||||
if sval1 is not None and sval2 is not None:
|
||||
return (
|
||||
self._input.get_axis_name(sval1)
|
||||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# If they're looking for triggers.
|
||||
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
|
||||
|
|
@ -561,7 +577,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
return str(1.0)
|
||||
|
||||
# For dpad buttons: show individual buttons if any are set.
|
||||
# Otherwise show whichever dpad is set (defaulting to 1).
|
||||
# Otherwise show whichever dpad is set.
|
||||
dpad_buttons = [
|
||||
'buttonLeft' + self._ext,
|
||||
'buttonRight' + self._ext,
|
||||
|
|
@ -576,24 +592,28 @@ class GamepadSettingsWindow(bui.Window):
|
|||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# No dpad buttons - show the dpad number for all 4.
|
||||
return bui.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(
|
||||
self._settings['dpad' + self._ext]
|
||||
if 'dpad' + self._ext in self._settings
|
||||
else 2
|
||||
if self._is_secondary
|
||||
else 1
|
||||
),
|
||||
),
|
||||
],
|
||||
dpadnum = (
|
||||
self._settings['dpad' + self._ext]
|
||||
if 'dpad' + self._ext in self._settings
|
||||
else 2
|
||||
if self._is_secondary
|
||||
else None
|
||||
)
|
||||
assert isinstance(dpadnum, (int, type(None)))
|
||||
if dpadnum is not None:
|
||||
return bui.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(dpadnum),
|
||||
),
|
||||
],
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# other buttons..
|
||||
# Other buttons.
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
|
@ -604,9 +624,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
event: dict[str, Any],
|
||||
dialog: AwaitGamepadInputWindow,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
assert self._settings is not None
|
||||
ext = self._ext
|
||||
|
||||
|
|
@ -636,10 +654,6 @@ class GamepadSettingsWindow(bui.Window):
|
|||
if btn in self._settings:
|
||||
del self._settings[btn]
|
||||
if event['hat'] == (2 if self._is_secondary else 1):
|
||||
# Exclude value in default case.
|
||||
if 'dpad' + ext in self._settings:
|
||||
del self._settings['dpad' + ext]
|
||||
else:
|
||||
self._settings['dpad' + ext] = event['hat']
|
||||
|
||||
# Update the 4 dpad button txt widgets.
|
||||
|
|
@ -668,10 +682,6 @@ class GamepadSettingsWindow(bui.Window):
|
|||
if abs(event['value']) > 0.5:
|
||||
axis = event['axis']
|
||||
if axis == (5 if self._is_secondary else 1):
|
||||
# Exclude value in default case.
|
||||
if 'analogStickLR' + ext in self._settings:
|
||||
del self._settings['analogStickLR' + ext]
|
||||
else:
|
||||
self._settings['analogStickLR' + ext] = axis
|
||||
bui.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
|
|
@ -701,10 +711,6 @@ class GamepadSettingsWindow(bui.Window):
|
|||
lr_axis = 5 if self._is_secondary else 1
|
||||
if axis != lr_axis:
|
||||
if axis == (6 if self._is_secondary else 2):
|
||||
# Exclude value in default case.
|
||||
if 'analogStickUD' + ext in self._settings:
|
||||
del self._settings['analogStickUD' + ext]
|
||||
else:
|
||||
self._settings['analogStickUD' + ext] = axis
|
||||
bui.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
|
|
@ -783,25 +789,34 @@ class GamepadSettingsWindow(bui.Window):
|
|||
),
|
||||
)
|
||||
|
||||
bui.apptimer(0, doit)
|
||||
bui.pushcall(doit)
|
||||
return btn
|
||||
|
||||
def _cancel(self) -> None:
|
||||
from bauiv1lib.settings.controls import ControlsSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
if self._is_main_menu:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save(self) -> None:
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
|
@ -846,7 +861,8 @@ class GamepadSettingsWindow(bui.Window):
|
|||
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
),
|
||||
)
|
||||
|
||||
bui.apptimer(0, doit)
|
||||
bui.pushcall(doit)
|
||||
return btn, btn2
|
||||
|
||||
def _inc(
|
||||
|
|
|
|||
|
|
@ -29,16 +29,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
|
|||
logging.exception('Error transitioning out main_menu_window.')
|
||||
bui.getsound('activateBeep').play()
|
||||
bui.getsound('swish').play()
|
||||
inputdevice = event['input_device']
|
||||
assert isinstance(inputdevice, bs.InputDevice)
|
||||
if inputdevice.allows_configuring:
|
||||
device = event['input_device']
|
||||
assert isinstance(device, bs.InputDevice)
|
||||
if device.allows_configuring:
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
gamepad.GamepadSettingsWindow(inputdevice).get_root_widget()
|
||||
gamepad.GamepadSettingsWindow(device).get_root_widget(),
|
||||
from_window=None,
|
||||
)
|
||||
else:
|
||||
width = 700
|
||||
height = 200
|
||||
button_width = 100
|
||||
button_width = 80
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
dlg = bui.containerwidget(
|
||||
scale=(
|
||||
|
|
@ -51,9 +52,14 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
|
|||
size=(width, height),
|
||||
transition='in_right',
|
||||
)
|
||||
bui.app.ui_v1.set_main_menu_window(dlg)
|
||||
device_name = inputdevice.name
|
||||
if device_name == 'iDevice':
|
||||
bui.app.ui_v1.set_main_menu_window(dlg, from_window=None)
|
||||
|
||||
if device.allows_configuring_in_system_settings:
|
||||
msg = bui.Lstr(
|
||||
resource='configureDeviceInSystemSettingsText',
|
||||
subs=[('${DEVICE}', device.name)],
|
||||
)
|
||||
elif device.is_controller_app:
|
||||
msg = bui.Lstr(
|
||||
resource='bsRemoteConfigureInAppText',
|
||||
subs=[('${REMOTE_APP_NAME}', bui.get_remote_app_name())],
|
||||
|
|
@ -61,7 +67,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
|
|||
else:
|
||||
msg = bui.Lstr(
|
||||
resource='cantConfigureDeviceText',
|
||||
subs=[('${DEVICE}', device_name)],
|
||||
subs=[('${DEVICE}', device.name)],
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=dlg,
|
||||
|
|
@ -76,12 +82,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
|
|||
def _ok() -> None:
|
||||
from bauiv1lib.settings import controls
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not dlg or dlg.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=dlg, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=dlg,
|
||||
)
|
||||
|
||||
bui.buttonwidget(
|
||||
|
|
@ -186,11 +197,16 @@ class GamepadSelectWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.settings import controls
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bs.release_gamepad_input()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
self._show_fullscreen = False
|
||||
fullscreen_spacing_top = spacing * 0.2
|
||||
fullscreen_spacing = spacing * 1.2
|
||||
if bui.can_toggle_fullscreen():
|
||||
if bui.fullscreen_control_available():
|
||||
self._show_fullscreen = True
|
||||
height += fullscreen_spacing + fullscreen_spacing_top
|
||||
|
||||
|
|
@ -122,21 +122,29 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
self._fullscreen_checkbox: bui.Widget | None = None
|
||||
if self._show_fullscreen:
|
||||
v -= fullscreen_spacing_top
|
||||
self._fullscreen_checkbox = ConfigCheckBox(
|
||||
# Fullscreen control does not necessarily talk to the
|
||||
# app config so we have to wrangle it manually instead of
|
||||
# using a config-checkbox.
|
||||
label = bui.Lstr(resource=f'{self._r}.fullScreenText')
|
||||
|
||||
# Show keyboard shortcut alongside the control if they
|
||||
# provide one.
|
||||
shortcut = bui.fullscreen_control_key_shortcut()
|
||||
if shortcut is not None:
|
||||
label = bui.Lstr(
|
||||
value='$(NAME) [$(SHORTCUT)]',
|
||||
subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)],
|
||||
)
|
||||
self._fullscreen_checkbox = bui.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(100, v),
|
||||
maxwidth=200,
|
||||
value=bui.fullscreen_control_get(),
|
||||
on_value_change_call=bui.fullscreen_control_set,
|
||||
maxwidth=250,
|
||||
size=(300, 30),
|
||||
configkey='Fullscreen',
|
||||
displayname=bui.Lstr(
|
||||
resource=self._r
|
||||
+ (
|
||||
'.fullScreenCmdText'
|
||||
if app.classic.platform == 'mac'
|
||||
else '.fullScreenCtrlText'
|
||||
)
|
||||
),
|
||||
).widget
|
||||
text=label,
|
||||
)
|
||||
|
||||
if not self._have_selected_child:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget,
|
||||
|
|
@ -259,9 +267,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
bui.Lstr(resource='nativeText'),
|
||||
]
|
||||
for res in [1440, 1080, 960, 720, 480]:
|
||||
# Nav bar is 72px so lets allow for that in what
|
||||
# choices we show.
|
||||
if native_res[1] >= res - 72:
|
||||
if native_res[1] >= res:
|
||||
res_str = f'{res}p'
|
||||
choices.append(res_str)
|
||||
choices_display.append(bui.Lstr(value=res_str))
|
||||
|
|
@ -430,6 +436,10 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.settings import allsettings
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Applying max-fps takes a few moments. Apply if it hasn't been
|
||||
# yet.
|
||||
self._apply_max_fps()
|
||||
|
|
@ -441,7 +451,8 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
allsettings.AllSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _set_quality(self, quality: str) -> None:
|
||||
|
|
@ -528,8 +539,10 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
and bui.apptime() - self._last_max_fps_set_time > 1.0
|
||||
):
|
||||
self._apply_max_fps()
|
||||
|
||||
if self._show_fullscreen:
|
||||
# Keep the fullscreen checkbox up to date with the current value.
|
||||
bui.checkboxwidget(
|
||||
edit=self._fullscreen_checkbox,
|
||||
value=bui.app.config.resolve('Fullscreen'),
|
||||
value=bui.fullscreen_control_get(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -213,6 +213,12 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
scale=1.0,
|
||||
)
|
||||
|
||||
def _pretty_button_name(self, button_name: str) -> bui.Lstr:
|
||||
button_id = self._settings[button_name]
|
||||
if button_id == -1:
|
||||
return bs.Lstr(resource='configGamepadWindow.unsetText')
|
||||
return self._input.get_button_name(button_id)
|
||||
|
||||
def _capture_button(
|
||||
self,
|
||||
pos: tuple[float, float],
|
||||
|
|
@ -250,7 +256,7 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
v_align='top',
|
||||
scale=uiscale,
|
||||
maxwidth=maxwidth,
|
||||
text=self._input.get_button_name(self._settings[button]),
|
||||
text=self._pretty_button_name(button),
|
||||
)
|
||||
bui.buttonwidget(
|
||||
edit=btn,
|
||||
|
|
@ -265,15 +271,24 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
def _cancel(self) -> None:
|
||||
from bauiv1lib.settings.controls import ControlsSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _save(self) -> None:
|
||||
from bauiv1lib.settings.controls import ControlsSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
assert bui.app.classic is not None
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
bui.getsound('gunCocking').play()
|
||||
|
|
@ -308,7 +323,8 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
)
|
||||
bui.app.config.apply_and_commit()
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -135,8 +135,14 @@ class NetTestingWindow(bui.Window):
|
|||
|
||||
def _show_val_testing(self) -> None:
|
||||
assert bui.app.classic is not None
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
NetValTestingWindow().get_root_widget()
|
||||
NetValTestingWindow().get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
|
|
@ -144,9 +150,14 @@ class NetTestingWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
|
||||
|
|
|
|||
|
|
@ -110,11 +110,11 @@ class PluginWindow(bui.Window):
|
|||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 38),
|
||||
position=(self._width * 0.5, self._height - 41),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource='pluginsText'),
|
||||
color=app.ui_v1.title_color,
|
||||
maxwidth=200,
|
||||
maxwidth=170,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
|
@ -129,6 +129,15 @@ class PluginWindow(bui.Window):
|
|||
|
||||
settings_button_x = 670 if uiscale is bui.UIScale.SMALL else 570
|
||||
|
||||
self._num_plugins_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(settings_button_x - 130, self._height - 41),
|
||||
size=(0, 0),
|
||||
text='',
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
self._category_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
scale=0.7,
|
||||
|
|
@ -174,6 +183,17 @@ class PluginWindow(bui.Window):
|
|||
)
|
||||
bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
|
||||
self._no_plugins_installed_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.5),
|
||||
size=(0, 0),
|
||||
text='',
|
||||
color=(0.6, 0.6, 0.6),
|
||||
scale=0.8,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
if bui.app.meta.scanresults is None:
|
||||
bui.screenmessage(
|
||||
'Still scanning plugins; please try again.', color=(1, 0, 0)
|
||||
|
|
@ -212,11 +232,16 @@ class PluginWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.pluginsettings import PluginSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PluginSettingsWindow(transition='in_right').get_root_widget()
|
||||
PluginSettingsWindow(transition='in_right').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _show_category_options(self) -> None:
|
||||
|
|
@ -263,6 +288,7 @@ class PluginWindow(bui.Window):
|
|||
def _show_plugins(self) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
plugspecs = bui.app.plugins.plugin_specs
|
||||
plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
|
||||
assert isinstance(plugstates, dict)
|
||||
|
|
@ -274,6 +300,11 @@ class PluginWindow(bui.Window):
|
|||
|
||||
plugspecs_sorted = sorted(plugspecs.items())
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._no_plugins_installed_text,
|
||||
text='',
|
||||
)
|
||||
|
||||
for _classpath, plugspec in plugspecs_sorted:
|
||||
# counting number of enabled and disabled plugins
|
||||
# plugstate = plugstates.setdefault(plugspec[0], {})
|
||||
|
|
@ -372,6 +403,17 @@ class PluginWindow(bui.Window):
|
|||
bui.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40)
|
||||
num_shown += 1
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._num_plugins_text,
|
||||
text=str(num_shown),
|
||||
)
|
||||
|
||||
if num_shown == 0:
|
||||
bui.textwidget(
|
||||
edit=self._no_plugins_installed_text,
|
||||
text=bui.Lstr(resource='noPluginsInstalledText'),
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
|
|
@ -412,11 +454,16 @@ class PluginWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -161,10 +161,15 @@ class PluginSettingsWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.plugins import PluginWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
PluginWindow(transition='in_left').get_root_widget()
|
||||
PluginWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -138,10 +138,15 @@ class RemoteAppSettingsWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.settings import controls
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -217,6 +217,10 @@ class TestingWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
backwin = (
|
||||
self._back_call()
|
||||
|
|
@ -224,4 +228,6 @@ class TestingWindow(bui.Window):
|
|||
else AdvancedSettingsWindow(transition='in_left')
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(backwin.get_root_widget())
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
backwin.get_root_widget(), from_window=self._root_widget
|
||||
)
|
||||
|
|
|
|||
|
|
@ -276,11 +276,16 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
def _back(self) -> None:
|
||||
from bauiv1lib.settings import controls
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
bs.set_touchscreen_editing(False)
|
||||
|
|
|
|||
|
|
@ -394,13 +394,18 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings import audio
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
audio.AudioSettingsWindow(transition='in_left').get_root_widget()
|
||||
audio.AudioSettingsWindow(transition='in_left').get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _edit_soundtrack_with_sound(self) -> None:
|
||||
|
|
@ -421,6 +426,10 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
from bauiv1lib.purchase import PurchaseWindow
|
||||
from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
if (
|
||||
bui.app.classic is not None
|
||||
and not bui.app.classic.accounts.have_pro_options()
|
||||
|
|
@ -443,7 +452,8 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
bui.app.ui_v1.set_main_menu_window(
|
||||
SoundtrackEditWindow(
|
||||
existing_soundtrack=self._selected_soundtrack
|
||||
).get_root_widget()
|
||||
).get_root_widget(),
|
||||
from_window=self._root_widget,
|
||||
)
|
||||
|
||||
def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue