mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-10-20 00:00:39 +00:00
updating ba_data
This commit is contained in:
parent
b0b6865bdf
commit
48af420a73
92 changed files with 3174 additions and 1075 deletions
2
dist/ba_data/python/babase/__init__.py
vendored
2
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -27,6 +27,7 @@ from _babase import (
|
|||
apptime,
|
||||
apptimer,
|
||||
AppTimer,
|
||||
asset_loads_allowed,
|
||||
fullscreen_control_available,
|
||||
fullscreen_control_get,
|
||||
fullscreen_control_key_shortcut,
|
||||
|
|
@ -207,6 +208,7 @@ __all__ = [
|
|||
'apptime',
|
||||
'apptimer',
|
||||
'AppTimer',
|
||||
'asset_loads_allowed',
|
||||
'Call',
|
||||
'fullscreen_control_available',
|
||||
'fullscreen_control_get',
|
||||
|
|
|
|||
4
dist/ba_data/python/babase/_accountv2.py
vendored
4
dist/ba_data/python/babase/_accountv2.py
vendored
|
|
@ -6,9 +6,9 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import logging
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
from efro.call import tpartial
|
||||
from efro.error import CommunicationError
|
||||
from bacommon.login import LoginType
|
||||
import _babase
|
||||
|
|
@ -223,7 +223,7 @@ class AccountV2Subsystem:
|
|||
if service_str is not None:
|
||||
_babase.apptimer(
|
||||
2.0,
|
||||
tpartial(
|
||||
partial(
|
||||
_babase.screenmessage,
|
||||
Lstr(
|
||||
resource='notUsingAccountText',
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/_app.py
vendored
10
dist/ba_data/python/babase/_app.py
vendored
|
|
@ -7,13 +7,11 @@ from __future__ import annotations
|
|||
import os
|
||||
import logging
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, TypeVar, override
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from threading import RLock
|
||||
|
||||
|
||||
from efro.call import tpartial
|
||||
|
||||
import _babase
|
||||
from babase._language import LanguageSubsystem
|
||||
from babase._plugin import PluginSubsystem
|
||||
|
|
@ -512,7 +510,7 @@ class App:
|
|||
|
||||
# Do the actual work of calcing our app-mode/etc. in a bg thread
|
||||
# since it may block for a moment to load modules/etc.
|
||||
self.threadpool_submit_no_wait(tpartial(self._set_intent, intent))
|
||||
self.threadpool_submit_no_wait(partial(self._set_intent, intent))
|
||||
|
||||
def push_apply_app_config(self) -> None:
|
||||
"""Internal. Use app.config.apply() to apply app config changes."""
|
||||
|
|
@ -644,13 +642,13 @@ class App:
|
|||
# kick back to the logic thread to apply.
|
||||
mode = modetype()
|
||||
_babase.pushcall(
|
||||
tpartial(self._apply_intent, intent, mode),
|
||||
partial(self._apply_intent, intent, mode),
|
||||
from_other_thread=True,
|
||||
)
|
||||
except Exception:
|
||||
logging.exception('Error setting app intent to %s.', intent)
|
||||
_babase.pushcall(
|
||||
tpartial(self._display_set_intent_error, intent),
|
||||
partial(self._display_set_intent_error, intent),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
||||
|
|
|
|||
6
dist/ba_data/python/babase/_apputils.py
vendored
6
dist/ba_data/python/babase/_apputils.py
vendored
|
|
@ -7,10 +7,10 @@ import gc
|
|||
import os
|
||||
import logging
|
||||
from threading import Thread
|
||||
from functools import partial
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from efro.call import tpartial
|
||||
from efro.log import LogLevel
|
||||
from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ import _babase
|
|||
from babase._appsubsystem import AppSubsystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, TextIO
|
||||
from typing import Any, TextIO, Callable
|
||||
|
||||
import babase
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ def dump_app_state(
|
|||
# We want this to work from any thread, so need to kick this part
|
||||
# over to the logic thread so timer works.
|
||||
_babase.pushcall(
|
||||
tpartial(_babase.apptimer, delay + 1.0, log_dumped_app_state),
|
||||
partial(_babase.apptimer, delay + 1.0, log_dumped_app_state),
|
||||
from_other_thread=True,
|
||||
suppress_other_thread_warning=True,
|
||||
)
|
||||
|
|
|
|||
31
dist/ba_data/python/babase/_general.py
vendored
31
dist/ba_data/python/babase/_general.py
vendored
|
|
@ -3,6 +3,7 @@
|
|||
"""Utility snippets applying to generic Python code."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import types
|
||||
import weakref
|
||||
import random
|
||||
|
|
@ -15,8 +16,8 @@ from efro.terminal import Clr
|
|||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import functools
|
||||
from typing import Any
|
||||
from efro.call import Call as Call # 'as Call' so we re-export.
|
||||
|
||||
|
||||
# Declare distinct types for different time measurements we use so the
|
||||
|
|
@ -66,7 +67,9 @@ def existing(obj: ExistableT | None) -> ExistableT | None:
|
|||
return obj if obj is not None and obj.exists() else None
|
||||
|
||||
|
||||
def getclass(name: str, subclassof: type[T]) -> type[T]:
|
||||
def getclass(
|
||||
name: str, subclassof: type[T], check_sdlib_modulename_clash: bool = False
|
||||
) -> type[T]:
|
||||
"""Given a full class name such as foo.bar.MyClass, return the class.
|
||||
|
||||
Category: **General Utility Functions**
|
||||
|
|
@ -79,6 +82,8 @@ def getclass(name: str, subclassof: type[T]) -> type[T]:
|
|||
splits = name.split('.')
|
||||
modulename = '.'.join(splits[:-1])
|
||||
classname = splits[-1]
|
||||
if modulename in sys.stdlib_module_names and check_sdlib_modulename_clash:
|
||||
raise Exception(f'{modulename} is an inbuilt module.')
|
||||
module = importlib.import_module(modulename)
|
||||
cls: type = getattr(module, classname)
|
||||
|
||||
|
|
@ -166,7 +171,7 @@ class _WeakCall:
|
|||
if not self._did_invalid_call_warning:
|
||||
logging.warning(
|
||||
'Warning: callable passed to babase.WeakCall() is not'
|
||||
' weak-referencable (%s); use babase.Call() instead'
|
||||
' weak-referencable (%s); use functools.partial instead'
|
||||
' to avoid this warning.',
|
||||
stack_info=True,
|
||||
)
|
||||
|
|
@ -199,9 +204,13 @@ class _Call:
|
|||
The callable is strong-referenced so it won't die until this
|
||||
object does.
|
||||
|
||||
WARNING: This is exactly the same as Python's built in functools.partial().
|
||||
Use functools.partial instead of this for new code, as this will probably
|
||||
be deprecated at some point.
|
||||
|
||||
Note that a bound method (ex: ``myobj.dosomething``) contains a reference
|
||||
to ``self`` (``myobj`` in that case), so you will be keeping that object
|
||||
alive too. Use babase.WeakCall if you want to pass a method to callback
|
||||
alive too. Use babase.WeakCall if you want to pass a method to a callback
|
||||
without keeping its object alive.
|
||||
"""
|
||||
|
||||
|
|
@ -239,12 +248,16 @@ class _Call:
|
|||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Some interaction between our ballistica pylint plugin
|
||||
# and this code is crashing starting on pylint 2.15.0.
|
||||
# This seems to fix things for now.
|
||||
# For type-checking, point at functools.partial which gives us full
|
||||
# type checking on both positional and keyword arguments (as of mypy
|
||||
# 1.11).
|
||||
|
||||
# Note: Something here is wonky with pylint, possibly related to our
|
||||
# custom pylint plugin. Disabling all checks seems to fix it.
|
||||
# pylint: disable=all
|
||||
WeakCall = Call
|
||||
Call = Call
|
||||
|
||||
WeakCall = functools.partial
|
||||
Call = functools.partial
|
||||
else:
|
||||
WeakCall = _WeakCall
|
||||
WeakCall.__name__ = 'WeakCall'
|
||||
|
|
|
|||
123
dist/ba_data/python/babase/_language.py
vendored
123
dist/ba_data/python/babase/_language.py
vendored
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, overload, override
|
||||
|
||||
import _babase
|
||||
|
|
@ -32,6 +33,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||
self._language: str | None = None
|
||||
self._language_target: AttrDict | None = None
|
||||
self._language_merged: AttrDict | None = None
|
||||
self._test_timer: babase.AppTimer | None = None
|
||||
|
||||
@property
|
||||
def locale(self) -> str:
|
||||
|
|
@ -93,9 +95,44 @@ class LanguageSubsystem(AppSubsystem):
|
|||
name for name in names if self._can_display_language(name)
|
||||
)
|
||||
|
||||
def testlanguage(self, langid: str) -> None:
|
||||
"""Set the app to test an in-progress language.
|
||||
|
||||
Pass a language id from the translation editor website as 'langid';
|
||||
something like 'Gibberish_3263'. Once set to testing, the engine
|
||||
will repeatedly download and apply that same test language, so
|
||||
changes can be made to it and observed live.
|
||||
"""
|
||||
print(
|
||||
f'Language test mode enabled.'
|
||||
f' Will fetch and apply \'{langid}\' every 5 seconds,'
|
||||
f' so you can see your changes live.'
|
||||
)
|
||||
self._test_timer = _babase.AppTimer(
|
||||
5.0, partial(self._update_test_language, langid), repeat=True
|
||||
)
|
||||
self._update_test_language(langid)
|
||||
|
||||
def _on_test_lang_response(
|
||||
self, langid: str, response: None | dict[str, Any]
|
||||
) -> None:
|
||||
if response is None:
|
||||
return
|
||||
self.setlanguage(response)
|
||||
print(f'Fetched and applied {langid}.')
|
||||
|
||||
def _update_test_language(self, langid: str) -> None:
|
||||
if _babase.app.classic is None:
|
||||
raise RuntimeError('This requires classic.')
|
||||
_babase.app.classic.master_server_v1_get(
|
||||
'bsLangGet',
|
||||
{'lang': langid, 'format': 'json'},
|
||||
partial(self._on_test_lang_response, langid),
|
||||
)
|
||||
|
||||
def setlanguage(
|
||||
self,
|
||||
language: str | None,
|
||||
language: str | dict | None,
|
||||
print_change: bool = True,
|
||||
store_to_config: bool = True,
|
||||
) -> None:
|
||||
|
|
@ -110,18 +147,6 @@ class LanguageSubsystem(AppSubsystem):
|
|||
cfg = _babase.app.config
|
||||
cur_language = cfg.get('Lang', None)
|
||||
|
||||
# Store this in the config if its changing.
|
||||
if language != cur_language and store_to_config:
|
||||
if language is None:
|
||||
if 'Lang' in cfg:
|
||||
del cfg['Lang'] # Clear it out for default.
|
||||
else:
|
||||
cfg['Lang'] = language
|
||||
cfg.commit()
|
||||
switched = True
|
||||
else:
|
||||
switched = False
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
_babase.app.env.data_directory,
|
||||
|
|
@ -134,32 +159,55 @@ class LanguageSubsystem(AppSubsystem):
|
|||
) as infile:
|
||||
lenglishvalues = json.loads(infile.read())
|
||||
|
||||
# None implies default.
|
||||
if language is None:
|
||||
language = self.default_language
|
||||
try:
|
||||
if language == 'English':
|
||||
lmodvalues = None
|
||||
else:
|
||||
lmodfile = os.path.join(
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
language.lower() + '.json',
|
||||
)
|
||||
with open(lmodfile, encoding='utf-8') as infile:
|
||||
lmodvalues = json.loads(infile.read())
|
||||
except Exception:
|
||||
logging.exception("Error importing language '%s'.", language)
|
||||
_babase.screenmessage(
|
||||
f"Error setting language to '{language}'; see log for details.",
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
# Special case - passing a complete dict for testing.
|
||||
if isinstance(language, dict):
|
||||
self._language = 'Custom'
|
||||
lmodvalues = language
|
||||
switched = False
|
||||
lmodvalues = None
|
||||
print_change = False
|
||||
store_to_config = False
|
||||
else:
|
||||
# Ok, we're setting a real language.
|
||||
|
||||
self._language = language
|
||||
# Store this in the config if its changing.
|
||||
if language != cur_language and store_to_config:
|
||||
if language is None:
|
||||
if 'Lang' in cfg:
|
||||
del cfg['Lang'] # Clear it out for default.
|
||||
else:
|
||||
cfg['Lang'] = language
|
||||
cfg.commit()
|
||||
switched = True
|
||||
else:
|
||||
switched = False
|
||||
|
||||
# None implies default.
|
||||
if language is None:
|
||||
language = self.default_language
|
||||
try:
|
||||
if language == 'English':
|
||||
lmodvalues = None
|
||||
else:
|
||||
lmodfile = os.path.join(
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
language.lower() + '.json',
|
||||
)
|
||||
with open(lmodfile, encoding='utf-8') as infile:
|
||||
lmodvalues = json.loads(infile.read())
|
||||
except Exception:
|
||||
logging.exception("Error importing language '%s'.", language)
|
||||
_babase.screenmessage(
|
||||
f"Error setting language to '{language}';"
|
||||
f' see log for details.',
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
switched = False
|
||||
lmodvalues = None
|
||||
|
||||
self._language = language
|
||||
|
||||
# Create an attrdict of *just* our target language.
|
||||
self._language_target = AttrDict()
|
||||
|
|
@ -207,6 +255,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||
random_names = [n for n in random_names if n != '']
|
||||
_babase.set_internal_language_keys(internal_vals, random_names)
|
||||
if switched and print_change:
|
||||
assert isinstance(language, str)
|
||||
_babase.screenmessage(
|
||||
Lstr(
|
||||
resource='languageSetText',
|
||||
|
|
|
|||
17
dist/ba_data/python/babase/_login.py
vendored
17
dist/ba_data/python/babase/_login.py
vendored
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||
|
||||
import time
|
||||
import logging
|
||||
from functools import partial
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, final, override
|
||||
|
||||
|
|
@ -162,8 +163,8 @@ class LoginAdapter:
|
|||
the adapter will attempt to sign in if possible. An exception will
|
||||
be returned if the sign-in attempt fails.
|
||||
"""
|
||||
|
||||
assert _babase.in_logic_thread()
|
||||
from babase._general import Call
|
||||
|
||||
# Have been seeing multiple sign-in attempts come through
|
||||
# nearly simultaneously which can be problematic server-side.
|
||||
|
|
@ -185,7 +186,7 @@ class LoginAdapter:
|
|||
appnow,
|
||||
)
|
||||
_babase.pushcall(
|
||||
Call(
|
||||
partial(
|
||||
result_cb,
|
||||
self,
|
||||
RuntimeError('sign_in called too soon after last.'),
|
||||
|
|
@ -215,7 +216,7 @@ class LoginAdapter:
|
|||
self.login_type.name,
|
||||
)
|
||||
_babase.pushcall(
|
||||
Call(
|
||||
partial(
|
||||
result_cb,
|
||||
self,
|
||||
RuntimeError('fetch-sign-in-token failed.'),
|
||||
|
|
@ -245,7 +246,7 @@ class LoginAdapter:
|
|||
self.login_type.name,
|
||||
response,
|
||||
)
|
||||
_babase.pushcall(Call(result_cb, self, response))
|
||||
_babase.pushcall(partial(result_cb, self, response))
|
||||
else:
|
||||
# This means our credentials were explicitly rejected.
|
||||
if response.credentials is None:
|
||||
|
|
@ -262,7 +263,7 @@ class LoginAdapter:
|
|||
result2 = self.SignInResult(
|
||||
credentials=response.credentials
|
||||
)
|
||||
_babase.pushcall(Call(result_cb, self, result2))
|
||||
_babase.pushcall(partial(result_cb, self, result2))
|
||||
|
||||
assert _babase.app.plus is not None
|
||||
_babase.app.plus.cloud.send_message_cb(
|
||||
|
|
@ -293,10 +294,9 @@ class LoginAdapter:
|
|||
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
|
||||
|
||||
# Default implementation simply fails immediately.
|
||||
_babase.pushcall(Call(completion_cb, None))
|
||||
_babase.pushcall(partial(completion_cb, None))
|
||||
|
||||
def _update_implicit_login_state(self) -> None:
|
||||
# If we've received an implicit login state, schedule it to be
|
||||
|
|
@ -304,7 +304,6 @@ class LoginAdapter:
|
|||
# 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:
|
||||
from babase._general import Call
|
||||
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
|
|
@ -315,7 +314,7 @@ class LoginAdapter:
|
|||
|
||||
assert _babase.app.plus is not None
|
||||
_babase.pushcall(
|
||||
Call(
|
||||
partial(
|
||||
_babase.app.plus.accounts.on_implicit_login_state_changed,
|
||||
self.login_type,
|
||||
self._implicit_login_state,
|
||||
|
|
|
|||
8
dist/ba_data/python/babase/_meta.py
vendored
8
dist/ba_data/python/babase/_meta.py
vendored
|
|
@ -7,12 +7,12 @@ from __future__ import annotations
|
|||
import os
|
||||
import time
|
||||
import logging
|
||||
from threading import Thread
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from efro.call import tpartial
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -116,7 +116,7 @@ class MetadataSubsystem:
|
|||
loading work happens, pass completion_cb_in_bg_thread=True.
|
||||
"""
|
||||
Thread(
|
||||
target=tpartial(
|
||||
target=partial(
|
||||
self._load_exported_classes,
|
||||
cls,
|
||||
completion_cb,
|
||||
|
|
@ -145,7 +145,7 @@ class MetadataSubsystem:
|
|||
except Exception:
|
||||
logging.exception('Error loading exported classes.')
|
||||
|
||||
completion_call = tpartial(completion_cb, classes)
|
||||
completion_call = partial(completion_cb, classes)
|
||||
if completion_cb_in_bg_thread:
|
||||
completion_call()
|
||||
else:
|
||||
|
|
|
|||
2
dist/ba_data/python/babase/_mgen/enums.py
vendored
2
dist/ba_data/python/babase/_mgen/enums.py
vendored
|
|
@ -126,7 +126,7 @@ class SpecialChar(Enum):
|
|||
OUYA_BUTTON_U = 23
|
||||
OUYA_BUTTON_Y = 24
|
||||
OUYA_BUTTON_A = 25
|
||||
OUYA_LOGO = 26
|
||||
TOKEN = 26
|
||||
LOGO = 27
|
||||
TICKET = 28
|
||||
GOOGLE_PLAY_GAMES_LOGO = 29
|
||||
|
|
|
|||
50
dist/ba_data/python/babase/_net.py
vendored
50
dist/ba_data/python/babase/_net.py
vendored
|
|
@ -4,11 +4,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ssl
|
||||
import socket
|
||||
import threading
|
||||
import ipaddress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import socket
|
||||
pass
|
||||
|
||||
# Timeout for standard functions talking to the master-server/etc.
|
||||
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
|
||||
|
|
@ -18,6 +20,10 @@ class NetworkSubsystem:
|
|||
"""Network related app subsystem."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Our shared SSL context. Creating these can be expensive so we
|
||||
# create it here once and recycle for our various connections.
|
||||
self.sslcontext = ssl.create_default_context()
|
||||
|
||||
# Anyone accessing/modifying zone_pings should hold this lock,
|
||||
# as it is updated by a background thread.
|
||||
self.zone_pings_lock = threading.Lock()
|
||||
|
|
@ -27,8 +33,6 @@ class NetworkSubsystem:
|
|||
# that a nearby server has been pinged.
|
||||
self.zone_pings: dict[str, float] = {}
|
||||
|
||||
self._sslcontext: ssl.SSLContext | None = None
|
||||
|
||||
# For debugging.
|
||||
self.v1_test_log: str = ''
|
||||
self.v1_ctest_results: dict[int, str] = {}
|
||||
|
|
@ -36,42 +40,12 @@ class NetworkSubsystem:
|
|||
self.transport_state = 'uninited'
|
||||
self.server_time_offset_hours: float | None = None
|
||||
|
||||
@property
|
||||
def sslcontext(self) -> ssl.SSLContext:
|
||||
"""Create/return our shared SSLContext.
|
||||
|
||||
This can be reused for all standard urllib requests/etc.
|
||||
"""
|
||||
# Note: I've run into older Android devices taking upwards of 1 second
|
||||
# to put together a default SSLContext, so recycling one can definitely
|
||||
# be a worthwhile optimization. This was suggested to me in this
|
||||
# thread by one of Python's SSL maintainers:
|
||||
# https://github.com/python/cpython/issues/94637
|
||||
if self._sslcontext is None:
|
||||
self._sslcontext = ssl.create_default_context()
|
||||
return self._sslcontext
|
||||
|
||||
|
||||
def get_ip_address_type(addr: str) -> socket.AddressFamily:
|
||||
"""Return socket.AF_INET6 or socket.AF_INET4 for the provided address."""
|
||||
import socket
|
||||
|
||||
socket_type = None
|
||||
|
||||
# First try it as an ipv4 address.
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, addr)
|
||||
socket_type = socket.AF_INET
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Hmm apparently not ipv4; try ipv6.
|
||||
if socket_type is None:
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, addr)
|
||||
socket_type = socket.AF_INET6
|
||||
except OSError:
|
||||
pass
|
||||
if socket_type is None:
|
||||
raise ValueError(f'addr seems to be neither v4 or v6: {addr}')
|
||||
return socket_type
|
||||
version = ipaddress.ip_address(addr).version
|
||||
if version == 4:
|
||||
return socket.AF_INET
|
||||
assert version == 6
|
||||
return socket.AF_INET6
|
||||
|
|
|
|||
2
dist/ba_data/python/babase/_plugin.py
vendored
2
dist/ba_data/python/babase/_plugin.py
vendored
|
|
@ -278,7 +278,7 @@ class PluginSpec:
|
|||
if not self.loadable:
|
||||
return None
|
||||
try:
|
||||
cls = getclass(self.class_path, Plugin)
|
||||
cls = getclass(self.class_path, Plugin, True)
|
||||
except Exception as exc:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/_workspace.py
vendored
10
dist/ba_data/python/babase/_workspace.py
vendored
|
|
@ -9,9 +9,9 @@ import sys
|
|||
import logging
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.call import tpartial
|
||||
from efro.error import CleanError
|
||||
import _babase
|
||||
import bacommon.cloud
|
||||
|
|
@ -118,7 +118,7 @@ class WorkspaceSubsystem:
|
|||
state.iteration += 1
|
||||
|
||||
_babase.pushcall(
|
||||
tpartial(
|
||||
partial(
|
||||
self._successmsg,
|
||||
Lstr(
|
||||
resource='activatedText',
|
||||
|
|
@ -130,7 +130,7 @@ class WorkspaceSubsystem:
|
|||
|
||||
except _SkipSyncError:
|
||||
_babase.pushcall(
|
||||
tpartial(
|
||||
partial(
|
||||
self._errmsg,
|
||||
Lstr(
|
||||
resource='workspaceSyncReuseText',
|
||||
|
|
@ -145,7 +145,7 @@ class WorkspaceSubsystem:
|
|||
# be in wonky state.
|
||||
set_path = False
|
||||
_babase.pushcall(
|
||||
tpartial(self._errmsg, Lstr(value=str(exc))),
|
||||
partial(self._errmsg, Lstr(value=str(exc))),
|
||||
from_other_thread=True,
|
||||
)
|
||||
except Exception:
|
||||
|
|
@ -153,7 +153,7 @@ class WorkspaceSubsystem:
|
|||
set_path = False
|
||||
logging.exception("Error syncing workspace '%s'.", workspacename)
|
||||
_babase.pushcall(
|
||||
tpartial(
|
||||
partial(
|
||||
self._errmsg,
|
||||
Lstr(
|
||||
resource='workspaceSyncErrorText',
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/modutils.py
vendored
10
dist/ba_data/python/babase/modutils.py
vendored
|
|
@ -133,7 +133,10 @@ def create_user_system_scripts() -> None:
|
|||
if env.python_directory_app is None:
|
||||
raise RuntimeError('app python dir unset')
|
||||
|
||||
path = f'{env.python_directory_user}/sys/{env.engine_version}'
|
||||
path = (
|
||||
f'{env.python_directory_user}/sys/'
|
||||
f'{env.engine_version}_{env.engine_build_number}'
|
||||
)
|
||||
pathtmp = path + '_tmp'
|
||||
if os.path.exists(path):
|
||||
print('Delete Existing User Scripts first!')
|
||||
|
|
@ -181,7 +184,10 @@ def delete_user_system_scripts() -> None:
|
|||
if env.python_directory_user is None:
|
||||
raise RuntimeError('user python dir unset')
|
||||
|
||||
path = f'{env.python_directory_user}/sys/{env.engine_version}'
|
||||
path = (
|
||||
f'{env.python_directory_user}/sys/'
|
||||
f'{env.engine_version}_{env.engine_build_number}'
|
||||
)
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
print('User system scripts deleted.')
|
||||
|
|
|
|||
5
dist/ba_data/python/baclassic/_accountv1.py
vendored
5
dist/ba_data/python/baclassic/_accountv1.py
vendored
|
|
@ -271,7 +271,10 @@ class AccountV1Subsystem:
|
|||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
babase.getsimplesound('click01').play()
|
||||
# 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('click01').play()
|
||||
|
||||
def on_account_state_changed(self) -> None:
|
||||
"""(internal)"""
|
||||
|
|
|
|||
12
dist/ba_data/python/baclassic/_net.py
vendored
12
dist/ba_data/python/baclassic/_net.py
vendored
|
|
@ -96,13 +96,9 @@ class MasterServerV1CallThread(threading.Thread):
|
|||
self._data = babase.utf8_all(self._data)
|
||||
babase.set_thread_name('BA_ServerCallThread')
|
||||
if self._request_type == 'get':
|
||||
url = (
|
||||
plus.get_master_server_address()
|
||||
+ '/'
|
||||
+ self._request
|
||||
+ '?'
|
||||
+ urllib.parse.urlencode(self._data)
|
||||
)
|
||||
msaddr = plus.get_master_server_address()
|
||||
dataenc = urllib.parse.urlencode(self._data)
|
||||
url = f'{msaddr}/{self._request}?{dataenc}'
|
||||
assert url is not None
|
||||
response = urllib.request.urlopen(
|
||||
urllib.request.Request(
|
||||
|
|
@ -114,7 +110,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||
timeout=babase.DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
elif self._request_type == 'post':
|
||||
url = plus.get_master_server_address() + '/' + self._request
|
||||
url = f'{plus.get_master_server_address()}/{self._request}'
|
||||
assert url is not None
|
||||
response = urllib.request.urlopen(
|
||||
urllib.request.Request(
|
||||
|
|
|
|||
2
dist/ba_data/python/baclassic/_servermode.py
vendored
2
dist/ba_data/python/baclassic/_servermode.py
vendored
|
|
@ -460,6 +460,6 @@ class ServerController:
|
|||
bascenev1.new_host_session(sessiontype)
|
||||
|
||||
# Run an access check if we're trying to make a public party.
|
||||
if not self._ran_access_check:
|
||||
if not self._ran_access_check :
|
||||
self._run_access_check()
|
||||
self._ran_access_check = True
|
||||
|
|
|
|||
68
dist/ba_data/python/bacommon/bacloud.py
vendored
68
dist/ba_data/python/bacommon/bacloud.py
vendored
|
|
@ -14,7 +14,26 @@ if TYPE_CHECKING:
|
|||
|
||||
# Version is sent to the master-server with all commands. Can be incremented
|
||||
# if we need to change behavior server-side to go along with client changes.
|
||||
BACLOUD_VERSION = 8
|
||||
BACLOUD_VERSION = 13
|
||||
|
||||
|
||||
def asset_file_cache_path(filehash: str) -> str:
|
||||
"""Given a sha256 hex file hash, return a storage path."""
|
||||
|
||||
# We expect a 64 byte hex str with only lowercase letters and
|
||||
# numbers. Note to self: I considered base64 hashes to save space
|
||||
# but then remembered that lots of filesystems out there ignore case
|
||||
# so that would not end well.
|
||||
assert len(filehash) == 64
|
||||
assert filehash.islower()
|
||||
assert filehash.isalnum()
|
||||
|
||||
# Split into a few levels of directories to keep directory listings
|
||||
# and operations reasonable. This will give 256 top level dirs, each
|
||||
# with 256 subdirs. So if we have 65,536 files in our cache then
|
||||
# dirs will average 1 file each. That seems like a reasonable spread
|
||||
# I think.
|
||||
return f'{filehash[:2]}/{filehash[2:4]}/{filehash[4:]}'
|
||||
|
||||
|
||||
@ioprepped
|
||||
|
|
@ -32,7 +51,6 @@ class RequestData:
|
|||
@ioprepped
|
||||
@dataclass
|
||||
class ResponseData:
|
||||
# noinspection PyUnresolvedReferences
|
||||
"""Response sent from the bacloud server to the client.
|
||||
|
||||
Attributes:
|
||||
|
|
@ -49,11 +67,13 @@ class ResponseData:
|
|||
It should be added to end_command args as 'manifest'.
|
||||
uploads: If present, client should upload the requested files (arg1)
|
||||
individually to a server command (arg2) with provided args (arg3).
|
||||
uploads_inline: If present, a list of pathnames that should be base64
|
||||
gzipped and uploaded to an 'uploads_inline' dict in end_command args.
|
||||
uploads_inline: If present, a list of pathnames that should be gzipped
|
||||
and uploaded to an 'uploads_inline' bytes dict in end_command args.
|
||||
This should be limited to relatively small files.
|
||||
deletes: If present, file paths that should be deleted on the client.
|
||||
downloads_inline: If present, pathnames mapped to base64 gzipped data to
|
||||
downloads: If present, describes files the client should individually
|
||||
request from the server if not already present on the client.
|
||||
downloads_inline: If present, pathnames mapped to gzipped data to
|
||||
be written to the client. This should only be used for relatively
|
||||
small files as they are all included inline as part of the response.
|
||||
dir_prune_empty: If present, all empty dirs under this one should be
|
||||
|
|
@ -69,6 +89,39 @@ class ResponseData:
|
|||
end_command: If present, this command is run with these args at the end
|
||||
of response processing.
|
||||
"""
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Downloads:
|
||||
"""Info about downloads included in a response."""
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Entry:
|
||||
"""Individual download."""
|
||||
|
||||
path: Annotated[str, IOAttrs('p')]
|
||||
# Args include with this particular request (combined with
|
||||
# baseargs).
|
||||
args: Annotated[dict[str, str], IOAttrs('a')]
|
||||
# TODO: could add a hash here if we want the client to
|
||||
# verify hashes.
|
||||
|
||||
# If present, will be prepended to all entry paths via os.path.join.
|
||||
basepath: Annotated[str | None, IOAttrs('p')]
|
||||
|
||||
# Server command that should be called for each download. The
|
||||
# server command is expected to respond with a downloads_inline
|
||||
# containing a single 'default' entry. In the future this may
|
||||
# be expanded to a more streaming-friendly process.
|
||||
cmd: Annotated[str, IOAttrs('c')]
|
||||
|
||||
# Args that should be included with all download requests.
|
||||
baseargs: Annotated[dict[str, str], IOAttrs('a')]
|
||||
|
||||
# Everything that should be downloaded.
|
||||
entries: Annotated[list[Entry], IOAttrs('e')]
|
||||
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
|
||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
|
|
@ -87,8 +140,11 @@ class ResponseData:
|
|||
deletes: Annotated[
|
||||
list[str] | None, IOAttrs('dlt', store_default=False)
|
||||
] = None
|
||||
downloads: Annotated[
|
||||
Downloads | None, IOAttrs('dl', store_default=False)
|
||||
] = None
|
||||
downloads_inline: Annotated[
|
||||
dict[str, str] | None, IOAttrs('dinl', store_default=False)
|
||||
dict[str, bytes] | None, IOAttrs('dinl', store_default=False)
|
||||
] = None
|
||||
dir_prune_empty: Annotated[
|
||||
str | None, IOAttrs('dpe', store_default=False)
|
||||
|
|
|
|||
73
dist/ba_data/python/bacommon/cloud.py
vendored
73
dist/ba_data/python/bacommon/cloud.py
vendored
|
|
@ -16,6 +16,13 @@ if TYPE_CHECKING:
|
|||
pass
|
||||
|
||||
|
||||
class WebLocation(Enum):
|
||||
"""Set of places we can be directed on ballistica.net."""
|
||||
|
||||
ACCOUNT_EDITOR = 'e'
|
||||
ACCOUNT_DELETE_SECTION = 'd'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyRequestMessage(Message):
|
||||
|
|
@ -238,6 +245,10 @@ class SignInResponse(Response):
|
|||
class ManageAccountMessage(Message):
|
||||
"""Message asking for a manage-account url."""
|
||||
|
||||
weblocation: Annotated[WebLocation, IOAttrs('l')] = (
|
||||
WebLocation.ACCOUNT_EDITOR
|
||||
)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
|
|
@ -250,3 +261,65 @@ class ManageAccountResponse(Response):
|
|||
"""Here's that sign-in result you asked for, boss."""
|
||||
|
||||
url: Annotated[str | None, IOAttrs('u')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class StoreQueryMessage(Message):
|
||||
"""Message asking about purchasable stuff and store related state."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [StoreQueryResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class StoreQueryResponse(Response):
|
||||
"""Here's that store info you asked for, boss."""
|
||||
|
||||
class Result(Enum):
|
||||
"""Our overall result."""
|
||||
|
||||
SUCCESS = 's'
|
||||
ERROR = 'e'
|
||||
|
||||
@dataclass
|
||||
class Purchase:
|
||||
"""Info about a purchasable thing."""
|
||||
|
||||
purchaseid: Annotated[str, IOAttrs('id')]
|
||||
|
||||
# Overall result; all data is undefined if not SUCCESS.
|
||||
result: Annotated[Result, IOAttrs('r')]
|
||||
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
|
||||
available_purchases: Annotated[list[Purchase], IOAttrs('p')]
|
||||
token_info_url: Annotated[str, IOAttrs('tiu')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BSPrivatePartyMessage(Message):
|
||||
"""Message asking about info we need for private-party UI."""
|
||||
|
||||
need_datacode: Annotated[bool, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [BSPrivatePartyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BSPrivatePartyResponse(Response):
|
||||
"""Here's that private party UI info you asked for, boss."""
|
||||
|
||||
success: Annotated[bool, IOAttrs('s')]
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
datacode: Annotated[str | None, IOAttrs('d')]
|
||||
|
|
|
|||
12
dist/ba_data/python/bacommon/login.py
vendored
12
dist/ba_data/python/bacommon/login.py
vendored
|
|
@ -40,3 +40,15 @@ class LoginType(Enum):
|
|||
return 'Google Play Games'
|
||||
case cls.GAME_CENTER:
|
||||
return 'Game Center'
|
||||
|
||||
@property
|
||||
def displaynameshort(self) -> str:
|
||||
"""Human readable name for this value."""
|
||||
cls = type(self)
|
||||
match self:
|
||||
case cls.EMAIL:
|
||||
return 'Email'
|
||||
case cls.GPGS:
|
||||
return 'GPGS'
|
||||
case cls.GAME_CENTER:
|
||||
return 'Game Center'
|
||||
|
|
|
|||
1
dist/ba_data/python/bacommon/net.py
vendored
1
dist/ba_data/python/bacommon/net.py
vendored
|
|
@ -64,6 +64,7 @@ class PrivateHostingState:
|
|||
unavailable_error: str | None = None
|
||||
party_code: str | None = None
|
||||
tickets_to_host_now: int = 0
|
||||
tokens_to_host_now: int = 0
|
||||
minutes_until_free_host: float | None = None
|
||||
free_host_minutes_remaining: float | None = None
|
||||
|
||||
|
|
|
|||
3
dist/ba_data/python/bacommon/workspace/__init__.py
vendored
Normal file
3
dist/ba_data/python/bacommon/workspace/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Workspace functionality."""
|
||||
97
dist/ba_data/python/bacommon/workspace/assetsv1.py
vendored
Normal file
97
dist/ba_data/python/bacommon/workspace/assetsv1.py
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Public types for assets-v1 workspaces.
|
||||
|
||||
These types may only be used server-side, but they are exposed here
|
||||
for reference when setting workspace config data by hand or for use
|
||||
in client-side workspace modification tools. There may be advanced
|
||||
settings that are not accessible through the UI/etc.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AssetsV1GlobalVals:
|
||||
"""Global values for an assets_v1 workspace."""
|
||||
|
||||
base_assets: Annotated[
|
||||
str | None, IOAttrs('base_assets', store_default=False)
|
||||
] = None
|
||||
|
||||
base_assets_filter: Annotated[
|
||||
str, IOAttrs('base_assets_filter', store_default=False)
|
||||
] = ''
|
||||
|
||||
|
||||
class AssetsV1PathValsTypeID(Enum):
|
||||
"""Types of vals we can store for paths."""
|
||||
|
||||
TEX_V1 = 'tex_v1'
|
||||
|
||||
|
||||
class AssetsV1PathVals(IOMultiType[AssetsV1PathValsTypeID]):
|
||||
"""Top level class for path vals classes."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return 'type'
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> AssetsV1PathValsTypeID:
|
||||
# Require child classes to supply this themselves. If we
|
||||
# did a full type registry/lookup here it would require us
|
||||
# to import everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(
|
||||
cls, type_id: AssetsV1PathValsTypeID
|
||||
) -> type[AssetsV1PathVals]:
|
||||
# pylint: disable=cyclic-import
|
||||
out: type[AssetsV1PathVals]
|
||||
t = AssetsV1PathValsTypeID
|
||||
|
||||
if type_id is t.TEX_V1:
|
||||
out = AssetsV1PathValsTexV1
|
||||
else:
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
return out
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AssetsV1PathValsTexV1(AssetsV1PathVals):
|
||||
"""Path-specific values for an assets_v1 workspace path."""
|
||||
|
||||
class TextureQuality(Enum):
|
||||
"""Quality settings for our textures."""
|
||||
|
||||
LOW = 'low'
|
||||
MEDIUM = 'medium'
|
||||
HIGH = 'high'
|
||||
|
||||
# Just dummy testing values for now.
|
||||
texture_quality: Annotated[
|
||||
TextureQuality, IOAttrs('texture_quality', store_default=False)
|
||||
] = TextureQuality.MEDIUM
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> AssetsV1PathValsTypeID:
|
||||
return AssetsV1PathValsTypeID.TEX_V1
|
||||
20
dist/ba_data/python/baenv.py
vendored
20
dist/ba_data/python/baenv.py
vendored
|
|
@ -52,8 +52,8 @@ if TYPE_CHECKING:
|
|||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21879
|
||||
TARGET_BALLISTICA_VERSION = '1.7.35'
|
||||
TARGET_BALLISTICA_BUILD = 21949
|
||||
TARGET_BALLISTICA_VERSION = '1.7.37'
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -348,12 +348,16 @@ def _setup_paths(
|
|||
if user_python_dir is None:
|
||||
user_python_dir = str(Path(config_dir, 'mods'))
|
||||
|
||||
# Wherever our user_python_dir is, if we find a sys/FOO dir
|
||||
# under it where FOO matches our version, use that as our
|
||||
# app_python_dir. This allows modding built-in stuff on
|
||||
# platforms where there is no write access to said built-in
|
||||
# stuff.
|
||||
check_dir = Path(user_python_dir, 'sys', TARGET_BALLISTICA_VERSION)
|
||||
# Wherever our user_python_dir is, if we find a sys/FOO_BAR dir
|
||||
# under it where FOO matches our version and BAR matches our
|
||||
# build number, use that as our app_python_dir. This allows
|
||||
# modding built-in stuff on platforms where there is no write
|
||||
# access to said built-in stuff.
|
||||
check_dir = Path(
|
||||
user_python_dir,
|
||||
'sys',
|
||||
f'{TARGET_BALLISTICA_VERSION}_{TARGET_BALLISTICA_BUILD}',
|
||||
)
|
||||
try:
|
||||
if check_dir.is_dir():
|
||||
app_python_dir = str(check_dir)
|
||||
|
|
|
|||
22
dist/ba_data/python/baplus/_cloud.py
vendored
22
dist/ba_data/python/baplus/_cloud.py
vendored
|
|
@ -100,6 +100,24 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.cloud.StoreQueryMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.cloud.StoreQueryResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.cloud.BSPrivatePartyMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.cloud.BSPrivatePartyResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: Message,
|
||||
|
|
@ -194,6 +212,10 @@ def cloud_console_exec(code: str) -> None:
|
|||
except Exception:
|
||||
import traceback
|
||||
|
||||
# Note to self: Seems like we should just use
|
||||
# logging.exception() here. Except currently that winds up
|
||||
# triggering our cloud logging stuff so we'd probably want a
|
||||
# specific logger or whatnot to avoid that.
|
||||
apptime = babase.apptime()
|
||||
print(f'Exec error at time {apptime:.2f}.', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
|
|
|
|||
5
dist/ba_data/python/baplus/_subsystem.py
vendored
5
dist/ba_data/python/baplus/_subsystem.py
vendored
|
|
@ -254,6 +254,11 @@ class PlusSubsystem(AppSubsystem):
|
|||
"""(internal)"""
|
||||
return _baplus.tournament_query(callback, args)
|
||||
|
||||
@staticmethod
|
||||
def supports_purchases() -> bool:
|
||||
"""Does this platform support in-app-purchases?"""
|
||||
return _baplus.supports_purchases()
|
||||
|
||||
@staticmethod
|
||||
def have_incentivized_ad() -> bool:
|
||||
"""Is an incentivized ad available?"""
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import babase
|
|||
import _bascenev1
|
||||
from bascenev1._activity import Activity
|
||||
|
||||
# False-positive from pylint due to our class-generics-filter.
|
||||
from bascenev1._player import EmptyPlayer # pylint: disable=W0611
|
||||
from bascenev1._team import EmptyTeam # pylint: disable=W0611
|
||||
from bascenev1._player import EmptyPlayer
|
||||
from bascenev1._team import EmptyTeam
|
||||
from bascenev1._music import MusicType, setmusic
|
||||
|
||||
|
||||
|
|
|
|||
23
dist/ba_data/python/bascenev1lib/game/assault.py
vendored
23
dist/ba_data/python/bascenev1lib/game/assault.py
vendored
|
|
@ -244,8 +244,21 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
|
|||
bs.timer(0.5, light.delete)
|
||||
bs.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.5: 0})
|
||||
if player.actor:
|
||||
player.actor.handlemessage(
|
||||
bs.StandMessage(new_pos, random.uniform(0, 360))
|
||||
random_num = random.uniform(0, 360)
|
||||
|
||||
# Slightly hacky workaround: normally,
|
||||
# teleporting back to base with a sticky
|
||||
# bomb stuck to you gives a crazy whiplash
|
||||
# rubber-band effect. Running the teleport
|
||||
# twice in a row seems to suppress that
|
||||
# though. Would be better to fix this at a
|
||||
# lower level, but this works for now.
|
||||
self._teleport(player, new_pos, random_num)
|
||||
bs.timer(
|
||||
0.01,
|
||||
bs.Call(
|
||||
self._teleport, player, new_pos, random_num
|
||||
),
|
||||
)
|
||||
|
||||
# Have teammates celebrate.
|
||||
|
|
@ -258,6 +271,12 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
|
|||
if player_team.score >= self._score_to_win:
|
||||
self.end_game()
|
||||
|
||||
def _teleport(
|
||||
self, client: Player, pos: Sequence[float], num: float
|
||||
) -> None:
|
||||
if client.actor:
|
||||
client.actor.handlemessage(bs.StandMessage(pos, num))
|
||||
|
||||
@override
|
||||
def end_game(self) -> None:
|
||||
results = bs.GameResults()
|
||||
|
|
|
|||
86
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
86
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
|
|
@ -274,9 +274,9 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
|
||||
# Need different versions of this: taps/buttons/keys.
|
||||
txt = (
|
||||
bs.Lstr(resource=self._r + '.cpuBenchmarkText')
|
||||
bs.Lstr(resource=f'{self._r}.cpuBenchmarkText')
|
||||
if self._benchmark_type == 'cpu'
|
||||
else bs.Lstr(resource=self._r + '.toSkipPressAnythingText')
|
||||
else bs.Lstr(resource=f'{self._r}.toSkipPressAnythingText')
|
||||
)
|
||||
t = self._skip_text = bs.newnode(
|
||||
'text',
|
||||
|
|
@ -852,13 +852,13 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
DelayOld(1000),
|
||||
AnalyticsScreen('Tutorial Section 1'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase01Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase01Text')
|
||||
), # hi there
|
||||
Celebrate('left'),
|
||||
DelayOld(2000),
|
||||
Text(
|
||||
bs.Lstr(
|
||||
resource=self._r + '.phrase02Text',
|
||||
resource=f'{self._r}.phrase02Text',
|
||||
subs=[
|
||||
('${APP_NAME}', bs.Lstr(resource='titleText'))
|
||||
],
|
||||
|
|
@ -888,7 +888,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
MoveUD(0),
|
||||
DelayOld(1500),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase03Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase03Text')
|
||||
), # here's a few tips
|
||||
DelayOld(1000),
|
||||
ShowControls(),
|
||||
|
|
@ -900,7 +900,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
AnalyticsScreen('Tutorial Section 2'),
|
||||
Text(
|
||||
bs.Lstr(
|
||||
resource=self._r + '.phrase04Text',
|
||||
resource=f'{self._r}.phrase04Text',
|
||||
subs=[
|
||||
('${APP_NAME}', bs.Lstr(resource='titleText'))
|
||||
],
|
||||
|
|
@ -1261,7 +1261,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
DelayOld(1000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase05Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase05Text')
|
||||
), # for example when you punch..
|
||||
DelayOld(510),
|
||||
Move(0, -0.01),
|
||||
|
|
@ -1279,7 +1279,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
(-3.1, 4.3, -2.0),
|
||||
make_current=False,
|
||||
color=(1, 1, 0.4),
|
||||
name=bs.Lstr(resource=self._r + '.randomName1Text'),
|
||||
name=bs.Lstr(resource=f'{self._r}.randomName1Text'),
|
||||
),
|
||||
Move(-1.0, 0),
|
||||
DelayOld(1050),
|
||||
|
|
@ -1288,7 +1288,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
DelayOld(1000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase06Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase06Text')
|
||||
), # your damage is based
|
||||
DelayOld(1200),
|
||||
Move(-0.05, 0),
|
||||
|
|
@ -1304,12 +1304,12 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
Text(
|
||||
bs.Lstr(
|
||||
resource=self._r + '.phrase07Text',
|
||||
resource=f'{self._r}.phrase07Text',
|
||||
subs=[
|
||||
(
|
||||
'${NAME}',
|
||||
bs.Lstr(
|
||||
resource=self._r + '.randomName1Text'
|
||||
resource=f'{self._r}.randomName1Text'
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -1319,7 +1319,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Celebrate('right', spaz_num=1),
|
||||
DelayOld(1400),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase08Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase08Text')
|
||||
), # lets jump and spin to get more speed
|
||||
DelayOld(30),
|
||||
MoveLR(0),
|
||||
|
|
@ -1520,12 +1520,12 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
DelayOld(1000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase09Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase09Text')
|
||||
), # ah that's better
|
||||
DelayOld(1900),
|
||||
AnalyticsScreen('Tutorial Section 3'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase10Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase10Text')
|
||||
), # running also helps
|
||||
DelayOld(100),
|
||||
SpawnSpaz(
|
||||
|
|
@ -1536,11 +1536,11 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
(3.3, 4.2, -5.8),
|
||||
make_current=False,
|
||||
color=(0.9, 0.5, 1.0),
|
||||
name=bs.Lstr(resource=self._r + '.randomName2Text'),
|
||||
name=bs.Lstr(resource=f'{self._r}.randomName2Text'),
|
||||
),
|
||||
DelayOld(1800),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase11Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase11Text')
|
||||
), # hold ANY button to run
|
||||
DelayOld(300),
|
||||
MoveUD(0),
|
||||
|
|
@ -1797,7 +1797,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
MoveUD(0),
|
||||
AnalyticsScreen('Tutorial Section 4'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase12Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase12Text')
|
||||
), # for extra-awesome punches,...
|
||||
DelayOld(200),
|
||||
SpawnSpaz(
|
||||
|
|
@ -1816,7 +1816,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
make_current=False,
|
||||
color=(1.0, 0.7, 0.3),
|
||||
# name=R.randomName3Text),
|
||||
name=bs.Lstr(resource=self._r + '.randomName3Text'),
|
||||
name=bs.Lstr(resource=f'{self._r}.randomName3Text'),
|
||||
),
|
||||
DelayOld(100),
|
||||
Powerup(1, (2.5, 0.0, 0), relative_to=0),
|
||||
|
|
@ -2015,12 +2015,12 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
MoveLR(0),
|
||||
Text(
|
||||
bs.Lstr(
|
||||
resource=self._r + '.phrase13Text',
|
||||
resource=f'{self._r}.phrase13Text',
|
||||
subs=[
|
||||
(
|
||||
'${NAME}',
|
||||
bs.Lstr(
|
||||
resource=self._r + '.randomName3Text'
|
||||
resource=f'{self._r}.randomName3Text'
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -2031,12 +2031,12 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
AnalyticsScreen('Tutorial Section 5'),
|
||||
Text(
|
||||
bs.Lstr(
|
||||
resource=self._r + '.phrase14Text',
|
||||
resource=f'{self._r}.phrase14Text',
|
||||
subs=[
|
||||
(
|
||||
'${NAME}',
|
||||
bs.Lstr(
|
||||
resource=self._r + '.randomName4Text'
|
||||
resource=f'{self._r}.randomName4Text'
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -2055,7 +2055,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
relative_to=0,
|
||||
make_current=False,
|
||||
color=(0.4, 1.0, 0.7),
|
||||
name=bs.Lstr(resource=self._r + '.randomName4Text'),
|
||||
name=bs.Lstr(resource=f'{self._r}.randomName4Text'),
|
||||
),
|
||||
DelayOld(1000),
|
||||
Celebrate('left', 1, duration=1000),
|
||||
|
|
@ -2083,11 +2083,11 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
),
|
||||
AnalyticsScreen('Tutorial Section 6'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase15Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase15Text')
|
||||
), # lastly there's bombs
|
||||
DelayOld(1900),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase16Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase16Text')
|
||||
), # throwing bombs takes practice
|
||||
DelayOld(2000),
|
||||
Bomb(),
|
||||
|
|
@ -2099,11 +2099,11 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Bomb(),
|
||||
DelayOld(2000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase17Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase17Text')
|
||||
), # not a very good throw
|
||||
DelayOld(3000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase18Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase18Text')
|
||||
), # moving helps you get distance
|
||||
DelayOld(1000),
|
||||
Bomb(),
|
||||
|
|
@ -2121,7 +2121,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
DelayOld(2500),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase19Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase19Text')
|
||||
), # jumping helps you get height
|
||||
DelayOld(2000),
|
||||
Bomb(),
|
||||
|
|
@ -2141,7 +2141,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
DelayOld(2000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase20Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase20Text')
|
||||
), # whiplash your bombs
|
||||
DelayOld(1000),
|
||||
Bomb(release=False),
|
||||
|
|
@ -2303,7 +2303,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
DelayOld(2000),
|
||||
AnalyticsScreen('Tutorial Section 7'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase21Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase21Text')
|
||||
), # timing your bombs can be tricky
|
||||
Move(-1, 0),
|
||||
DelayOld(1000),
|
||||
|
|
@ -2323,7 +2323,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
relative_to=0,
|
||||
make_current=False,
|
||||
color=(0.3, 0.8, 1.0),
|
||||
name=bs.Lstr(resource=self._r + '.randomName5Text'),
|
||||
name=bs.Lstr(resource=f'{self._r}.randomName5Text'),
|
||||
),
|
||||
DelayOld2(1000),
|
||||
Move(-1, 0),
|
||||
|
|
@ -2341,12 +2341,12 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
DelayOld2(1000),
|
||||
Move(0, 0),
|
||||
DelayOld2(1500),
|
||||
Text(bs.Lstr(resource=self._r + '.phrase22Text')), # dang
|
||||
Text(bs.Lstr(resource=f'{self._r}.phrase22Text')), # dang
|
||||
Delay(1500),
|
||||
Text(''),
|
||||
Delay(200),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase23Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase23Text')
|
||||
), # try cooking off
|
||||
Delay(1500),
|
||||
Bomb(),
|
||||
|
|
@ -2362,7 +2362,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
Move(0, 0),
|
||||
Delay(2000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase24Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase24Text')
|
||||
), # hooray nicely cooked
|
||||
Celebrate(),
|
||||
DelayOld(2000),
|
||||
|
|
@ -2376,23 +2376,23 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
DelayOld(1000),
|
||||
AnalyticsScreen('Tutorial Section 8'),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase25Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase25Text')
|
||||
), # well that's just about it
|
||||
DelayOld(2000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase26Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase26Text')
|
||||
), # go get em tiger
|
||||
DelayOld(2000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase27Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase27Text')
|
||||
), # remember you training
|
||||
DelayOld(3000),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase28Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase28Text')
|
||||
), # well maybe
|
||||
DelayOld(1600),
|
||||
Text(
|
||||
bs.Lstr(resource=self._r + '.phrase29Text')
|
||||
bs.Lstr(resource=f'{self._r}.phrase29Text')
|
||||
), # good luck
|
||||
Celebrate('right', duration=10000),
|
||||
DelayOld(1000),
|
||||
|
|
@ -2440,7 +2440,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
assert self._skip_count_text
|
||||
self._skip_count_text.text = (
|
||||
bs.Lstr(
|
||||
resource=self._r + '.skipVoteCountText',
|
||||
resource=f'{self._r}.skipVoteCountText',
|
||||
subs=[
|
||||
('${COUNT}', str(count)),
|
||||
('${TOTAL}', str(len(self.players))),
|
||||
|
|
@ -2460,7 +2460,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
bs.getsound('swish').play()
|
||||
# self._skip_count_text.text = self._r.skippingText
|
||||
self._skip_count_text.text = bs.Lstr(
|
||||
resource=self._r + '.skippingText'
|
||||
resource=f'{self._r}.skippingText'
|
||||
)
|
||||
assert self._skip_text
|
||||
self._skip_text.text = ''
|
||||
|
|
@ -2474,7 +2474,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
self._issued_warning = True
|
||||
assert self._skip_text
|
||||
self._skip_text.text = bs.Lstr(
|
||||
resource=self._r + '.skipConfirmText'
|
||||
resource=f'{self._r}.skipConfirmText'
|
||||
)
|
||||
self._skip_text.color = (1, 1, 1)
|
||||
self._skip_text.scale = 1.3
|
||||
|
|
@ -2510,7 +2510,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
|
|||
def _revert_confirm(self) -> None:
|
||||
assert self._skip_text
|
||||
self._skip_text.text = bs.Lstr(
|
||||
resource=self._r + '.toSkipPressAnythingText'
|
||||
resource=f'{self._r}.toSkipPressAnythingText'
|
||||
)
|
||||
self._skip_text.color = (1, 1, 1)
|
||||
self._issued_warning = False
|
||||
|
|
|
|||
6
dist/ba_data/python/bauiv1/_hooks.py
vendored
6
dist/ba_data/python/bauiv1/_hooks.py
vendored
|
|
@ -61,13 +61,11 @@ def party_icon_activate(origin: Sequence[float]) -> None:
|
|||
|
||||
|
||||
def on_button_press_x() ->None:
|
||||
import ui_hooks
|
||||
ui_hooks.on_button_xy_press("X")
|
||||
print("button X pressed from UI or keyboard")
|
||||
|
||||
|
||||
def on_button_press_y() ->None:
|
||||
import ui_hooks
|
||||
ui_hooks.on_button_xy_press("X")
|
||||
print("button Y pressed from UI or keyboard")
|
||||
|
||||
|
||||
def quit_window(quit_type: babase.QuitType) -> None:
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ from __future__ import annotations
|
|||
import time
|
||||
import logging
|
||||
|
||||
from bacommon.cloud import WebLocation
|
||||
from bacommon.login import LoginType
|
||||
import bacommon.cloud
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
# These days we're directing people to the web based account settings
|
||||
# for V2 account linking and trying to get them to disconnect remaining
|
||||
# V1 links, but leaving this escape hatch here in case needed.
|
||||
|
|
@ -150,7 +152,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 41),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=app.ui_v1.title_color,
|
||||
maxwidth=self._width - 340,
|
||||
h_align='center',
|
||||
|
|
@ -346,11 +348,12 @@ class AccountSettingsWindow(bui.Window):
|
|||
show_reset_progress_button = False
|
||||
reset_progress_button_space = 70.0
|
||||
|
||||
show_manage_v2_account_button = (
|
||||
self._v1_signed_in and v1_account_type == 'V2'
|
||||
)
|
||||
show_manage_v2_account_button = primary_v2_account is not None
|
||||
manage_v2_account_button_space = 100.0
|
||||
|
||||
show_delete_account_button = primary_v2_account is not None
|
||||
delete_account_button_space = 80.0
|
||||
|
||||
show_player_profiles_button = self._v1_signed_in
|
||||
player_profiles_button_space = (
|
||||
70.0 if show_manage_v2_account_button else 100.0
|
||||
|
|
@ -365,22 +368,19 @@ class AccountSettingsWindow(bui.Window):
|
|||
unlink_accounts_button_space = 90.0
|
||||
|
||||
# Phasing this out.
|
||||
# show_v2_link_info = self._v1_signed_in
|
||||
# and not show_link_accounts_button
|
||||
show_v2_link_info = False
|
||||
v2_link_info_space = 70.0
|
||||
|
||||
legacy_unlink_button_space = 120.0
|
||||
|
||||
show_sign_out_button = self._v1_signed_in and v1_account_type in [
|
||||
'Local',
|
||||
'V2',
|
||||
]
|
||||
show_sign_out_button = primary_v2_account is not None or (
|
||||
self._v1_signed_in and v1_account_type == 'Local'
|
||||
)
|
||||
sign_out_button_space = 80.0
|
||||
|
||||
# We can show cancel if we're either waiting on an adapter to
|
||||
# provide us with v2 credentials or waiting for those credentials
|
||||
# to be verified.
|
||||
# provide us with v2 credentials or waiting for those
|
||||
# credentials to be verified.
|
||||
show_cancel_sign_in_button = self._signing_in_adapter is not None or (
|
||||
plus.accounts.have_primary_credentials()
|
||||
and primary_v2_account is None
|
||||
|
|
@ -435,6 +435,8 @@ class AccountSettingsWindow(bui.Window):
|
|||
self._sub_height += legacy_unlink_button_space
|
||||
if show_sign_out_button:
|
||||
self._sub_height += sign_out_button_space
|
||||
if show_delete_account_button:
|
||||
self._sub_height += delete_account_button_space
|
||||
if show_cancel_sign_in_button:
|
||||
self._sub_height += cancel_sign_in_button_space
|
||||
self._subcontainer = bui.containerwidget(
|
||||
|
|
@ -579,7 +581,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v + sign_in_benefits_space * 0.4,
|
||||
),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.signInInfoText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.signInInfoText'),
|
||||
max_height=sign_in_benefits_space * 0.9,
|
||||
scale=0.9,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
|
|
@ -624,7 +626,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithText',
|
||||
resource=f'{self._r}.signInWithText',
|
||||
subs=[
|
||||
(
|
||||
'${SERVICE}',
|
||||
|
|
@ -669,7 +671,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithText',
|
||||
resource=f'{self._r}.signInWithText',
|
||||
subs=[('${SERVICE}', 'Game Center')],
|
||||
),
|
||||
),
|
||||
|
|
@ -703,11 +705,11 @@ class AccountSettingsWindow(bui.Window):
|
|||
)
|
||||
|
||||
v2labeltext: bui.Lstr | str = (
|
||||
bui.Lstr(resource=self._r + '.signInWithAnEmailAddressText')
|
||||
bui.Lstr(resource=f'{self._r}.signInWithAnEmailAddressText')
|
||||
if show_game_center_sign_in_button
|
||||
or show_google_play_sign_in_button
|
||||
or show_device_sign_in_button
|
||||
else bui.Lstr(resource=self._r + '.signInText')
|
||||
else bui.Lstr(resource=f'{self._r}.signInText')
|
||||
)
|
||||
v2infotext: bui.Lstr | str | None = None
|
||||
|
||||
|
|
@ -796,7 +798,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithDeviceText'
|
||||
resource=f'{self._r}.signInWithDeviceText'
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -811,7 +813,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v_align='center',
|
||||
size=(0, 0),
|
||||
position=(self._sub_width * 0.5, v - 4),
|
||||
text=bui.Lstr(resource=self._r + '.signInWithDeviceInfoText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.signInWithDeviceInfoText'),
|
||||
flatness=1.0,
|
||||
scale=0.57,
|
||||
maxwidth=button_width * 0.9,
|
||||
|
|
@ -1042,10 +1044,10 @@ class AccountSettingsWindow(bui.Window):
|
|||
button_width = 250
|
||||
if show_reset_progress_button:
|
||||
confirm_text = (
|
||||
bui.Lstr(resource=self._r + '.resetProgressConfirmText')
|
||||
bui.Lstr(resource=f'{self._r}.resetProgressConfirmText')
|
||||
if self._can_reset_achievements
|
||||
else bui.Lstr(
|
||||
resource=self._r + '.resetProgressConfirmNoAchievementsText'
|
||||
resource=f'{self._r}.resetProgressConfirmNoAchievementsText'
|
||||
)
|
||||
)
|
||||
v -= reset_progress_button_space
|
||||
|
|
@ -1056,7 +1058,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
textcolor=(0.75, 0.7, 0.8),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=self._r + '.resetProgressText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.resetProgressText'),
|
||||
on_activate_call=lambda: confirm.ConfirmWindow(
|
||||
text=confirm_text,
|
||||
width=500,
|
||||
|
|
@ -1083,7 +1085,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
scale=0.9,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
maxwidth=self._sub_width * 0.95,
|
||||
text=bui.Lstr(resource=self._r + '.linkedAccountsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.linkedAccountsText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
|
@ -1112,7 +1114,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v_align='center',
|
||||
size=(0, 0),
|
||||
position=(self._sub_width * 0.5, v + 17 + 20),
|
||||
text=bui.Lstr(resource=self._r + '.linkAccountsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.linkAccountsText'),
|
||||
maxwidth=button_width * 0.8,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
)
|
||||
|
|
@ -1123,7 +1125,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v_align='center',
|
||||
size=(0, 0),
|
||||
position=(self._sub_width * 0.5, v - 4 + 20),
|
||||
text=bui.Lstr(resource=self._r + '.linkAccountsInfoText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.linkAccountsInfoText'),
|
||||
flatness=1.0,
|
||||
scale=0.5,
|
||||
maxwidth=button_width * 0.8,
|
||||
|
|
@ -1157,7 +1159,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
v_align='center',
|
||||
size=(0, 0),
|
||||
position=(self._sub_width * 0.5, v + 55),
|
||||
text=bui.Lstr(resource=self._r + '.unlinkAccountsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.unlinkAccountsText'),
|
||||
maxwidth=button_width * 0.8,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
)
|
||||
|
|
@ -1212,7 +1214,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
size=(button_width_w, 60),
|
||||
label=bui.Lstr(
|
||||
resource=self._r + '.unlinkLegacyV1AccountsText'
|
||||
resource=f'{self._r}.unlinkLegacyV1AccountsText'
|
||||
),
|
||||
textcolor=(0.8, 0.4, 0),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
|
|
@ -1225,7 +1227,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=self._r + '.signOutText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.signOutText'),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
autoselect=True,
|
||||
|
|
@ -1261,6 +1263,27 @@ class AccountSettingsWindow(bui.Window):
|
|||
)
|
||||
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
|
||||
|
||||
if show_delete_account_button:
|
||||
v -= delete_account_button_space
|
||||
self._delete_account_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteAccountText'),
|
||||
color=(0.85, 0.5, 0.6),
|
||||
textcolor=(0.9, 0.7, 0.8),
|
||||
autoselect=True,
|
||||
on_activate_call=self._on_delete_account_press,
|
||||
)
|
||||
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, show_buffer_bottom=15)
|
||||
|
||||
# Whatever the topmost selectable thing is, we want it to scroll all
|
||||
# the way up when we select it.
|
||||
if first_selectable is not None:
|
||||
|
|
@ -1303,6 +1326,12 @@ class AccountSettingsWindow(bui.Window):
|
|||
show_what_is_v2_page()
|
||||
|
||||
def _on_manage_account_press(self) -> None:
|
||||
self._do_manage_account_press(WebLocation.ACCOUNT_EDITOR)
|
||||
|
||||
def _on_delete_account_press(self) -> None:
|
||||
self._do_manage_account_press(WebLocation.ACCOUNT_DELETE_SECTION)
|
||||
|
||||
def _do_manage_account_press(self, weblocation: WebLocation) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -1327,7 +1356,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
|
||||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.cloud.ManageAccountMessage(),
|
||||
bacommon.cloud.ManageAccountMessage(weblocation=weblocation),
|
||||
on_response=bui.WeakCall(self._on_manage_account_response),
|
||||
)
|
||||
|
||||
|
|
@ -1414,7 +1443,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
subs=[
|
||||
(
|
||||
'${L}',
|
||||
bui.Lstr(resource=self._r + '.linkedAccountsText'),
|
||||
bui.Lstr(resource=f'{self._r}.linkedAccountsText'),
|
||||
),
|
||||
('${A}', accounts_str),
|
||||
],
|
||||
|
|
@ -1434,7 +1463,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
# Last level cant be completed; hence the -1;
|
||||
progress = min(1.0, float(levels_complete) / (len(levels) - 1))
|
||||
p_str = bui.Lstr(
|
||||
resource=self._r + '.campaignProgressText',
|
||||
resource=f'{self._r}.campaignProgressText',
|
||||
subs=[('${PROGRESS}', str(int(progress * 100.0)) + '%')],
|
||||
)
|
||||
except Exception:
|
||||
|
|
@ -1456,7 +1485,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
bui.textwidget(
|
||||
edit=self._tickets_text,
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.ticketsText', subs=[('${COUNT}', tc_str)]
|
||||
resource=f'{self._r}.ticketsText', subs=[('${COUNT}', tc_str)]
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -1496,7 +1525,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
)
|
||||
total = len(bui.app.classic.ach.achievements)
|
||||
txt_final = bui.Lstr(
|
||||
resource=self._r + '.achievementProgressText',
|
||||
resource=f'{self._r}.achievementProgressText',
|
||||
subs=[('${COUNT}', str(complete)), ('${TOTAL}', str(total))],
|
||||
)
|
||||
|
||||
|
|
@ -1575,7 +1604,7 @@ class AccountSettingsWindow(bui.Window):
|
|||
cfg.commit()
|
||||
bui.buttonwidget(
|
||||
edit=self._sign_out_button,
|
||||
label=bui.Lstr(resource=self._r + '.signingOutText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.signingOutText'),
|
||||
)
|
||||
|
||||
# Speed UI updates along.
|
||||
|
|
|
|||
19
dist/ba_data/python/bauiv1lib/config.py
vendored
19
dist/ba_data/python/bauiv1lib/config.py
vendored
|
|
@ -109,15 +109,20 @@ class ConfigNumberEdit:
|
|||
self._value = bui.app.config.resolve(configkey)
|
||||
except ValueError:
|
||||
self._value = bui.app.config.get(configkey, fallback_value)
|
||||
self._value = (
|
||||
self._minval
|
||||
if self._minval > self._value
|
||||
else self._maxval if self._maxval < self._value else self._value
|
||||
)
|
||||
self._as_percent = as_percent
|
||||
self._f = f
|
||||
|
||||
self.nametext = bui.textwidget(
|
||||
parent=parent,
|
||||
position=position,
|
||||
size=(100, 30),
|
||||
position=(position[0], position[1] + 12.0),
|
||||
size=(0, 0),
|
||||
text=displayname,
|
||||
maxwidth=160 + xoffset,
|
||||
maxwidth=150 + xoffset,
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
|
|
@ -125,8 +130,8 @@ class ConfigNumberEdit:
|
|||
)
|
||||
self.valuetext = bui.textwidget(
|
||||
parent=parent,
|
||||
position=(246 + xoffset, position[1]),
|
||||
size=(60, 28),
|
||||
position=(position[0] + 216 + xoffset, position[1] + 12.0),
|
||||
size=(0, 0),
|
||||
editable=False,
|
||||
color=(0.3, 1.0, 0.3, 1.0),
|
||||
h_align='right',
|
||||
|
|
@ -136,7 +141,7 @@ class ConfigNumberEdit:
|
|||
)
|
||||
self.minusbutton = bui.buttonwidget(
|
||||
parent=parent,
|
||||
position=(330 + xoffset, position[1]),
|
||||
position=(position[0] + 230 + xoffset, position[1]),
|
||||
size=(28, 28),
|
||||
label='-',
|
||||
autoselect=True,
|
||||
|
|
@ -146,7 +151,7 @@ class ConfigNumberEdit:
|
|||
)
|
||||
self.plusbutton = bui.buttonwidget(
|
||||
parent=parent,
|
||||
position=(380 + xoffset, position[1]),
|
||||
position=(position[0] + 280 + xoffset, position[1]),
|
||||
size=(28, 28),
|
||||
label='+',
|
||||
autoselect=True,
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/confirm.py
vendored
4
dist/ba_data/python/bauiv1lib/confirm.py
vendored
|
|
@ -18,7 +18,7 @@ class ConfirmWindow:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
text: str | bui.Lstr = 'Are you sure?',
|
||||
text: str | bui.Lstr | None = None,
|
||||
action: Callable[[], Any] | None = None,
|
||||
width: float = 360.0,
|
||||
height: float = 100.0,
|
||||
|
|
@ -31,6 +31,8 @@ class ConfirmWindow:
|
|||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
if text is None:
|
||||
text = bui.Lstr(resource='areYouSureText')
|
||||
if ok_text is None:
|
||||
ok_text = bui.Lstr(resource='okText')
|
||||
if cancel_text is None:
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ def wait_for_connectivity(
|
|||
assert plus is not None
|
||||
|
||||
# Quick-out: if we're already connected, don't bother with the UI.
|
||||
# We do, however, push this call instead of calling it immediately
|
||||
# so as to be consistent with the waiting path.
|
||||
if plus.cloud.connected:
|
||||
on_connected()
|
||||
bui.pushcall(on_connected)
|
||||
return
|
||||
|
||||
WaitForConnectivityWindow(on_connected=on_connected, on_cancel=on_cancel)
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/continues.py
vendored
4
dist/ba_data/python/bauiv1lib/continues.py
vendored
|
|
@ -214,7 +214,7 @@ class ContinuesWindow(bui.Window):
|
|||
self._on_cancel()
|
||||
|
||||
def _on_continue_press(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -238,7 +238,7 @@ class ContinuesWindow(bui.Window):
|
|||
self._counting_down = False
|
||||
bui.textwidget(edit=self._counter_text, text='')
|
||||
bui.getsound('error').play()
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
return
|
||||
if not self._transitioning_out:
|
||||
bui.getsound('swish').play()
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@ class CoopBrowserWindow(bui.Window):
|
|||
text=bui.Lstr(
|
||||
value='${C} (${P})',
|
||||
subs=[
|
||||
('${C}', bui.Lstr(resource=self._r + '.campaignText')),
|
||||
('${C}', bui.Lstr(resource=f'{self._r}.campaignText')),
|
||||
('${P}', p_str),
|
||||
],
|
||||
),
|
||||
|
|
@ -694,7 +694,7 @@ class CoopBrowserWindow(bui.Window):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.confirm import ConfirmWindow
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.tournamentInfoText')
|
||||
txt = bui.Lstr(resource=f'{self._r}.tournamentInfoText')
|
||||
ConfirmWindow(
|
||||
txt,
|
||||
cancel_button=False,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class TournamentButton:
|
|||
position=(x + 360, y + scly - 20),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.entryFeeText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.entryFeeText'),
|
||||
v_align='center',
|
||||
maxwidth=100,
|
||||
scale=0.9,
|
||||
|
|
@ -167,7 +167,7 @@ class TournamentButton:
|
|||
position=(x + 447 + x_offs, y + scly - 20),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.prizesText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.prizesText'),
|
||||
v_align='center',
|
||||
maxwidth=130,
|
||||
scale=0.9,
|
||||
|
|
@ -267,7 +267,7 @@ class TournamentButton:
|
|||
position=(x + 620 + x_offs, y + scly - 20),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.currentBestText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.currentBestText'),
|
||||
v_align='center',
|
||||
maxwidth=180,
|
||||
scale=0.9,
|
||||
|
|
@ -290,6 +290,7 @@ class TournamentButton:
|
|||
text='-',
|
||||
v_align='center',
|
||||
maxwidth=170,
|
||||
glow_type='uniform',
|
||||
scale=1.4,
|
||||
color=value_color,
|
||||
flatness=1.0,
|
||||
|
|
@ -331,7 +332,7 @@ class TournamentButton:
|
|||
position=(x + 820 + x_offs, y + scly - 20),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.timeRemainingText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.timeRemainingText'),
|
||||
v_align='center',
|
||||
maxwidth=180,
|
||||
scale=0.9,
|
||||
|
|
@ -532,13 +533,13 @@ class TournamentButton:
|
|||
bui.textwidget(edit=self.current_leader_score_text, text=leader_score)
|
||||
bui.buttonwidget(
|
||||
edit=self.more_scores_button,
|
||||
label=bui.Lstr(resource=self._r + '.seeMoreText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.seeMoreText'),
|
||||
)
|
||||
out_of_time_text: str | bui.Lstr = (
|
||||
'-'
|
||||
if 'totalTime' not in entry
|
||||
else bui.Lstr(
|
||||
resource=self._r + '.ofTotalTimeText',
|
||||
resource=f'{self._r}.ofTotalTimeText',
|
||||
subs=[
|
||||
(
|
||||
'${TOTAL}',
|
||||
|
|
|
|||
30
dist/ba_data/python/bauiv1lib/creditslist.py
vendored
30
dist/ba_data/python/bauiv1lib/creditslist.py
vendored
|
|
@ -95,7 +95,7 @@ class CreditsListWindow(bui.Window):
|
|||
position=(0, height - (59 if uiscale is bui.UIScale.SMALL else 54)),
|
||||
size=(width, 30),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.titleText',
|
||||
resource=f'{self._r}.titleText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
),
|
||||
h_align='center',
|
||||
|
|
@ -156,7 +156,7 @@ class CreditsListWindow(bui.Window):
|
|||
return sval
|
||||
|
||||
sound_and_music = bui.Lstr(
|
||||
resource=self._r + '.songCreditText'
|
||||
resource=f'{self._r}.songCreditText'
|
||||
).evaluate()
|
||||
sound_and_music = sound_and_music.replace(
|
||||
'${TITLE}', "'William Tell (Trumpet Entry)'"
|
||||
|
|
@ -232,41 +232,41 @@ class CreditsListWindow(bui.Window):
|
|||
# (or add mesh splitting under the hood)
|
||||
credits_text = (
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.codingGraphicsAudioText')
|
||||
+ bui.Lstr(resource=f'{self._r}.codingGraphicsAudioText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'Eric Froemling')
|
||||
+ '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.additionalAudioArtIdeasText')
|
||||
+ bui.Lstr(resource=f'{self._r}.additionalAudioArtIdeasText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'Raphael Suter')
|
||||
+ '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.soundAndMusicText').evaluate()
|
||||
+ bui.Lstr(resource=f'{self._r}.soundAndMusicText').evaluate()
|
||||
+ '\n'
|
||||
'\n' + sound_and_music + '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.publicDomainMusicViaText')
|
||||
+ bui.Lstr(resource=f'{self._r}.publicDomainMusicViaText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'Musopen.com')
|
||||
+ '\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.thanksEspeciallyToText')
|
||||
+ bui.Lstr(resource=f'{self._r}.thanksEspeciallyToText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'the US Army, Navy, and Marine Bands')
|
||||
+ '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.additionalMusicFromText')
|
||||
+ bui.Lstr(resource=f'{self._r}.additionalMusicFromText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'The YouTube Audio Library')
|
||||
+ '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.soundsText')
|
||||
+ bui.Lstr(resource=f'{self._r}.soundsText')
|
||||
.evaluate()
|
||||
.replace('${SOURCE}', 'Freesound.org')
|
||||
+ '\n'
|
||||
|
|
@ -274,7 +274,7 @@ class CreditsListWindow(bui.Window):
|
|||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(
|
||||
resource=self._r + '.languageTranslationsText'
|
||||
resource=f'{self._r}.languageTranslationsText'
|
||||
).evaluate()
|
||||
+ '\n'
|
||||
'\n'
|
||||
|
|
@ -295,25 +295,25 @@ class CreditsListWindow(bui.Window):
|
|||
' Holiday theme vector art designed by Freepik\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.specialThanksText').evaluate()
|
||||
+ bui.Lstr(resource=f'{self._r}.specialThanksText').evaluate()
|
||||
+ '\n'
|
||||
'\n'
|
||||
' Todd, Laura, and Robert Froemling\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.allMyFamilyText')
|
||||
+ bui.Lstr(resource=f'{self._r}.allMyFamilyText')
|
||||
.evaluate()
|
||||
.replace('\n', '\n ')
|
||||
+ '\n'
|
||||
' '
|
||||
+ bui.Lstr(
|
||||
resource=self._r + '.whoeverInventedCoffeeText'
|
||||
resource=f'{self._r}.whoeverInventedCoffeeText'
|
||||
).evaluate()
|
||||
+ '\n'
|
||||
'\n'
|
||||
' ' + bui.Lstr(resource=self._r + '.legalText').evaluate() + '\n'
|
||||
' ' + bui.Lstr(resource=f'{self._r}.legalText').evaluate() + '\n'
|
||||
'\n'
|
||||
' '
|
||||
+ bui.Lstr(resource=self._r + '.softwareBasedOnText')
|
||||
+ bui.Lstr(resource=f'{self._r}.softwareBasedOnText')
|
||||
.evaluate()
|
||||
.replace('${NAME}', 'the Khronos Group')
|
||||
+ '\n'
|
||||
|
|
|
|||
22
dist/ba_data/python/bauiv1lib/debug.py
vendored
22
dist/ba_data/python/bauiv1lib/debug.py
vendored
|
|
@ -70,7 +70,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, height - 60),
|
||||
size=(width, 30),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
h_align='center',
|
||||
color=bui.app.ui_v1.title_color,
|
||||
v_align='center',
|
||||
|
|
@ -98,7 +98,7 @@ class DebugWindow(bui.Window):
|
|||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.runCPUBenchmarkText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.runCPUBenchmarkText'),
|
||||
on_activate_call=self._run_cpu_benchmark_pressed,
|
||||
)
|
||||
bui.widget(
|
||||
|
|
@ -111,7 +111,7 @@ class DebugWindow(bui.Window):
|
|||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.runGPUBenchmarkText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.runGPUBenchmarkText'),
|
||||
on_activate_call=self._run_gpu_benchmark_pressed,
|
||||
)
|
||||
v -= 60
|
||||
|
|
@ -121,7 +121,7 @@ class DebugWindow(bui.Window):
|
|||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.runMediaReloadBenchmarkText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.runMediaReloadBenchmarkText'),
|
||||
on_activate_call=self._run_media_reload_benchmark_pressed,
|
||||
)
|
||||
v -= 60
|
||||
|
|
@ -130,7 +130,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v + 22),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.stressTestTitleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.stressTestTitleText'),
|
||||
maxwidth=200,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.85,
|
||||
|
|
@ -144,7 +144,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(x_offs - 10, v + 22),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.stressTestPlaylistTypeText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.stressTestPlaylistTypeText'),
|
||||
maxwidth=130,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
|
|
@ -174,7 +174,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(x_offs - 10, v + 22),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.stressTestPlaylistNameText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.stressTestPlaylistNameText'),
|
||||
maxwidth=130,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
|
|
@ -192,7 +192,7 @@ class DebugWindow(bui.Window):
|
|||
autoselect=True,
|
||||
color=(0.9, 0.9, 0.9, 1.0),
|
||||
description=bui.Lstr(
|
||||
resource=self._r + '.stressTestPlaylistDescriptionText'
|
||||
resource=f'{self._r}.stressTestPlaylistDescriptionText'
|
||||
),
|
||||
editable=True,
|
||||
padding=4,
|
||||
|
|
@ -205,7 +205,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(x_offs - 10, v),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.stressTestPlayerCountText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.stressTestPlayerCountText'),
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
|
|
@ -250,7 +250,7 @@ class DebugWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(x_offs - 10, v),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.stressTestRoundDurationText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.stressTestRoundDurationText'),
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
|
|
@ -298,7 +298,7 @@ class DebugWindow(bui.Window):
|
|||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.runStressTestText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.runStressTestText'),
|
||||
on_activate_call=self._stress_test_pressed,
|
||||
)
|
||||
bui.widget(btn, show_buffer_bottom=50)
|
||||
|
|
|
|||
|
|
@ -70,12 +70,12 @@ class FileSelectorWindow(bui.Window):
|
|||
h_align='center',
|
||||
v_align='center',
|
||||
text=(
|
||||
bui.Lstr(resource=self._r + '.titleFolderText')
|
||||
bui.Lstr(resource=f'{self._r}.titleFolderText')
|
||||
if (allow_folders and not valid_file_extensions)
|
||||
else (
|
||||
bui.Lstr(resource=self._r + '.titleFileText')
|
||||
bui.Lstr(resource=f'{self._r}.titleFileText')
|
||||
if not allow_folders
|
||||
else bui.Lstr(resource=self._r + '.titleFileFolderText')
|
||||
else bui.Lstr(resource=f'{self._r}.titleFileFolderText')
|
||||
)
|
||||
),
|
||||
maxwidth=210,
|
||||
|
|
@ -382,7 +382,7 @@ class FileSelectorWindow(bui.Window):
|
|||
),
|
||||
size=(self._button_width, 50),
|
||||
label=bui.Lstr(
|
||||
resource=self._r + '.useThisFolderButtonText'
|
||||
resource=f'{self._r}.useThisFolderButtonText'
|
||||
),
|
||||
on_activate_call=self._on_folder_entry_activated,
|
||||
)
|
||||
|
|
|
|||
12
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
12
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
|
|
@ -165,7 +165,7 @@ class GatherWindow(bui.Window):
|
|||
),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=550,
|
||||
)
|
||||
|
||||
|
|
@ -174,23 +174,23 @@ class GatherWindow(bui.Window):
|
|||
|
||||
# Build up the set of tabs we want.
|
||||
tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [
|
||||
(self.TabID.ABOUT, bui.Lstr(resource=self._r + '.aboutText'))
|
||||
(self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText'))
|
||||
]
|
||||
if plus.get_v1_account_misc_read_val('enablePublicParties', True):
|
||||
tabdefs.append(
|
||||
(
|
||||
self.TabID.INTERNET,
|
||||
bui.Lstr(resource=self._r + '.publicText'),
|
||||
bui.Lstr(resource=f'{self._r}.publicText'),
|
||||
)
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.PRIVATE, bui.Lstr(resource=self._r + '.privateText'))
|
||||
(self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.NEARBY, bui.Lstr(resource=self._r + '.nearbyText'))
|
||||
(self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.MANUAL, bui.Lstr(resource=self._r + '.manualText'))
|
||||
(self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText'))
|
||||
)
|
||||
|
||||
# On small UI, push our tabs up closer to the top of the screen to
|
||||
|
|
|
|||
256
dist/ba_data/python/bauiv1lib/gather/privatetab.py
vendored
256
dist/ba_data/python/bauiv1lib/gather/privatetab.py
vendored
|
|
@ -13,14 +13,17 @@ from enum import Enum
|
|||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, cast, override
|
||||
|
||||
from efro.error import CommunicationError
|
||||
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
|
||||
import bacommon.cloud
|
||||
from bacommon.net import (
|
||||
PrivateHostingState,
|
||||
PrivateHostingConfig,
|
||||
PrivatePartyConnectResult,
|
||||
)
|
||||
from bauiv1lib.gather import GatherTab
|
||||
from bauiv1lib.getcurrency import GetCurrencyWindow, show_get_tickets_prompt
|
||||
|
||||
from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_prompt
|
||||
import bascenev1 as bs
|
||||
import bauiv1 as bui
|
||||
|
||||
|
|
@ -55,7 +58,9 @@ class PrivateGatherTab(GatherTab):
|
|||
super().__init__(window)
|
||||
self._container: bui.Widget | None = None
|
||||
self._state: State = State()
|
||||
self._last_datacode_refresh_time: float | None = None
|
||||
self._hostingstate = PrivateHostingState()
|
||||
self._v2state: bacommon.cloud.BSPrivatePartyResponse | None = None
|
||||
self._join_sub_tab_text: bui.Widget | None = None
|
||||
self._host_sub_tab_text: bui.Widget | None = None
|
||||
self._update_timer: bui.AppTimer | None = None
|
||||
|
|
@ -63,14 +68,15 @@ class PrivateGatherTab(GatherTab):
|
|||
self._c_width: float = 0.0
|
||||
self._c_height: float = 0.0
|
||||
self._last_hosting_state_query_time: float | None = None
|
||||
self._last_v2_state_query_time: float | None = None
|
||||
self._waiting_for_initial_state = True
|
||||
self._waiting_for_start_stop_response = True
|
||||
self._host_playlist_button: bui.Widget | None = None
|
||||
self._host_copy_button: bui.Widget | None = None
|
||||
self._host_connect_button: bui.Widget | None = None
|
||||
self._host_start_stop_button: bui.Widget | None = None
|
||||
self._get_tickets_button: bui.Widget | None = None
|
||||
self._ticket_count_text: bui.Widget | None = None
|
||||
self._get_tokens_button: bui.Widget | None = None
|
||||
self._token_count_text: bui.Widget | None = None
|
||||
self._showing_not_signed_in_screen = False
|
||||
self._create_time = time.time()
|
||||
self._last_action_send_time: float | None = None
|
||||
|
|
@ -159,7 +165,9 @@ class PrivateGatherTab(GatherTab):
|
|||
# Prevent taking any action until we've updated our state.
|
||||
self._waiting_for_initial_state = True
|
||||
|
||||
# This will get a state query sent out immediately.
|
||||
# Force some immediate refreshes.
|
||||
self._last_datacode_refresh_time = None
|
||||
self._last_v2_state_query_time = None
|
||||
self._last_action_send_time = None # Ensure we don't ignore response.
|
||||
self._last_hosting_state_query_time = None
|
||||
self._update()
|
||||
|
|
@ -263,19 +271,20 @@ class PrivateGatherTab(GatherTab):
|
|||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
try:
|
||||
t_str = str(plus.get_v1_account_ticket_count())
|
||||
except Exception:
|
||||
t_str = '?'
|
||||
if self._get_tickets_button:
|
||||
if self._v2state is not None:
|
||||
t_str = str(self._v2state.tokens)
|
||||
else:
|
||||
t_str = '-'
|
||||
|
||||
if self._get_tokens_button:
|
||||
bui.buttonwidget(
|
||||
edit=self._get_tickets_button,
|
||||
label=bui.charstr(bui.SpecialChar.TICKET) + t_str,
|
||||
edit=self._get_tokens_button,
|
||||
label=bui.charstr(bui.SpecialChar.TOKEN) + t_str,
|
||||
)
|
||||
if self._ticket_count_text:
|
||||
if self._token_count_text:
|
||||
bui.textwidget(
|
||||
edit=self._ticket_count_text,
|
||||
text=bui.charstr(bui.SpecialChar.TICKET) + t_str,
|
||||
edit=self._token_count_text,
|
||||
text=bui.charstr(bui.SpecialChar.TOKEN) + t_str,
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
|
|
@ -292,11 +301,11 @@ class PrivateGatherTab(GatherTab):
|
|||
# If we're not signed in, just refresh to show that.
|
||||
if (
|
||||
plus.get_v1_account_state() != 'signed_in'
|
||||
and self._showing_not_signed_in_screen
|
||||
):
|
||||
or plus.accounts.primary is None
|
||||
) and not self._showing_not_signed_in_screen:
|
||||
self._refresh_sub_tab()
|
||||
else:
|
||||
# Query an updated state periodically.
|
||||
# Query an updated v1 state periodically.
|
||||
if (
|
||||
self._last_hosting_state_query_time is None
|
||||
or now - self._last_hosting_state_query_time > 15.0
|
||||
|
|
@ -309,23 +318,78 @@ class PrivateGatherTab(GatherTab):
|
|||
'expire_time': time.time() + 20,
|
||||
},
|
||||
callback=bui.WeakCall(
|
||||
self._hosting_state_idle_response
|
||||
self._idle_hosting_state_response
|
||||
),
|
||||
)
|
||||
plus.run_v1_account_transactions()
|
||||
else:
|
||||
self._hosting_state_idle_response(None)
|
||||
self._idle_hosting_state_response(None)
|
||||
self._last_hosting_state_query_time = now
|
||||
|
||||
def _hosting_state_idle_response(
|
||||
# Query an updated v2 state periodically.
|
||||
if (
|
||||
self._last_v2_state_query_time is None
|
||||
or now - self._last_v2_state_query_time > 12.0
|
||||
):
|
||||
self._debug_server_comm('querying pp v2 state')
|
||||
|
||||
if plus.accounts.primary is not None:
|
||||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.cloud.BSPrivatePartyMessage(
|
||||
need_datacode=(
|
||||
self._last_datacode_refresh_time is None
|
||||
or time.monotonic()
|
||||
- self._last_datacode_refresh_time
|
||||
> 30.0
|
||||
)
|
||||
),
|
||||
on_response=bui.WeakCall(
|
||||
self._on_private_party_query_response
|
||||
),
|
||||
)
|
||||
|
||||
self._last_v2_state_query_time = now
|
||||
|
||||
def _on_private_party_query_response(
|
||||
self, response: bacommon.cloud.BSPrivatePartyResponse | Exception
|
||||
) -> None:
|
||||
if isinstance(response, Exception):
|
||||
self._debug_server_comm('got pp v2 state response (err)')
|
||||
# We expect comm errors sometimes. Make noise on anything else.
|
||||
if not isinstance(response, CommunicationError):
|
||||
logging.exception('Error on private-party-query-response')
|
||||
return
|
||||
|
||||
# Ignore if something went wrong server-side.
|
||||
if not response.success:
|
||||
self._debug_server_comm('got pp v2 state response (serverside err)')
|
||||
return
|
||||
|
||||
self._debug_server_comm('got pp v2 state response')
|
||||
|
||||
existing_datacode = (
|
||||
None if self._v2state is None else self._v2state.datacode
|
||||
)
|
||||
|
||||
self._v2state = response
|
||||
if self._v2state.datacode is None:
|
||||
# We don't fetch datacode each time; preserve our existing
|
||||
# if we didn't.
|
||||
self._v2state.datacode = existing_datacode
|
||||
else:
|
||||
# If we *did* fetch it, note the time.
|
||||
self._last_datacode_refresh_time = time.monotonic()
|
||||
|
||||
def _idle_hosting_state_response(
|
||||
self, result: dict[str, Any] | None
|
||||
) -> None:
|
||||
# This simply passes through to our standard response handler.
|
||||
# The one exception is if we've recently sent an action to the
|
||||
# server (start/stop hosting/etc.) In that case we want to ignore
|
||||
# idle background updates and wait for the response to our action.
|
||||
# (this keeps the button showing 'one moment...' until the change
|
||||
# takes effect, etc.)
|
||||
# server (start/stop hosting/etc.) In that case we want to
|
||||
# ignore idle background updates and wait for the response to
|
||||
# our action. (this keeps the button showing 'one moment...'
|
||||
# until the change takes effect, etc.)
|
||||
if (
|
||||
self._last_action_send_time is not None
|
||||
and time.time() - self._last_action_send_time < 5.0
|
||||
|
|
@ -354,8 +418,8 @@ class PrivateGatherTab(GatherTab):
|
|||
else:
|
||||
self._debug_server_comm('private party state response errored')
|
||||
|
||||
# Hmm I guess let's just ignore failed responses?...
|
||||
# Or should we show some sort of error state to the user?...
|
||||
# Hmm I guess let's just ignore failed responses?... Or should
|
||||
# we show some sort of error state to the user?...
|
||||
if result is None or state is None:
|
||||
return
|
||||
|
||||
|
|
@ -369,14 +433,17 @@ class PrivateGatherTab(GatherTab):
|
|||
if playsound:
|
||||
bui.getsound('click01').play()
|
||||
|
||||
# If switching from join to host, do a fresh state query.
|
||||
# If switching from join to host, force some refreshes.
|
||||
if self._state.sub_tab is SubTabType.JOIN and value is SubTabType.HOST:
|
||||
# Prevent taking any action until we've gotten a fresh state.
|
||||
# Prevent taking any action until we've gotten a fresh
|
||||
# state.
|
||||
self._waiting_for_initial_state = True
|
||||
|
||||
# This will get a state query sent out immediately.
|
||||
# Get some refreshes going immediately.
|
||||
self._last_hosting_state_query_time = None
|
||||
self._last_action_send_time = None # So we don't ignore response.
|
||||
self._last_datacode_refresh_time = None
|
||||
self._last_v2_state_query_time = None
|
||||
self._update()
|
||||
|
||||
self._state.sub_tab = value
|
||||
|
|
@ -403,14 +470,14 @@ class PrivateGatherTab(GatherTab):
|
|||
self._host_copy_button,
|
||||
self._host_connect_button,
|
||||
self._host_start_stop_button,
|
||||
self._get_tickets_button,
|
||||
self._get_tokens_button,
|
||||
]
|
||||
|
||||
def _refresh_sub_tab(self) -> None:
|
||||
assert self._container
|
||||
|
||||
# Store an index for our current selection so we can
|
||||
# reselect the equivalent recreated widget if possible.
|
||||
# Store an index for our current selection so we can reselect
|
||||
# the equivalent recreated widget if possible.
|
||||
selindex: int | None = None
|
||||
selchild = self._container.get_selected_child()
|
||||
if selchild is not None:
|
||||
|
|
@ -481,13 +548,13 @@ class PrivateGatherTab(GatherTab):
|
|||
edit=self._join_party_code_text, on_return_press_call=btn.activate
|
||||
)
|
||||
|
||||
def _on_get_tickets_press(self) -> None:
|
||||
def _on_get_tokens_press(self) -> None:
|
||||
if self._waiting_for_start_stop_response:
|
||||
return
|
||||
|
||||
# Bring up get-tickets window and then kill ourself (we're on the
|
||||
# overlay layer so we'd show up above it).
|
||||
GetCurrencyWindow(modal=True, origin_widget=self._get_tickets_button)
|
||||
# Bring up get-tickets window and then kill ourself (we're on
|
||||
# the overlay layer so we'd show up above it).
|
||||
GetTokensWindow(origin_widget=self._get_tokens_button)
|
||||
|
||||
def _build_host_tab(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
|
|
@ -498,13 +565,20 @@ class PrivateGatherTab(GatherTab):
|
|||
assert plus is not None
|
||||
|
||||
hostingstate = self._hostingstate
|
||||
|
||||
havegoldpass = self._v2state is not None and self._v2state.gold_pass
|
||||
|
||||
# We use both v1 and v2 account functionality here (sigh). So
|
||||
# make sure we're signed in on both ends.
|
||||
|
||||
# Make sure the V1 side is good to go.
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=200,
|
||||
maxwidth=self._c_width * 0.8,
|
||||
scale=0.8,
|
||||
color=(0.6, 0.56, 0.6),
|
||||
position=(self._c_width * 0.5, self._c_height * 0.5),
|
||||
|
|
@ -512,14 +586,31 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
self._showing_not_signed_in_screen = True
|
||||
return
|
||||
|
||||
# Make sure the V2 side is good to go.
|
||||
if plus.accounts.primary is None:
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._c_width * 0.8,
|
||||
scale=0.8,
|
||||
color=(0.6, 0.56, 0.6),
|
||||
position=(self._c_width * 0.5, self._c_height * 0.5),
|
||||
text=bui.Lstr(resource='v2AccountRequiredText'),
|
||||
)
|
||||
self._showing_not_signed_in_screen = True
|
||||
return
|
||||
|
||||
self._showing_not_signed_in_screen = False
|
||||
|
||||
# At first we don't want to show anything until we've gotten a state.
|
||||
# Update: In this situation we now simply show our existing state
|
||||
# but give the start/stop button a loading message and disallow its
|
||||
# use. This keeps things a lot less jumpy looking and allows selecting
|
||||
# playlists/etc without having to wait for the server each time
|
||||
# back to the ui.
|
||||
# At first we don't want to show anything until we've gotten a
|
||||
# state. Update: In this situation we now simply show our
|
||||
# existing state but give the start/stop button a loading
|
||||
# message and disallow its use. This keeps things a lot less
|
||||
# jumpy looking and allows selecting playlists/etc without
|
||||
# having to wait for the server each time back to the ui.
|
||||
if self._waiting_for_initial_state and bool(False):
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
|
|
@ -537,16 +628,21 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
return
|
||||
|
||||
# If we're not currently hosting and hosting requires tickets,
|
||||
# If we're not currently hosting and hosting requires tokens,
|
||||
# Show our count (possibly with a link to purchase more).
|
||||
if (
|
||||
not self._waiting_for_initial_state
|
||||
and hostingstate.party_code is None
|
||||
and hostingstate.tickets_to_host_now != 0
|
||||
and not havegoldpass
|
||||
):
|
||||
if not bui.app.ui_v1.use_toolbars:
|
||||
if bui.app.classic.allow_ticket_purchases:
|
||||
self._get_tickets_button = bui.buttonwidget(
|
||||
|
||||
# Currently have no allow_token_purchases value like
|
||||
# we had with tickets; just assuming we always allow.
|
||||
if bool(True):
|
||||
# if bui.app.classic.allow_ticket_purchases:
|
||||
self._get_tokens_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
position=(
|
||||
self._c_width - 210 + 125,
|
||||
|
|
@ -555,24 +651,25 @@ class PrivateGatherTab(GatherTab):
|
|||
autoselect=True,
|
||||
scale=0.6,
|
||||
size=(120, 60),
|
||||
textcolor=(0.2, 1, 0.2),
|
||||
label=bui.charstr(bui.SpecialChar.TICKET),
|
||||
textcolor=(1.0, 0.6, 0.0),
|
||||
label=bui.charstr(bui.SpecialChar.TOKEN),
|
||||
color=(0.65, 0.5, 0.8),
|
||||
on_activate_call=self._on_get_tickets_press,
|
||||
on_activate_call=self._on_get_tokens_press,
|
||||
)
|
||||
else:
|
||||
self._ticket_count_text = bui.textwidget(
|
||||
self._token_count_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
scale=0.6,
|
||||
position=(
|
||||
self._c_width - 210 + 125,
|
||||
self._c_height - 44,
|
||||
),
|
||||
color=(0.2, 1, 0.2),
|
||||
color=(1.0, 0.6, 0.0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
# Set initial ticket count.
|
||||
|
||||
# Set initial token count.
|
||||
self._update_currency_ui()
|
||||
|
||||
v = self._c_height - 90
|
||||
|
|
@ -594,7 +691,8 @@ class PrivateGatherTab(GatherTab):
|
|||
|
||||
v -= 100
|
||||
if hostingstate.party_code is None:
|
||||
# We've got no current party running; show options to set one up.
|
||||
# We've got no current party running; show options to set
|
||||
# one up.
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(0, 0),
|
||||
|
|
@ -713,8 +811,14 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
),
|
||||
)
|
||||
elif havegoldpass:
|
||||
# If we have a gold pass, none of the
|
||||
# timing/free-server-availability info below is relevant to
|
||||
# us.
|
||||
pass
|
||||
elif hostingstate.free_host_minutes_remaining is not None:
|
||||
# If we've been pre-approved to start/stop for free, show that.
|
||||
# If we've been pre-approved to start/stop for free, show
|
||||
# that.
|
||||
bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(0, 0),
|
||||
|
|
@ -811,12 +915,12 @@ class PrivateGatherTab(GatherTab):
|
|||
resource='gatherWindow.hostingUnavailableText'
|
||||
)
|
||||
elif hostingstate.party_code is None:
|
||||
ticon = bui.charstr(bui.SpecialChar.TICKET)
|
||||
nowtickets = hostingstate.tickets_to_host_now
|
||||
if nowtickets > 0:
|
||||
ticon = bui.charstr(bui.SpecialChar.TOKEN)
|
||||
nowtokens = hostingstate.tokens_to_host_now
|
||||
if nowtokens > 0 and not havegoldpass:
|
||||
btnlabel = bui.Lstr(
|
||||
resource='gatherWindow.startHostingPaidText',
|
||||
subs=[('${COST}', f'{ticon}{nowtickets}')],
|
||||
subs=[('${COST}', f'{ticon}{nowtokens}')],
|
||||
)
|
||||
else:
|
||||
btnlabel = bui.Lstr(
|
||||
|
|
@ -867,8 +971,8 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
|
||||
def _connect_to_party_code(self, code: str) -> None:
|
||||
# Ignore attempted followup sends for a few seconds.
|
||||
# (this will reset if we get a response)
|
||||
# Ignore attempted followup sends for a few seconds. (this will
|
||||
# reset if we get a response)
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -915,21 +1019,30 @@ class PrivateGatherTab(GatherTab):
|
|||
|
||||
bui.getsound('click01').play()
|
||||
|
||||
# We need our v2 info for this.
|
||||
if self._v2state is None or self._v2state.datacode is None:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.unavailableNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
# If we're not hosting, start.
|
||||
if self._hostingstate.party_code is None:
|
||||
# If there's a ticket cost, make sure we have enough tickets.
|
||||
if self._hostingstate.tickets_to_host_now > 0:
|
||||
ticket_count: int | None
|
||||
try:
|
||||
ticket_count = plus.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
# FIXME: should add a bui.NotSignedInError we can use here.
|
||||
ticket_count = None
|
||||
ticket_cost = self._hostingstate.tickets_to_host_now
|
||||
if ticket_count is not None and ticket_count < ticket_cost:
|
||||
show_get_tickets_prompt()
|
||||
# If there's a token cost, make sure we have enough tokens
|
||||
# or a gold pass.
|
||||
if self._hostingstate.tokens_to_host_now > 0:
|
||||
|
||||
if (
|
||||
not self._v2state.gold_pass
|
||||
and self._v2state.tokens
|
||||
< self._hostingstate.tokens_to_host_now
|
||||
):
|
||||
show_get_tokens_prompt()
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
self._last_action_send_time = time.time()
|
||||
plus.add_v1_account_transaction(
|
||||
{
|
||||
|
|
@ -937,6 +1050,7 @@ class PrivateGatherTab(GatherTab):
|
|||
'config': dataclass_to_dict(self._hostingconfig),
|
||||
'region_pings': bui.app.net.zone_pings,
|
||||
'expire_time': time.time() + 20,
|
||||
'datacode': self._v2state.datacode,
|
||||
},
|
||||
callback=bui.WeakCall(self._hosting_state_response),
|
||||
)
|
||||
|
|
|
|||
881
dist/ba_data/python/bauiv1lib/gettickets.py
vendored
Normal file
881
dist/ba_data/python/bauiv1lib/gettickets.py
vendored
Normal file
|
|
@ -0,0 +1,881 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for purchasing/acquiring currency."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.util import utc_now
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class GetTicketsWindow(bui.Window):
|
||||
"""Window for purchasing/acquiring classic tickets."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
from_modal_store: bool = False,
|
||||
modal: bool = False,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
store_back_location: str | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
bui.set_analytics_screen('Get Tickets Window')
|
||||
|
||||
self._transitioning_out = False
|
||||
self._store_back_location = store_back_location # ew.
|
||||
|
||||
self._ad_button_greyed = False
|
||||
self._smooth_update_timer: bui.AppTimer | None = None
|
||||
self._ad_button = None
|
||||
self._ad_label = None
|
||||
self._ad_image = None
|
||||
self._ad_time_text = 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
|
||||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
|
||||
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = 480.0
|
||||
|
||||
self._modal = modal
|
||||
self._from_modal_store = from_modal_store
|
||||
self._r = 'getTicketsWindow'
|
||||
|
||||
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
color=(0.4, 0.37, 0.55),
|
||||
scale=(
|
||||
1.63
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
stack_offset=(
|
||||
(0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(55 + x_inset, self._height - 79),
|
||||
size=(140, 60),
|
||||
scale=1.0,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='doneText' if modal else 'backText'),
|
||||
button_type='regular' if modal else 'back',
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5 - 15, self._height - 47),
|
||||
size=(0, 0),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=1.2,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
# text='Testing really long text here blah blah',
|
||||
maxwidth=260,
|
||||
)
|
||||
|
||||
# Get Tokens button
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 72),
|
||||
color=(0.65, 0.5, 0.7),
|
||||
textcolor=bui.app.ui_v1.title_color,
|
||||
size=(190, 50),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='tokens.getTokensText'),
|
||||
on_activate_call=self._get_tokens_press,
|
||||
)
|
||||
|
||||
# 'New!' by tokens button
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource='newExclaimText'),
|
||||
position=(self._width * 0.5 + 25, self._height - 32),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 0, 1.0),
|
||||
rotate=22,
|
||||
shadow=1.0,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.7,
|
||||
)
|
||||
|
||||
if not modal:
|
||||
bui.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
b_size = (220.0, 180.0)
|
||||
v = self._height - b_size[1] - 80
|
||||
spacing = 1
|
||||
|
||||
self._ad_button = None
|
||||
|
||||
def _add_button(
|
||||
item: str,
|
||||
position: tuple[float, float],
|
||||
size: tuple[float, float],
|
||||
label: bui.Lstr,
|
||||
price: str | None = None,
|
||||
tex_name: str | None = None,
|
||||
tex_opacity: float = 1.0,
|
||||
tex_scale: float = 1.0,
|
||||
enabled: bool = True,
|
||||
text_scale: float = 1.0,
|
||||
) -> bui.Widget:
|
||||
btn2 = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=position,
|
||||
button_type='square',
|
||||
size=size,
|
||||
label='',
|
||||
autoselect=True,
|
||||
color=None if enabled else (0.5, 0.5, 0.5),
|
||||
on_activate_call=(
|
||||
bui.Call(self._purchase, item)
|
||||
if enabled
|
||||
else self._disabled_press
|
||||
),
|
||||
)
|
||||
txt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=label,
|
||||
position=(
|
||||
position[0] + size[0] * 0.5,
|
||||
position[1] + size[1] * 0.3,
|
||||
),
|
||||
scale=text_scale,
|
||||
maxwidth=size[0] * 0.75,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn2,
|
||||
color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
|
||||
)
|
||||
if price is not None and enabled:
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=price,
|
||||
position=(
|
||||
position[0] + size[0] * 0.5,
|
||||
position[1] + size[1] * 0.17,
|
||||
),
|
||||
scale=0.7,
|
||||
maxwidth=size[0] * 0.75,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn2,
|
||||
color=(0.4, 0.9, 0.4, 1.0),
|
||||
)
|
||||
i = None
|
||||
if tex_name is not None:
|
||||
tex_size = 90.0 * tex_scale
|
||||
i = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture(tex_name),
|
||||
position=(
|
||||
position[0] + size[0] * 0.5 - tex_size * 0.5,
|
||||
position[1] + size[1] * 0.66 - tex_size * 0.5,
|
||||
),
|
||||
size=(tex_size, tex_size),
|
||||
draw_controller=btn2,
|
||||
opacity=tex_opacity * (1.0 if enabled else 0.25),
|
||||
)
|
||||
if item == 'ad':
|
||||
self._ad_button = btn2
|
||||
self._ad_label = txt
|
||||
assert i is not None
|
||||
self._ad_image = i
|
||||
self._ad_time_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text='1m 10s',
|
||||
position=(
|
||||
position[0] + size[0] * 0.5,
|
||||
position[1] + size[1] * 0.5,
|
||||
),
|
||||
scale=text_scale * 1.2,
|
||||
maxwidth=size[0] * 0.85,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn2,
|
||||
color=(0.4, 0.9, 0.4, 1.0),
|
||||
)
|
||||
return btn2
|
||||
|
||||
rsrc = f'{self._r}.ticketsText'
|
||||
|
||||
c2txt = bui.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val('tickets2Amount', 500)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
c3txt = bui.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val(
|
||||
'tickets3Amount', 1500
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
c4txt = bui.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val(
|
||||
'tickets4Amount', 5000
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
c5txt = bui.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val(
|
||||
'tickets5Amount', 15000
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
h = 110.0
|
||||
|
||||
# Enable buttons if we have prices.
|
||||
tickets2_price = plus.get_price('tickets2')
|
||||
tickets3_price = plus.get_price('tickets3')
|
||||
tickets4_price = plus.get_price('tickets4')
|
||||
tickets5_price = plus.get_price('tickets5')
|
||||
|
||||
# TEMP
|
||||
# tickets1_price = '$0.99'
|
||||
# tickets2_price = '$4.99'
|
||||
# tickets3_price = '$9.99'
|
||||
# tickets4_price = '$19.99'
|
||||
# tickets5_price = '$49.99'
|
||||
|
||||
_add_button(
|
||||
'tickets2',
|
||||
enabled=(tickets2_price is not None),
|
||||
position=(
|
||||
self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
|
||||
v,
|
||||
),
|
||||
size=b_size,
|
||||
label=c2txt,
|
||||
price=tickets2_price,
|
||||
tex_name='ticketsMore',
|
||||
) # 0.99-ish
|
||||
_add_button(
|
||||
'tickets3',
|
||||
enabled=(tickets3_price is not None),
|
||||
position=(
|
||||
self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
|
||||
v,
|
||||
),
|
||||
size=b_size,
|
||||
label=c3txt,
|
||||
price=tickets3_price,
|
||||
tex_name='ticketRoll',
|
||||
) # 4.99-ish
|
||||
v -= b_size[1] - 5
|
||||
_add_button(
|
||||
'tickets4',
|
||||
enabled=(tickets4_price is not None),
|
||||
position=(
|
||||
self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
|
||||
v,
|
||||
),
|
||||
size=b_size,
|
||||
label=c4txt,
|
||||
price=tickets4_price,
|
||||
tex_name='ticketRollBig',
|
||||
tex_scale=1.2,
|
||||
) # 9.99-ish
|
||||
_add_button(
|
||||
'tickets5',
|
||||
enabled=(tickets5_price is not None),
|
||||
position=(
|
||||
self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
|
||||
v,
|
||||
),
|
||||
size=b_size,
|
||||
label=c5txt,
|
||||
price=tickets5_price,
|
||||
tex_name='ticketRolls',
|
||||
tex_scale=1.2,
|
||||
) # 19.99-ish
|
||||
|
||||
self._enable_ad_button = plus.has_video_ads()
|
||||
h = self._width * 0.5 + 110.0
|
||||
v = self._height - b_size[1] - 115.0
|
||||
|
||||
if self._enable_ad_button:
|
||||
h_offs = 35
|
||||
b_size_3 = (150, 120)
|
||||
cdb = _add_button(
|
||||
'ad',
|
||||
position=(h + h_offs, v),
|
||||
size=b_size_3,
|
||||
label=bui.Lstr(
|
||||
resource=f'{self._r}.ticketsFromASponsorText',
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val(
|
||||
'sponsorTickets', 5
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
tex_name='ticketsMore',
|
||||
enabled=self._enable_ad_button,
|
||||
tex_opacity=0.6,
|
||||
tex_scale=0.7,
|
||||
text_scale=0.7,
|
||||
)
|
||||
bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
|
||||
|
||||
self._ad_free_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=f'{self._r}.freeText'),
|
||||
position=(
|
||||
h + h_offs + b_size_3[0] * 0.5,
|
||||
v + b_size_3[1] * 0.5 + 25,
|
||||
),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 0, 1.0),
|
||||
draw_controller=cdb,
|
||||
rotate=15,
|
||||
shadow=1.0,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=1.0,
|
||||
)
|
||||
v -= 125
|
||||
else:
|
||||
v -= 20
|
||||
|
||||
if bool(True):
|
||||
h_offs = 35
|
||||
b_size_3 = (150, 120)
|
||||
cdb = _add_button(
|
||||
'app_invite',
|
||||
position=(h + h_offs, v),
|
||||
size=b_size_3,
|
||||
label=bui.Lstr(
|
||||
resource='gatherWindow.earnTicketsForRecommendingText',
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
plus.get_v1_account_misc_read_val(
|
||||
'sponsorTickets', 5
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
tex_name='ticketsMore',
|
||||
enabled=True,
|
||||
tex_opacity=0.6,
|
||||
tex_scale=0.7,
|
||||
text_scale=0.7,
|
||||
)
|
||||
bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=f'{self._r}.freeText'),
|
||||
position=(
|
||||
h + h_offs + b_size_3[0] * 0.5,
|
||||
v + b_size_3[1] * 0.5 + 25,
|
||||
),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 0, 1.0),
|
||||
draw_controller=cdb,
|
||||
rotate=15,
|
||||
shadow=1.0,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=1.0,
|
||||
)
|
||||
tc_y_offs = 0
|
||||
else:
|
||||
tc_y_offs = 0
|
||||
|
||||
h = self._width - (185 + x_inset)
|
||||
v = self._height - 105 + tc_y_offs
|
||||
|
||||
txt1 = (
|
||||
bui.Lstr(resource=f'{self._r}.youHaveText')
|
||||
.evaluate()
|
||||
.partition('${COUNT}')[0]
|
||||
.strip()
|
||||
)
|
||||
txt2 = (
|
||||
bui.Lstr(resource=f'{self._r}.youHaveText')
|
||||
.evaluate()
|
||||
.rpartition('${COUNT}')[-1]
|
||||
.strip()
|
||||
)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=txt1,
|
||||
position=(h, v),
|
||||
size=(0, 0),
|
||||
color=(0.5, 0.5, 0.6),
|
||||
maxwidth=200,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.8,
|
||||
)
|
||||
v -= 30
|
||||
self._ticket_count_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h, v),
|
||||
size=(0, 0),
|
||||
color=(0.2, 1.0, 0.2),
|
||||
maxwidth=200,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=1.6,
|
||||
)
|
||||
v -= 30
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=txt2,
|
||||
position=(h, v),
|
||||
size=(0, 0),
|
||||
color=(0.5, 0.5, 0.6),
|
||||
maxwidth=200,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.8,
|
||||
)
|
||||
|
||||
self._ticking_sound: bui.Sound | None = None
|
||||
self._smooth_ticket_count: float | None = None
|
||||
self._ticket_count = 0
|
||||
self._update()
|
||||
self._update_timer = bui.AppTimer(
|
||||
1.0, bui.WeakCall(self._update), repeat=True
|
||||
)
|
||||
self._smooth_increase_speed = 1.0
|
||||
|
||||
def __del__(self) -> None:
|
||||
if self._ticking_sound is not None:
|
||||
self._ticking_sound.stop()
|
||||
self._ticking_sound = None
|
||||
|
||||
def _smooth_update(self) -> None:
|
||||
if not self._ticket_count_text:
|
||||
self._smooth_update_timer = None
|
||||
return
|
||||
|
||||
finished = False
|
||||
|
||||
# If we're going down, do it immediately.
|
||||
assert self._smooth_ticket_count is not None
|
||||
if int(self._smooth_ticket_count) >= self._ticket_count:
|
||||
self._smooth_ticket_count = float(self._ticket_count)
|
||||
finished = True
|
||||
else:
|
||||
# We're going up; start a sound if need be.
|
||||
self._smooth_ticket_count = min(
|
||||
self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
|
||||
self._ticket_count,
|
||||
)
|
||||
if int(self._smooth_ticket_count) >= self._ticket_count:
|
||||
finished = True
|
||||
self._smooth_ticket_count = float(self._ticket_count)
|
||||
elif self._ticking_sound is None:
|
||||
self._ticking_sound = bui.getsound('scoreIncrease')
|
||||
self._ticking_sound.play()
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._ticket_count_text,
|
||||
text=str(int(self._smooth_ticket_count)),
|
||||
)
|
||||
|
||||
# If we've reached the target, kill the timer/sound/etc.
|
||||
if finished:
|
||||
self._smooth_update_timer = None
|
||||
if self._ticking_sound is not None:
|
||||
self._ticking_sound.stop()
|
||||
self._ticking_sound = None
|
||||
bui.getsound('cashRegister2').play()
|
||||
|
||||
def _update(self) -> None:
|
||||
import datetime
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# If we somehow get signed out, just die.
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
self._back()
|
||||
return
|
||||
|
||||
self._ticket_count = plus.get_v1_account_ticket_count()
|
||||
|
||||
# Update our incentivized ad button depending on whether ads are
|
||||
# available.
|
||||
if self._ad_button is not None:
|
||||
next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
|
||||
'nextRewardAdTime', None
|
||||
)
|
||||
if next_reward_ad_time is not None:
|
||||
next_reward_ad_time = datetime.datetime.fromtimestamp(
|
||||
next_reward_ad_time, datetime.UTC
|
||||
)
|
||||
now = utc_now()
|
||||
if plus.have_incentivized_ad() and (
|
||||
next_reward_ad_time is None or next_reward_ad_time <= now
|
||||
):
|
||||
self._ad_button_greyed = False
|
||||
bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
|
||||
bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
|
||||
bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
|
||||
bui.imagewidget(edit=self._ad_image, opacity=0.6)
|
||||
bui.textwidget(edit=self._ad_time_text, text='')
|
||||
else:
|
||||
self._ad_button_greyed = True
|
||||
bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
|
||||
bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
|
||||
bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
|
||||
bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
|
||||
sval: str | bui.Lstr
|
||||
if (
|
||||
next_reward_ad_time is not None
|
||||
and next_reward_ad_time > now
|
||||
):
|
||||
sval = bui.timestring(
|
||||
(next_reward_ad_time - now).total_seconds(), centi=False
|
||||
)
|
||||
else:
|
||||
sval = ''
|
||||
bui.textwidget(edit=self._ad_time_text, text=sval)
|
||||
|
||||
# If this is our first update, assign immediately; otherwise kick
|
||||
# off a smooth transition if the value has changed.
|
||||
if self._smooth_ticket_count is None:
|
||||
self._smooth_ticket_count = float(self._ticket_count)
|
||||
self._smooth_update() # will set the text widget
|
||||
|
||||
elif (
|
||||
self._ticket_count != int(self._smooth_ticket_count)
|
||||
and self._smooth_update_timer is None
|
||||
):
|
||||
self._smooth_update_timer = bui.AppTimer(
|
||||
0.05, bui.WeakCall(self._smooth_update), repeat=True
|
||||
)
|
||||
diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
|
||||
self._smooth_increase_speed = (
|
||||
diff / 100.0
|
||||
if diff >= 5000
|
||||
else (
|
||||
diff / 50.0
|
||||
if diff >= 1500
|
||||
else diff / 30.0 if diff >= 500 else diff / 15.0
|
||||
)
|
||||
)
|
||||
|
||||
def _disabled_press(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# If we're on a platform without purchases, inform the user they
|
||||
# can link their accounts and buy stuff elsewhere.
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
if (
|
||||
app.env.test
|
||||
or (
|
||||
app.classic.platform == 'android'
|
||||
and app.classic.subplatform in ['oculus', 'cardboard']
|
||||
)
|
||||
) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=f'{self._r}.unavailableLinkAccountText'),
|
||||
color=(1, 0.5, 0),
|
||||
)
|
||||
else:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=f'{self._r}.unavailableText'),
|
||||
color=(1, 0.5, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
||||
def _purchase(self, item: str) -> None:
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib import appinvite
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
if bui.app.classic is None:
|
||||
raise RuntimeError('This requires classic support.')
|
||||
|
||||
if item == 'app_invite':
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
appinvite.handle_app_invites_press()
|
||||
return
|
||||
|
||||
# Here we ping the server to ask if it's valid for us to
|
||||
# purchase this.. (better to fail now than after we've paid
|
||||
# locally).
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'bsAccountPurchaseCheck',
|
||||
{
|
||||
'item': item,
|
||||
'platform': app.classic.platform,
|
||||
'subplatform': app.classic.subplatform,
|
||||
'version': app.env.engine_version,
|
||||
'buildNumber': app.env.engine_build_number,
|
||||
},
|
||||
callback=bui.WeakCall(self._purchase_check_result, item),
|
||||
)
|
||||
|
||||
def _purchase_check_result(
|
||||
self, item: str, result: dict[str, Any] | None
|
||||
) -> None:
|
||||
if result is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.unavailableNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
else:
|
||||
if result['allow']:
|
||||
self._do_purchase(item)
|
||||
else:
|
||||
if result['reason'] == 'versionTooOld':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
else:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='getTicketsWindow.unavailableText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
# Actually start the purchase locally.
|
||||
def _do_purchase(self, item: str) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
if item == 'ad':
|
||||
import datetime
|
||||
|
||||
# If ads are disabled until some time, error.
|
||||
next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
|
||||
'nextRewardAdTime', None
|
||||
)
|
||||
if next_reward_ad_time is not None:
|
||||
next_reward_ad_time = datetime.datetime.fromtimestamp(
|
||||
next_reward_ad_time, datetime.UTC
|
||||
)
|
||||
now = utc_now()
|
||||
if (
|
||||
next_reward_ad_time is not None and next_reward_ad_time > now
|
||||
) or self._ad_button_greyed:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource='getTicketsWindow.unavailableTemporarilyText'
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
elif self._enable_ad_button:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.classic.ads.show_ad('tickets')
|
||||
else:
|
||||
plus.purchase(item)
|
||||
|
||||
def _get_tokens_press(self) -> None:
|
||||
from functools import partial
|
||||
|
||||
from bauiv1lib.gettokens import GetTokensWindow
|
||||
|
||||
# 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='out_left')
|
||||
|
||||
# Note: Make sure we don't pass anything here that would
|
||||
# capture 'self'. (a lambda would implicitly do this by capturing
|
||||
# the stack frame).
|
||||
restorecall = partial(
|
||||
_restore_get_tickets_window,
|
||||
self._modal,
|
||||
self._from_modal_store,
|
||||
self._store_back_location,
|
||||
)
|
||||
|
||||
window = GetTokensWindow(
|
||||
transition='in_right',
|
||||
restore_previous_call=restorecall,
|
||||
).get_root_widget()
|
||||
if not self._modal and not self._from_modal_store:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
window, from_window=self._root_widget
|
||||
)
|
||||
self._transitioning_out = True
|
||||
|
||||
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
|
||||
)
|
||||
if not self._modal:
|
||||
window = browser.StoreBrowserWindow(
|
||||
transition='in_left',
|
||||
modal=self._from_modal_store,
|
||||
back_location=self._store_back_location,
|
||||
).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, from_window=self._root_widget
|
||||
)
|
||||
self._transitioning_out = True
|
||||
|
||||
|
||||
# A call we can bundle up and pass to windows we open; allows them to
|
||||
# get back to us without having to explicitly know about us.
|
||||
def _restore_get_tickets_window(
|
||||
modal: bool,
|
||||
from_modal_store: bool,
|
||||
store_back_location: str | None,
|
||||
from_window: bui.Widget,
|
||||
) -> None:
|
||||
restored = GetTicketsWindow(
|
||||
transition='in_left',
|
||||
modal=modal,
|
||||
from_modal_store=from_modal_store,
|
||||
store_back_location=store_back_location,
|
||||
)
|
||||
if not modal and not from_modal_store:
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.set_main_menu_window(
|
||||
restored.get_root_widget(), from_window=from_window
|
||||
)
|
||||
|
||||
|
||||
def show_get_tickets_prompt() -> None:
|
||||
"""Show a 'not enough tickets' prompt with an option to purchase more.
|
||||
|
||||
Note that the purchase option may not always be available
|
||||
depending on the build of the game.
|
||||
"""
|
||||
from bauiv1lib.confirm import ConfirmWindow
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
if bui.app.classic.allow_ticket_purchases:
|
||||
ConfirmWindow(
|
||||
bui.Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'You don\'t have enough tickets for this!',
|
||||
)
|
||||
),
|
||||
lambda: GetTicketsWindow(modal=True),
|
||||
ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
|
||||
width=460,
|
||||
height=130,
|
||||
)
|
||||
else:
|
||||
ConfirmWindow(
|
||||
bui.Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'You don\'t have enough tickets for this!',
|
||||
)
|
||||
),
|
||||
cancel_button=False,
|
||||
width=460,
|
||||
height=130,
|
||||
)
|
||||
809
dist/ba_data/python/bauiv1lib/gettokens.py
vendored
Normal file
809
dist/ba_data/python/bauiv1lib/gettokens.py
vendored
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for purchasing/acquiring currency."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
import bacommon.cloud
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
@dataclass
|
||||
class _ButtonDef:
|
||||
itemid: str
|
||||
width: float
|
||||
color: tuple[float, float, float]
|
||||
imgdefs: list[_ImgDef]
|
||||
txtdefs: list[_TxtDef]
|
||||
prepad: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class _ImgDef:
|
||||
tex: str
|
||||
pos: tuple[float, float]
|
||||
size: tuple[float, float]
|
||||
color: tuple[float, float, float] = (1, 1, 1)
|
||||
opacity: float = 1.0
|
||||
draw_controller_mult: float | None = None
|
||||
|
||||
|
||||
class TextContents(Enum):
|
||||
"""Some type of text to show."""
|
||||
|
||||
PRICE = 'price'
|
||||
|
||||
|
||||
@dataclass
|
||||
class _TxtDef:
|
||||
text: str | TextContents | bui.Lstr
|
||||
pos: tuple[float, float]
|
||||
maxwidth: float | None
|
||||
scale: float = 1.0
|
||||
color: tuple[float, float, float] = (1, 1, 1)
|
||||
rotate: float | None = None
|
||||
|
||||
|
||||
class GetTokensWindow(bui.Window):
|
||||
"""Window for purchasing/acquiring classic tickets."""
|
||||
|
||||
class State(Enum):
|
||||
"""What are we doing?"""
|
||||
|
||||
LOADING = 'loading'
|
||||
NOT_SIGNED_IN = 'not_signed_in'
|
||||
HAVE_GOLD_PASS = 'have_gold_pass'
|
||||
SHOWING_STORE = 'showing_store'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
restore_previous_call: Callable[[bui.Widget], None] | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
bwidthstd = 170
|
||||
bwidthwide = 300
|
||||
ycolor = (0, 0, 0.3)
|
||||
pcolor = (0, 0, 0.3)
|
||||
pos1 = 65
|
||||
pos2 = 34
|
||||
titlescale = 0.9
|
||||
pricescale = 0.65
|
||||
bcapcol1 = (0.25, 0.13, 0.02)
|
||||
self._buttondefs: list[_ButtonDef] = [
|
||||
_ButtonDef(
|
||||
itemid='tokens1',
|
||||
width=bwidthstd,
|
||||
color=ycolor,
|
||||
imgdefs=[
|
||||
_ImgDef(
|
||||
'tokens1',
|
||||
pos=(-3, 85),
|
||||
size=(172, 172),
|
||||
opacity=1.0,
|
||||
draw_controller_mult=0.5,
|
||||
),
|
||||
_ImgDef(
|
||||
'windowBottomCap',
|
||||
pos=(1.5, 4),
|
||||
size=(bwidthstd * 0.960, 100),
|
||||
color=bcapcol1,
|
||||
opacity=1.0,
|
||||
),
|
||||
],
|
||||
txtdefs=[
|
||||
_TxtDef(
|
||||
bui.Lstr(
|
||||
resource='tokens.numTokensText',
|
||||
subs=[('${COUNT}', '50')],
|
||||
),
|
||||
pos=(bwidthstd * 0.5, pos1),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=titlescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
_TxtDef(
|
||||
TextContents.PRICE,
|
||||
pos=(bwidthstd * 0.5, pos2),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=pricescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ButtonDef(
|
||||
itemid='tokens2',
|
||||
width=bwidthstd,
|
||||
color=ycolor,
|
||||
imgdefs=[
|
||||
_ImgDef(
|
||||
'tokens2',
|
||||
pos=(-3, 85),
|
||||
size=(172, 172),
|
||||
opacity=1.0,
|
||||
draw_controller_mult=0.5,
|
||||
),
|
||||
_ImgDef(
|
||||
'windowBottomCap',
|
||||
pos=(1.5, 4),
|
||||
size=(bwidthstd * 0.960, 100),
|
||||
color=bcapcol1,
|
||||
opacity=1.0,
|
||||
),
|
||||
],
|
||||
txtdefs=[
|
||||
_TxtDef(
|
||||
bui.Lstr(
|
||||
resource='tokens.numTokensText',
|
||||
subs=[('${COUNT}', '500')],
|
||||
),
|
||||
pos=(bwidthstd * 0.5, pos1),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=titlescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
_TxtDef(
|
||||
TextContents.PRICE,
|
||||
pos=(bwidthstd * 0.5, pos2),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=pricescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ButtonDef(
|
||||
itemid='tokens3',
|
||||
width=bwidthstd,
|
||||
color=ycolor,
|
||||
imgdefs=[
|
||||
_ImgDef(
|
||||
'tokens3',
|
||||
pos=(-3, 85),
|
||||
size=(172, 172),
|
||||
opacity=1.0,
|
||||
draw_controller_mult=0.5,
|
||||
),
|
||||
_ImgDef(
|
||||
'windowBottomCap',
|
||||
pos=(1.5, 4),
|
||||
size=(bwidthstd * 0.960, 100),
|
||||
color=bcapcol1,
|
||||
opacity=1.0,
|
||||
),
|
||||
],
|
||||
txtdefs=[
|
||||
_TxtDef(
|
||||
bui.Lstr(
|
||||
resource='tokens.numTokensText',
|
||||
subs=[('${COUNT}', '1200')],
|
||||
),
|
||||
pos=(bwidthstd * 0.5, pos1),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=titlescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
_TxtDef(
|
||||
TextContents.PRICE,
|
||||
pos=(bwidthstd * 0.5, pos2),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=pricescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ButtonDef(
|
||||
itemid='tokens4',
|
||||
width=bwidthstd,
|
||||
color=ycolor,
|
||||
imgdefs=[
|
||||
_ImgDef(
|
||||
'tokens4',
|
||||
pos=(-3, 85),
|
||||
size=(172, 172),
|
||||
opacity=1.0,
|
||||
draw_controller_mult=0.5,
|
||||
),
|
||||
_ImgDef(
|
||||
'windowBottomCap',
|
||||
pos=(1.5, 4),
|
||||
size=(bwidthstd * 0.960, 100),
|
||||
color=bcapcol1,
|
||||
opacity=1.0,
|
||||
),
|
||||
],
|
||||
txtdefs=[
|
||||
_TxtDef(
|
||||
bui.Lstr(
|
||||
resource='tokens.numTokensText',
|
||||
subs=[('${COUNT}', '2600')],
|
||||
),
|
||||
pos=(bwidthstd * 0.5, pos1),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=titlescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
_TxtDef(
|
||||
TextContents.PRICE,
|
||||
pos=(bwidthstd * 0.5, pos2),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=pricescale,
|
||||
maxwidth=bwidthstd * 0.9,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ButtonDef(
|
||||
itemid='gold_pass',
|
||||
width=bwidthwide,
|
||||
color=pcolor,
|
||||
imgdefs=[
|
||||
_ImgDef(
|
||||
'goldPass',
|
||||
pos=(-7, 102),
|
||||
size=(312, 156),
|
||||
draw_controller_mult=0.3,
|
||||
),
|
||||
_ImgDef(
|
||||
'windowBottomCap',
|
||||
pos=(8, 4),
|
||||
size=(bwidthwide * 0.923, 116),
|
||||
color=(0.25, 0.12, 0.15),
|
||||
opacity=1.0,
|
||||
),
|
||||
],
|
||||
txtdefs=[
|
||||
_TxtDef(
|
||||
bui.Lstr(resource='goldPass.goldPassText'),
|
||||
pos=(bwidthwide * 0.5, pos1 + 27),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=titlescale,
|
||||
maxwidth=bwidthwide * 0.8,
|
||||
),
|
||||
_TxtDef(
|
||||
bui.Lstr(resource='goldPass.desc1InfTokensText'),
|
||||
pos=(bwidthwide * 0.5, pos1 + 6),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=0.4,
|
||||
maxwidth=bwidthwide * 0.8,
|
||||
),
|
||||
_TxtDef(
|
||||
bui.Lstr(resource='goldPass.desc2NoAdsText'),
|
||||
pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 1),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=0.4,
|
||||
maxwidth=bwidthwide * 0.8,
|
||||
),
|
||||
_TxtDef(
|
||||
bui.Lstr(resource='goldPass.desc3ForeverText'),
|
||||
pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 2),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=0.4,
|
||||
maxwidth=bwidthwide * 0.8,
|
||||
),
|
||||
_TxtDef(
|
||||
TextContents.PRICE,
|
||||
pos=(bwidthwide * 0.5, pos2 - 9),
|
||||
color=(1.1, 1.05, 1.0),
|
||||
scale=pricescale,
|
||||
maxwidth=bwidthwide * 0.8,
|
||||
),
|
||||
],
|
||||
prepad=-8,
|
||||
),
|
||||
]
|
||||
|
||||
self._transitioning_out = False
|
||||
self._restore_previous_call = restore_previous_call
|
||||
self._textcolor = (0.92, 0.92, 2.0)
|
||||
|
||||
self._query_in_flight = False
|
||||
self._last_query_time = -1.0
|
||||
self._last_query_response: bacommon.cloud.StoreQueryResponse | None = (
|
||||
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 = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
|
||||
self._x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = 480.0
|
||||
|
||||
self._r = 'getTokensWindow'
|
||||
|
||||
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
color=(0.3, 0.23, 0.36),
|
||||
scale=(
|
||||
1.63
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
stack_offset=(
|
||||
(0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(45 + self._x_inset, self._height - 80),
|
||||
size=(
|
||||
(140, 60) if self._restore_previous_call is None else (60, 60)
|
||||
),
|
||||
scale=1.0,
|
||||
autoselect=True,
|
||||
label=(
|
||||
bui.Lstr(resource='doneText')
|
||||
if self._restore_previous_call is None
|
||||
else bui.charstr(bui.SpecialChar.BACK)
|
||||
),
|
||||
button_type=(
|
||||
'regular'
|
||||
if self._restore_previous_call is None
|
||||
else 'backSmall'
|
||||
),
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 47),
|
||||
size=(0, 0),
|
||||
color=self._textcolor,
|
||||
flatness=0.0,
|
||||
shadow=1.0,
|
||||
scale=1.2,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource='tokens.getTokensText'),
|
||||
maxwidth=260,
|
||||
)
|
||||
|
||||
self._status_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
size=(0, 0),
|
||||
position=(self._width * 0.5, self._height * 0.5),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
color=(0.6, 0.6, 0.6),
|
||||
scale=0.75,
|
||||
text=bui.Lstr(resource='store.loadingText'),
|
||||
)
|
||||
|
||||
self._core_widgets = [
|
||||
self._back_button,
|
||||
self._title_text,
|
||||
self._status_text,
|
||||
]
|
||||
|
||||
self._token_count_widget: bui.Widget | None = None
|
||||
self._smooth_update_timer: bui.AppTimer | None = None
|
||||
self._smooth_token_count: float | None = None
|
||||
self._token_count: int = 0
|
||||
self._smooth_increase_speed = 1.0
|
||||
self._ticking_sound: bui.Sound | None = None
|
||||
|
||||
# Get all textures used by our buttons preloading so hopefully
|
||||
# they'll be in place by the time we show them.
|
||||
for bdef in self._buttondefs:
|
||||
for bimg in bdef.imgdefs:
|
||||
bui.gettexture(bimg.tex)
|
||||
|
||||
self._state = self.State.LOADING
|
||||
|
||||
self._update_timer = bui.AppTimer(
|
||||
0.789, bui.WeakCall(self._update), repeat=True
|
||||
)
|
||||
self._update()
|
||||
|
||||
def __del__(self) -> None:
|
||||
if self._ticking_sound is not None:
|
||||
self._ticking_sound.stop()
|
||||
self._ticking_sound = None
|
||||
|
||||
def _update(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
|
||||
|
||||
plus = bui.app.plus
|
||||
|
||||
if plus is None or plus.accounts.primary is None:
|
||||
self._update_state(self.State.NOT_SIGNED_IN)
|
||||
return
|
||||
|
||||
# Poll for relevant changes to the store or our account.
|
||||
now = time.monotonic()
|
||||
if not self._query_in_flight and now - self._last_query_time > 2.0:
|
||||
self._last_query_time = now
|
||||
self._query_in_flight = True
|
||||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.cloud.StoreQueryMessage(),
|
||||
on_response=bui.WeakCall(self._on_store_query_response),
|
||||
)
|
||||
|
||||
# Can't do much until we get a store state.
|
||||
if self._last_query_response is None:
|
||||
return
|
||||
|
||||
# If we've got a gold-pass, just show that. No need to offer any
|
||||
# other purchases.
|
||||
if self._last_query_response.gold_pass:
|
||||
self._update_state(self.State.HAVE_GOLD_PASS)
|
||||
return
|
||||
|
||||
# Ok we seem to be signed in and have store stuff we can show.
|
||||
# Do that.
|
||||
self._update_state(self.State.SHOWING_STORE)
|
||||
|
||||
def _update_state(self, state: State) -> None:
|
||||
|
||||
# We don't do much when state is unchanged.
|
||||
if state is self._state:
|
||||
# Update a few things in store mode though, such as token
|
||||
# count.
|
||||
if state is self.State.SHOWING_STORE:
|
||||
self._update_store_state()
|
||||
return
|
||||
|
||||
# Ok, state is changing. Start by resetting to a blank slate.
|
||||
self._token_count_widget = None
|
||||
for widget in self._root_widget.get_children():
|
||||
if widget not in self._core_widgets:
|
||||
widget.delete()
|
||||
|
||||
# Build up new state.
|
||||
if state is self.State.NOT_SIGNED_IN:
|
||||
bui.textwidget(
|
||||
edit=self._status_text,
|
||||
color=(1, 0, 0),
|
||||
text=bui.Lstr(resource='notSignedInErrorText'),
|
||||
)
|
||||
elif state is self.State.LOADING:
|
||||
raise RuntimeError('Should never return to loading state.')
|
||||
elif state is self.State.HAVE_GOLD_PASS:
|
||||
bui.textwidget(
|
||||
edit=self._status_text,
|
||||
color=(0, 1, 0),
|
||||
text=bui.Lstr(resource='tokens.youHaveGoldPassText'),
|
||||
)
|
||||
elif state is self.State.SHOWING_STORE:
|
||||
assert self._last_query_response is not None
|
||||
bui.textwidget(edit=self._status_text, text='')
|
||||
self._build_store_for_response(self._last_query_response)
|
||||
else:
|
||||
# Make sure we handle all cases.
|
||||
assert_never(state)
|
||||
|
||||
self._state = state
|
||||
|
||||
def _on_load_error(self) -> None:
|
||||
bui.textwidget(
|
||||
edit=self._status_text,
|
||||
text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
def _on_store_query_response(
|
||||
self, response: bacommon.cloud.StoreQueryResponse | Exception
|
||||
) -> None:
|
||||
self._query_in_flight = False
|
||||
if isinstance(response, bacommon.cloud.StoreQueryResponse):
|
||||
self._last_query_response = response
|
||||
# Hurry along any effects of this response.
|
||||
self._update()
|
||||
|
||||
def _build_store_for_response(
|
||||
self, response: bacommon.cloud.StoreQueryResponse
|
||||
) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
plus = bui.app.plus
|
||||
|
||||
bui.textwidget(edit=self._status_text, text='')
|
||||
|
||||
xinset = 40
|
||||
|
||||
scrollwidth = self._width - 2 * (self._x_inset + xinset)
|
||||
scrollheight = 280
|
||||
buttonpadding = -5
|
||||
|
||||
yoffs = 5
|
||||
|
||||
# We currently don't handle the zero-button case.
|
||||
assert self._buttondefs
|
||||
|
||||
total_button_width = sum(
|
||||
b.width + b.prepad for b in self._buttondefs
|
||||
) + buttonpadding * (len(self._buttondefs) - 1)
|
||||
|
||||
h_scroll = bui.hscrollwidget(
|
||||
parent=self._root_widget,
|
||||
size=(scrollwidth, scrollheight),
|
||||
position=(self._x_inset + xinset, 45),
|
||||
claims_left_right=True,
|
||||
highlight=False,
|
||||
border_opacity=0.25,
|
||||
)
|
||||
subcontainer = bui.containerwidget(
|
||||
parent=h_scroll,
|
||||
background=False,
|
||||
size=(max(total_button_width, scrollwidth), scrollheight),
|
||||
)
|
||||
tinfobtn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='learnMoreText'),
|
||||
position=(self._width * 0.5 - 75, self._height * 0.703),
|
||||
size=(180, 43),
|
||||
scale=0.8,
|
||||
color=(0.4, 0.25, 0.5),
|
||||
textcolor=self._textcolor,
|
||||
on_activate_call=partial(
|
||||
self._on_learn_more_press, response.token_info_url
|
||||
),
|
||||
)
|
||||
|
||||
x = 0.0
|
||||
bwidgets: list[bui.Widget] = []
|
||||
for i, buttondef in enumerate(self._buttondefs):
|
||||
|
||||
price = None if plus is None else plus.get_price(buttondef.itemid)
|
||||
|
||||
x += buttondef.prepad
|
||||
tdelay = 0.3 - i / len(self._buttondefs) * 0.25
|
||||
btn = bui.buttonwidget(
|
||||
autoselect=True,
|
||||
label='',
|
||||
color=buttondef.color,
|
||||
transition_delay=tdelay,
|
||||
up_widget=tinfobtn,
|
||||
parent=subcontainer,
|
||||
size=(buttondef.width, 275),
|
||||
position=(x, -10 + yoffs),
|
||||
button_type='square',
|
||||
on_activate_call=partial(
|
||||
self._purchase_press, buttondef.itemid
|
||||
),
|
||||
)
|
||||
bwidgets.append(btn)
|
||||
for imgdef in buttondef.imgdefs:
|
||||
_img = bui.imagewidget(
|
||||
parent=subcontainer,
|
||||
size=imgdef.size,
|
||||
position=(x + imgdef.pos[0], imgdef.pos[1] + yoffs),
|
||||
draw_controller=btn,
|
||||
draw_controller_mult=imgdef.draw_controller_mult,
|
||||
color=imgdef.color,
|
||||
texture=bui.gettexture(imgdef.tex),
|
||||
transition_delay=tdelay,
|
||||
opacity=imgdef.opacity,
|
||||
)
|
||||
for txtdef in buttondef.txtdefs:
|
||||
txt: bui.Lstr | str
|
||||
if isinstance(txtdef.text, TextContents):
|
||||
if txtdef.text is TextContents.PRICE:
|
||||
tcolor = (
|
||||
(1, 1, 1, 0.5) if price is None else txtdef.color
|
||||
)
|
||||
txt = (
|
||||
bui.Lstr(resource='unavailableText')
|
||||
if price is None
|
||||
else price
|
||||
)
|
||||
else:
|
||||
# Make sure we cover all cases.
|
||||
assert_never(txtdef.text)
|
||||
else:
|
||||
tcolor = txtdef.color
|
||||
txt = txtdef.text
|
||||
_txt = bui.textwidget(
|
||||
parent=subcontainer,
|
||||
text=txt,
|
||||
position=(x + txtdef.pos[0], txtdef.pos[1] + yoffs),
|
||||
size=(0, 0),
|
||||
scale=txtdef.scale,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=tcolor,
|
||||
transition_delay=tdelay,
|
||||
flatness=0.0,
|
||||
shadow=1.0,
|
||||
rotate=txtdef.rotate,
|
||||
maxwidth=txtdef.maxwidth,
|
||||
)
|
||||
x += buttondef.width + buttonpadding
|
||||
bui.containerwidget(edit=subcontainer, visible_child=bwidgets[0])
|
||||
|
||||
_tinfotxt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.812),
|
||||
color=self._textcolor,
|
||||
shadow=1.0,
|
||||
scale=0.7,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource='tokens.shinyNewCurrencyText'),
|
||||
)
|
||||
self._token_count_widget = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - self._x_inset - 120.0, self._height - 48),
|
||||
color=(2.0, 0.7, 0.0),
|
||||
shadow=1.0,
|
||||
flatness=0.0,
|
||||
size=(0, 0),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
text='',
|
||||
)
|
||||
self._token_count = response.tokens
|
||||
self._smooth_token_count = float(self._token_count)
|
||||
self._smooth_update() # will set the text widget.
|
||||
|
||||
_tlabeltxt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - self._x_inset - 123.0, self._height - 48),
|
||||
size=(0, 0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
text=bui.charstr(bui.SpecialChar.TOKEN),
|
||||
)
|
||||
|
||||
def _purchase_press(self, itemid: str) -> None:
|
||||
plus = bui.app.plus
|
||||
|
||||
price = None if plus is None else plus.get_price(itemid)
|
||||
|
||||
if price is None:
|
||||
if plus is not None and plus.supports_purchases():
|
||||
# Looks like internet is down or something temporary.
|
||||
errmsg = bui.Lstr(resource='purchaseNotAvailableText')
|
||||
else:
|
||||
# Looks like purchases will never work here.
|
||||
errmsg = bui.Lstr(resource='purchaseNeverAvailableText')
|
||||
|
||||
bui.screenmessage(errmsg, color=(1, 0.5, 0))
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
assert plus is not None
|
||||
plus.purchase(itemid)
|
||||
|
||||
def _update_store_state(self) -> None:
|
||||
"""Called to make minor updates to an already shown store."""
|
||||
assert self._token_count_widget is not None
|
||||
assert self._last_query_response is not None
|
||||
|
||||
self._token_count = self._last_query_response.tokens
|
||||
|
||||
# Kick off new smooth update if need be.
|
||||
assert self._smooth_token_count is not None
|
||||
if (
|
||||
self._token_count != int(self._smooth_token_count)
|
||||
and self._smooth_update_timer is None
|
||||
):
|
||||
self._smooth_update_timer = bui.AppTimer(
|
||||
0.05, bui.WeakCall(self._smooth_update), repeat=True
|
||||
)
|
||||
diff = abs(float(self._token_count) - self._smooth_token_count)
|
||||
self._smooth_increase_speed = (
|
||||
diff / 100.0
|
||||
if diff >= 5000
|
||||
else (
|
||||
diff / 50.0
|
||||
if diff >= 1500
|
||||
else diff / 30.0 if diff >= 500 else diff / 15.0
|
||||
)
|
||||
)
|
||||
|
||||
def _smooth_update(self) -> None:
|
||||
|
||||
# Stop if the count widget disappears.
|
||||
if not self._token_count_widget:
|
||||
self._smooth_update_timer = None
|
||||
return
|
||||
|
||||
finished = False
|
||||
|
||||
# If we're going down, do it immediately.
|
||||
assert self._smooth_token_count is not None
|
||||
if int(self._smooth_token_count) >= self._token_count:
|
||||
self._smooth_token_count = float(self._token_count)
|
||||
finished = True
|
||||
else:
|
||||
# We're going up; start a sound if need be.
|
||||
self._smooth_token_count = min(
|
||||
self._smooth_token_count + 1.0 * self._smooth_increase_speed,
|
||||
self._token_count,
|
||||
)
|
||||
if int(self._smooth_token_count) >= self._token_count:
|
||||
finished = True
|
||||
self._smooth_token_count = float(self._token_count)
|
||||
elif self._ticking_sound is None:
|
||||
self._ticking_sound = bui.getsound('scoreIncrease')
|
||||
self._ticking_sound.play()
|
||||
|
||||
bui.textwidget(
|
||||
edit=self._token_count_widget,
|
||||
text=str(int(self._smooth_token_count)),
|
||||
)
|
||||
|
||||
# If we've reached the target, kill the timer/sound/etc.
|
||||
if finished:
|
||||
self._smooth_update_timer = None
|
||||
if self._ticking_sound is not None:
|
||||
self._ticking_sound.stop()
|
||||
self._ticking_sound = None
|
||||
bui.getsound('cashRegister2').play()
|
||||
|
||||
def _back(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=self._transition_out
|
||||
)
|
||||
if self._restore_previous_call is not None:
|
||||
self._restore_previous_call(self._root_widget)
|
||||
|
||||
def _on_learn_more_press(self, url: str) -> None:
|
||||
bui.open_url(url)
|
||||
|
||||
|
||||
def show_get_tokens_prompt() -> None:
|
||||
"""Show a 'not enough tokens' prompt with an option to purchase more.
|
||||
|
||||
Note that the purchase option may not always be available
|
||||
depending on the build of the game.
|
||||
"""
|
||||
from bauiv1lib.confirm import ConfirmWindow
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
# Currently always allowing token purchases.
|
||||
if bool(True):
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource='tokens.notEnoughTokensText'),
|
||||
GetTokensWindow,
|
||||
ok_text=bui.Lstr(resource='tokens.getTokensText'),
|
||||
width=460,
|
||||
height=130,
|
||||
)
|
||||
else:
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource='tokens.notEnoughTokensText'),
|
||||
cancel_button=False,
|
||||
width=460,
|
||||
height=130,
|
||||
)
|
||||
66
dist/ba_data/python/bauiv1lib/helpui.py
vendored
66
dist/ba_data/python/bauiv1lib/helpui.py
vendored
|
|
@ -68,7 +68,7 @@ class HelpWindow(bui.Window):
|
|||
position=(0, height - (50 if uiscale is bui.UIScale.SMALL else 45)),
|
||||
size=(width, 25),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.titleText',
|
||||
resource=f'{self._r}.titleText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
|
|
@ -138,9 +138,9 @@ class HelpWindow(bui.Window):
|
|||
self._sub_width = 660
|
||||
self._sub_height = (
|
||||
1590
|
||||
+ bui.app.lang.get_resource(self._r + '.someDaysExtraSpace')
|
||||
+ bui.app.lang.get_resource(f'{self._r}.someDaysExtraSpace')
|
||||
+ bui.app.lang.get_resource(
|
||||
self._r + '.orPunchingSomethingExtraSpace'
|
||||
f'{self._r}.orPunchingSomethingExtraSpace'
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ class HelpWindow(bui.Window):
|
|||
paragraph = (0.8, 0.8, 1.0, 1.0)
|
||||
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.welcomeText',
|
||||
resource=f'{self._r}.welcomeText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
).evaluate()
|
||||
txt_scale = 1.4
|
||||
|
|
@ -198,7 +198,7 @@ class HelpWindow(bui.Window):
|
|||
assert app.classic is not None
|
||||
|
||||
v -= spacing * 50.0
|
||||
txt = bui.Lstr(resource=self._r + '.someDaysText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.someDaysText').evaluate()
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
|
|
@ -211,9 +211,9 @@ class HelpWindow(bui.Window):
|
|||
v_align='center',
|
||||
flatness=1.0,
|
||||
)
|
||||
v -= spacing * 25.0 + getres(self._r + '.someDaysExtraSpace')
|
||||
v -= spacing * 25.0 + getres(f'{self._r}.someDaysExtraSpace')
|
||||
txt_scale = 0.66
|
||||
txt = bui.Lstr(resource=self._r + '.orPunchingSomethingText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.orPunchingSomethingText').evaluate()
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
|
|
@ -226,10 +226,10 @@ class HelpWindow(bui.Window):
|
|||
v_align='center',
|
||||
flatness=1.0,
|
||||
)
|
||||
v -= spacing * 27.0 + getres(self._r + '.orPunchingSomethingExtraSpace')
|
||||
v -= spacing * 27.0 + getres(f'{self._r}.orPunchingSomethingExtraSpace')
|
||||
txt_scale = 1.0
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.canHelpText',
|
||||
resource=f'{self._r}.canHelpText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
).evaluate()
|
||||
bui.textwidget(
|
||||
|
|
@ -246,7 +246,7 @@ class HelpWindow(bui.Window):
|
|||
|
||||
v -= spacing * 70.0
|
||||
txt_scale = 1.0
|
||||
txt = bui.Lstr(resource=self._r + '.toGetTheMostText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.toGetTheMostText').evaluate()
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
|
|
@ -262,7 +262,7 @@ class HelpWindow(bui.Window):
|
|||
|
||||
v -= spacing * 40.0
|
||||
txt_scale = 0.74
|
||||
txt = bui.Lstr(resource=self._r + '.friendsText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.friendsText').evaluate()
|
||||
hval2 = h - 220
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
|
|
@ -278,7 +278,7 @@ class HelpWindow(bui.Window):
|
|||
)
|
||||
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.friendsGoodText',
|
||||
resource=f'{self._r}.friendsGoodText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
).evaluate()
|
||||
txt_scale = 0.7
|
||||
|
|
@ -298,9 +298,9 @@ class HelpWindow(bui.Window):
|
|||
|
||||
v -= spacing * 45.0
|
||||
txt = (
|
||||
bui.Lstr(resource=self._r + '.devicesText').evaluate()
|
||||
bui.Lstr(resource=f'{self._r}.devicesText').evaluate()
|
||||
if app.env.vr
|
||||
else bui.Lstr(resource=self._r + '.controllersText').evaluate()
|
||||
else bui.Lstr(resource=f'{self._r}.controllersText').evaluate()
|
||||
)
|
||||
txt_scale = 0.74
|
||||
hval2 = h - 220
|
||||
|
|
@ -322,7 +322,7 @@ class HelpWindow(bui.Window):
|
|||
infotxt = '.controllersInfoText'
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + infotxt,
|
||||
fallback_resource=self._r + '.controllersInfoText',
|
||||
fallback_resource=f'{self._r}.controllersInfoText',
|
||||
subs=[
|
||||
('${APP_NAME}', bui.Lstr(resource='titleText')),
|
||||
('${REMOTE_APP_NAME}', bui.get_remote_app_name()),
|
||||
|
|
@ -330,7 +330,7 @@ class HelpWindow(bui.Window):
|
|||
).evaluate()
|
||||
else:
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.devicesInfoText',
|
||||
resource=f'{self._r}.devicesInfoText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
).evaluate()
|
||||
|
||||
|
|
@ -349,7 +349,7 @@ class HelpWindow(bui.Window):
|
|||
|
||||
v -= spacing * 150.0
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.controlsText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.controlsText').evaluate()
|
||||
txt_scale = 1.4
|
||||
txt_maxwidth = 480
|
||||
bui.textwidget(
|
||||
|
|
@ -383,7 +383,7 @@ class HelpWindow(bui.Window):
|
|||
|
||||
txt_scale = 0.7
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.controlsSubtitleText',
|
||||
resource=f'{self._r}.controlsSubtitleText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
).evaluate()
|
||||
bui.textwidget(
|
||||
|
|
@ -413,8 +413,8 @@ class HelpWindow(bui.Window):
|
|||
color=(1, 0.7, 0.3),
|
||||
)
|
||||
|
||||
txt_scale = getres(self._r + '.punchInfoTextScale')
|
||||
txt = bui.Lstr(resource=self._r + '.punchInfoText').evaluate()
|
||||
txt_scale = getres(f'{self._r}.punchInfoTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.punchInfoText').evaluate()
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h - sep - 185 + 70, v + 120),
|
||||
|
|
@ -437,8 +437,8 @@ class HelpWindow(bui.Window):
|
|||
color=(1, 0.3, 0.3),
|
||||
)
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.bombInfoText').evaluate()
|
||||
txt_scale = getres(self._r + '.bombInfoTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.bombInfoText').evaluate()
|
||||
txt_scale = getres(f'{self._r}.bombInfoTextScale')
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + sep + 50 + 60, v - 35),
|
||||
|
|
@ -462,8 +462,8 @@ class HelpWindow(bui.Window):
|
|||
color=(0.5, 0.5, 1),
|
||||
)
|
||||
|
||||
txtl = bui.Lstr(resource=self._r + '.pickUpInfoText')
|
||||
txt_scale = getres(self._r + '.pickUpInfoTextScale')
|
||||
txtl = bui.Lstr(resource=f'{self._r}.pickUpInfoText')
|
||||
txt_scale = getres(f'{self._r}.pickUpInfoTextScale')
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 60 + 120, v + sep + 50),
|
||||
|
|
@ -486,8 +486,8 @@ class HelpWindow(bui.Window):
|
|||
color=(0.4, 1, 0.4),
|
||||
)
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.jumpInfoText').evaluate()
|
||||
txt_scale = getres(self._r + '.jumpInfoTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.jumpInfoText').evaluate()
|
||||
txt_scale = getres(f'{self._r}.jumpInfoTextScale')
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h - 250 + 75, v - sep - 15 + 30),
|
||||
|
|
@ -500,8 +500,8 @@ class HelpWindow(bui.Window):
|
|||
v_align='top',
|
||||
)
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.runInfoText').evaluate()
|
||||
txt_scale = getres(self._r + '.runInfoTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.runInfoText').evaluate()
|
||||
txt_scale = getres(f'{self._r}.runInfoTextScale')
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v - sep - 100),
|
||||
|
|
@ -517,7 +517,7 @@ class HelpWindow(bui.Window):
|
|||
|
||||
v -= spacing * 280.0
|
||||
|
||||
txt = bui.Lstr(resource=self._r + '.powerupsText').evaluate()
|
||||
txt = bui.Lstr(resource=f'{self._r}.powerupsText').evaluate()
|
||||
txt_scale = 1.4
|
||||
txt_maxwidth = 480
|
||||
bui.textwidget(
|
||||
|
|
@ -546,8 +546,8 @@ class HelpWindow(bui.Window):
|
|||
)
|
||||
|
||||
v -= spacing * 50.0
|
||||
txt_scale = getres(self._r + '.powerupsSubtitleTextScale')
|
||||
txt = bui.Lstr(resource=self._r + '.powerupsSubtitleText').evaluate()
|
||||
txt_scale = getres(f'{self._r}.powerupsSubtitleTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.powerupsSubtitleText').evaluate()
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
|
|
@ -586,8 +586,8 @@ class HelpWindow(bui.Window):
|
|||
'powerupLandMines',
|
||||
'powerupCurse',
|
||||
]:
|
||||
name = bui.Lstr(resource=self._r + '.' + tex + 'NameText')
|
||||
desc = bui.Lstr(resource=self._r + '.' + tex + 'DescriptionText')
|
||||
name = bui.Lstr(resource=f'{self._r}.' + tex + 'NameText')
|
||||
desc = bui.Lstr(resource=f'{self._r}.' + tex + 'DescriptionText')
|
||||
|
||||
v -= spacing * 60.0
|
||||
|
||||
|
|
|
|||
12
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
12
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
|
|
@ -69,7 +69,7 @@ class KioskWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
position=(self._width * 0.5, self._height + y_extra - 44),
|
||||
transition_delay=tdelay,
|
||||
text=bui.Lstr(resource=self._r + '.singlePlayerExamplesText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.singlePlayerExamplesText'),
|
||||
flatness=1.0,
|
||||
scale=1.2,
|
||||
h_align='center',
|
||||
|
|
@ -116,7 +116,7 @@ class KioskWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
position=(h, label_height),
|
||||
maxwidth=b_width * 0.7,
|
||||
text=bui.Lstr(resource=self._r + '.easyText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.easyText'),
|
||||
scale=1.3,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -151,7 +151,7 @@ class KioskWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
position=(h, label_height),
|
||||
maxwidth=b_width * 0.7,
|
||||
text=bui.Lstr(resource=self._r + '.mediumText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.mediumText'),
|
||||
scale=1.3,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -215,7 +215,7 @@ class KioskWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
position=(self._width * 0.5, self._height + y_extra - 44),
|
||||
transition_delay=tdelay,
|
||||
text=bui.Lstr(resource=self._r + '.versusExamplesText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.versusExamplesText'),
|
||||
flatness=1.0,
|
||||
scale=1.2,
|
||||
h_align='center',
|
||||
|
|
@ -312,7 +312,7 @@ class KioskWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
position=(h, label_height),
|
||||
maxwidth=b_width * 0.7,
|
||||
text=bui.Lstr(resource=self._r + '.epicModeText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.epicModeText'),
|
||||
scale=1.3,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -342,7 +342,7 @@ class KioskWindow(bui.Window):
|
|||
scale=0.5,
|
||||
position=(self._width * 0.5 - 60.0, b_v - 70.0),
|
||||
transition_delay=tdelay,
|
||||
label=bui.Lstr(resource=self._r + '.fullMenuText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.fullMenuText'),
|
||||
on_activate_call=self._do_full_menu,
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
55
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
55
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
|
|
@ -39,7 +39,8 @@ class MainMenuWindow(bui.Window):
|
|||
bui.set_analytics_screen('Main Menu')
|
||||
self._show_remote_app_info_on_first_launch()
|
||||
|
||||
# Make a vanilla container; we'll modify it to our needs in refresh.
|
||||
# Make a vanilla container; we'll modify it to our needs in
|
||||
# refresh.
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
transition=transition,
|
||||
|
|
@ -91,7 +92,6 @@ class MainMenuWindow(bui.Window):
|
|||
0.27, bui.WeakCall(self._check_refresh), repeat=True
|
||||
)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
"""Preload modules we use; avoids hitches (called in bg thread)."""
|
||||
|
|
@ -162,8 +162,9 @@ class MainMenuWindow(bui.Window):
|
|||
if now < self._next_refresh_allow_time:
|
||||
return
|
||||
|
||||
# Don't refresh for the first few seconds the game is up so we don't
|
||||
# interrupt the transition in.
|
||||
# Don't refresh for the first few seconds the game is up so we
|
||||
# don't interrupt the transition in.
|
||||
|
||||
# bui.app.main_menu_window_refresh_check_count += 1
|
||||
# if bui.app.main_menu_window_refresh_check_count < 4:
|
||||
# return
|
||||
|
|
@ -254,7 +255,7 @@ class MainMenuWindow(bui.Window):
|
|||
size=(self._button_width, self._button_height),
|
||||
scale=scale,
|
||||
autoselect=self._use_autoselect,
|
||||
label=bui.Lstr(resource=self._r + '.settingsText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.settingsText'),
|
||||
transition_delay=self._tdelay,
|
||||
on_activate_call=self._settings,
|
||||
)
|
||||
|
|
@ -323,7 +324,7 @@ class MainMenuWindow(bui.Window):
|
|||
scale=scale,
|
||||
size=(self._button_width, self._button_height),
|
||||
autoselect=self._use_autoselect,
|
||||
label=bui.Lstr(resource=self._r + '.leavePartyText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.leavePartyText'),
|
||||
on_activate_call=self._confirm_leave_party,
|
||||
)
|
||||
|
||||
|
|
@ -413,8 +414,8 @@ class MainMenuWindow(bui.Window):
|
|||
else:
|
||||
self._quit_button = None
|
||||
|
||||
# If we're not in-game, have no quit button, and this is android,
|
||||
# we want back presses to quit our activity.
|
||||
# If we're not in-game, have no quit button, and this is
|
||||
# android, we want back presses to quit our activity.
|
||||
if (
|
||||
not self._in_game
|
||||
and not self._have_quit_button
|
||||
|
|
@ -428,9 +429,9 @@ class MainMenuWindow(bui.Window):
|
|||
edit=self._root_widget, on_cancel_call=_do_quit
|
||||
)
|
||||
|
||||
# Add speed-up/slow-down buttons for replays.
|
||||
# (ideally this should be part of a fading-out playback bar like most
|
||||
# media players but this works for now).
|
||||
# Add speed-up/slow-down buttons for replays. Ideally this
|
||||
# should be part of a fading-out playback bar like most media
|
||||
# players but this works for now.
|
||||
if bs.is_in_replay():
|
||||
b_size = 50.0
|
||||
b_buffer_1 = 50.0
|
||||
|
|
@ -605,13 +606,13 @@ class MainMenuWindow(bui.Window):
|
|||
def _set_allow_time() -> None:
|
||||
self._next_refresh_allow_time = bui.apptime() + 2.5
|
||||
|
||||
# Slight hack: widget transitions currently only progress when
|
||||
# frames are being drawn, but this tends to get called before
|
||||
# frame drawing even starts, meaning we don't know exactly how
|
||||
# long we should wait before refreshing to avoid interrupting
|
||||
# the transition. To make things a bit better, let's do a
|
||||
# redundant set of the time in a deferred call which hopefully
|
||||
# happens closer to actual frame draw times.
|
||||
# Slight hack: widget transitions currently only progress
|
||||
# when frames are being drawn, but this tends to get called
|
||||
# before frame drawing even starts, meaning we don't know
|
||||
# exactly how long we should wait before refreshing to avoid
|
||||
# interrupting the transition. To make things a bit better,
|
||||
# let's do a redundant set of the time in a deferred call
|
||||
# which hopefully happens closer to actual frame draw times.
|
||||
_set_allow_time()
|
||||
bui.pushcall(_set_allow_time)
|
||||
|
||||
|
|
@ -880,7 +881,7 @@ class MainMenuWindow(bui.Window):
|
|||
scale=scale,
|
||||
autoselect=self._use_autoselect,
|
||||
size=(self._button_width, self._button_height),
|
||||
label=bui.Lstr(resource=self._r + '.howToPlayText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.howToPlayText'),
|
||||
transition_delay=self._tdelay,
|
||||
on_activate_call=self._howtoplay,
|
||||
)
|
||||
|
|
@ -912,7 +913,7 @@ class MainMenuWindow(bui.Window):
|
|||
position=(h - self._button_width * 0.5 * scale, v),
|
||||
size=(self._button_width, self._button_height),
|
||||
autoselect=self._use_autoselect,
|
||||
label=bui.Lstr(resource=self._r + '.creditsText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.creditsText'),
|
||||
scale=scale,
|
||||
transition_delay=self._tdelay,
|
||||
on_activate_call=self._credits,
|
||||
|
|
@ -1005,7 +1006,7 @@ class MainMenuWindow(bui.Window):
|
|||
position=(h - self._button_width / 2, v),
|
||||
size=(self._button_width, self._button_height),
|
||||
scale=scale,
|
||||
label=bui.Lstr(resource=self._r + '.resumeText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.resumeText'),
|
||||
autoselect=self._use_autoselect,
|
||||
on_activate_call=self._resume,
|
||||
)
|
||||
|
|
@ -1056,7 +1057,7 @@ class MainMenuWindow(bui.Window):
|
|||
and player_name[-1] != '>'
|
||||
):
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + '.justPlayerText',
|
||||
resource=f'{self._r}.justPlayerText',
|
||||
subs=[('${NAME}', player_name)],
|
||||
)
|
||||
else:
|
||||
|
|
@ -1070,7 +1071,7 @@ class MainMenuWindow(bui.Window):
|
|||
* (0.64 if player_name != '' else 0.5),
|
||||
),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.leaveGameText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.leaveGameText'),
|
||||
scale=(0.83 if player_name != '' else 1.0),
|
||||
color=(0.75, 1.0, 0.7),
|
||||
h_align='center',
|
||||
|
|
@ -1209,7 +1210,7 @@ class MainMenuWindow(bui.Window):
|
|||
# Select cancel by default; this occasionally gets called by accident
|
||||
# in a fit of button mashing and this will help reduce damage.
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource=self._r + '.exitToMenuText'),
|
||||
bui.Lstr(resource=f'{self._r}.exitToMenuText'),
|
||||
self._end_game,
|
||||
cancel_is_selected=True,
|
||||
)
|
||||
|
|
@ -1221,7 +1222,7 @@ class MainMenuWindow(bui.Window):
|
|||
# Select cancel by default; this occasionally gets called by accident
|
||||
# in a fit of button mashing and this will help reduce damage.
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource=self._r + '.exitToMenuText'),
|
||||
bui.Lstr(resource=f'{self._r}.exitToMenuText'),
|
||||
self._end_game,
|
||||
cancel_is_selected=True,
|
||||
)
|
||||
|
|
@ -1233,7 +1234,7 @@ class MainMenuWindow(bui.Window):
|
|||
# Select cancel by default; this occasionally gets called by accident
|
||||
# in a fit of button mashing and this will help reduce damage.
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource=self._r + '.exitToMenuText'),
|
||||
bui.Lstr(resource=f'{self._r}.exitToMenuText'),
|
||||
self._end_game,
|
||||
cancel_is_selected=True,
|
||||
)
|
||||
|
|
@ -1245,7 +1246,7 @@ class MainMenuWindow(bui.Window):
|
|||
# Select cancel by default; this occasionally gets called by accident
|
||||
# in a fit of button mashing and this will help reduce damage.
|
||||
ConfirmWindow(
|
||||
bui.Lstr(resource=self._r + '.leavePartyConfirmText'),
|
||||
bui.Lstr(resource=f'{self._r}.leavePartyConfirmText'),
|
||||
self._leave_party,
|
||||
cancel_is_selected=True,
|
||||
)
|
||||
|
|
|
|||
16
dist/ba_data/python/bauiv1lib/party.py
vendored
16
dist/ba_data/python/bauiv1lib/party.py
vendored
|
|
@ -96,7 +96,7 @@ class PartyWindow(bui.Window):
|
|||
if info is not None and info.name != '':
|
||||
title = bui.Lstr(value=info.name)
|
||||
else:
|
||||
title = bui.Lstr(resource=self._r + '.titleText')
|
||||
title = bui.Lstr(resource=f'{self._r}.titleText')
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -151,7 +151,7 @@ class PartyWindow(bui.Window):
|
|||
maxwidth=494,
|
||||
shadow=0.3,
|
||||
flatness=1.0,
|
||||
description=bui.Lstr(resource=self._r + '.chatMessageText'),
|
||||
description=bui.Lstr(resource=f'{self._r}.chatMessageText'),
|
||||
autoselect=True,
|
||||
v_align='center',
|
||||
corner_scale=0.7,
|
||||
|
|
@ -175,7 +175,7 @@ class PartyWindow(bui.Window):
|
|||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
size=(50, 35),
|
||||
label=bui.Lstr(resource=self._r + '.sendText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.sendText'),
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
position=(self._width - 70, 35),
|
||||
|
|
@ -294,7 +294,7 @@ class PartyWindow(bui.Window):
|
|||
top_section_height = 60
|
||||
bui.textwidget(
|
||||
edit=self._empty_str,
|
||||
text=bui.Lstr(resource=self._r + '.emptyText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.emptyText'),
|
||||
)
|
||||
bui.scrollwidget(
|
||||
edit=self._scrollwidget,
|
||||
|
|
@ -428,7 +428,7 @@ class PartyWindow(bui.Window):
|
|||
maxwidth=c_width * 0.96 - twd,
|
||||
color=(0.1, 1, 0.1, 0.5),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.hostText'
|
||||
resource=f'{self._r}.hostText'
|
||||
),
|
||||
scale=0.4,
|
||||
shadow=0.1,
|
||||
|
|
@ -578,8 +578,10 @@ class PartyWindow(bui.Window):
|
|||
self._popup_party_member_is_host = is_host
|
||||
|
||||
def _send_chat_message(self) -> None:
|
||||
bs.chatmessage(cast(str, bui.textwidget(query=self._text_field)))
|
||||
bui.textwidget(edit=self._text_field, text='')
|
||||
text = cast(str, bui.textwidget(query=self._text_field)).strip()
|
||||
if text != '':
|
||||
bs.chatmessage(text)
|
||||
bui.textwidget(edit=self._text_field, text='')
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the window."""
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/partyqueue.py
vendored
4
dist/ba_data/python/bauiv1lib/partyqueue.py
vendored
|
|
@ -564,7 +564,7 @@ class PartyQueueWindow(bui.Window):
|
|||
def on_boost_press(self) -> None:
|
||||
"""Boost was pressed."""
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -575,7 +575,7 @@ class PartyQueueWindow(bui.Window):
|
|||
|
||||
if plus.get_v1_account_ticket_count() < self._boost_tickets:
|
||||
bui.getsound('error').play()
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
return
|
||||
|
||||
bui.getsound('laserReverse').play()
|
||||
|
|
|
|||
8
dist/ba_data/python/bauiv1lib/play.py
vendored
8
dist/ba_data/python/bauiv1lib/play.py
vendored
|
|
@ -82,7 +82,7 @@ class PlayWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=(
|
||||
(self._r + '.titleText')
|
||||
(f'{self._r}.titleText')
|
||||
if self._is_main_menu
|
||||
else 'playlistsText'
|
||||
)
|
||||
|
|
@ -228,7 +228,7 @@ class PlayWindow(bui.Window):
|
|||
draw_controller=btn,
|
||||
position=(hoffs + scl * (-10), v + (scl * 54)),
|
||||
size=(scl * button_width, scl * 30),
|
||||
text=bui.Lstr(resource=self._r + '.oneToFourPlayersText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.83 * scl,
|
||||
|
|
@ -359,7 +359,7 @@ class PlayWindow(bui.Window):
|
|||
draw_controller=btn,
|
||||
position=(hoffs + scl * (-10), v + (scl * 54)),
|
||||
size=(scl * button_width, scl * 30),
|
||||
text=bui.Lstr(resource=self._r + '.twoToEightPlayersText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
res_scale=1.5,
|
||||
|
|
@ -480,7 +480,7 @@ class PlayWindow(bui.Window):
|
|||
draw_controller=btn,
|
||||
position=(hoffs + scl * (-10), v + (scl * 54)),
|
||||
size=(scl * button_width, scl * 30),
|
||||
text=bui.Lstr(resource=self._r + '.twoToEightPlayersText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.9 * scl,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class PlaylistAddGameWindow(bui.Window):
|
|||
position=(self._width * 0.5, self._height - 28),
|
||||
size=(0, 0),
|
||||
scale=1.0,
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
h_align='center',
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=250,
|
||||
|
|
@ -211,7 +211,7 @@ class PlaylistAddGameWindow(bui.Window):
|
|||
self._get_more_games_button = bui.buttonwidget(
|
||||
parent=self._column,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.getMoreGamesText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.getMoreGamesText'),
|
||||
color=(0.54, 0.52, 0.67),
|
||||
textcolor=(0.7, 0.65, 0.7),
|
||||
on_activate_call=self._on_get_more_games_press,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
position=(0, self._height - 47),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.titleText',
|
||||
resource=f'{self._r}.titleText',
|
||||
subs=[('${TYPE}', self._pvars.window_title_name)],
|
||||
),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
|
|
@ -129,7 +129,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(
|
||||
resource='newText', fallback_resource=self._r + '.newText'
|
||||
resource='newText', fallback_resource=f'{self._r}.newText'
|
||||
),
|
||||
)
|
||||
self._lock_images.append(
|
||||
|
|
@ -154,7 +154,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
button_type='square',
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(
|
||||
resource='editText', fallback_resource=self._r + '.editText'
|
||||
resource='editText', fallback_resource=f'{self._r}.editText'
|
||||
),
|
||||
)
|
||||
self._lock_images.append(
|
||||
|
|
@ -180,7 +180,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
text_scale=0.7,
|
||||
label=bui.Lstr(
|
||||
resource='duplicateText',
|
||||
fallback_resource=self._r + '.duplicateText',
|
||||
fallback_resource=f'{self._r}.duplicateText',
|
||||
),
|
||||
)
|
||||
self._lock_images.append(
|
||||
|
|
@ -205,7 +205,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
button_type='square',
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(
|
||||
resource='deleteText', fallback_resource=self._r + '.deleteText'
|
||||
resource='deleteText', fallback_resource=f'{self._r}.deleteText'
|
||||
),
|
||||
)
|
||||
self._lock_images.append(
|
||||
|
|
@ -400,7 +400,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
txtw = bui.textwidget(
|
||||
parent=self._columnwidget,
|
||||
size=(self._width - 40, 30),
|
||||
maxwidth=self._width - 110,
|
||||
maxwidth=440,
|
||||
text=self._get_playlist_display_name(pname),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
|
|
@ -509,7 +509,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
if self._selected_playlist_name == '__default__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantEditDefaultText')
|
||||
bui.Lstr(resource=f'{self._r}.cantEditDefaultText')
|
||||
)
|
||||
return
|
||||
self._save_playlist_selection()
|
||||
|
|
@ -598,7 +598,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
if self._selected_playlist_name == '__default__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantShareDefaultText'),
|
||||
bui.Lstr(resource=f'{self._r}.cantShareDefaultText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
|
|
@ -635,12 +635,12 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
|
|||
if self._selected_playlist_name == '__default__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantDeleteDefaultText')
|
||||
bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText')
|
||||
)
|
||||
else:
|
||||
ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.deleteConfirmText',
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[('${LIST}', self._selected_playlist_name)],
|
||||
),
|
||||
self._do_delete_playlist,
|
||||
|
|
|
|||
19
dist/ba_data/python/bauiv1lib/playlist/edit.py
vendored
19
dist/ba_data/python/bauiv1lib/playlist/edit.py
vendored
|
|
@ -90,7 +90,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(-10, self._height - 50),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=1.05,
|
||||
h_align='center',
|
||||
|
|
@ -104,7 +104,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=self._r + '.listNameText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.listNameText'),
|
||||
position=(196 + x_inset, v + 31),
|
||||
maxwidth=150,
|
||||
color=(0.8, 0.8, 0.8, 0.5),
|
||||
|
|
@ -122,9 +122,10 @@ class PlaylistEditWindow(bui.Window):
|
|||
h_align='left',
|
||||
v_align='center',
|
||||
max_chars=40,
|
||||
maxwidth=380,
|
||||
autoselect=True,
|
||||
color=(0.9, 0.9, 0.9, 1.0),
|
||||
description=bui.Lstr(resource=self._r + '.listNameText'),
|
||||
description=bui.Lstr(resource=f'{self._r}.listNameText'),
|
||||
editable=True,
|
||||
padding=4,
|
||||
on_return_press_call=self._save_press_with_sound,
|
||||
|
|
@ -160,7 +161,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
color=b_color,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.8,
|
||||
label=bui.Lstr(resource=self._r + '.addGameText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.addGameText'),
|
||||
)
|
||||
bui.widget(edit=add_game_button, up_widget=self._text_field)
|
||||
v -= 63.0 * scl
|
||||
|
|
@ -176,7 +177,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
color=b_color,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.8,
|
||||
label=bui.Lstr(resource=self._r + '.editGameText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.editGameText'),
|
||||
)
|
||||
v -= 63.0 * scl
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
button_type='square',
|
||||
color=b_color,
|
||||
textcolor=b_textcolor,
|
||||
label=bui.Lstr(resource=self._r + '.removeGameText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.removeGameText'),
|
||||
)
|
||||
v -= 40
|
||||
h += 9
|
||||
|
|
@ -330,7 +331,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
]
|
||||
):
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
|
||||
bui.Lstr(resource=f'{self._r}.cantSaveAlreadyExistsText')
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
@ -339,7 +340,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
return
|
||||
if not self._editcontroller.get_playlist():
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantSaveEmptyListText')
|
||||
bui.Lstr(resource=f'{self._r}.cantSaveEmptyListText')
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
@ -348,7 +349,7 @@ class PlaylistEditWindow(bui.Window):
|
|||
# using its exact name to avoid confusion.
|
||||
if new_name == self._editcontroller.get_default_list_name().evaluate():
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantOverwriteDefaultText')
|
||||
bui.Lstr(resource=f'{self._r}.cantOverwriteDefaultText')
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ class PlaylistEditGameWindow(bui.Window):
|
|||
scale=0.75,
|
||||
text_scale=1.3,
|
||||
label=(
|
||||
bui.Lstr(resource=self._r + '.addGameText')
|
||||
bui.Lstr(resource=f'{self._r}.addGameText')
|
||||
if is_add
|
||||
else bui.Lstr(resource='doneText')
|
||||
),
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
4
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
|
|
@ -341,7 +341,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
scale=1.0,
|
||||
size=(250, 30),
|
||||
autoselect=True,
|
||||
text=bui.Lstr(resource=self._r + '.shuffleGameOrderText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.shuffleGameOrderText'),
|
||||
maxwidth=300,
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
value=self._do_randomize_val,
|
||||
|
|
@ -362,7 +362,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
scale=1.0,
|
||||
size=(250, 30),
|
||||
autoselect=True,
|
||||
text=bui.Lstr(resource=self._r + '.showTutorialText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.showTutorialText'),
|
||||
maxwidth=300,
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
value=show_tutorial,
|
||||
|
|
|
|||
14
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
14
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
|
|
@ -96,7 +96,7 @@ class ProfileBrowserWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 36),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=300,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=0.9,
|
||||
|
|
@ -134,7 +134,7 @@ class ProfileBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.newButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.newButtonText'),
|
||||
)
|
||||
v -= 70.0 * scl
|
||||
self._edit_button = bui.buttonwidget(
|
||||
|
|
@ -147,7 +147,7 @@ class ProfileBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.editButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.editButtonText'),
|
||||
)
|
||||
v -= 70.0 * scl
|
||||
self._delete_button = bui.buttonwidget(
|
||||
|
|
@ -160,7 +160,7 @@ class ProfileBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.deleteButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteButtonText'),
|
||||
)
|
||||
|
||||
v = self._height - 87
|
||||
|
|
@ -169,7 +169,7 @@ class ProfileBrowserWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 71),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.explanationText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.explanationText'),
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
maxwidth=self._width * 0.83,
|
||||
scale=0.6,
|
||||
|
|
@ -269,13 +269,13 @@ class ProfileBrowserWindow(bui.Window):
|
|||
if self._selected_profile == '__account__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantDeleteAccountProfileText'),
|
||||
bui.Lstr(resource=f'{self._r}.cantDeleteAccountProfileText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
confirm.ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.deleteConfirmText',
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[('${PROFILE}', self._selected_profile)],
|
||||
),
|
||||
self._do_delete_profile,
|
||||
|
|
|
|||
18
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
18
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
|
|
@ -110,9 +110,9 @@ class EditProfileWindow(bui.Window):
|
|||
position=(self._width * 0.5, height - 38),
|
||||
size=(0, 0),
|
||||
text=(
|
||||
bui.Lstr(resource=self._r + '.titleNewText')
|
||||
bui.Lstr(resource=f'{self._r}.titleNewText')
|
||||
if existing_profile is None
|
||||
else bui.Lstr(resource=self._r + '.titleEditText')
|
||||
else bui.Lstr(resource=f'{self._r}.titleEditText')
|
||||
),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=290,
|
||||
|
|
@ -200,7 +200,7 @@ class EditProfileWindow(bui.Window):
|
|||
if not self._is_account_profile and not self._global:
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=self._r + '.nameText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.nameText'),
|
||||
position=(200 + x_inset, v - 6),
|
||||
size=(0, 0),
|
||||
h_align='right',
|
||||
|
|
@ -286,7 +286,7 @@ class EditProfileWindow(bui.Window):
|
|||
position=(self._width * 0.5 - 160, v - 55 - 15),
|
||||
size=(0, 0),
|
||||
draw_controller=btn,
|
||||
text=bui.Lstr(resource=self._r + '.iconText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.iconText'),
|
||||
scale=0.7,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=120,
|
||||
|
|
@ -343,7 +343,7 @@ class EditProfileWindow(bui.Window):
|
|||
h_align='left',
|
||||
v_align='center',
|
||||
max_chars=16,
|
||||
description=bui.Lstr(resource=self._r + '.nameDescriptionText'),
|
||||
description=bui.Lstr(resource=f'{self._r}.nameDescriptionText'),
|
||||
autoselect=True,
|
||||
editable=True,
|
||||
padding=4,
|
||||
|
|
@ -433,7 +433,7 @@ class EditProfileWindow(bui.Window):
|
|||
position=(self._width * 0.5 - b_offs, v - 65),
|
||||
size=(0, 0),
|
||||
draw_controller=btn,
|
||||
text=bui.Lstr(resource=self._r + '.colorText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.colorText'),
|
||||
scale=0.7,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=120,
|
||||
|
|
@ -461,7 +461,7 @@ class EditProfileWindow(bui.Window):
|
|||
position=(self._width * 0.5, v - 80),
|
||||
size=(0, 0),
|
||||
draw_controller=btn,
|
||||
text=bui.Lstr(resource=self._r + '.characterText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.characterText'),
|
||||
scale=0.7,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=130,
|
||||
|
|
@ -505,7 +505,7 @@ class EditProfileWindow(bui.Window):
|
|||
position=(self._width * 0.5 + b_offs, v - 65),
|
||||
size=(0, 0),
|
||||
draw_controller=btn,
|
||||
text=bui.Lstr(resource=self._r + '.highlightText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.highlightText'),
|
||||
scale=0.7,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=120,
|
||||
|
|
@ -545,8 +545,6 @@ class EditProfileWindow(bui.Window):
|
|||
for n in [
|
||||
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
|
||||
bui.SpecialChar.GAME_CENTER_LOGO,
|
||||
bui.SpecialChar.GAME_CIRCLE_LOGO,
|
||||
bui.SpecialChar.OUYA_LOGO,
|
||||
bui.SpecialChar.LOCAL_ACCOUNT,
|
||||
bui.SpecialChar.OCULUS_LOGO,
|
||||
bui.SpecialChar.NVIDIA_LOGO,
|
||||
|
|
|
|||
14
dist/ba_data/python/bauiv1lib/profile/upgrade.py
vendored
14
dist/ba_data/python/bauiv1lib/profile/upgrade.py
vendored
|
|
@ -87,7 +87,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 38),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.upgradeToGlobalProfileText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.upgradeToGlobalProfileText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=self._width * 0.45,
|
||||
scale=1.0,
|
||||
|
|
@ -100,7 +100,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 100),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.upgradeProfileInfoText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.upgradeProfileInfoText'),
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
maxwidth=self._width * 0.8,
|
||||
scale=0.7,
|
||||
|
|
@ -113,7 +113,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
position=(self._width * 0.5, self._height - 160),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.checkingAvailabilityText',
|
||||
resource=f'{self._r}.checkingAvailabilityText',
|
||||
subs=[('${NAME}', self._name)],
|
||||
),
|
||||
color=(0.8, 0.4, 0.0),
|
||||
|
|
@ -183,7 +183,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
bui.textwidget(
|
||||
edit=self._status_text,
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.availableText',
|
||||
resource=f'{self._r}.availableText',
|
||||
subs=[('${NAME}', self._name)],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
|
|
@ -197,7 +197,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
bui.textwidget(
|
||||
edit=self._status_text,
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.unavailableText',
|
||||
resource=f'{self._r}.unavailableText',
|
||||
subs=[('${NAME}', self._name)],
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
|
|
@ -210,7 +210,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
)
|
||||
|
||||
def _on_upgrade_press(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
if self._status is None:
|
||||
plus = bui.app.plus
|
||||
|
|
@ -220,7 +220,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||
tickets = plus.get_v1_account_ticket_count()
|
||||
if tickets < self._cost:
|
||||
bui.getsound('error').play()
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
return
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='purchasingText'), color=(0, 1, 0)
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/purchase.py
vendored
4
dist/ba_data/python/bauiv1lib/purchase.py
vendored
|
|
@ -162,7 +162,7 @@ class PurchaseWindow(bui.Window):
|
|||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
def _purchase(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -176,7 +176,7 @@ class PurchaseWindow(bui.Window):
|
|||
except Exception:
|
||||
ticket_count = None
|
||||
if ticket_count is not None and ticket_count < self._price:
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
|
|
|
|||
6
dist/ba_data/python/bauiv1lib/sendinfo.py
vendored
6
dist/ba_data/python/bauiv1lib/sendinfo.py
vendored
|
|
@ -109,7 +109,7 @@ class SendInfoWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
text=bui.Lstr(
|
||||
resource=(
|
||||
self._r + '.codeText'
|
||||
f'{self._r}.codeText'
|
||||
if legacy_code_mode
|
||||
else 'descriptionText'
|
||||
)
|
||||
|
|
@ -133,7 +133,7 @@ class SendInfoWindow(bui.Window):
|
|||
color=(0.9, 0.9, 0.9, 1.0),
|
||||
description=bui.Lstr(
|
||||
resource=(
|
||||
self._r + '.codeText'
|
||||
f'{self._r}.codeText'
|
||||
if legacy_code_mode
|
||||
else 'descriptionText'
|
||||
)
|
||||
|
|
@ -152,7 +152,7 @@ class SendInfoWindow(bui.Window):
|
|||
size=(b_width, 60),
|
||||
scale=1.0,
|
||||
label=bui.Lstr(
|
||||
resource='submitText', fallback_resource=self._r + '.enterText'
|
||||
resource='submitText', fallback_resource=f'{self._r}.enterText'
|
||||
),
|
||||
on_activate_call=self._do_enter,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class AllSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, height - 44),
|
||||
size=(width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -143,7 +143,7 @@ class AllSettingsWindow(bui.Window):
|
|||
bbtn = bui.get_special_widget('back_button')
|
||||
bui.widget(edit=ctb, left_widget=bbtn)
|
||||
_b_title(
|
||||
x_offs2, v, ctb, bui.Lstr(resource=self._r + '.controllersText')
|
||||
x_offs2, v, ctb, bui.Lstr(resource=f'{self._r}.controllersText')
|
||||
)
|
||||
imgw = imgh = 130
|
||||
bui.imagewidget(
|
||||
|
|
@ -166,7 +166,7 @@ class AllSettingsWindow(bui.Window):
|
|||
if bui.app.ui_v1.use_toolbars:
|
||||
pbtn = bui.get_special_widget('party_button')
|
||||
bui.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
|
||||
_b_title(x_offs3, v, gfxb, bui.Lstr(resource=self._r + '.graphicsText'))
|
||||
_b_title(x_offs3, v, gfxb, bui.Lstr(resource=f'{self._r}.graphicsText'))
|
||||
imgw = imgh = 110
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -187,7 +187,7 @@ class AllSettingsWindow(bui.Window):
|
|||
label='',
|
||||
on_activate_call=self._do_audio,
|
||||
)
|
||||
_b_title(x_offs4, v, abtn, bui.Lstr(resource=self._r + '.audioText'))
|
||||
_b_title(x_offs4, v, abtn, bui.Lstr(resource=f'{self._r}.audioText'))
|
||||
imgw = imgh = 120
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -207,7 +207,7 @@ class AllSettingsWindow(bui.Window):
|
|||
label='',
|
||||
on_activate_call=self._do_advanced,
|
||||
)
|
||||
_b_title(x_offs5, v, avb, bui.Lstr(resource=self._r + '.advancedText'))
|
||||
_b_title(x_offs5, v, avb, bui.Lstr(resource=f'{self._r}.advancedText'))
|
||||
imgw = imgh = 120
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
|
|
|
|||
14
dist/ba_data/python/bauiv1lib/settings/audio.py
vendored
14
dist/ba_data/python/bauiv1lib/settings/audio.py
vendored
|
|
@ -97,7 +97,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 32),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=180,
|
||||
h_align='center',
|
||||
|
|
@ -116,7 +116,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
position=(40, v),
|
||||
xoffset=10,
|
||||
configkey='Sound Volume',
|
||||
displayname=bui.Lstr(resource=self._r + '.soundVolumeText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.soundVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.05,
|
||||
|
|
@ -133,7 +133,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
position=(40, v),
|
||||
xoffset=10,
|
||||
configkey='Music Volume',
|
||||
displayname=bui.Lstr(resource=self._r + '.musicVolumeText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.musicVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.05,
|
||||
|
|
@ -151,7 +151,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(40, v + 24),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.headRelativeVRAudioText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.headRelativeVRAudioText'),
|
||||
color=(0.8, 0.8, 0.8),
|
||||
maxwidth=230,
|
||||
h_align='left',
|
||||
|
|
@ -179,7 +179,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
position=(width * 0.5, v - 11),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.headRelativeVRAudioInfoText'
|
||||
resource=f'{self._r}.headRelativeVRAudioInfoText'
|
||||
),
|
||||
scale=0.5,
|
||||
color=(0.7, 0.8, 0.7),
|
||||
|
|
@ -200,7 +200,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
position=((width - 310) / 2, v),
|
||||
size=(310, 50),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.soundtrackButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.soundtrackButtonText'),
|
||||
on_activate_call=self._do_soundtracks,
|
||||
)
|
||||
v -= spacing * 0.5
|
||||
|
|
@ -208,7 +208,7 @@ class AudioSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, v),
|
||||
size=(width, 20),
|
||||
text=bui.Lstr(resource=self._r + '.soundtrackDescriptionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.soundtrackDescriptionText'),
|
||||
flatness=1.0,
|
||||
h_align='center',
|
||||
scale=0.5,
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, height - 49),
|
||||
size=(width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
|
|
@ -173,7 +173,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||
position=((width - button_width) / 2, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.configureTouchText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
|
||||
on_activate_call=self._do_touchscreen,
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
|
|
@ -197,7 +197,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||
position=((width - button_width) / 2 - 7, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.configureControllersText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
|
||||
on_activate_call=self._do_gamepads,
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
|
|
@ -223,12 +223,15 @@ class ControlsSettingsWindow(bui.Window):
|
|||
if show_keyboard:
|
||||
self._keyboard_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2 + 5, v),
|
||||
position=((width - button_width) / 2 - 5, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.configureKeyboardText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.configureKeyboardText'),
|
||||
on_activate_call=self._config_keyboard,
|
||||
)
|
||||
bui.widget(
|
||||
edit=self._keyboard_button, left_widget=self._keyboard_button
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
edit=btn,
|
||||
|
|
@ -249,10 +252,14 @@ class ControlsSettingsWindow(bui.Window):
|
|||
position=((width - button_width) / 2 - 3, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.configureKeyboard2Text'),
|
||||
label=bui.Lstr(resource=f'{self._r}.configureKeyboard2Text'),
|
||||
on_activate_call=self._config_keyboard2,
|
||||
)
|
||||
v -= spacing
|
||||
bui.widget(
|
||||
edit=self._keyboard_2_button,
|
||||
left_widget=self._keyboard_2_button,
|
||||
)
|
||||
if show_space_2:
|
||||
v -= space_height
|
||||
if show_remote:
|
||||
|
|
@ -261,9 +268,12 @@ class ControlsSettingsWindow(bui.Window):
|
|||
position=((width - button_width) / 2 - 5, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.configureMobileText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.configureMobileText'),
|
||||
on_activate_call=self._do_mobile_devices,
|
||||
)
|
||||
bui.widget(
|
||||
edit=self._idevices_button, left_widget=self._idevices_button
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
edit=btn,
|
||||
|
|
@ -289,7 +299,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||
bui.getsound('gunCocking').play()
|
||||
bui.set_low_level_config_value('enablexinput', not value)
|
||||
|
||||
bui.checkboxwidget(
|
||||
xinput_checkbox = bui.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(100, v + 3),
|
||||
size=(120, 30),
|
||||
|
|
@ -310,6 +320,11 @@ class ControlsSettingsWindow(bui.Window):
|
|||
color=bui.app.ui_v1.infotextcolor,
|
||||
maxwidth=width * 0.8,
|
||||
)
|
||||
bui.widget(
|
||||
edit=xinput_checkbox,
|
||||
left_widget=xinput_checkbox,
|
||||
right_widget=xinput_checkbox,
|
||||
)
|
||||
v -= spacing
|
||||
|
||||
if show_mac_controller_subsystem:
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, v + 5),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=310,
|
||||
h_align='center',
|
||||
|
|
@ -229,7 +229,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(50, v + 10),
|
||||
size=(self._width - 100, 30),
|
||||
text=bui.Lstr(resource=self._r + '.appliesToAllText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.appliesToAllText'),
|
||||
maxwidth=330,
|
||||
scale=0.65,
|
||||
color=(0.5, 0.6, 0.5, 1.0),
|
||||
|
|
@ -244,7 +244,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, v + 5),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.secondaryText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.secondaryText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=300,
|
||||
h_align='center',
|
||||
|
|
@ -256,7 +256,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(50, v + 10),
|
||||
size=(self._width - 100, 30),
|
||||
text=bui.Lstr(resource=self._r + '.secondHalfText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.secondHalfText'),
|
||||
maxwidth=300,
|
||||
scale=0.65,
|
||||
color=(0.6, 0.8, 0.6, 1.0),
|
||||
|
|
@ -269,7 +269,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
on_value_change_call=self._enable_check_box_changed,
|
||||
size=(200, 30),
|
||||
text=bui.Lstr(resource=self._r + '.secondaryEnableText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.secondaryEnableText'),
|
||||
scale=1.2,
|
||||
)
|
||||
v = self._height - 205
|
||||
|
|
@ -279,8 +279,8 @@ class GamepadSettingsWindow(bui.Window):
|
|||
d_color = (0.4, 0.4, 0.8)
|
||||
sclx = 1.2
|
||||
scly = 0.98
|
||||
dpm = bui.Lstr(resource=self._r + '.pressAnyButtonOrDpadText')
|
||||
dpm2 = bui.Lstr(resource=self._r + '.ifNothingHappensTryAnalogText')
|
||||
dpm = bui.Lstr(resource=f'{self._r}.pressAnyButtonOrDpadText')
|
||||
dpm2 = bui.Lstr(resource=f'{self._r}.ifNothingHappensTryAnalogText')
|
||||
self._capture_button(
|
||||
pos=(h_offs, v + scly * dist),
|
||||
color=d_color,
|
||||
|
|
@ -318,7 +318,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
message2=dpm2,
|
||||
)
|
||||
|
||||
dpm3 = bui.Lstr(resource=self._r + '.ifNothingHappensTryDpadText')
|
||||
dpm3 = bui.Lstr(resource=f'{self._r}.ifNothingHappensTryDpadText')
|
||||
self._capture_button(
|
||||
pos=(h_offs + 130, v - 125),
|
||||
color=(0.4, 0.4, 0.6),
|
||||
|
|
@ -326,7 +326,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
maxwidth=140,
|
||||
texture=bui.gettexture('analogStick'),
|
||||
scale=1.2,
|
||||
message=bui.Lstr(resource=self._r + '.pressLeftRightText'),
|
||||
message=bui.Lstr(resource=f'{self._r}.pressLeftRightText'),
|
||||
message2=dpm3,
|
||||
)
|
||||
|
||||
|
|
@ -563,13 +563,13 @@ class GamepadSettingsWindow(bui.Window):
|
|||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
return bui.Lstr(resource=f'{self._r}.unsetText')
|
||||
|
||||
# If they're looking for triggers.
|
||||
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
|
||||
if control in self._settings:
|
||||
return self._input.get_axis_name(self._settings[control])
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
return bui.Lstr(resource=f'{self._r}.unsetText')
|
||||
|
||||
# Dead-zone.
|
||||
if control == 'analogStickDeadZone' + self._ext:
|
||||
|
|
@ -590,7 +590,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
if any(b in self._settings for b in dpad_buttons):
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
return bui.Lstr(resource=f'{self._r}.unsetText')
|
||||
|
||||
# No dpad buttons - show the dpad number for all 4.
|
||||
dpadnum = (
|
||||
|
|
@ -603,19 +603,19 @@ class GamepadSettingsWindow(bui.Window):
|
|||
return bui.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
|
||||
('${A}', bui.Lstr(resource=f'{self._r}.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(dpadnum),
|
||||
),
|
||||
],
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
return bui.Lstr(resource=f'{self._r}.unsetText')
|
||||
|
||||
# Other buttons.
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
return bui.Lstr(resource=f'{self._r}.unsetText')
|
||||
|
||||
def _gamepad_event(
|
||||
self,
|
||||
|
|
@ -694,7 +694,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
self._input,
|
||||
'analogStickUD' + ext,
|
||||
self._gamepad_event,
|
||||
bui.Lstr(resource=self._r + '.pressUpDownText'),
|
||||
bui.Lstr(resource=f'{self._r}.pressUpDownText'),
|
||||
)
|
||||
|
||||
elif control == 'analogStickUD' + ext:
|
||||
|
|
@ -745,7 +745,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
maxwidth: float = 80.0,
|
||||
) -> bui.Widget:
|
||||
if message is None:
|
||||
message = bui.Lstr(resource=self._r + '.pressAnyButtonText')
|
||||
message = bui.Lstr(resource=f'{self._r}.pressAnyButtonText')
|
||||
base_size = 79
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -852,7 +852,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||
'reset',
|
||||
]
|
||||
choices_display: list[bui.Lstr] = [
|
||||
bui.Lstr(resource=self._r + '.advancedText'),
|
||||
bui.Lstr(resource=f'{self._r}.advancedText'),
|
||||
bui.Lstr(resource='settingsWindowAdvanced.resetText'),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
self._height - (40 if uiscale is bui.UIScale.SMALL else 34),
|
||||
),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.advancedTitleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.advancedTitleText'),
|
||||
maxwidth=320,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
|
|
@ -126,7 +126,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h + 70, v),
|
||||
size=(500, 30),
|
||||
text=bui.Lstr(resource=self._r + '.unassignedButtonsRunText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.unassignedButtonsRunText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=330,
|
||||
scale=1.0,
|
||||
|
|
@ -140,7 +140,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
v -= 60
|
||||
capb = self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.runButton1Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.runButton1Text'),
|
||||
control='buttonRun1' + self._parent_window.get_ext(),
|
||||
)
|
||||
if self._parent_window.get_is_secondary():
|
||||
|
|
@ -149,14 +149,14 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.runButton2Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.runButton2Text'),
|
||||
control='buttonRun2' + self._parent_window.get_ext(),
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 24),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.runTriggerDescriptionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.runTriggerDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
|
|
@ -168,16 +168,16 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.runTrigger1Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.runTrigger1Text'),
|
||||
control='triggerRun1' + self._parent_window.get_ext(),
|
||||
message=bui.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
|
||||
message=bui.Lstr(resource=f'{self._r}.pressAnyAnalogTriggerText'),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.runTrigger2Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.runTrigger2Text'),
|
||||
control='triggerRun2' + self._parent_window.get_ext(),
|
||||
message=bui.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
|
||||
message=bui.Lstr(resource=f'{self._r}.pressAnyAnalogTriggerText'),
|
||||
)
|
||||
|
||||
# in vr mode, allow assigning a reset-view button
|
||||
|
|
@ -185,45 +185,45 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
v -= 50
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.vrReorientButtonText'),
|
||||
name=bui.Lstr(resource=f'{self._r}.vrReorientButtonText'),
|
||||
control='buttonVRReorient' + self._parent_window.get_ext(),
|
||||
)
|
||||
|
||||
v -= 60
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.extraStartButtonText'),
|
||||
name=bui.Lstr(resource=f'{self._r}.extraStartButtonText'),
|
||||
control='buttonStart2' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 60
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.ignoredButton1Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.ignoredButton1Text'),
|
||||
control='buttonIgnored' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.ignoredButton2Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.ignoredButton2Text'),
|
||||
control='buttonIgnored2' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.ignoredButton3Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.ignoredButton3Text'),
|
||||
control='buttonIgnored3' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=bui.Lstr(resource=self._r + '.ignoredButton4Text'),
|
||||
name=bui.Lstr(resource=f'{self._r}.ignoredButton4Text'),
|
||||
control='buttonIgnored4' + self._parent_window.get_ext(),
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 14),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.ignoredButtonDescriptionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.ignoredButtonDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
scale=0.8,
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
|
|
@ -239,7 +239,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.startButtonActivatesDefaultText'
|
||||
resource=f'{self._r}.startButtonActivatesDefaultText'
|
||||
),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
|
|
@ -254,7 +254,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.startButtonActivatesDefaultDescriptionText'
|
||||
resource=f'{self._r}.startButtonActivatesDefaultDescriptionText'
|
||||
),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
|
|
@ -269,7 +269,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=bui.Lstr(resource=self._r + '.uiOnlyText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.uiOnlyText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
|
|
@ -280,7 +280,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.uiOnlyDescriptionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.uiOnlyDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
|
|
@ -294,7 +294,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=bui.Lstr(resource=self._r + '.ignoreCompletelyText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.ignoreCompletelyText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
|
|
@ -306,7 +306,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.ignoreCompletelyDescriptionText'
|
||||
resource=f'{self._r}.ignoreCompletelyDescriptionText'
|
||||
),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
|
|
@ -322,7 +322,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=bui.Lstr(resource=self._r + '.autoRecalibrateText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.autoRecalibrateText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
|
|
@ -333,7 +333,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.autoRecalibrateDescriptionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.autoRecalibrateDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
|
|
@ -343,7 +343,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
v -= 80
|
||||
|
||||
buttons = self._config_value_editor(
|
||||
bui.Lstr(resource=self._r + '.analogStickDeadZoneText'),
|
||||
bui.Lstr(resource=f'{self._r}.analogStickDeadZoneText'),
|
||||
control=('analogStickDeadZone' + self._parent_window.get_ext()),
|
||||
position=(h + 40, v),
|
||||
min_val=0,
|
||||
|
|
@ -359,7 +359,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.analogStickDeadZoneDescriptionText'
|
||||
resource=f'{self._r}.analogStickDeadZoneDescriptionText'
|
||||
),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
|
|
@ -375,7 +375,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource=self._r + '.twoInOneSetupText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.twoInOneSetupText'),
|
||||
position=(40, v),
|
||||
size=(self._sub_width - 80, 50),
|
||||
on_activate_call=self._parent_window.show_secondary_editor,
|
||||
|
|
@ -414,7 +414,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||
left_widget=btn,
|
||||
color=(0.45, 0.4, 0.5),
|
||||
textcolor=(0.65, 0.6, 0.7),
|
||||
label=bui.Lstr(resource=self._r + '.clearText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.clearText'),
|
||||
size=(110, 50),
|
||||
scale=0.7,
|
||||
on_activate_call=bui.Call(self._clear_control, control),
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class GamepadSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(20, height - 50),
|
||||
size=(width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=250,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
|
|
@ -168,7 +168,7 @@ class GamepadSelectWindow(bui.Window):
|
|||
position=(15, v),
|
||||
size=(width - 30, 30),
|
||||
scale=0.8,
|
||||
text=bui.Lstr(resource=self._r + '.pressAnyButtonText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.pressAnyButtonText'),
|
||||
maxwidth=width * 0.95,
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
h_align='center',
|
||||
|
|
@ -181,7 +181,7 @@ class GamepadSelectWindow(bui.Window):
|
|||
position=(15, v),
|
||||
size=(width - 30, 30),
|
||||
scale=0.46,
|
||||
text=bui.Lstr(resource=self._r + '.androidNoteText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.androidNoteText'),
|
||||
maxwidth=width * 0.95,
|
||||
color=(0.7, 0.9, 0.7, 0.5),
|
||||
h_align='center',
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, height - 44),
|
||||
size=(width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
|
|
@ -159,7 +159,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(60, v),
|
||||
size=(160, 25),
|
||||
text=bui.Lstr(resource=self._r + '.visualsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.visualsText'),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
|
|
@ -179,10 +179,10 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
),
|
||||
choices_display=[
|
||||
bui.Lstr(resource='autoText'),
|
||||
bui.Lstr(resource=self._r + '.higherText'),
|
||||
bui.Lstr(resource=self._r + '.highText'),
|
||||
bui.Lstr(resource=self._r + '.mediumText'),
|
||||
bui.Lstr(resource=self._r + '.lowText'),
|
||||
bui.Lstr(resource=f'{self._r}.higherText'),
|
||||
bui.Lstr(resource=f'{self._r}.highText'),
|
||||
bui.Lstr(resource=f'{self._r}.mediumText'),
|
||||
bui.Lstr(resource=f'{self._r}.lowText'),
|
||||
],
|
||||
current_choice=bui.app.config.resolve('Graphics Quality'),
|
||||
on_value_change_call=self._set_quality,
|
||||
|
|
@ -193,7 +193,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(230, v),
|
||||
size=(160, 25),
|
||||
text=bui.Lstr(resource=self._r + '.texturesText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.texturesText'),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
|
|
@ -208,9 +208,9 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
choices=['Auto', 'High', 'Medium', 'Low'],
|
||||
choices_display=[
|
||||
bui.Lstr(resource='autoText'),
|
||||
bui.Lstr(resource=self._r + '.highText'),
|
||||
bui.Lstr(resource=self._r + '.mediumText'),
|
||||
bui.Lstr(resource=self._r + '.lowText'),
|
||||
bui.Lstr(resource=f'{self._r}.highText'),
|
||||
bui.Lstr(resource=f'{self._r}.mediumText'),
|
||||
bui.Lstr(resource=f'{self._r}.lowText'),
|
||||
],
|
||||
current_choice=bui.app.config.resolve('Texture Quality'),
|
||||
on_value_change_call=self._set_textures,
|
||||
|
|
@ -231,7 +231,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(h_offs + 60, v),
|
||||
size=(160, 25),
|
||||
text=bui.Lstr(resource=self._r + '.resolutionText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.resolutionText'),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
|
|
@ -319,7 +319,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(230, v),
|
||||
size=(160, 25),
|
||||
text=bui.Lstr(resource=self._r + '.verticalSyncText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.verticalSyncText'),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
|
|
@ -334,8 +334,8 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
choices=['Auto', 'Always', 'Never'],
|
||||
choices_display=[
|
||||
bui.Lstr(resource='autoText'),
|
||||
bui.Lstr(resource=self._r + '.alwaysText'),
|
||||
bui.Lstr(resource=self._r + '.neverText'),
|
||||
bui.Lstr(resource=f'{self._r}.alwaysText'),
|
||||
bui.Lstr(resource=f'{self._r}.neverText'),
|
||||
],
|
||||
current_choice=bui.app.config.resolve('Vertical Sync'),
|
||||
on_value_change_call=self._set_vsync,
|
||||
|
|
@ -360,7 +360,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(155, v + 10),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.maxFPSText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.maxFPSText'),
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
scale=0.9,
|
||||
maxwidth=90,
|
||||
|
|
@ -399,7 +399,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
size=(210, 30),
|
||||
scale=0.86,
|
||||
configkey='Show FPS',
|
||||
displayname=bui.Lstr(resource=self._r + '.showFPSText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.showFPSText'),
|
||||
maxwidth=130,
|
||||
)
|
||||
if self._max_fps_text is not None:
|
||||
|
|
@ -419,7 +419,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||
size=(210, 30),
|
||||
scale=0.86,
|
||||
configkey='TV Border',
|
||||
displayname=bui.Lstr(resource=self._r + '.tvBorderText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.tvBorderText'),
|
||||
maxwidth=130,
|
||||
)
|
||||
bui.widget(edit=fpsc.widget, right_widget=tvc.widget)
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
position=(self._width * 0.5, v + 15),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.configuringText',
|
||||
resource=f'{self._r}.configuringText',
|
||||
subs=[('${DEVICE}', self._displayname)],
|
||||
),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
|
|
@ -129,7 +129,7 @@ class ConfigKeyboardWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(0, v + 19),
|
||||
size=(self._width, 50),
|
||||
text=bui.Lstr(resource=self._r + '.keyboard2NoteText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.keyboard2NoteText'),
|
||||
scale=0.7,
|
||||
maxwidth=self._width * 0.75,
|
||||
max_height=110,
|
||||
|
|
|
|||
|
|
@ -349,7 +349,12 @@ class PluginWindow(bui.Window):
|
|||
text=bui.Lstr(value=classpath),
|
||||
autoselect=True,
|
||||
value=enabled,
|
||||
maxwidth=self._scroll_width - 200,
|
||||
maxwidth=self._scroll_width
|
||||
- (
|
||||
200
|
||||
if plugin is not None and plugin.has_settings_ui()
|
||||
else 80
|
||||
),
|
||||
position=(10, item_y),
|
||||
size=(self._scroll_width - 40, 50),
|
||||
on_value_change_call=bui.Call(
|
||||
|
|
@ -388,7 +393,9 @@ class PluginWindow(bui.Window):
|
|||
edit=check,
|
||||
up_widget=self._back_button,
|
||||
left_widget=self._back_button,
|
||||
right_widget=self._settings_button,
|
||||
right_widget=(
|
||||
self._settings_button if button is None else button
|
||||
),
|
||||
)
|
||||
if button is not None:
|
||||
bui.widget(edit=button, up_widget=self._back_button)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class RemoteAppSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 42),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=370,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=0.8,
|
||||
|
|
@ -73,7 +73,7 @@ class RemoteAppSettingsWindow(bui.Window):
|
|||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=0.8,
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.explanationText',
|
||||
resource=f'{self._r}.explanationText',
|
||||
subs=[
|
||||
('${APP_NAME}', bui.Lstr(resource='titleText')),
|
||||
('${REMOTE_APP_NAME}', bui.get_remote_app_name()),
|
||||
|
|
@ -106,7 +106,7 @@ class RemoteAppSettingsWindow(bui.Window):
|
|||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 0.8),
|
||||
scale=0.65,
|
||||
text=bui.Lstr(resource=self._r + '.bestResultsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.bestResultsText'),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.19,
|
||||
h_align='center',
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(25, self._height - 50),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=280,
|
||||
h_align='center',
|
||||
|
|
@ -111,7 +111,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(-10, v + 43),
|
||||
size=(self._sub_width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.swipeInfoText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.swipeInfoText'),
|
||||
flatness=1.0,
|
||||
color=(0, 0.9, 0.1, 0.7),
|
||||
maxwidth=self._sub_width * 0.9,
|
||||
|
|
@ -124,7 +124,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h, v - 2),
|
||||
size=(0, 30),
|
||||
text=bui.Lstr(resource=self._r + '.movementText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.movementText'),
|
||||
maxwidth=190,
|
||||
color=clr,
|
||||
v_align='center',
|
||||
|
|
@ -133,7 +133,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h + 220, v),
|
||||
size=(170, 30),
|
||||
text=bui.Lstr(resource=self._r + '.joystickText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.joystickText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
|
|
@ -142,7 +142,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h + 357, v),
|
||||
size=(170, 30),
|
||||
text=bui.Lstr(resource=self._r + '.swipeText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.swipeText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
value=False,
|
||||
|
|
@ -158,7 +158,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
xoffset=65,
|
||||
configkey='Touch Controls Scale Movement',
|
||||
displayname=bui.Lstr(
|
||||
resource=self._r + '.movementControlScaleText'
|
||||
resource=f'{self._r}.movementControlScaleText'
|
||||
),
|
||||
changesound=False,
|
||||
minval=0.1,
|
||||
|
|
@ -171,7 +171,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h, v - 2),
|
||||
size=(0, 30),
|
||||
text=bui.Lstr(resource=self._r + '.actionsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.actionsText'),
|
||||
maxwidth=190,
|
||||
color=clr,
|
||||
v_align='center',
|
||||
|
|
@ -180,7 +180,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h + 220, v),
|
||||
size=(170, 30),
|
||||
text=bui.Lstr(resource=self._r + '.buttonsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.buttonsText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
|
|
@ -189,7 +189,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(h + 357, v),
|
||||
size=(170, 30),
|
||||
text=bui.Lstr(resource=self._r + '.swipeText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.swipeText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
|
|
@ -203,7 +203,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
position=(h, v),
|
||||
xoffset=65,
|
||||
configkey='Touch Controls Scale Actions',
|
||||
displayname=bui.Lstr(resource=self._r + '.actionControlScaleText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.actionControlScaleText'),
|
||||
changesound=False,
|
||||
minval=0.1,
|
||||
maxval=4.0,
|
||||
|
|
@ -217,7 +217,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
size=(400, 30),
|
||||
maxwidth=400,
|
||||
configkey='Touch Controls Swipe Hidden',
|
||||
displayname=bui.Lstr(resource=self._r + '.swipeControlsHiddenText'),
|
||||
displayname=bui.Lstr(resource=f'{self._r}.swipeControlsHiddenText'),
|
||||
)
|
||||
v -= 65
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 - 70, v),
|
||||
size=(170, 60),
|
||||
label=bui.Lstr(resource=self._r + '.resetText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.resetText'),
|
||||
scale=0.75,
|
||||
on_activate_call=self._reset,
|
||||
)
|
||||
|
|
@ -235,7 +235,7 @@ class TouchscreenSettingsWindow(bui.Window):
|
|||
position=(self._width * 0.5, 38),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.dragControlsText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.dragControlsText'),
|
||||
maxwidth=self._width * 0.8,
|
||||
scale=0.65,
|
||||
color=(1, 1, 1, 0.4),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
position=(self._width * 0.5, self._height - 35),
|
||||
size=(0, 0),
|
||||
maxwidth=300,
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -119,7 +119,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.newText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.newText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
bui.imagewidget(
|
||||
|
|
@ -148,7 +148,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.editText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.editText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
bui.imagewidget(
|
||||
|
|
@ -176,7 +176,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
color=b_color,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.duplicateText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.duplicateText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
bui.imagewidget(
|
||||
|
|
@ -204,7 +204,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
autoselect=True,
|
||||
textcolor=b_textcolor,
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=self._r + '.deleteText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteText'),
|
||||
)
|
||||
self._lock_images.append(
|
||||
bui.imagewidget(
|
||||
|
|
@ -303,13 +303,13 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
if self._selected_soundtrack == '__default__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantDeleteDefaultText'),
|
||||
bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
else:
|
||||
ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.deleteConfirmText',
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[('${NAME}', self._selected_soundtrack)],
|
||||
),
|
||||
self._do_delete_soundtrack,
|
||||
|
|
@ -438,7 +438,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
if self._selected_soundtrack == '__default__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantEditDefaultText'),
|
||||
bui.Lstr(resource=f'{self._r}.cantEditDefaultText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
|
|
@ -455,7 +455,7 @@ class SoundtrackBrowserWindow(bui.Window):
|
|||
|
||||
def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:
|
||||
if soundtrack == '__default__':
|
||||
return bui.Lstr(resource=self._r + '.defaultSoundtrackNameText')
|
||||
return bui.Lstr(resource=f'{self._r}.defaultSoundtrackNameText')
|
||||
return bui.Lstr(value=soundtrack)
|
||||
|
||||
def _refresh(self, select_soundtrack: str | None = None) -> None:
|
||||
|
|
|
|||
18
dist/ba_data/python/bauiv1lib/soundtrack/edit.py
vendored
18
dist/ba_data/python/bauiv1lib/soundtrack/edit.py
vendored
|
|
@ -121,7 +121,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=bui.Lstr(resource=self._r + '.nameText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.nameText'),
|
||||
maxwidth=80,
|
||||
scale=0.8,
|
||||
position=(105 + x_inset, v + 19),
|
||||
|
|
@ -135,7 +135,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
if existing_soundtrack is None:
|
||||
i = 1
|
||||
st_name_text = bui.Lstr(
|
||||
resource=self._r + '.newSoundtrackNameText'
|
||||
resource=f'{self._r}.newSoundtrackNameText'
|
||||
).evaluate()
|
||||
if '${COUNT}' not in st_name_text:
|
||||
# make sure we insert number *somewhere*
|
||||
|
|
@ -155,7 +155,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
v_align='center',
|
||||
max_chars=32,
|
||||
autoselect=True,
|
||||
description=bui.Lstr(resource=self._r + '.nameText'),
|
||||
description=bui.Lstr(resource=f'{self._r}.nameText'),
|
||||
editable=True,
|
||||
padding=4,
|
||||
on_return_press_call=self._do_it_with_sound,
|
||||
|
|
@ -305,7 +305,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
btn = bui.buttonwidget(
|
||||
parent=row,
|
||||
size=(50, 32),
|
||||
label=bui.Lstr(resource=self._r + '.testText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.testText'),
|
||||
text_scale=0.6,
|
||||
on_activate_call=bui.Call(self._test, bs.MusicType(song_type)),
|
||||
up_widget=(
|
||||
|
|
@ -389,7 +389,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
if bui.app.config.resolve('Music Volume') < 0.01:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.musicVolumeZeroWarning'),
|
||||
bui.Lstr(resource=f'{self._r}.musicVolumeZeroWarning'),
|
||||
color=(1, 0.5, 0),
|
||||
)
|
||||
music.set_music_play_mode(bui.app.classic.MusicPlayMode.TEST)
|
||||
|
|
@ -405,7 +405,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
etype = music.get_soundtrack_entry_type(entry)
|
||||
ename: str | bui.Lstr
|
||||
if etype == 'default':
|
||||
ename = bui.Lstr(resource=self._r + '.defaultGameMusicText')
|
||||
ename = bui.Lstr(resource=f'{self._r}.defaultGameMusicText')
|
||||
elif etype in ('musicFile', 'musicFolder'):
|
||||
ename = os.path.basename(music.get_soundtrack_entry_name(entry))
|
||||
else:
|
||||
|
|
@ -453,7 +453,7 @@ class SoundtrackEditWindow(bui.Window):
|
|||
new_name = cast(str, bui.textwidget(query=self._text_field))
|
||||
if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
|
||||
bui.Lstr(resource=f'{self._r}.cantSaveAlreadyExistsText')
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
@ -463,11 +463,11 @@ class SoundtrackEditWindow(bui.Window):
|
|||
if (
|
||||
new_name
|
||||
== bui.Lstr(
|
||||
resource=self._r + '.defaultSoundtrackNameText'
|
||||
resource=f'{self._r}.defaultSoundtrackNameText'
|
||||
).evaluate()
|
||||
):
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.cantOverwriteDefaultText')
|
||||
bui.Lstr(resource=f'{self._r}.cantOverwriteDefaultText')
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 32),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=self._r + '.selectASourceText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.selectASourceText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
maxwidth=230,
|
||||
h_align='center',
|
||||
|
|
@ -110,7 +110,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=bui.Lstr(resource=self._r + '.useDefaultGameMusicText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.useDefaultGameMusicText'),
|
||||
on_activate_call=self._on_default_press,
|
||||
)
|
||||
if current_entry_type == 'default':
|
||||
|
|
@ -122,7 +122,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=bui.Lstr(resource=self._r + '.useITunesPlaylistText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.useITunesPlaylistText'),
|
||||
on_activate_call=self._on_mac_music_app_playlist_press,
|
||||
icon=None,
|
||||
)
|
||||
|
|
@ -135,7 +135,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=bui.Lstr(resource=self._r + '.useMusicFileText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.useMusicFileText'),
|
||||
on_activate_call=self._on_music_file_press,
|
||||
icon=bui.gettexture('file'),
|
||||
)
|
||||
|
|
@ -148,7 +148,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
size=(self._width - 100, 60),
|
||||
position=(50, v),
|
||||
label=bui.Lstr(resource=self._r + '.useMusicFolderText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.useMusicFolderText'),
|
||||
on_activate_call=self._on_music_folder_press,
|
||||
icon=bui.gettexture('folder'),
|
||||
icon_color=(1.1, 0.8, 0.2),
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class MacMusicAppPlaylistSelectWindow(bui.Window):
|
|||
parent=self._root_widget,
|
||||
position=(20, self._height - 54),
|
||||
size=(self._width, 25),
|
||||
text=bui.Lstr(resource=self._r + '.selectAPlaylistText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.selectAPlaylistText'),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
|
|
@ -75,7 +75,7 @@ class MacMusicAppPlaylistSelectWindow(bui.Window):
|
|||
bui.textwidget(
|
||||
parent=self._column,
|
||||
size=(self._width - 80, 22),
|
||||
text=bui.Lstr(resource=self._r + '.fetchingITunesText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.fetchingITunesText'),
|
||||
color=(0.6, 0.9, 0.6, 1.0),
|
||||
scale=0.8,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ class SpecialOfferWindow(bui.Window):
|
|||
|
||||
def _on_get_more_tickets_press(self) -> None:
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -448,10 +448,10 @@ class SpecialOfferWindow(bui.Window):
|
|||
if plus.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
getcurrency.GetCurrencyWindow(modal=True).get_root_widget()
|
||||
gettickets.GetTicketsWindow(modal=True).get_root_widget()
|
||||
|
||||
def _purchase(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
from bauiv1lib import confirm
|
||||
|
||||
plus = bui.app.plus
|
||||
|
|
@ -474,7 +474,7 @@ class SpecialOfferWindow(bui.Window):
|
|||
except Exception:
|
||||
ticket_count = None
|
||||
if ticket_count is not None and ticket_count < self._offer['price']:
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
|
|
|
|||
31
dist/ba_data/python/bauiv1lib/store/browser.py
vendored
31
dist/ba_data/python/bauiv1lib/store/browser.py
vendored
|
|
@ -205,17 +205,17 @@ class StoreBrowserWindow(bui.Window):
|
|||
tab_buffer_h = 250 + 2 * x_inset
|
||||
|
||||
tabs_def = [
|
||||
(self.TabID.EXTRAS, bui.Lstr(resource=self._r + '.extrasText')),
|
||||
(self.TabID.MAPS, bui.Lstr(resource=self._r + '.mapsText')),
|
||||
(self.TabID.EXTRAS, bui.Lstr(resource=f'{self._r}.extrasText')),
|
||||
(self.TabID.MAPS, bui.Lstr(resource=f'{self._r}.mapsText')),
|
||||
(
|
||||
self.TabID.MINIGAMES,
|
||||
bui.Lstr(resource=self._r + '.miniGamesText'),
|
||||
bui.Lstr(resource=f'{self._r}.miniGamesText'),
|
||||
),
|
||||
(
|
||||
self.TabID.CHARACTERS,
|
||||
bui.Lstr(resource=self._r + '.charactersText'),
|
||||
bui.Lstr(resource=f'{self._r}.charactersText'),
|
||||
),
|
||||
(self.TabID.ICONS, bui.Lstr(resource=self._r + '.iconsText')),
|
||||
(self.TabID.ICONS, bui.Lstr(resource=f'{self._r}.iconsText')),
|
||||
]
|
||||
|
||||
self._tab_row = TabRow(
|
||||
|
|
@ -449,7 +449,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
color=(1, 0.7, 1, 0.5),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.loadingText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.loadingText'),
|
||||
maxwidth=self._scroll_width * 0.9,
|
||||
)
|
||||
|
||||
|
|
@ -574,7 +574,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
"""Attempt to purchase the provided item."""
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib.confirm import ConfirmWindow
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
|
@ -620,7 +620,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
our_tickets = plus.get_v1_account_ticket_count()
|
||||
if price is not None and our_tickets < price:
|
||||
bui.getsound('error').play()
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
else:
|
||||
|
||||
def do_it() -> None:
|
||||
|
|
@ -653,7 +653,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
def _print_already_own(self, charname: str) -> None:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.alreadyOwnText',
|
||||
resource=f'{self._r}.alreadyOwnText',
|
||||
subs=[('${NAME}', charname)],
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
|
|
@ -868,7 +868,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
color=(1, 0.3, 0.3, 1.0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.loadErrorText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.loadErrorText'),
|
||||
maxwidth=self._scroll_width * 0.9,
|
||||
)
|
||||
else:
|
||||
|
|
@ -1238,7 +1238,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
color=(1, 1, 0.3, 1.0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.comingSoonText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.comingSoonText'),
|
||||
maxwidth=self._scroll_width * 0.9,
|
||||
)
|
||||
|
||||
|
|
@ -1320,7 +1320,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
def _on_get_more_tickets_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.account import show_sign_in_prompt
|
||||
from bauiv1lib.getcurrency import GetCurrencyWindow
|
||||
from bauiv1lib.gettickets import GetTicketsWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
|
|
@ -1334,7 +1334,7 @@ class StoreBrowserWindow(bui.Window):
|
|||
return
|
||||
self._save_state()
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
window = GetCurrencyWindow(
|
||||
window = GetTicketsWindow(
|
||||
from_modal_store=self._modal,
|
||||
store_back_location=self._back_location,
|
||||
).get_root_widget()
|
||||
|
|
@ -1391,7 +1391,7 @@ def _check_merch_availability_in_bg_thread() -> None:
|
|||
|
||||
def _store_in_logic_thread() -> None:
|
||||
cfg = bui.app.config
|
||||
current: str | None = cfg.get(MERCH_LINK_KEY)
|
||||
current = cfg.get(MERCH_LINK_KEY)
|
||||
if not isinstance(current, str | None):
|
||||
current = None
|
||||
if current != response.url:
|
||||
|
|
@ -1414,6 +1414,9 @@ def _check_merch_availability_in_bg_thread() -> None:
|
|||
# Slight hack; start checking merch availability in the bg (but only if
|
||||
# it looks like we've been imported for use in a running app; don't want
|
||||
# to do this during docs generation/etc.)
|
||||
|
||||
# TODO: Should wire this up explicitly to app bootstrapping; not good to
|
||||
# be kicking off work at module import time.
|
||||
if (
|
||||
os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') != '1'
|
||||
and bui.app.state is not bui.app.State.NOT_STARTED
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ class TournamentEntryWindow(PopupWindow):
|
|||
bui.apptimer(0 if practice else 1.25, self._transition_out)
|
||||
|
||||
def _on_pay_with_tickets_press(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -675,7 +675,7 @@ class TournamentEntryWindow(PopupWindow):
|
|||
ticket_count = None
|
||||
ticket_cost = self._purchase_price
|
||||
if ticket_count is not None and ticket_count < ticket_cost:
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
gettickets.show_get_tickets_prompt()
|
||||
bui.getsound('error').play()
|
||||
self._transition_out()
|
||||
return
|
||||
|
|
@ -781,7 +781,7 @@ class TournamentEntryWindow(PopupWindow):
|
|||
self._launch()
|
||||
|
||||
def _on_get_tickets_press(self) -> None:
|
||||
from bauiv1lib import getcurrency
|
||||
from bauiv1lib import gettickets
|
||||
|
||||
# If we're already entering, ignore presses.
|
||||
if self._entering:
|
||||
|
|
@ -789,7 +789,7 @@ class TournamentEntryWindow(PopupWindow):
|
|||
|
||||
# Bring up get-tickets window and then kill ourself (we're on the
|
||||
# overlay layer so we'd show up above it).
|
||||
getcurrency.GetCurrencyWindow(
|
||||
gettickets.GetTicketsWindow(
|
||||
modal=True, origin_widget=self._get_tickets_button
|
||||
)
|
||||
self._transition_out()
|
||||
|
|
|
|||
26
dist/ba_data/python/bauiv1lib/watch.py
vendored
26
dist/ba_data/python/bauiv1lib/watch.py
vendored
|
|
@ -116,14 +116,14 @@ class WatchWindow(bui.Window):
|
|||
scale=1.5,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(resource=self._r + '.titleText'),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=400,
|
||||
)
|
||||
|
||||
tabdefs = [
|
||||
(
|
||||
self.TabID.MY_REPLAYS,
|
||||
bui.Lstr(resource=self._r + '.myReplaysText'),
|
||||
bui.Lstr(resource=f'{self._r}.myReplaysText'),
|
||||
),
|
||||
# (self.TabID.TEST_TAB, bui.Lstr(value='Testing')),
|
||||
]
|
||||
|
|
@ -276,7 +276,7 @@ class WatchWindow(bui.Window):
|
|||
textcolor=b_textcolor,
|
||||
on_activate_call=self._on_my_replay_play_press,
|
||||
text_scale=tscl,
|
||||
label=bui.Lstr(resource=self._r + '.watchReplayButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.watchReplayButtonText'),
|
||||
autoselect=True,
|
||||
)
|
||||
bui.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button)
|
||||
|
|
@ -296,7 +296,7 @@ class WatchWindow(bui.Window):
|
|||
textcolor=b_textcolor,
|
||||
on_activate_call=self._on_my_replay_rename_press,
|
||||
text_scale=tscl,
|
||||
label=bui.Lstr(resource=self._r + '.renameReplayButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.renameReplayButtonText'),
|
||||
autoselect=True,
|
||||
)
|
||||
btnv -= b_height + b_space_extra
|
||||
|
|
@ -309,7 +309,7 @@ class WatchWindow(bui.Window):
|
|||
textcolor=b_textcolor,
|
||||
on_activate_call=self._on_my_replay_delete_press,
|
||||
text_scale=tscl,
|
||||
label=bui.Lstr(resource=self._r + '.deleteReplayButtonText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteReplayButtonText'),
|
||||
autoselect=True,
|
||||
)
|
||||
|
||||
|
|
@ -339,7 +339,7 @@ class WatchWindow(bui.Window):
|
|||
|
||||
def _no_replay_selected_error(self) -> None:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.noReplaySelectedErrorText'),
|
||||
bui.Lstr(resource=f'{self._r}.noReplaySelectedErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
|
@ -395,7 +395,7 @@ class WatchWindow(bui.Window):
|
|||
h_align='center',
|
||||
v_align='center',
|
||||
text=bui.Lstr(
|
||||
resource=self._r + '.renameReplayText',
|
||||
resource=f'{self._r}.renameReplayText',
|
||||
subs=[('${REPLAY}', dname)],
|
||||
),
|
||||
maxwidth=c_width * 0.8,
|
||||
|
|
@ -408,7 +408,7 @@ class WatchWindow(bui.Window):
|
|||
v_align='center',
|
||||
text=dname,
|
||||
editable=True,
|
||||
description=bui.Lstr(resource=self._r + '.replayNameText'),
|
||||
description=bui.Lstr(resource=f'{self._r}.replayNameText'),
|
||||
position=(c_width * 0.1, c_height - 140),
|
||||
autoselect=True,
|
||||
maxwidth=c_width * 0.7,
|
||||
|
|
@ -427,7 +427,7 @@ class WatchWindow(bui.Window):
|
|||
)
|
||||
okb = bui.buttonwidget(
|
||||
parent=cnt,
|
||||
label=bui.Lstr(resource=self._r + '.renameText'),
|
||||
label=bui.Lstr(resource=f'{self._r}.renameText'),
|
||||
size=(180, 60),
|
||||
position=(c_width - 230, 30),
|
||||
on_activate_call=bui.Call(
|
||||
|
|
@ -477,7 +477,7 @@ class WatchWindow(bui.Window):
|
|||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.replayRenameErrorInvalidName'
|
||||
resource=f'{self._r}.replayRenameErrorInvalidName'
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
|
@ -492,7 +492,7 @@ class WatchWindow(bui.Window):
|
|||
)
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.replayRenameErrorText'),
|
||||
bui.Lstr(resource=f'{self._r}.replayRenameErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
|
@ -508,7 +508,7 @@ class WatchWindow(bui.Window):
|
|||
return
|
||||
confirm.ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=self._r + '.deleteConfirmText',
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[
|
||||
(
|
||||
'${REPLAY}',
|
||||
|
|
@ -540,7 +540,7 @@ class WatchWindow(bui.Window):
|
|||
logging.exception("Error deleting replay '%s'.", replay)
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=self._r + '.replayDeleteErrorText'),
|
||||
bui.Lstr(resource=f'{self._r}.replayDeleteErrorText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
|
|
|||
328
dist/ba_data/python/efro/call.py
vendored
328
dist/ba_data/python/efro/call.py
vendored
|
|
@ -4,328 +4,14 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast
|
||||
import functools
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, overload
|
||||
pass
|
||||
|
||||
CT = TypeVar('CT', bound=Callable)
|
||||
|
||||
|
||||
class _CallbackCall(Generic[CT]):
|
||||
"""Descriptor for exposing a call with a type defined by a TypeVar."""
|
||||
|
||||
def __get__(self, obj: Any, type_in: Any = None) -> CT:
|
||||
return cast(CT, None)
|
||||
|
||||
|
||||
class CallbackSet(Generic[CT]):
|
||||
"""Wrangles callbacks for a particular event in a type-safe manner."""
|
||||
|
||||
# In the type-checker's eyes, our 'run' attr is a CallbackCall which
|
||||
# returns a callable with the type we were created with. This lets us
|
||||
# type-check our run calls. (Is there another way to expose a function
|
||||
# with a signature defined by a generic?..)
|
||||
# At runtime, run() simply passes its args verbatim to its registered
|
||||
# callbacks; no types are checked.
|
||||
if TYPE_CHECKING:
|
||||
run: _CallbackCall[CT] = _CallbackCall()
|
||||
else:
|
||||
|
||||
def run(self, *args, **keywds):
|
||||
"""Run all callbacks."""
|
||||
print('HELLO FROM RUN', *args, **keywds)
|
||||
|
||||
def __init__(self) -> None:
|
||||
print('CallbackSet()')
|
||||
|
||||
def __del__(self) -> None:
|
||||
print('~CallbackSet()')
|
||||
|
||||
def add(self, call: CT) -> None:
|
||||
"""Add a callback to be run."""
|
||||
print('Would add call', call)
|
||||
|
||||
|
||||
# Define Call() which can be used in type-checking call-wrappers that behave
|
||||
# similarly to functools.partial (in that they take a callable and some
|
||||
# positional arguments to be passed to it).
|
||||
|
||||
# In type-checking land, We define several different _CallXArg classes
|
||||
# corresponding to different argument counts and define Call() as an
|
||||
# overloaded function which returns one of them based on how many args are
|
||||
# passed.
|
||||
|
||||
# To use this, simply assign your call type to this Call for type checking:
|
||||
# Example:
|
||||
# class _MyCallWrapper:
|
||||
# <runtime class defined here>
|
||||
# if TYPE_CHECKING:
|
||||
# MyCallWrapper = efro.call.Call
|
||||
# else:
|
||||
# MyCallWrapper = _MyCallWrapper
|
||||
|
||||
# Note that this setup currently only works with positional arguments; if you
|
||||
# would like to pass args via keyword you can wrap a lambda or local function
|
||||
# which takes keyword args and converts to a call containing keywords.
|
||||
|
||||
if TYPE_CHECKING:
|
||||
In1T = TypeVar('In1T')
|
||||
In2T = TypeVar('In2T')
|
||||
In3T = TypeVar('In3T')
|
||||
In4T = TypeVar('In4T')
|
||||
In5T = TypeVar('In5T')
|
||||
In6T = TypeVar('In6T')
|
||||
In7T = TypeVar('In7T')
|
||||
OutT = TypeVar('OutT')
|
||||
|
||||
class _CallNoArgs(Generic[OutT]):
|
||||
"""Single argument variant of call wrapper."""
|
||||
|
||||
def __init__(self, _call: Callable[[], OutT]): ...
|
||||
|
||||
def __call__(self) -> OutT: ...
|
||||
|
||||
class _Call1Arg(Generic[In1T, OutT]):
|
||||
"""Single argument variant of call wrapper."""
|
||||
|
||||
def __init__(self, _call: Callable[[In1T], OutT]): ...
|
||||
|
||||
def __call__(self, _arg1: In1T) -> OutT: ...
|
||||
|
||||
class _Call2Args(Generic[In1T, In2T, OutT]):
|
||||
"""Two argument variant of call wrapper"""
|
||||
|
||||
def __init__(self, _call: Callable[[In1T, In2T], OutT]): ...
|
||||
|
||||
def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT: ...
|
||||
|
||||
class _Call3Args(Generic[In1T, In2T, In3T, OutT]):
|
||||
"""Three argument variant of call wrapper"""
|
||||
|
||||
def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]): ...
|
||||
|
||||
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT: ...
|
||||
|
||||
class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]):
|
||||
"""Four argument variant of call wrapper"""
|
||||
|
||||
def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]): ...
|
||||
|
||||
def __call__(
|
||||
self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T
|
||||
) -> OutT: ...
|
||||
|
||||
class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]):
|
||||
"""Five argument variant of call wrapper"""
|
||||
|
||||
def __init__(
|
||||
self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT]
|
||||
): ...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
_arg1: In1T,
|
||||
_arg2: In2T,
|
||||
_arg3: In3T,
|
||||
_arg4: In4T,
|
||||
_arg5: In5T,
|
||||
) -> OutT: ...
|
||||
|
||||
class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]):
|
||||
"""Six argument variant of call wrapper"""
|
||||
|
||||
def __init__(
|
||||
self, _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT]
|
||||
): ...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
_arg1: In1T,
|
||||
_arg2: In2T,
|
||||
_arg3: In3T,
|
||||
_arg4: In4T,
|
||||
_arg5: In5T,
|
||||
_arg6: In6T,
|
||||
) -> OutT: ...
|
||||
|
||||
class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]):
|
||||
"""Seven argument variant of call wrapper"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
|
||||
): ...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
_arg1: In1T,
|
||||
_arg2: In2T,
|
||||
_arg3: In3T,
|
||||
_arg4: In4T,
|
||||
_arg5: In5T,
|
||||
_arg6: In6T,
|
||||
_arg7: In7T,
|
||||
) -> OutT: ...
|
||||
|
||||
# No arg call; no args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 1 arg call; 1 arg bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 1 arg call; no args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]: ...
|
||||
|
||||
# 2 arg call; 2 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T], OutT], arg1: In1T, arg2: In2T
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 2 arg call; 1 arg bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T], OutT], arg1: In1T
|
||||
) -> _Call1Arg[In2T, OutT]: ...
|
||||
|
||||
# 2 arg call; no args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T], OutT]
|
||||
) -> _Call2Args[In1T, In2T, OutT]: ...
|
||||
|
||||
# 3 arg call; 3 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 3 arg call; 2 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T
|
||||
) -> _Call1Arg[In3T, OutT]: ...
|
||||
|
||||
# 3 arg call; 1 arg bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T
|
||||
) -> _Call2Args[In2T, In3T, OutT]: ...
|
||||
|
||||
# 3 arg call; no args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T], OutT]
|
||||
) -> _Call3Args[In1T, In2T, In3T, OutT]: ...
|
||||
|
||||
# 4 arg call; 4 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
arg4: In4T,
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 4 arg call; 3 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
) -> _Call1Arg[In4T, OutT]: ...
|
||||
|
||||
# 4 arg call; 2 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
) -> _Call2Args[In3T, In4T, OutT]: ...
|
||||
|
||||
# 4 arg call; 1 arg bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T], OutT],
|
||||
arg1: In1T,
|
||||
) -> _Call3Args[In2T, In3T, In4T, OutT]: ...
|
||||
|
||||
# 4 arg call; no args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T], OutT],
|
||||
) -> _Call4Args[In1T, In2T, In3T, In4T, OutT]: ...
|
||||
|
||||
# 5 arg call; 5 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
arg4: In4T,
|
||||
arg5: In5T,
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 6 arg call; 6 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
arg4: In4T,
|
||||
arg5: In5T,
|
||||
arg6: In6T,
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# 7 arg call; 7 args bundled.
|
||||
# noinspection PyPep8Naming
|
||||
@overload
|
||||
def Call(
|
||||
call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
|
||||
arg1: In1T,
|
||||
arg2: In2T,
|
||||
arg3: In3T,
|
||||
arg4: In4T,
|
||||
arg5: In5T,
|
||||
arg6: In6T,
|
||||
arg7: In7T,
|
||||
) -> _CallNoArgs[OutT]: ...
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
def Call(*_args: Any, **_keywds: Any) -> Any: ...
|
||||
|
||||
# (Type-safe Partial)
|
||||
# A convenient wrapper around functools.partial which adds type-safety
|
||||
# (though it does not support keyword arguments).
|
||||
tpartial = Call
|
||||
else:
|
||||
tpartial = functools.partial
|
||||
# TODO: should deprecate tpartial since it nowadays simply wraps
|
||||
# functools.partial (mypy added support for functools.partial in 1.11 so
|
||||
# there's no benefit to rolling our own type-safe version anymore).
|
||||
# Perhaps we can use Python 13's @warnings.deprecated() stuff for this.
|
||||
tpartial = functools.partial
|
||||
|
|
|
|||
18
dist/ba_data/python/efro/dataclassio/_api.py
vendored
18
dist/ba_data/python/efro/dataclassio/_api.py
vendored
|
|
@ -44,6 +44,7 @@ def dataclass_to_dict(
|
|||
obj: Any,
|
||||
codec: Codec = Codec.JSON,
|
||||
coerce_to_float: bool = True,
|
||||
discard_extra_attrs: bool = False,
|
||||
) -> dict:
|
||||
"""Given a dataclass object, return a json-friendly dict.
|
||||
|
||||
|
|
@ -62,7 +63,11 @@ def dataclass_to_dict(
|
|||
"""
|
||||
|
||||
out = _Outputter(
|
||||
obj, create=True, codec=codec, coerce_to_float=coerce_to_float
|
||||
obj,
|
||||
create=True,
|
||||
codec=codec,
|
||||
coerce_to_float=coerce_to_float,
|
||||
discard_extra_attrs=discard_extra_attrs,
|
||||
).run()
|
||||
assert isinstance(out, dict)
|
||||
return out
|
||||
|
|
@ -157,14 +162,21 @@ def dataclass_from_json(
|
|||
|
||||
|
||||
def dataclass_validate(
|
||||
obj: Any, coerce_to_float: bool = True, codec: Codec = Codec.JSON
|
||||
obj: Any,
|
||||
coerce_to_float: bool = True,
|
||||
codec: Codec = Codec.JSON,
|
||||
discard_extra_attrs: bool = False,
|
||||
) -> None:
|
||||
"""Ensure that values in a dataclass instance are the correct types."""
|
||||
|
||||
# Simply run an output pass but tell it not to generate data;
|
||||
# only run validation.
|
||||
_Outputter(
|
||||
obj, create=False, codec=codec, coerce_to_float=coerce_to_float
|
||||
obj,
|
||||
create=False,
|
||||
codec=codec,
|
||||
coerce_to_float=coerce_to_float,
|
||||
discard_extra_attrs=discard_extra_attrs,
|
||||
).run()
|
||||
|
||||
|
||||
|
|
|
|||
23
dist/ba_data/python/efro/dataclassio/_base.py
vendored
23
dist/ba_data/python/efro/dataclassio/_base.py
vendored
|
|
@ -61,6 +61,29 @@ class IOExtendedData:
|
|||
type-safe form.
|
||||
"""
|
||||
|
||||
# pylint: disable=useless-return
|
||||
|
||||
@classmethod
|
||||
def handle_input_error(cls, exc: Exception) -> Self | None:
|
||||
"""Called when an error occurs during input decoding.
|
||||
|
||||
This allows a type to optionally return substitute data
|
||||
to be used in place of the failed decode. If it returns
|
||||
None, the original exception is re-raised.
|
||||
|
||||
It is generally a bad idea to apply catch-alls such as this,
|
||||
as it can lead to silent data loss. This should only be used
|
||||
in specific cases such as user settings where an occasional
|
||||
reset is harmless and is preferable to keeping all contained
|
||||
enums and other values backward compatible indefinitely.
|
||||
"""
|
||||
del exc # Unused.
|
||||
|
||||
# By default we let things fail.
|
||||
return None
|
||||
|
||||
# pylint: enable=useless-return
|
||||
|
||||
|
||||
EnumT = TypeVar('EnumT', bound=Enum)
|
||||
|
||||
|
|
|
|||
|
|
@ -236,6 +236,28 @@ class _Inputter:
|
|||
sets should be passed as lists, enums should be passed as their
|
||||
associated values, and nested dataclasses should be passed as dicts.
|
||||
"""
|
||||
try:
|
||||
return self._do_dataclass_from_input(cls, fieldpath, values)
|
||||
except Exception as exc:
|
||||
# Extended data types can choose to sub default data in case
|
||||
# of failures (generally not a good idea but occasionally
|
||||
# useful).
|
||||
if issubclass(cls, IOExtendedData):
|
||||
fallback = cls.handle_input_error(exc)
|
||||
if fallback is None:
|
||||
raise
|
||||
# Make sure fallback gave us the right type.
|
||||
if not isinstance(fallback, cls):
|
||||
raise RuntimeError(
|
||||
f'handle_input_error() was expected to return a {cls}'
|
||||
f' but returned a {type(fallback)}.'
|
||||
) from exc
|
||||
return fallback
|
||||
raise
|
||||
|
||||
def _do_dataclass_from_input(
|
||||
self, cls: type, fieldpath: str, values: dict
|
||||
) -> Any:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
|
|
@ -377,6 +399,7 @@ class _Inputter:
|
|||
create=False,
|
||||
codec=self._codec,
|
||||
coerce_to_float=self._coerce_to_float,
|
||||
discard_extra_attrs=False,
|
||||
)
|
||||
self._soft_default_validator.soft_default_check(
|
||||
value=value, anntype=anntype, fieldpath=fieldpath
|
||||
|
|
|
|||
|
|
@ -38,25 +38,35 @@ class _Outputter:
|
|||
"""Validates or exports data contained in a dataclass instance."""
|
||||
|
||||
def __init__(
|
||||
self, obj: Any, create: bool, codec: Codec, coerce_to_float: bool
|
||||
self,
|
||||
obj: Any,
|
||||
create: bool,
|
||||
codec: Codec,
|
||||
coerce_to_float: bool,
|
||||
discard_extra_attrs: bool,
|
||||
) -> None:
|
||||
self._obj = obj
|
||||
self._create = create
|
||||
self._codec = codec
|
||||
self._coerce_to_float = coerce_to_float
|
||||
self._discard_extra_attrs = discard_extra_attrs
|
||||
|
||||
def run(self) -> Any:
|
||||
"""Do the thing."""
|
||||
|
||||
obj = self._obj
|
||||
|
||||
# mypy workaround - if we check 'obj' here it assumes the
|
||||
# isinstance call below fails.
|
||||
assert dataclasses.is_dataclass(self._obj)
|
||||
|
||||
# For special extended data types, call their 'will_output' callback.
|
||||
# FIXME - should probably move this into _process_dataclass so it
|
||||
# can work on nested values.
|
||||
if isinstance(self._obj, IOExtendedData):
|
||||
self._obj.will_output()
|
||||
if isinstance(obj, IOExtendedData):
|
||||
obj.will_output()
|
||||
|
||||
return self._process_dataclass(type(self._obj), self._obj, '')
|
||||
return self._process_dataclass(type(obj), obj, '')
|
||||
|
||||
def soft_default_check(
|
||||
self, value: Any, anntype: Any, fieldpath: str
|
||||
|
|
@ -133,17 +143,18 @@ class _Outputter:
|
|||
out[storagename] = outvalue
|
||||
|
||||
# If there's extra-attrs stored on us, check/include them.
|
||||
extra_attrs = getattr(obj, EXTRA_ATTRS_ATTR, None)
|
||||
if isinstance(extra_attrs, dict):
|
||||
if not _is_valid_for_codec(extra_attrs, self._codec):
|
||||
raise TypeError(
|
||||
f'Extra attrs on \'{fieldpath}\' contains data type(s)'
|
||||
f' not supported by \'{self._codec.value}\' codec:'
|
||||
f' {extra_attrs}.'
|
||||
)
|
||||
if self._create:
|
||||
assert out is not None
|
||||
out.update(extra_attrs)
|
||||
if not self._discard_extra_attrs:
|
||||
extra_attrs = getattr(obj, EXTRA_ATTRS_ATTR, None)
|
||||
if isinstance(extra_attrs, dict):
|
||||
if not _is_valid_for_codec(extra_attrs, self._codec):
|
||||
raise TypeError(
|
||||
f'Extra attrs on \'{fieldpath}\' contains data type(s)'
|
||||
f' not supported by \'{self._codec.value}\' codec:'
|
||||
f' {extra_attrs}.'
|
||||
)
|
||||
if self._create:
|
||||
assert out is not None
|
||||
out.update(extra_attrs)
|
||||
|
||||
# If this obj inherits from multi-type, store its type id.
|
||||
if isinstance(obj, IOMultiType):
|
||||
|
|
|
|||
39
dist/ba_data/python/efro/debug.py
vendored
39
dist/ba_data/python/efro/debug.py
vendored
|
|
@ -399,8 +399,8 @@ class DeadlockWatcher:
|
|||
|
||||
Use the enable_deadlock_watchers() to enable this system.
|
||||
|
||||
Next, create these in contexts where they will be torn down after
|
||||
some operation completes. If any is not torn down within the
|
||||
Next, use these wrapped in a with statement around some operation
|
||||
that may deadlock. If the with statement does not complete within the
|
||||
timeout period, a traceback of all threads will be dumped.
|
||||
|
||||
Note that the checker thread runs a cycle every ~5 seconds, so
|
||||
|
|
@ -442,10 +442,39 @@ class DeadlockWatcher:
|
|||
if curthread.ident is None
|
||||
else hex(curthread.ident).removeprefix('0x')
|
||||
)
|
||||
self.active = False
|
||||
|
||||
with cls.watchers_lock:
|
||||
cls.watchers.append(weakref.ref(self))
|
||||
|
||||
# Support the with statement.
|
||||
def __enter__(self) -> Any:
|
||||
self.active = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Any, exc_value: Any, exc_tb: Any) -> None:
|
||||
self.active = False
|
||||
|
||||
# Print if we lived past our deadline. This is just an extra
|
||||
# data point. The watcher thread should be doing the actual
|
||||
# stack dumps/etc.
|
||||
if self.logger is None or self.logextra is None:
|
||||
return
|
||||
|
||||
duration = time.monotonic() - self.create_time
|
||||
if duration > self.timeout:
|
||||
self.logger.error(
|
||||
'DeadlockWatcher %s at %s in thread %s lived %.2fs,'
|
||||
' past timeout %.2fs. This should have triggered'
|
||||
' a deadlock dump.',
|
||||
id(self),
|
||||
self.caller_source_loc,
|
||||
self.thread_id,
|
||||
duration,
|
||||
self.timeout,
|
||||
extra=self.logextra,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def enable_deadlock_watchers(cls) -> None:
|
||||
"""Spins up deadlock-watcher functionality.
|
||||
|
|
@ -486,7 +515,7 @@ class DeadlockWatcher:
|
|||
|
||||
found_fresh_expired = False
|
||||
|
||||
# If any watcher is still alive but expired, sleep past the
|
||||
# If any watcher is still active and expired, sleep past the
|
||||
# timeout to force the dumper to do its thing.
|
||||
with cls.watchers_lock:
|
||||
|
||||
|
|
@ -496,14 +525,16 @@ class DeadlockWatcher:
|
|||
w is not None
|
||||
and now - w.create_time > w.timeout
|
||||
and not w.noted_expire
|
||||
and w.active
|
||||
):
|
||||
# If they supplied a logger, let them know they
|
||||
# should check stderr for a dump.
|
||||
if w.logger is not None:
|
||||
w.logger.error(
|
||||
'DeadlockWatcher at %s in thread %s'
|
||||
'DeadlockWatcher %s at %s in thread %s'
|
||||
' with time %.2f expired;'
|
||||
' check stderr for stack traces.',
|
||||
id(w),
|
||||
w.caller_source_loc,
|
||||
w.thread_id,
|
||||
w.timeout,
|
||||
|
|
|
|||
6
dist/ba_data/python/efro/error.py
vendored
6
dist/ba_data/python/efro/error.py
vendored
|
|
@ -73,9 +73,9 @@ class RemoteError(Exception):
|
|||
occurs remotely. The error string can consist of a remote stack
|
||||
trace or a simple message depending on the context.
|
||||
|
||||
Communication systems should raise more specific error types locally
|
||||
when more introspection/control is needed; this is intended somewhat
|
||||
as a catch-all.
|
||||
Communication systems should aim to communicate specific errors
|
||||
gracefully as standard message responses when specific details are
|
||||
needed; this is intended more as a catch-all.
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str, peer_desc: str):
|
||||
|
|
|
|||
42
dist/ba_data/python/efro/log.py
vendored
42
dist/ba_data/python/efro/log.py
vendored
|
|
@ -10,13 +10,13 @@ import logging
|
|||
import datetime
|
||||
import itertools
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Annotated, override
|
||||
from threading import Thread, current_thread, Lock
|
||||
|
||||
from efro.util import utc_now
|
||||
from efro.call import tpartial
|
||||
from efro.terminal import Clr
|
||||
from efro.dataclassio import ioprepped, IOAttrs, dataclass_to_json
|
||||
|
||||
|
|
@ -150,7 +150,6 @@ class LogHandler(logging.Handler):
|
|||
self._cache = deque[tuple[int, LogEntry]]()
|
||||
self._cache_index_offset = 0
|
||||
self._cache_lock = Lock()
|
||||
# self._report_blocking_io_on_echo_error = False
|
||||
self._printed_callback_error = False
|
||||
self._thread_bootstrapped = False
|
||||
self._thread = Thread(target=self._log_thread_main, daemon=True)
|
||||
|
|
@ -179,7 +178,7 @@ class LogHandler(logging.Handler):
|
|||
# process cached entries at the same time to ensure there are no
|
||||
# race conditions that could cause entries to be skipped/etc.
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(self._add_callback_in_thread, call, feed_existing_logs)
|
||||
partial(self._add_callback_in_thread, call, feed_existing_logs)
|
||||
)
|
||||
|
||||
def _add_callback_in_thread(
|
||||
|
|
@ -343,7 +342,7 @@ class LogHandler(logging.Handler):
|
|||
if __debug__:
|
||||
formattime = echotime = time.monotonic()
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(
|
||||
partial(
|
||||
self._emit_in_thread,
|
||||
record.name,
|
||||
record.levelno,
|
||||
|
|
@ -396,7 +395,7 @@ class LogHandler(logging.Handler):
|
|||
echotime = time.monotonic()
|
||||
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(
|
||||
partial(
|
||||
self._emit_in_thread,
|
||||
record.name,
|
||||
record.levelno,
|
||||
|
|
@ -428,7 +427,7 @@ class LogHandler(logging.Handler):
|
|||
# the bg event loop thread we've already got.
|
||||
self._last_slow_emit_warning_time = now
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(
|
||||
partial(
|
||||
logging.warning,
|
||||
'efro.log.LogHandler emit took too long'
|
||||
' (%.2fs total; %.2fs format, %.2fs echo,'
|
||||
|
|
@ -478,7 +477,7 @@ class LogHandler(logging.Handler):
|
|||
# another thread for each character. Perhaps should do some sort
|
||||
# of basic accumulation here?
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(self._file_write_in_thread, name, output)
|
||||
partial(self._file_write_in_thread, name, output)
|
||||
)
|
||||
|
||||
def _file_write_in_thread(self, name: str, output: str) -> None:
|
||||
|
|
@ -515,11 +514,30 @@ class LogHandler(logging.Handler):
|
|||
|
||||
traceback.print_exc(file=self._echofile)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Block until all pending logs/prints are done."""
|
||||
done = False
|
||||
self.file_flush('stdout')
|
||||
self.file_flush('stderr')
|
||||
|
||||
def _set_done() -> None:
|
||||
nonlocal done
|
||||
done = True
|
||||
|
||||
self._event_loop.call_soon_threadsafe(_set_done)
|
||||
|
||||
starttime = time.monotonic()
|
||||
while not done:
|
||||
if time.monotonic() - starttime > 5.0:
|
||||
print('LogHandler shutdown hung!!!', file=sys.stderr)
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
def file_flush(self, name: str) -> None:
|
||||
"""Send raw stdout/stderr flush to the logger to be collated."""
|
||||
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(self._file_flush_in_thread, name)
|
||||
partial(self._file_flush_in_thread, name)
|
||||
)
|
||||
|
||||
def _file_flush_in_thread(self, name: str) -> None:
|
||||
|
|
@ -721,11 +739,7 @@ def setup_logging(
|
|||
# Optionally intercept Python's stdout/stderr output and generate
|
||||
# log entries from it.
|
||||
if log_stdout_stderr:
|
||||
sys.stdout = FileLogEcho( # type: ignore
|
||||
sys.stdout, 'stdout', loghandler
|
||||
)
|
||||
sys.stderr = FileLogEcho( # type: ignore
|
||||
sys.stderr, 'stderr', loghandler
|
||||
)
|
||||
sys.stdout = FileLogEcho(sys.stdout, 'stdout', loghandler)
|
||||
sys.stderr = FileLogEcho(sys.stderr, 'stderr', loghandler)
|
||||
|
||||
return loghandler
|
||||
|
|
|
|||
23
dist/ba_data/python/efro/message/_protocol.py
vendored
23
dist/ba_data/python/efro/message/_protocol.py
vendored
|
|
@ -45,7 +45,7 @@ class MessageProtocol:
|
|||
forward_communication_errors: bool = False,
|
||||
forward_clean_errors: bool = False,
|
||||
remote_errors_include_stack_traces: bool = False,
|
||||
log_remote_errors: bool = True,
|
||||
log_errors_on_receiver: bool = True,
|
||||
) -> None:
|
||||
"""Create a protocol with a given configuration.
|
||||
|
||||
|
|
@ -62,8 +62,8 @@ class MessageProtocol:
|
|||
|
||||
When an exception is not covered by the optional forwarding
|
||||
mechanisms above, it will come across as efro.error.RemoteError
|
||||
and the exception will be logged on the receiver
|
||||
end - at least by default (see details below).
|
||||
and the exception will be logged on the receiver end - at least
|
||||
by default (see details below).
|
||||
|
||||
If 'remote_errors_include_stack_traces' is True, stringified
|
||||
stack traces will be returned with efro.error.RemoteError
|
||||
|
|
@ -77,8 +77,8 @@ class MessageProtocol:
|
|||
goal is usually to avoid returning opaque RemoteErrors and to
|
||||
instead return something meaningful as part of the expected
|
||||
response type (even if that value itself represents a logical
|
||||
error state). If 'log_remote_errors' is False, however, such
|
||||
exceptions will not be logged on the receiver. This can be
|
||||
error state). If 'log_errors_on_receiver' is False, however, such
|
||||
exceptions will *not* be logged on the receiver. This can be
|
||||
useful in combination with 'remote_errors_include_stack_traces'
|
||||
and 'forward_clean_errors' in situations where all error
|
||||
logging/management will be happening on the sender end. Be
|
||||
|
|
@ -168,7 +168,7 @@ class MessageProtocol:
|
|||
self.remote_errors_include_stack_traces = (
|
||||
remote_errors_include_stack_traces
|
||||
)
|
||||
self.log_remote_errors = log_remote_errors
|
||||
self.log_errors_on_receiver = log_errors_on_receiver
|
||||
|
||||
@staticmethod
|
||||
def encode_dict(obj: dict) -> str:
|
||||
|
|
@ -213,13 +213,20 @@ class MessageProtocol:
|
|||
return (
|
||||
ErrorSysResponse(
|
||||
error_message=(
|
||||
traceback.format_exc()
|
||||
# Note: need to format exception ourself here; it
|
||||
# might not be current so we can't use
|
||||
# traceback.format_exc().
|
||||
''.join(
|
||||
traceback.format_exception(
|
||||
type(exc), exc, exc.__traceback__
|
||||
)
|
||||
)
|
||||
if self.remote_errors_include_stack_traces
|
||||
else 'An internal error has occurred.'
|
||||
),
|
||||
error_type=ErrorSysResponse.ErrorType.REMOTE,
|
||||
),
|
||||
self.log_remote_errors,
|
||||
self.log_errors_on_receiver,
|
||||
)
|
||||
|
||||
def _to_dict(
|
||||
|
|
|
|||
58
dist/ba_data/python/efro/message/_receiver.py
vendored
58
dist/ba_data/python/efro/message/_receiver.py
vendored
|
|
@ -38,6 +38,7 @@ class MessageReceiver:
|
|||
|
||||
# MyMessageReceiver fills out handler() overloads to ensure all
|
||||
# registered handlers have valid types/return-types.
|
||||
|
||||
@receiver.handler
|
||||
def handle_some_message_type(self, message: SomeMsg) -> SomeResponse:
|
||||
# Deal with this message type here.
|
||||
|
|
@ -47,7 +48,7 @@ class MessageReceiver:
|
|||
obj.receiver.handle_raw_message(some_raw_data)
|
||||
|
||||
Any unhandled Exception occurring during message handling will result in
|
||||
an Exception being raised on the sending end.
|
||||
an efro.error.RemoteError being raised on the sending end.
|
||||
"""
|
||||
|
||||
is_async = False
|
||||
|
|
@ -89,20 +90,6 @@ class MessageReceiver:
|
|||
f' got {sig.args}'
|
||||
)
|
||||
|
||||
# Make sure we are only given async methods if we are an async handler
|
||||
# and sync ones otherwise.
|
||||
# UPDATE - can't do this anymore since we now sometimes use
|
||||
# regular functions which return awaitables instead of having
|
||||
# the entire function be async.
|
||||
# is_async = inspect.iscoroutinefunction(call)
|
||||
# if self.is_async != is_async:
|
||||
# msg = (
|
||||
# 'Expected a sync method; found an async one.'
|
||||
# if is_async
|
||||
# else 'Expected an async method; found a sync one.'
|
||||
# )
|
||||
# raise ValueError(msg)
|
||||
|
||||
# Check annotation types to determine what message types we handle.
|
||||
# Return-type annotation can be a Union, but we probably don't
|
||||
# have it available at runtime. Explicitly pull it in.
|
||||
|
|
@ -161,7 +148,7 @@ class MessageReceiver:
|
|||
|
||||
if msgtype in self._handlers:
|
||||
raise TypeError(
|
||||
f'Message type {msgtype} already has a registered' f' handler.'
|
||||
f'Message type {msgtype} already has a registered handler.'
|
||||
)
|
||||
|
||||
# Make sure the responses exactly matches what the message expects.
|
||||
|
|
@ -284,7 +271,6 @@ class MessageReceiver:
|
|||
"""
|
||||
assert not self.is_async, "can't call sync handler on async receiver"
|
||||
msg_decoded: Message | None = None
|
||||
msgtype: type[Message] | None = None
|
||||
try:
|
||||
msg_decoded = self._decode_incoming_message(bound_obj, msg)
|
||||
msgtype = type(msg_decoded)
|
||||
|
|
@ -304,7 +290,8 @@ class MessageReceiver:
|
|||
bound_obj, msg_decoded, exc
|
||||
)
|
||||
if dolog:
|
||||
if msgtype is not None:
|
||||
if msg_decoded is not None:
|
||||
msgtype = type(msg_decoded)
|
||||
logging.exception(
|
||||
'Error handling %s.%s message.',
|
||||
msgtype.__module__,
|
||||
|
|
@ -312,7 +299,9 @@ class MessageReceiver:
|
|||
)
|
||||
else:
|
||||
logging.exception(
|
||||
'Error handling raw efro.message. msg=%s', msg
|
||||
'Error handling raw efro.message'
|
||||
' (likely a message format incompatibility): %s.',
|
||||
msg,
|
||||
)
|
||||
return rstr
|
||||
|
||||
|
|
@ -329,9 +318,8 @@ class MessageReceiver:
|
|||
# able to guarantee that messages handlers would be called in the
|
||||
# order the messages were received.
|
||||
|
||||
assert self.is_async, "can't call async handler on sync receiver"
|
||||
assert self.is_async, "Can't call async handler on sync receiver."
|
||||
msg_decoded: Message | None = None
|
||||
msgtype: type[Message] | None = None
|
||||
try:
|
||||
msg_decoded = self._decode_incoming_message(bound_obj, msg)
|
||||
msgtype = type(msg_decoded)
|
||||
|
|
@ -346,43 +334,51 @@ class MessageReceiver:
|
|||
):
|
||||
raise
|
||||
return self._handle_raw_message_async_error(
|
||||
bound_obj, msg_decoded, msgtype, exc
|
||||
bound_obj, msg, msg_decoded, exc
|
||||
)
|
||||
|
||||
# Return an awaitable to handle the rest asynchronously.
|
||||
return self._handle_raw_message_async(
|
||||
bound_obj, msg_decoded, msgtype, handler_awaitable
|
||||
bound_obj, msg, msg_decoded, handler_awaitable
|
||||
)
|
||||
|
||||
async def _handle_raw_message_async_error(
|
||||
self,
|
||||
bound_obj: Any,
|
||||
msg_raw: str,
|
||||
msg_decoded: Message | None,
|
||||
msgtype: type[Message] | None,
|
||||
exc: Exception,
|
||||
) -> str:
|
||||
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded, exc)
|
||||
if dolog:
|
||||
if msgtype is not None:
|
||||
if msg_decoded is not None:
|
||||
msgtype = type(msg_decoded)
|
||||
logging.exception(
|
||||
'Error handling %s.%s message.',
|
||||
msgtype.__module__,
|
||||
msgtype.__qualname__,
|
||||
# We need to explicitly provide the exception here,
|
||||
# otherwise it shows up at None. I assume related to
|
||||
# the fact that we're an async function.
|
||||
exc_info=exc,
|
||||
)
|
||||
else:
|
||||
logging.exception(
|
||||
'Error handling raw async efro.message.'
|
||||
' msgtype=%s msg_decoded=%s.',
|
||||
msgtype,
|
||||
msg_decoded,
|
||||
'Error handling raw async efro.message'
|
||||
' (likely a message format incompatibility): %s.',
|
||||
msg_raw,
|
||||
# We need to explicitly provide the exception here,
|
||||
# otherwise it shows up at None. I assume related to
|
||||
# the fact that we're an async function.
|
||||
exc_info=exc,
|
||||
)
|
||||
return rstr
|
||||
|
||||
async def _handle_raw_message_async(
|
||||
self,
|
||||
bound_obj: Any,
|
||||
msg_raw: str,
|
||||
msg_decoded: Message,
|
||||
msgtype: type[Message] | None,
|
||||
handler_awaitable: Awaitable[Response | None],
|
||||
) -> str:
|
||||
"""Should be called when the receiver gets a message.
|
||||
|
|
@ -396,7 +392,7 @@ class MessageReceiver:
|
|||
|
||||
except Exception as exc:
|
||||
return await self._handle_raw_message_async_error(
|
||||
bound_obj, msg_decoded, msgtype, exc
|
||||
bound_obj, msg_raw, msg_decoded, exc
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
56
dist/ba_data/python/efro/message/_sender.py
vendored
56
dist/ba_data/python/efro/message/_sender.py
vendored
|
|
@ -20,27 +20,41 @@ if TYPE_CHECKING:
|
|||
|
||||
class MessageSender:
|
||||
"""Facilitates sending messages to a target and receiving responses.
|
||||
This is instantiated at the class level and used to register unbound
|
||||
class methods to handle raw message sending.
|
||||
|
||||
These are instantiated at the class level and used to register unbound
|
||||
class methods to handle raw message sending. Generally this class is not
|
||||
used directly, but instead autogenerated subclasses which provide type
|
||||
safe overloads are used instead.
|
||||
|
||||
Example:
|
||||
(In this example, MyMessageSender is an autogenerated class that
|
||||
inherits from MessageSender).
|
||||
|
||||
class MyClass:
|
||||
msg = MyMessageSender(some_protocol)
|
||||
msg = MyMessageSender()
|
||||
|
||||
@msg.send_method
|
||||
def send_raw_message(self, message: str) -> str:
|
||||
# Actually send the message here.
|
||||
|
||||
# MyMessageSender class should provide overloads for send(), send_async(),
|
||||
# etc. to ensure all sending happens with valid types.
|
||||
obj = MyClass()
|
||||
obj.msg.send(SomeMessageType())
|
||||
|
||||
# The MyMessageSender generated class would provides overloads for
|
||||
# send(), send_async(), etc. to provide type-safety for message types
|
||||
# and their associated response types.
|
||||
# Thus, given the statement below, a type-checker would know that
|
||||
# 'response' is a SomeResponseType or whatever is associated with
|
||||
# SomeMessageType.
|
||||
response = obj.msg.send(SomeMessageType())
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, protocol: MessageProtocol) -> None:
|
||||
self.protocol = protocol
|
||||
self._send_raw_message_call: Callable[[Any, str], str] | None = None
|
||||
self._send_raw_message_ex_call: (
|
||||
Callable[[Any, str, Message], str] | None
|
||||
) = None
|
||||
self._send_async_raw_message_call: (
|
||||
Callable[[Any, str], Awaitable[str]] | None
|
||||
) = None
|
||||
|
|
@ -69,6 +83,19 @@ class MessageSender:
|
|||
self._send_raw_message_call = call
|
||||
return call
|
||||
|
||||
def send_ex_method(
|
||||
self, call: Callable[[Any, str, Message], str]
|
||||
) -> Callable[[Any, str, Message], str]:
|
||||
"""Function decorator for extended send method.
|
||||
|
||||
Version of send_method which is also is passed the original
|
||||
unencoded message; can be useful for cases where metadata is sent
|
||||
along with messages referring to their payloads/etc.
|
||||
"""
|
||||
assert self._send_raw_message_ex_call is None
|
||||
self._send_raw_message_ex_call = call
|
||||
return call
|
||||
|
||||
def send_async_method(
|
||||
self, call: Callable[[Any, str], Awaitable[str]]
|
||||
) -> Callable[[Any, str], Awaitable[str]]:
|
||||
|
|
@ -189,14 +216,23 @@ class MessageSender:
|
|||
for when message sending and response handling need to happen
|
||||
in different contexts/threads.
|
||||
"""
|
||||
if self._send_raw_message_call is None:
|
||||
if (
|
||||
self._send_raw_message_call is None
|
||||
and self._send_raw_message_ex_call is None
|
||||
):
|
||||
raise RuntimeError('send() is unimplemented for this type.')
|
||||
|
||||
msg_encoded = self._encode_message(bound_obj, message)
|
||||
try:
|
||||
response_encoded = self._send_raw_message_call(
|
||||
bound_obj, msg_encoded
|
||||
)
|
||||
if self._send_raw_message_ex_call is not None:
|
||||
response_encoded = self._send_raw_message_ex_call(
|
||||
bound_obj, msg_encoded, message
|
||||
)
|
||||
else:
|
||||
assert self._send_raw_message_call is not None
|
||||
response_encoded = self._send_raw_message_call(
|
||||
bound_obj, msg_encoded
|
||||
)
|
||||
except Exception as exc:
|
||||
response = ErrorSysResponse(
|
||||
error_message='Error in MessageSender @send_method.',
|
||||
|
|
|
|||
7
dist/ba_data/python/efro/rpc.py
vendored
7
dist/ba_data/python/efro/rpc.py
vendored
|
|
@ -9,6 +9,7 @@ import asyncio
|
|||
import logging
|
||||
import weakref
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from threading import current_thread
|
||||
|
|
@ -84,7 +85,6 @@ def ssl_stream_writer_underlying_transport_info(
|
|||
|
||||
def ssl_stream_writer_force_close_check(writer: asyncio.StreamWriter) -> None:
|
||||
"""Ensure a writer is closed; hacky workaround for odd hang."""
|
||||
from efro.call import tpartial
|
||||
from threading import Thread
|
||||
|
||||
# Disabling for now..
|
||||
|
|
@ -100,9 +100,8 @@ def ssl_stream_writer_force_close_check(writer: asyncio.StreamWriter) -> None:
|
|||
raw_transport = getattr(sslproto, '_transport', None)
|
||||
if raw_transport is not None:
|
||||
Thread(
|
||||
target=tpartial(
|
||||
_do_writer_force_close_check,
|
||||
weakref.ref(raw_transport),
|
||||
target=partial(
|
||||
_do_writer_force_close_check, weakref.ref(raw_transport)
|
||||
),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
|
|
|||
1
dist/ba_data/python/efro/terminal.py
vendored
1
dist/ba_data/python/efro/terminal.py
vendored
|
|
@ -72,6 +72,7 @@ def _default_color_enabled() -> bool:
|
|||
import platform
|
||||
|
||||
# If our stdout is not attached to a terminal, go with no-color.
|
||||
assert sys.__stdout__ is not None
|
||||
if not sys.__stdout__.isatty():
|
||||
return False
|
||||
|
||||
|
|
|
|||
83
dist/ba_data/python/efro/util.py
vendored
83
dist/ba_data/python/efro/util.py
vendored
|
|
@ -8,14 +8,12 @@ import os
|
|||
import time
|
||||
import weakref
|
||||
import datetime
|
||||
import functools
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, cast, TypeVar, Generic
|
||||
from typing import TYPE_CHECKING, cast, TypeVar, Generic, overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import asyncio
|
||||
from efro.call import Call as Call # 'as Call' so we re-export.
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Literal
|
||||
|
||||
T = TypeVar('T')
|
||||
ValT = TypeVar('ValT')
|
||||
|
|
@ -35,13 +33,6 @@ _g_empty_weak_ref = weakref.ref(_EmptyObj())
|
|||
assert _g_empty_weak_ref() is None
|
||||
|
||||
|
||||
# TODO: kill this and just use efro.call.tpartial
|
||||
if TYPE_CHECKING:
|
||||
Call = Call
|
||||
else:
|
||||
Call = functools.partial
|
||||
|
||||
|
||||
def explicit_bool(val: bool) -> bool:
|
||||
"""Return a non-inferable boolean value.
|
||||
|
||||
|
|
@ -536,6 +527,21 @@ def make_hash(obj: Any) -> int:
|
|||
return hash(tuple(frozenset(sorted(new_obj.items()))))
|
||||
|
||||
|
||||
def float_hash_from_string(s: str) -> float:
|
||||
"""Given a string value, returns a float between 0 and 1.
|
||||
|
||||
If consistent across processes. Can be useful for assigning db ids
|
||||
shard values for efficient parallel processing.
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
hash_bytes = hashlib.md5(s.encode()).digest()
|
||||
|
||||
# Generate a random 64 bit int from hash digest bytes.
|
||||
ival = int.from_bytes(hash_bytes[:8])
|
||||
return ival / ((1 << 64) - 1)
|
||||
|
||||
|
||||
def asserttype(obj: Any, typ: type[T]) -> T:
|
||||
"""Return an object typed as a given type.
|
||||
|
||||
|
|
@ -898,3 +904,58 @@ def split_list(input_list: list[T], max_length: int) -> list[list[T]]:
|
|||
input_list[i : i + max_length]
|
||||
for i in range(0, len(input_list), max_length)
|
||||
]
|
||||
|
||||
|
||||
def extract_flag(args: list[str], name: str) -> bool:
|
||||
"""Given a list of args and a flag name, returns whether it is present.
|
||||
|
||||
The arg flag, if present, is removed from the arg list.
|
||||
"""
|
||||
from efro.error import CleanError
|
||||
|
||||
count = args.count(name)
|
||||
if count > 1:
|
||||
raise CleanError(f'Flag {name} passed multiple times.')
|
||||
if not count:
|
||||
return False
|
||||
args.remove(name)
|
||||
return True
|
||||
|
||||
|
||||
@overload
|
||||
def extract_arg(
|
||||
args: list[str], name: str, required: Literal[False] = False
|
||||
) -> str | None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def extract_arg(args: list[str], name: str, required: Literal[True]) -> str: ...
|
||||
|
||||
|
||||
def extract_arg(
|
||||
args: list[str], name: str, required: bool = False
|
||||
) -> str | None:
|
||||
"""Given a list of args and an arg name, returns a value.
|
||||
|
||||
The arg flag and value are removed from the arg list.
|
||||
raises CleanErrors on any problems.
|
||||
"""
|
||||
from efro.error import CleanError
|
||||
|
||||
count = args.count(name)
|
||||
if not count:
|
||||
if required:
|
||||
raise CleanError(f'Required argument {name} not passed.')
|
||||
return None
|
||||
|
||||
if count > 1:
|
||||
raise CleanError(f'Arg {name} passed multiple times.')
|
||||
|
||||
argindex = args.index(name)
|
||||
if argindex + 1 >= len(args):
|
||||
raise CleanError(f'No value passed after {name} arg.')
|
||||
|
||||
val = args[argindex + 1]
|
||||
del args[argindex : argindex + 2]
|
||||
|
||||
return val
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue